diff --git a/config.lua.dist b/config.lua.dist
index 6f8969fe71b..a64937631a0 100644
--- a/config.lua.dist
+++ b/config.lua.dist
@@ -43,8 +43,8 @@ maxItem = 2000
maxContainer = 100
-- Version
-clientVersion = 1264
-clientVersionStr = "12.64"
+clientVersion = 1272
+clientVersionStr = "12.72"
-- Depot Limit
freeDepotLimit = 2000
diff --git a/data/items/items.otb b/data/items/items.otb
index 8170caa02e9..2ae3d9094bb 100644
Binary files a/data/items/items.otb and b/data/items/items.otb differ
diff --git a/data/items/items.xml b/data/items/items.xml
index c8e5dd0430e..f5d062909fb 100644
--- a/data/items/items.xml
+++ b/data/items/items.xml
@@ -5080,7 +5080,7 @@
-
+
@@ -15452,13 +15452,13 @@
-
+
-
-
+
-
@@ -15599,14 +15599,14 @@
-
+
-
-
+
@@ -15614,7 +15614,7 @@
-
-
+
@@ -15622,21 +15622,21 @@
-
-
+
-
-
+
-
-
+
@@ -15711,7 +15711,7 @@
-
-
+
@@ -16676,7 +16676,7 @@
-
+
@@ -17433,7 +17433,7 @@
-
+
@@ -19885,7 +19885,7 @@
-
-
+
@@ -20954,7 +20954,7 @@
-
-
+
@@ -21016,7 +21016,7 @@
-
+
-
@@ -21043,7 +21043,7 @@
-
-
+
@@ -23510,7 +23510,7 @@
-
+
@@ -23760,7 +23760,7 @@
-
+
-
@@ -24163,7 +24163,7 @@
-
+
@@ -25002,7 +25002,7 @@
-
+
-
@@ -25041,7 +25041,7 @@
-
+
-
@@ -25049,7 +25049,7 @@
-
+
-
@@ -25057,12 +25057,12 @@
-
+
-
-
+
@@ -25134,7 +25134,7 @@
-
+
-
@@ -25150,7 +25150,7 @@
-
+
-
@@ -25158,7 +25158,7 @@
-
+
-
@@ -29983,7 +29983,7 @@
-
+
@@ -30400,7 +30400,7 @@
-
+
@@ -30411,7 +30411,7 @@
-
+
@@ -30422,7 +30422,7 @@
-
+
@@ -31684,7 +31684,7 @@
-
+
@@ -33647,7 +33647,7 @@
-
-
+
@@ -34098,7 +34098,7 @@
-
+
@@ -34106,7 +34106,7 @@
-
+
@@ -34114,7 +34114,7 @@
-
+
@@ -34122,7 +34122,7 @@
-
+
@@ -34446,7 +34446,7 @@
-
-
+
@@ -34520,7 +34520,7 @@
-
+
@@ -35395,7 +35395,7 @@
-
-
+
@@ -35410,7 +35410,7 @@
-
+
@@ -35440,7 +35440,7 @@
-
-
+
@@ -35462,7 +35462,7 @@
-
+
@@ -37158,7 +37158,7 @@
-
-
+
@@ -37166,7 +37166,7 @@
-
-
+
@@ -37332,7 +37332,7 @@
-
+
@@ -39139,14 +39139,14 @@
-
-
+
-
-
+
@@ -39715,7 +39715,7 @@
-
+
@@ -39733,7 +39733,7 @@
-
+
@@ -41119,7 +41119,7 @@
-
+
@@ -41136,7 +41136,7 @@
-
-
+
@@ -41145,7 +41145,7 @@
-
-
+
@@ -41659,7 +41659,7 @@
-
-
+
@@ -41668,7 +41668,7 @@
-
-
+
@@ -42225,7 +42225,7 @@
-
+
@@ -42244,7 +42244,7 @@
-
+
@@ -42272,7 +42272,7 @@
-
+
@@ -42280,14 +42280,14 @@
-
+
-
-
+
@@ -42297,7 +42297,7 @@
-
+
@@ -42331,7 +42331,7 @@
-
+
@@ -43797,13 +43797,13 @@
-
-
+
-
-
+
@@ -43817,7 +43817,7 @@
-
+
@@ -45194,7 +45194,7 @@
-
+
@@ -45234,7 +45234,7 @@
-
+
@@ -45242,7 +45242,7 @@
-
+
@@ -46618,7 +46618,7 @@
-
+
-
@@ -46626,7 +46626,7 @@
-
+
-
@@ -48396,7 +48396,7 @@
-
+
@@ -48405,7 +48405,7 @@
-
+
@@ -48417,14 +48417,14 @@
-
-
+
-
-
+
@@ -48442,7 +48442,7 @@
-
+
@@ -48451,7 +48451,7 @@
-
+
@@ -48723,7 +48723,7 @@
-
+
@@ -48734,7 +48734,7 @@
-
+
@@ -48742,7 +48742,7 @@
-
-
+
@@ -49369,7 +49369,7 @@
-
-
+
@@ -49378,7 +49378,7 @@
-
-
+
@@ -49389,7 +49389,7 @@
-
+
@@ -49399,7 +49399,7 @@
-
+
diff --git a/data/scripts/actions/other/tibiadrome_potions.lua b/data/scripts/actions/other/tibiadrome_potions.lua
new file mode 100644
index 00000000000..872f53fe103
--- /dev/null
+++ b/data/scripts/actions/other/tibiadrome_potions.lua
@@ -0,0 +1,106 @@
+local potionConditionsConfig = {
+ [36724] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_SKILL_CRITICAL_HIT_CHANCE, 5}}},
+ [36726] = {1 * 60 * 60 * 1000},
+ [36727] = {1 * 60 * 60 * 1000},
+ [36728] = {1 * 60 * 60 * 1000},
+ [36729] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_ABSORB_FIREPERCENT, 8}}},
+ [36730] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_ABSORB_ICEPERCENT, 8}}},
+ [36731] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_ABSORB_EARTHPERCENT, 8}}},
+ [36732] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_ABSORB_ENERGYPERCENT, 8}}},
+ [36733] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_ABSORB_HOLYPERCENT, 8}}},
+ [36734] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_ABSORB_DEATHPERCENT, 8}}},
+ [36735] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_ABSORB_PHYSICALPERCENT, 8}}},
+ [36736] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_INCREASE_FIREPERCENT, 8}}},
+ [36737] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_INCREASE_ICEPERCENT, 8}}},
+ [36738] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_INCREASE_EARTHPERCENT, 8}}},
+ [36739] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_INCREASE_ENERGYPERCENT, 8}}},
+ [36740] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_INCREASE_HOLYPERCENT, 8}}},
+ [36741] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_INCREASE_DEATHPERCENT, 8}}},
+ [36742] = {1 * 60 * 60 * 1000, {{CONDITION_PARAM_INCREASE_PHYSICALPERCENT, 8}}},
+}
+
+local potionConditions = {}
+
+local potionFunctions = {
+ [36723] = function(player)
+ for i=1, 200 do
+ if player:hasCondition(CONDITION_SPELLCOOLDOWN, i) then
+ player:removeCondition(CONDITION_SPELLCOOLDOWN, CONDITIONID_DEFAULT, i)
+ player:sendSpellCooldown(i, 0)
+ end
+ end
+ for i=1, 8 do
+ if player:hasCondition(CONDITION_SPELLGROUPCOOLDOWN, i) then
+ player:removeCondition(CONDITION_SPELLGROUPCOOLDOWN, CONDITIONID_DEFAULT, i)
+ player:sendSpellGroupCooldown(i, 0)
+ end
+ end
+ end,
+ [36725] = function(p)
+ p:setStamina(p:getStamina() + 60)
+ end,
+}
+
+for id, data in pairs(potionConditionsConfig) do
+ potionConditions[id] = {}
+ if data[1] then
+ if data[2] then
+ local condition = Condition(CONDITION_ATTRIBUTES, CONDITIONID_DEFAULT)
+ condition:setParameter(CONDITION_PARAM_SUBID, id)
+ condition:setParameter(CONDITION_PARAM_TICKS, data[1])
+ if data[2] then
+ for _, params in pairs(data[2]) do
+ condition:setParameter(params[1], params[2])
+ end
+ end
+ table.insert(potionConditions[id], condition)
+ end
+ local condition = Condition(CONDITION_TIBIADROMEPOTIONS, CONDITIONID_DEFAULT)
+ condition:setParameter(CONDITION_PARAM_SUBID, id)
+ condition:setParameter(CONDITION_PARAM_TICKS, data[1])
+ table.insert(potionConditions[id], condition)
+ end
+end
+
+
+
+
+local tibiaDromePotions = Action()
+
+function tibiaDromePotions.onUse(cid, item, fromPosition, itemEx, toPosition)
+ local player = Player(cid)
+ if (not player) then
+ return false
+ end
+ local itemID = item:getId()
+ if player:getStorageValue(itemID) >= os.time() then
+ player:sendCancelMessage("You can only use this potion once every 24 hours.")
+ player:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return true
+ end
+
+ if player:getCondition(CONDITION_TIBIADROMEPOTIONS, CONDITIONID_DEFAULT, itemID) then
+ player:sendCancelMessage("You still have the effects from this potion.")
+ player:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return true
+ end
+ player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN)
+ player:setStorageValue(itemID, os.time() + 24 * 60 * 60)
+ item:remove(1)
+ local potionConditions = potionConditions[itemID]
+ if potionConditions then
+ for _, condition in pairs(potionConditions) do
+ player:addCondition(condition)
+ end
+ end
+ local potionFunction = potionFunctions[itemID]
+ if potionFunction then
+ potionFunction(player)
+ end
+ return true
+end
+
+for itemid = 36723, 36742 do
+ tibiaDromePotions:id(itemid)
+end
+tibiaDromePotions:register()
diff --git a/data/scripts/movements/unscripted_movements.lua b/data/scripts/movements/unscripted_movements.lua
index 7cd9c829afa..8ba1589e3db 100644
--- a/data/scripts/movements/unscripted_movements.lua
+++ b/data/scripts/movements/unscripted_movements.lua
@@ -1,4 +1,1231 @@
local items = {
+ {
+ -- gilded eldritch rod
+ itemid = 36675,
+ type = "equip",
+ slot = "hand",
+ level = 250,
+ vocation = {
+ {"Druid", true, true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- gilded eldritch rod
+ itemid = 36675,
+ type = "deequip",
+ slot = "hand",
+ level = 250,
+ vocation = {
+ {"Druid", true, true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- eldritch rod
+ itemid = 36674,
+ type = "equip",
+ slot = "hand",
+ level = 250,
+ vocation = {
+ {"Druid", true, true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- eldritch rod
+ itemid = 36674,
+ type = "deequip",
+ slot = "hand",
+ level = 250,
+ vocation = {
+ {"Druid", true, true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- eldritch tome
+ itemid = 36673,
+ type = "equip",
+ slot = "shield",
+ level = 300,
+ vocation = {
+ {"Druid", true, true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- eldritch tome
+ itemid = 36673,
+ type = "deequip",
+ slot = "shield",
+ level = 300,
+ vocation = {
+ {"Druid", true, true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- eldritch folio
+ itemid = 36672,
+ type = "equip",
+ slot = "shield",
+ level = 300,
+ vocation = {
+ {"Sorcerer", true, true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- eldritch folio
+ itemid = 36672,
+ type = "deequip",
+ slot = "shield",
+ level = 300,
+ vocation = {
+ {"Sorcerer", true, true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- eldritch hood
+ itemid = 36671,
+ type = "equip",
+ slot = "head",
+ level = 250,
+ vocation = {
+ {"Druid", true, true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- eldritch hood
+ itemid = 36671,
+ type = "deequip",
+ slot = "head",
+ level = 250,
+ vocation = {
+ {"Druid", true, true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- eldritch cowl
+ itemid = 36670,
+ type = "equip",
+ slot = "head",
+ level = 250,
+ vocation = {
+ {"Sorcerer", true, true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- eldritch cowl
+ itemid = 36670,
+ type = "deequip",
+ slot = "head",
+ level = 250,
+ vocation = {
+ {"Sorcerer", true, true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- gilded eldritch wand
+ itemid = 36669,
+ type = "equip",
+ slot = "hand",
+ level = 250,
+ vocation = {
+ {"Sorcerer", true, true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- gilded eldritch wand
+ itemid = 36669,
+ type = "deequip",
+ slot = "hand",
+ level = 250,
+ vocation = {
+ {"Sorcerer", true, true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- eldritch wand
+ itemid = 36668,
+ type = "equip",
+ slot = "hand",
+ level = 250,
+ vocation = {
+ {"Sorcerer", true, true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- eldritch wand
+ itemid = 36668,
+ type = "deequip",
+ slot = "hand",
+ level = 250,
+ vocation = {
+ {"Sorcerer", true, true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- eldritch breeches
+ itemid = 36667,
+ type = "equip",
+ slot = "legs",
+ level = 250,
+ vocation = {
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- eldritch breeches
+ itemid = 36667,
+ type = "deequip",
+ slot = "legs",
+ level = 250,
+ vocation = {
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- eldritch quiver
+ itemid = 36666,
+ type = "equip",
+ slot = "right-hand",
+ level = 250,
+ vocation = {
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- eldritch quiver
+ itemid = 36666,
+ type = "deequip",
+ slot = "right-hand",
+ level = 250,
+ vocation = {
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- gilded eldritch bow
+ itemid = 36665,
+ type = "equip",
+ slot = "hand",
+ level = 250,
+ vocation = {
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- gilded eldritch bow
+ itemid = 36665,
+ type = "deequip",
+ slot = "hand",
+ level = 250,
+ vocation = {
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- eldritch bow
+ itemid = 36664,
+ type = "equip",
+ slot = "hand",
+ level = 250,
+ vocation = {
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- eldritch bow
+ itemid = 36664,
+ type = "deequip",
+ slot = "hand",
+ level = 250,
+ vocation = {
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- eldritch cuirass
+ itemid = 36663,
+ type = "equip",
+ slot = "armor",
+ level = 250,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- eldritch cuirass
+ itemid = 36663,
+ type = "deequip",
+ slot = "armor",
+ level = 250,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- gilded eldritch greataxe
+ itemid = 36662,
+ type = "equip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- gilded eldritch greataxe
+ itemid = 36662,
+ type = "deequip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- eldritch greataxe
+ itemid = 36661,
+ type = "equip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- eldritch greataxe
+ itemid = 36661,
+ type = "deequip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- gilded eldritch warmace
+ itemid = 36660,
+ type = "equip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- gilded eldritch warmace
+ itemid = 36660,
+ type = "deequip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- eldritch warmace
+ itemid = 36659,
+ type = "equip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- eldritch warmace
+ itemid = 36659,
+ type = "deequip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- gilded eldritch claymore
+ itemid = 36658,
+ type = "equip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- gilded eldritch claymore
+ itemid = 36658,
+ type = "deequip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- eldritch claymore
+ itemid = 36657,
+ type = "equip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- eldritch claymore
+ itemid = 36657,
+ type = "deequip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- eldritch shield
+ itemid = 36656,
+ type = "equip",
+ slot = "shield",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- eldritch shield
+ itemid = 36656,
+ type = "deequip",
+ slot = "shield",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- spectral bolt (no decay)
+ itemid = 35902,
+ type = "equip",
+ slot = "ammo"
+ },
+ {
+ -- spectral bolt (no decay)
+ itemid = 35902,
+ type = "deequip",
+ slot = "ammo"
+ },
+ {
+ -- red quiver
+ itemid = 35849,
+ type = "equip",
+ slot = "right-hand",
+ vocation = {
+ {"None", true},
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- red quiver
+ itemid = 35849,
+ type = "deequip",
+ slot = "right-hand",
+ vocation = {
+ {"None", true},
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- blue quiver
+ itemid = 35848,
+ type = "equip",
+ slot = "right-hand",
+ vocation = {
+ {"None", true},
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- blue quiver
+ itemid = 35848,
+ type = "deequip",
+ slot = "right-hand",
+ vocation = {
+ {"None", true},
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- quiver
+ itemid = 35562,
+ type = "equip",
+ slot = "right-hand",
+ vocation = {
+ {"None", true},
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- quiver
+ itemid = 35562,
+ type = "deequip",
+ slot = "right-hand",
+ vocation = {
+ {"None", true},
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- jungle quiver
+ itemid = 35524,
+ type = "equip",
+ slot = "right-hand",
+ level = 150,
+ vocation = {
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- jungle quiver
+ itemid = 35524,
+ type = "deequip",
+ slot = "right-hand",
+ level = 150,
+ vocation = {
+ {"Paladin", true, true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- exotic amulet
+ itemid = 35523,
+ type = "equip",
+ slot = "necklace",
+ level = 180
+ },
+ {
+ -- exotic amulet
+ itemid = 35523,
+ type = "deequip",
+ slot = "necklace"
+ },
+ {
+ -- jungle wand
+ itemid = 35522,
+ type = "equip",
+ slot = "hand",
+ level = 150,
+ vocation = {
+ {"Sorcerer", true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- jungle wand
+ itemid = 35522,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- jungle rod
+ itemid = 35521,
+ type = "equip",
+ slot = "hand",
+ level = 150,
+ vocation = {
+ {"Druid", true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- jungle rod
+ itemid = 35521,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- make-do boots
+ itemid = 35520,
+ type = "equip",
+ slot = "feet",
+ level = 150,
+ vocation = {
+ {"Druid", true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- make-do boots
+ itemid = 35520,
+ type = "deequip",
+ slot = "feet",
+ },
+ {
+ -- makeshift boots
+ itemid = 35519,
+ type = "equip",
+ slot = "feet",
+ level = 150,
+ vocation = {
+ {"Sorcerer", true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- makeshift boots
+ itemid = 35519,
+ type = "deequip",
+ slot = "feet",
+ },
+ {
+ -- jungle bow
+ itemid = 35518,
+ type = "equip",
+ slot = "hand",
+ level = 150,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- jungle bow
+ itemid = 35518,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- bast legs
+ itemid = 35517,
+ type = "equip",
+ slot = "legs",
+ level = 150,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- bast legs
+ itemid = 35517,
+ type = "deequip",
+ slot = "legs",
+ },
+ {
+ -- exotic legs
+ itemid = 35516,
+ type = "equip",
+ slot = "legs",
+ level = 130,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- exotic legs
+ itemid = 35516,
+ type = "deequip",
+ slot = "legs",
+ },
+ {
+ -- throwing axe
+ itemid = 35515,
+ type = "equip",
+ slot = "hand",
+ level = 150,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- throwing axe
+ itemid = 35515,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- jungle flail
+ itemid = 35514,
+ type = "equip",
+ slot = "hand",
+ level = 150,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- jungle flail
+ itemid = 35514,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- lion hammer
+ itemid = 34254,
+ type = "equip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- lion hammer
+ itemid = 34254,
+ type = "deequip",
+ slot = "hand"
+ },
+ {
+ -- lion axe
+ itemid = 34253,
+ type = "equip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- lion axe
+ itemid = 34253,
+ type = "deequip",
+ slot = "hand"
+ },
+ {
+ -- lion amulet
+ itemid = 34158,
+ type = "equip",
+ slot = "necklace",
+ level = 150
+ },
+ {
+ -- lion amulet
+ itemid = 34158,
+ type = "deequip",
+ slot = "necklace",
+ level = 150
+ },
+ {
+ -- lion plate
+ itemid = 34157,
+ type = "deequip",
+ slot = "armor",
+ level = 270
+ },
+ {
+ -- lion plate
+ itemid = 34157,
+ type = "equip",
+ slot = "armor",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- lion spangenhelm
+ itemid = 34156,
+ type = "deequip",
+ slot = "head"
+ },
+ {
+ -- lion spangenhelm
+ itemid = 34156,
+ type = "equip",
+ slot = "head",
+ level = 230,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- lion longsword
+ itemid = 34155,
+ type = "equip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- lion longsword
+ itemid = 34155,
+ type = "deequip",
+ slot = "hand"
+ },
+ {
+ -- lion spellbook
+ itemid = 34153,
+ type = "deequip",
+ slot = "shield"
+ },
+ {
+ -- lion spellbook
+ itemid = 34153,
+ type = "equip",
+ slot = "shield",
+ level = 220,
+ vocation = {
+ {"Sorcerer", true},
+ {"Druid", true, true},
+ {"Master Sorcerer"},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- lion wand
+ itemid = 34152,
+ type = "equip",
+ slot = "hand",
+ level = 220,
+ vocation = {
+ {"Sorcerer", true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- lion wand
+ itemid = 34152,
+ type = "deequip",
+ slot = "hand"
+ },
+ {
+ -- lion rod
+ itemid = 34151,
+ type = "equip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Druid", true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- lion rod
+ itemid = 34151,
+ type = "deequip",
+ slot = "hand"
+ },
+ {
+ -- lion longbow
+ itemid = 34150,
+ type = "equip",
+ slot = "hand",
+ level = 270,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- lion longbow
+ itemid = 34150,
+ type = "deequip",
+ slot = "hand"
+ },
+ {
+ -- soulbastion shield
+ itemid = 34099,
+ type = "deequip",
+ slot = "shield"
+ },
+ {
+ -- soulbastion shield
+ itemid = 34099,
+ type = "equip",
+ slot = "shield",
+ level = 400,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- pair of soulstalkers
+ itemid = 34098,
+ type = "deequip",
+ slot = "feet"
+ },
+ {
+ -- pair of soulstalkers
+ itemid = 34098,
+ type = "equip",
+ slot = "feet",
+ level = 400,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- pair of soulwalkers
+ itemid = 34097,
+ type = "deequip",
+ slot = "feet"
+ },
+ {
+ -- pair of soulwalkers
+ itemid = 34097,
+ type = "equip",
+ slot = "feet",
+ level = 400,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- soulshroud armor
+ itemid = 34096,
+ type = "deequip",
+ slot = "armor"
+ },
+ {
+ -- soulshroud armor
+ itemid = 34096,
+ type = "equip",
+ slot = "armor",
+ level = 400,
+ vocation = {
+ {"Druid", true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- soulmantel armor
+ itemid = 34095,
+ type = "deequip",
+ slot = "armor"
+ },
+ {
+ -- soulmantel armor
+ itemid = 34095,
+ type = "equip",
+ slot = "armor",
+ level = 400,
+ vocation = {
+ {"Sorcerer", true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- soulshell armor
+ itemid = 34094,
+ type = "deequip",
+ slot = "armor",
+ level = 400
+ },
+ {
+ -- soulshell armor
+ itemid = 34094,
+ type = "equip",
+ slot = "armor",
+ level = 400,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- soulstrider legs
+ itemid = 34093,
+ type = "deequip",
+ slot = "legs",
+ level = 400
+ },
+ {
+ -- soulstrider legs
+ itemid = 34093,
+ type = "equip",
+ slot = "legs",
+ level = 400,
+ vocation = {
+ {"Druid", true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- soulshanks legs
+ itemid = 34092,
+ type = "deequip",
+ slot = "legs",
+ level = 400
+ },
+ {
+ -- soulshanks legs
+ itemid = 34092,
+ type = "equip",
+ slot = "legs",
+ level = 400,
+ vocation = {
+ {"Sorcerer", true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- soulhexer
+ itemid = 34091,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- soulhexer
+ itemid = 34091,
+ type = "equip",
+ slot = "hand",
+ level = 400,
+ vocation = {
+ {"Druid", true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- soultainter
+ itemid = 34090,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- soultainter
+ itemid = 34090,
+ type = "equip",
+ slot = "hand",
+ level = 400,
+ vocation = {
+ {"Sorcerer", true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- soulpiercer
+ itemid = 34089,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- soulpiercer
+ itemid = 34089,
+ type = "equip",
+ slot = "hand",
+ level = 400,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- soulbleeder
+ itemid = 34088,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- soulbleeder
+ itemid = 34088,
+ type = "equip",
+ slot = "hand",
+ level = 400,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- soulmaimer
+ itemid = 34087,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- soulmaimer
+ itemid = 34087,
+ type = "equip",
+ slot = "hand",
+ level = 400,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- soulcrusher
+ itemid = 34086,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- soulcrusher
+ itemid = 34086,
+ type = "equip",
+ slot = "hand",
+ level = 400,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- souleater
+ itemid = 34085,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- souleater
+ itemid = 34085,
+ type = "equip",
+ slot = "hand",
+ level = 400,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- soulbiter
+ itemid = 34084,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- soulbiter
+ itemid = 34084,
+ type = "equip",
+ slot = "hand",
+ level = 400,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- soulshredder
+ itemid = 34083,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- soulshredder
+ itemid = 34083,
+ type = "equip",
+ slot = "hand",
+ level = 400,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- soulcutter
+ itemid = 34082,
+ type = "deequip",
+ slot = "hand",
+ },
+ {
+ -- soulcutter
+ itemid = 34082,
+ type = "equip",
+ slot = "hand",
+ level = 400,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- lion ring
+ itemid = 34080,
+ type = "deequip",
+ slot = "ring",
+ level = 270
+ },
+ {
+ -- lion ring
+ itemid = 34080,
+ type = "equip",
+ slot = "ring",
+ level = 270,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- Lit Torch (Sparkling)
+ itemid = 34016,
+ type = "equip",
+ slot = "ammo"
+ },
+ {
+ -- Lit Torch (Sparkling)
+ itemid = 34016,
+ type = "deequip",
+ slot = "ammo"
+ },
+ {
+ -- pair of old bracers
+ itemid = 32705,
+ type = "equip",
+ slot = "armor"
+ },
+ {
+ -- pair of old bracers
+ itemid = 32705,
+ type = "deequip",
+ slot = "armor"
+ },
{
-- ring of souls
itemid = 32636,
diff --git a/data/scripts/weapons/unscripted_weapons.lua b/data/scripts/weapons/unscripted_weapons.lua
index 59f6096f8d2..169a974f0dd 100644
--- a/data/scripts/weapons/unscripted_weapons.lua
+++ b/data/scripts/weapons/unscripted_weapons.lua
@@ -1,4 +1,387 @@
local weapons = {
+ {
+ -- gilded eldritch rod
+ itemId = 36675,
+ type = WEAPON_WAND,
+ level = 250,
+ unproperly = true,
+ vocation = {
+ {"Druid", true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- eldritch rod
+ itemId = 36674,
+ type = WEAPON_WAND,
+ level = 250,
+ unproperly = true,
+ vocation = {
+ {"Druid", true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- gilded eldritch wand
+ itemId = 36669,
+ type = WEAPON_WAND,
+ level = 250,
+ unproperly = true,
+ vocation = {
+ {"Sorcerer", true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- eldritch wand
+ itemId = 36668,
+ type = WEAPON_WAND,
+ level = 250,
+ unproperly = true,
+ vocation = {
+ {"Sorcerer", true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- gilded eldritch bow
+ itemId = 36665,
+ type = WEAPON_DISTANCE,
+ level = 250,
+ unproperly = true,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- eldritch bow
+ itemId = 36664,
+ type = WEAPON_DISTANCE,
+ level = 250,
+ unproperly = true,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- gilded eldritch greataxe
+ itemId = 36662,
+ type = WEAPON_AXE,
+ level = 270,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- eldritch greataxe
+ itemId = 36661,
+ type = WEAPON_AXE,
+ level = 270,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- gilded eldritch warmace
+ itemId = 36660,
+ type = WEAPON_CLUB,
+ level = 270,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- eldritch warmace
+ itemId = 36659,
+ type = WEAPON_CLUB,
+ level = 270,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- gilded eldritch claymore
+ itemId = 36658,
+ type = WEAPON_SWORD,
+ level = 270,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- eldritch claymore
+ itemId = 36657,
+ type = WEAPON_SWORD,
+ level = 270,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- spectral bolt (no decay)
+ itemId = 35902,
+ type = WEAPON_AMMO,
+ level = 150,
+ unproperly = true,
+ action = "removecount"
+ },
+ {
+ -- jungle wand
+ itemId = 35522,
+ type = WEAPON_WAND,
+ wandType = "earth",
+ level = 150,
+ mana = 19,
+ damage = {80, 100},
+ vocation = {
+ {"Sorcerer", true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- jungle rod
+ itemId = 35521,
+ type = WEAPON_WAND,
+ wandType = "ice",
+ level = 150,
+ mana = 19,
+ damage = {80, 100},
+ vocation = {
+ {"Druid", true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- jungle bow
+ itemId = 35518,
+ type = WEAPON_DISTANCE,
+ level = 150,
+ unproperly = true,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- throwing axe
+ itemId = 35515,
+ type = WEAPON_AXE,
+ level = 150,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- jungle flail
+ itemId = 35514,
+ type = WEAPON_CLUB,
+ level = 150,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- lion longsword
+ itemId = 34155,
+ type = WEAPON_SWORD,
+ level = 270,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- lion hammer
+ itemId = 34254,
+ type = WEAPON_CLUB,
+ level = 270,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- lion axe
+ itemId = 34253,
+ type = WEAPON_AXE,
+ level = 270,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- lion wand
+ itemId = 34152,
+ type = WEAPON_WAND,
+ wandType = "ice",
+ level = 220,
+ mana = 21,
+ damage = {89, 109},
+ vocation = {
+ {"Sorcerer", true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- lion rod
+ itemId = 34151,
+ type = WEAPON_WAND,
+ wandType = "ice",
+ level = 270,
+ mana = 20,
+ damage = {85, 105},
+ vocation = {
+ {"Druid", true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- lion longbow
+ itemId = 34150,
+ type = WEAPON_DISTANCE,
+ level = 270,
+ unproperly = true,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- soulhexer rod
+ itemId = 34091,
+ type = WEAPON_WAND,
+ wandType = "ice",
+ level = 400,
+ mana = 21,
+ damage = {98, 118},
+ vocation = {
+ {"Druid", true},
+ {"Elder Druid"}
+ }
+ },
+ {
+ -- soultainter wand
+ itemId = 34090,
+ type = WEAPON_WAND,
+ wandType = "death",
+ level = 400,
+ mana = 21,
+ damage = {100, 120},
+ vocation = {
+ {"Sorcerer", true},
+ {"Master Sorcerer"}
+ }
+ },
+ {
+ -- soulpiercer crossbow
+ itemId = 34089,
+ type = WEAPON_DISTANCE,
+ level = 400,
+ unproperly = true,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- soulbleeder bow
+ itemId = 34088,
+ type = WEAPON_DISTANCE,
+ level = 400,
+ unproperly = true,
+ vocation = {
+ {"Paladin", true},
+ {"Royal Paladin"}
+ }
+ },
+ {
+ -- soulmaimer club
+ itemId = 34087,
+ type = WEAPON_CLUB,
+ level = 400,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- soulcrusher club
+ itemId = 34086,
+ type = WEAPON_CLUB,
+ level = 400,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- souleater axe
+ itemId = 34085,
+ type = WEAPON_AXE,
+ level = 400,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- soulbiter axe
+ itemId = 34084,
+ type = WEAPON_AXE,
+ level = 400,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- soulshredder sword
+ itemId = 34083,
+ type = WEAPON_SWORD,
+ level = 400,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
+ {
+ -- soulcutter sword
+ itemId = 34082,
+ type = WEAPON_SWORD,
+ level = 400,
+ unproperly = true,
+ vocation = {
+ {"Knight", true},
+ {"Elite Knight"}
+ }
+ },
{
-- phantasmal axe
itemid = 32616,
diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp
index 8e7efb49d99..bb8086d6416 100644
--- a/src/creatures/combat/combat.cpp
+++ b/src/creatures/combat/combat.cpp
@@ -53,7 +53,7 @@ CombatDamage Combat::getCombatDamage(Creature* creature, Creature* target) const
if (params.valueCallback) {
params.valueCallback->getMinMaxValues(player, damage, params.useCharges);
} else if (formulaType == COMBAT_FORMULA_LEVELMAGIC) {
- int32_t levelFormula = player->getLevel() * 2 + player->getMagicLevel() * 3;
+ int32_t levelFormula = player->getLevel() * 2 + (player->getMagicLevel() + player->getSpecializedMagicLevel(damage.primary.type, true)) * 3;
damage.primary.value = normal_random(
static_cast(levelFormula * mina + minb),
static_cast(levelFormula * maxa + maxb)
@@ -872,7 +872,7 @@ void Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& da
}
}
- if(caster && caster->getPlayer()){
+ if (!damage.extension && caster && caster->getPlayer()) {
// Critical damage
uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE);
// Charm low blow rune)
@@ -891,8 +891,8 @@ void Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& da
}
if (damage.primary.type != COMBAT_HEALING && chance != 0 && uniform_random(1, 100) <= chance) {
damage.critical = true;
- damage.primary.value += (damage.primary.value * caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE ))/100;
- damage.secondary.value += (damage.secondary.value * caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE ))/100;
+ damage.primary.value += (damage.primary.value * caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE)) / 100;
+ damage.secondary.value += (damage.secondary.value * caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE)) / 100;
}
}
if (canCombat) {
@@ -1074,7 +1074,7 @@ void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage, bool u
case COMBAT_FORMULA_LEVELMAGIC: {
//onGetPlayerMinMaxValues(player, level, maglevel)
lua_pushnumber(L, player->getLevel());
- lua_pushnumber(L, player->getMagicLevel());
+ lua_pushnumber(L, player->getMagicLevel() + player->getSpecializedMagicLevel(damage.primary.type, true));
parameters += 2;
break;
}
diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp
index c030ede1797..30376dcafae 100644
--- a/src/creatures/combat/condition.cpp
+++ b/src/creatures/combat/condition.cpp
@@ -198,6 +198,7 @@ Condition* Condition::createCondition(ConditionId_t id, ConditionType_t type, in
case CONDITION_CHANNELMUTEDTICKS:
case CONDITION_YELLTICKS:
case CONDITION_PACIFIED:
+ case CONDITION_TIBIADROMEPOTIONS:
return new ConditionGeneric(id, type, ticks, buff, subId);
default:
@@ -361,8 +362,16 @@ void ConditionAttributes::addCondition(Creature* creature, const Condition* addC
memcpy(statsPercent, conditionAttrs.statsPercent, sizeof(statsPercent));
memcpy(buffs, conditionAttrs.buffs, sizeof(buffs));
memcpy(buffsPercent, conditionAttrs.buffsPercent, sizeof(buffsPercent));
+ memcpy(absorbs, conditionAttrs.absorbs, sizeof(absorbs));
+ memcpy(absorbsPercent, conditionAttrs.absorbsPercent, sizeof(absorbsPercent));
+ memcpy(increases, conditionAttrs.increases, sizeof(increases));
+ memcpy(increasesPercent, conditionAttrs.increasesPercent, sizeof(increasesPercent));
updatePercentBuffs(creature);
updateBuffs(creature);
+ updatePercentAbsorbs(creature);
+ updateAbsorbs(creature);
+ updatePercentIncreases(creature);
+ updateIncreases(creature);
disableDefense = conditionAttrs.disableDefense;
if (Player* player = creature->getPlayer()) {
@@ -385,6 +394,12 @@ bool ConditionAttributes::unserializeProp(ConditionAttr_t attr, PropStream& prop
else if (attr == CONDITIONATTR_BUFFS) {
return propStream.read(buffs[currentBuff++]);
}
+ else if (attr == CONDITIONATTR_ABSORBS) {
+ return propStream.read(absorbs[currentAbsorb++]);
+ }
+ else if (attr == CONDITIONATTR_INCREASES) {
+ return propStream.read(increases[currentIncrease++]);
+ }
return Condition::unserializeProp(attr, propStream);
}
@@ -406,6 +421,16 @@ void ConditionAttributes::serialize(PropWriteStream& propWriteStream)
propWriteStream.write(CONDITIONATTR_BUFFS);
propWriteStream.write(buffs[i]);
}
+
+ for (size_t i = 0; i < COMBAT_COUNT; ++i) {
+ propWriteStream.write(CONDITIONATTR_ABSORBS);
+ propWriteStream.write(absorbs[i]);
+ }
+
+ for (size_t i = 0; i < COMBAT_COUNT; ++i) {
+ propWriteStream.write(CONDITIONATTR_INCREASES);
+ propWriteStream.write(increases[i]);
+ }
}
bool ConditionAttributes::startCondition(Creature* creature)
@@ -417,6 +442,10 @@ bool ConditionAttributes::startCondition(Creature* creature)
creature->setUseDefense(!disableDefense);
updatePercentBuffs(creature);
updateBuffs(creature);
+ updatePercentAbsorbs(creature);
+ updateAbsorbs(creature);
+ updatePercentIncreases(creature);
+ updateIncreases(creature);
if (Player* player = creature->getPlayer()) {
updatePercentSkills(player);
updateSkills(player);
@@ -499,6 +528,46 @@ void ConditionAttributes::updateSkills(Player* player)
}
}
+void ConditionAttributes::updatePercentAbsorbs(Creature* creature)
+{
+ for (uint8_t i = 0; i < COMBAT_COUNT; i++) {
+ if (absorbsPercent[i] == 0) {
+ continue;
+ }
+ absorbs[i] = std::round((100 - creature->getAbsorbPercent(indexToCombatType(i))) * absorbsPercent[i] / 100.);
+ }
+}
+
+void ConditionAttributes::updateAbsorbs(Creature* creature)
+{
+ for (uint8_t i = 0; i < COMBAT_COUNT; i++) {
+ if (absorbs[i] == 0) {
+ continue;
+ }
+ creature->setAbsorbPercent(indexToCombatType(i), absorbs[i]);
+ }
+}
+
+void ConditionAttributes::updatePercentIncreases(Creature* creature)
+{
+ for (uint8_t i = 0; i < COMBAT_COUNT; i++) {
+ if (increasesPercent[i] == 0) {
+ continue;
+ }
+ increases[i] = std::round((100 - creature->getIncreasePercent(indexToCombatType(i))) * increasesPercent[i] / 100.);
+ }
+}
+
+void ConditionAttributes::updateIncreases(Creature* creature)
+{
+ for (uint8_t i = 0; i < COMBAT_COUNT; i++) {
+ if (increases[i] == 0) {
+ continue;
+ }
+ creature->setIncreasePercent(indexToCombatType(i), increases[i]);
+ }
+}
+
void ConditionAttributes::updatePercentBuffs(Creature* creature)
{
for (int32_t i = BUFF_FIRST; i <= BUFF_LAST; ++i) {
@@ -562,6 +631,14 @@ void ConditionAttributes::endCondition(Creature* creature)
creature->setBuff(static_cast(i), -buffs[i]);
}
}
+ for (uint8_t i = 0; i < COMBAT_COUNT; i++) {
+ if (absorbs[i]) {
+ creature->setAbsorbPercent(indexToCombatType(i), -absorbs[i]);
+ }
+ if (increases[i]) {
+ creature->setIncreasePercent(indexToCombatType(i), -increases[i]);
+ }
+ }
if (creature->getMonster() && needUpdateIcons) {
g_game.updateCreatureIcon(creature);
}
@@ -740,6 +817,106 @@ bool ConditionAttributes::setParam(ConditionParam_t param, int32_t value)
return true;
}
+ case CONDITION_PARAM_ABSORB_PHYSICALPERCENT: {
+ absorbsPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_ABSORB_FIREPERCENT: {
+ absorbsPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_ABSORB_ENERGYPERCENT: {
+ absorbsPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_ABSORB_ICEPERCENT: {
+ absorbsPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_ABSORB_EARTHPERCENT: {
+ absorbsPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_ABSORB_DEATHPERCENT: {
+ absorbsPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_ABSORB_HOLYPERCENT: {
+ absorbsPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_ABSORB_LIFEDRAINPERCENT: {
+ absorbsPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_ABSORB_MANADRAINPERCENT: {
+ absorbsPercent[combatTypeToIndex(COMBAT_MANADRAIN)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_ABSORB_DROWNPERCENT: {
+ absorbsPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_INCREASE_PHYSICALPERCENT: {
+ increasesPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_INCREASE_FIREPERCENT: {
+ increasesPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_INCREASE_ENERGYPERCENT: {
+ increasesPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_INCREASE_ICEPERCENT: {
+ increasesPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_INCREASE_EARTHPERCENT: {
+ increasesPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_INCREASE_DEATHPERCENT: {
+ increasesPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_INCREASE_HOLYPERCENT: {
+ increasesPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_INCREASE_LIFEDRAINPERCENT: {
+ increasesPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_INCREASE_MANADRAINPERCENT: {
+ increasesPercent[combatTypeToIndex(COMBAT_MANADRAIN)] = value;
+ return true;
+ }
+
+ case CONDITION_PARAM_INCREASE_DROWNPERCENT: {
+ increasesPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] = value;
+ return true;
+ }
+
default:
return ret;
}
diff --git a/src/creatures/combat/condition.h b/src/creatures/combat/condition.h
index aa62b8c9cee..da932f753b2 100644
--- a/src/creatures/combat/condition.h
+++ b/src/creatures/combat/condition.h
@@ -131,9 +131,17 @@ class ConditionAttributes final : public ConditionGeneric
int32_t statsPercent[STAT_LAST + 1] = {};
int32_t buffsPercent[BUFF_LAST + 1] = {};
int32_t buffs[BUFF_LAST + 1] = {};
+
+ int32_t absorbs[COMBAT_COUNT] = {};
+ int32_t absorbsPercent[COMBAT_COUNT] = {};
+ int32_t increases[COMBAT_COUNT] = {};
+ int32_t increasesPercent[COMBAT_COUNT] = {};
+
int32_t currentSkill = 0;
int32_t currentStat = 0;
int32_t currentBuff = 0;
+ int32_t currentAbsorb = 0;
+ int32_t currentIncrease = 0;
bool disableDefense = false;
@@ -141,7 +149,11 @@ class ConditionAttributes final : public ConditionGeneric
void updateStats(Player* player);
void updatePercentSkills(Player* player);
void updateSkills(Player* player);
- void updatePercentBuffs(Creature* creature);
+ void updatePercentAbsorbs(Creature* creature);
+ void updateAbsorbs(Creature* creature);
+ void updatePercentIncreases(Creature* creature);
+ void updateIncreases(Creature* creature);
+ void updatePercentBuffs(Creature* creature);
void updateBuffs(Creature* creature);
};
diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp
index 9d0efe2d77e..da2dac5cb07 100644
--- a/src/creatures/creature.cpp
+++ b/src/creatures/creature.cpp
@@ -810,6 +810,21 @@ BlockType_t Creature::blockHit(Creature* attacker, CombatType_t combatType, int3
{
BlockType_t blockType = BLOCK_NONE;
+ if (combatType != COMBAT_HEALING && damage != 0) {
+ int32_t value = absorbPercent[combatTypeToIndex(combatType)];
+ if (value != 0)
+ damage -= damage * (value / 100.);
+ value = absorbFlat[combatTypeToIndex(combatType)];
+ if (value != 0)
+ damage = std::max(0, damage + value);
+
+ if (attacker) {
+ value = attacker->getIncreasePercent(combatType);
+ if (value != 0)
+ damage += damage * (value / 100.);
+ }
+ }
+
if (isImmune(combatType)) {
damage = 0;
blockType = BLOCK_IMMUNITY;
diff --git a/src/creatures/creature.h b/src/creatures/creature.h
index e4a32d7318d..f58855442ef 100644
--- a/src/creatures/creature.h
+++ b/src/creatures/creature.h
@@ -412,7 +412,45 @@ class Creature : virtual public Thing
virtual void onRemoveCreature(Creature* creature, bool isLogout);
virtual void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos,
const Tile* oldTile, const Position& oldPos, bool teleport);
+
+ virtual int32_t getReflectPercent(CombatType_t combatType, bool useCharges = false) const {
+ return reflectPercent[combatTypeToIndex(combatType)];
+ }
+ virtual int32_t getReflectFlat(CombatType_t combatType, bool useCharges = false) const {
+ return reflectFlat[combatTypeToIndex(combatType)];
+ }
+
+ virtual void setReflectPercent(CombatType_t combatType, int32_t value) {
+ this->reflectPercent[combatTypeToIndex(combatType)] = std::max(0, this->reflectPercent[combatTypeToIndex(combatType)] + value);
+ }
+ virtual void setReflectFlat(CombatType_t combatType, int32_t value) {
+ this->reflectFlat[combatTypeToIndex(combatType)] = std::max(0, this->reflectFlat[combatTypeToIndex(combatType)] + value);
+ }
+
+ int32_t getAbsorbFlat(CombatType_t combat) const {
+ return absorbFlat[combatTypeToIndex(combat)];
+ }
+
+ void setAbsorbFlat(CombatType_t combat, int32_t value) {
+ absorbFlat[combatTypeToIndex(combat)] += value;
+ }
+ int32_t getAbsorbPercent(CombatType_t combat) const {
+ return absorbPercent[combatTypeToIndex(combat)];
+ }
+
+ void setAbsorbPercent(CombatType_t combat, int32_t value) {
+ absorbPercent[combatTypeToIndex(combat)] += value;
+ }
+
+ int32_t getIncreasePercent(CombatType_t combat) const {
+ return increasePercent[combatTypeToIndex(combat)];
+ }
+
+ void setIncreasePercent(CombatType_t combat, int32_t value) {
+ increasePercent[combatTypeToIndex(combat)] += value;
+ }
+
virtual void onAttackedCreatureDisappear(bool) {}
virtual void onFollowCreatureDisappear(bool) {}
@@ -544,7 +582,14 @@ class Creature : virtual public Thing
uint16_t manaShield = 0;
uint16_t maxManaShield = 0;
- int32_t varBuffs[BUFF_LAST + 1] = { 100, 100 };
+ int32_t varBuffs[BUFF_LAST + 1] = { 100, 100, 100 };
+
+ int32_t reflectPercent[COMBAT_COUNT] = { 0 };
+ int32_t reflectFlat[COMBAT_COUNT] = { 0 };
+
+ int32_t absorbPercent[COMBAT_COUNT] = { 0 };
+ int32_t increasePercent[COMBAT_COUNT] = { 0 };
+ int32_t absorbFlat[COMBAT_COUNT] = { 0 };
Outfit_t currentOutfit;
Outfit_t defaultOutfit;
diff --git a/src/creatures/creatures_definitions.hpp b/src/creatures/creatures_definitions.hpp
index c9d3a5f2ed6..49b0b5af3d8 100644
--- a/src/creatures/creatures_definitions.hpp
+++ b/src/creatures/creatures_definitions.hpp
@@ -67,12 +67,14 @@ enum ConditionAttr_t {
CONDITIONATTR_ISBUFF,
CONDITIONATTR_SUBID,
CONDITIONATTR_MANASHIELD,
+ CONDITIONATTR_ABSORBS,
+ CONDITIONATTR_INCREASES,
//reserved for serialization
CONDITIONATTR_END = 254,
};
-enum ConditionType_t {
+enum ConditionType_t : uint64_t {
CONDITION_NONE,
CONDITION_POISON = 1 << 0,
@@ -103,7 +105,17 @@ enum ConditionType_t {
CONDITION_PACIFIED = 1 << 25,
CONDITION_SPELLCOOLDOWN = 1 << 26,
CONDITION_SPELLGROUPCOOLDOWN = 1 << 27,
- CONDITION_ROOTED = 1 << 28,
+ CONDITION_LESSERHEX = 1 << 28,
+ CONDITION_INTENSEHEX = 1 << 29,
+ CONDITION_GREATERHEX = 1 << 30,
+ CONDITION_ROOTED = static_cast(1) << 31,
+ CONDITION_FEARED = static_cast(1) << 32,
+ CONDITION_GOSHNAR1 = static_cast(1) << 33,
+ CONDITION_GOSHNAR2 = static_cast(1) << 34,
+ CONDITION_GOSHNAR3 = static_cast(1) << 35,
+ CONDITION_GOSHNAR4 = static_cast(1) << 36,
+ CONDITION_GOSHNAR5 = static_cast(1) << 37,
+ CONDITION_TIBIADROMEPOTIONS = static_cast(1) << 38,
};
enum ConditionParam_t {
@@ -164,6 +176,26 @@ enum ConditionParam_t {
CONDITION_PARAM_MANASHIELD = 55,
CONDITION_PARAM_BUFF_DAMAGEDEALT = 56,
CONDITION_PARAM_BUFF_DAMAGERECEIVED = 57,
+ CONDITION_PARAM_ABSORB_PHYSICALPERCENT = 58,
+ CONDITION_PARAM_ABSORB_FIREPERCENT = 59,
+ CONDITION_PARAM_ABSORB_ENERGYPERCENT = 60,
+ CONDITION_PARAM_ABSORB_ICEPERCENT = 61,
+ CONDITION_PARAM_ABSORB_EARTHPERCENT = 62,
+ CONDITION_PARAM_ABSORB_DEATHPERCENT = 63,
+ CONDITION_PARAM_ABSORB_HOLYPERCENT = 64,
+ CONDITION_PARAM_ABSORB_LIFEDRAINPERCENT = 65,
+ CONDITION_PARAM_ABSORB_MANADRAINPERCENT = 66,
+ CONDITION_PARAM_ABSORB_DROWNPERCENT = 67,
+ CONDITION_PARAM_INCREASE_PHYSICALPERCENT = 68,
+ CONDITION_PARAM_INCREASE_FIREPERCENT = 69,
+ CONDITION_PARAM_INCREASE_ENERGYPERCENT = 70,
+ CONDITION_PARAM_INCREASE_ICEPERCENT = 71,
+ CONDITION_PARAM_INCREASE_EARTHPERCENT = 72,
+ CONDITION_PARAM_INCREASE_DEATHPERCENT = 73,
+ CONDITION_PARAM_INCREASE_HOLYPERCENT = 74,
+ CONDITION_PARAM_INCREASE_LIFEDRAINPERCENT = 75,
+ CONDITION_PARAM_INCREASE_MANADRAINPERCENT = 76,
+ CONDITION_PARAM_INCREASE_DROWNPERCENT = 77,
};
enum stats_t {
@@ -180,9 +212,10 @@ enum stats_t {
enum buffs_t {
BUFF_DAMAGEDEALT,
BUFF_DAMAGERECEIVED,
+ BUFF_HEALINGRECEIVED,
BUFF_FIRST = BUFF_DAMAGEDEALT,
- BUFF_LAST = BUFF_DAMAGERECEIVED,
+ BUFF_LAST = BUFF_HEALINGRECEIVED,
};
enum formulaType_t {
@@ -403,6 +436,7 @@ enum RaceType_t : uint8_t {
RACE_UNDEAD,
RACE_FIRE,
RACE_ENERGY,
+ RACE_INK,
};
enum BlockType_t : uint8_t {
@@ -570,8 +604,9 @@ enum CombatType_t : uint16_t {
COMBAT_ICEDAMAGE = 1 << 9,
COMBAT_HOLYDAMAGE = 1 << 10,
COMBAT_DEATHDAMAGE = 1 << 11,
+ COMBAT_NEUTRALDAMAGE = 1 << 12,
- COMBAT_COUNT = 12
+ COMBAT_COUNT = 13
};
enum PlayerAsyncOngoingTaskFlags : uint64_t {
@@ -756,7 +791,8 @@ struct LightInfo {
constexpr LightInfo(uint8_t newLevel, uint8_t newColor) : level(newLevel), color(newColor) {}
};
-struct CombatDamage {
+struct CombatDamage
+{
struct {
CombatType_t type;
int32_t value;
@@ -766,9 +802,11 @@ struct CombatDamage {
bool critical;
int affected;
bool extension;
+
std::string exString;
- CombatDamage() {
+ CombatDamage()
+ {
origin = ORIGIN_NONE;
primary.type = secondary.type = COMBAT_NONE;
primary.value = secondary.value = 0;
diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp
index 0039d7b7fd0..03446291b86 100644
--- a/src/creatures/monsters/monster.cpp
+++ b/src/creatures/monsters/monster.cpp
@@ -106,12 +106,13 @@ bool Monster::canWalkOnFieldType(CombatType_t combatType) const
}
}
-uint32_t Monster::getReflectValue(CombatType_t reflectType) const {
+int32_t Monster::getReflectPercent(CombatType_t reflectType, bool useCharges) const {
+ int32_t result = Creature::getReflectPercent(reflectType, useCharges);
auto it = mType->info.reflectMap.find(reflectType);
if (it != mType->info.reflectMap.end()) {
- return it->second;
+ result += it->second;
}
- return 0;
+ return result;
}
uint32_t Monster::getHealingCombatValue(CombatType_t healingType) const {
@@ -1032,7 +1033,11 @@ void Monster::onThinkTarget(uint32_t interval)
}
if (mType->info.changeTargetChance >= uniform_random(1, 100)) {
- searchTarget(TARGETSEARCH_DEFAULT);
+ if (mType->info.targetDistance <= 1) {
+ searchTarget(TARGETSEARCH_RANDOM);
+ } else {
+ searchTarget(TARGETSEARCH_NEAREST);
+ }
}
}
}
diff --git a/src/creatures/monsters/monster.h b/src/creatures/monsters/monster.h
index 7996c288ef0..d70d1486a02 100644
--- a/src/creatures/monsters/monster.h
+++ b/src/creatures/monsters/monster.h
@@ -136,7 +136,7 @@ class Monster final : public Creature
this->spawnMonster = newSpawnMonster;
}
- uint32_t getReflectValue(CombatType_t combatType) const;
+ int32_t getReflectPercent(CombatType_t combatType, bool useCharges = false) const override;
uint32_t getHealingCombatValue(CombatType_t healingType) const;
bool canWalkOnFieldType(CombatType_t combatType) const;
diff --git a/src/creatures/monsters/monsters.cpp b/src/creatures/monsters/monsters.cpp
index cb67f56c658..b4fd8de264e 100644
--- a/src/creatures/monsters/monsters.cpp
+++ b/src/creatures/monsters/monsters.cpp
@@ -795,6 +795,8 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m
mType->info.race = RACE_FIRE;
} else if (tmpStrValue == "energy" || tmpInt == 5) {
mType->info.race = RACE_ENERGY;
+ } else if (tmpStrValue == "ink" || tmpInt == 6) {
+ mType->info.race = RACE_INK;
} else {
SPDLOG_WARN("[Monsters::loadMonster] - Unknown race type {}. {}",
attr.as_string(), file);
diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp
index 8712919d793..d04b5097809 100644
--- a/src/creatures/players/player.cpp
+++ b/src/creatures/players/player.cpp
@@ -230,27 +230,38 @@ Item* Player::getWeapon(Slots_t slot, bool ignoreAmmo) const
return nullptr;
}
- if (!ignoreAmmo && weaponType == WEAPON_DISTANCE) {
- const ItemType& it = Item::items[item->getID()];
- if (it.ammoType != AMMO_NONE) {
- Item* quiver = inventory[CONST_SLOT_RIGHT];
- if (!quiver || quiver->getWeaponType() != WEAPON_QUIVER)
- return nullptr;
- Container* container = quiver->getContainer();
- if (!container)
- return nullptr;
- bool found = false;
- for (Item* ammoItem : container->getItemList()) {
- if (ammoItem->getAmmoType() == it.ammoType) {
- item = ammoItem;
- found = true;
- break;
- }
- }
- if (!found)
- return nullptr;
- }
- }
+ if (!ignoreAmmo && weaponType == WEAPON_DISTANCE) {
+ const ItemType& it = Item::items[item->getID()];
+ if (it.ammoType != AMMO_NONE) {
+ Item* quiver = inventory[CONST_SLOT_RIGHT];
+ if (!quiver || quiver->getWeaponType() != WEAPON_QUIVER) {
+ return nullptr;
+ }
+
+ Container* container = quiver->getContainer();
+ if (!container) {
+ return nullptr;
+ }
+
+ bool found = false;
+ for (Item* ammoItem : container->getItemList()) {
+ if (ammoItem->getAmmoType() == it.ammoType) {
+ const Weapon* weapon = g_weapons->getWeapon(item);
+ if (weapon && weapon->getReqLevel() <= this->getLevel()) {
+ return nullptr;
+ }
+
+ item = ammoItem;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return nullptr;
+ }
+ }
+ }
return item;
}
@@ -2223,21 +2234,6 @@ BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_
}
}
}
- if (attacker) {
- const int16_t& reflectPercent = it.abilities->reflectPercent[combatTypeToIndex(combatType)];
- if (reflectPercent != 0) {
- CombatParams params;
- params.combatType = combatType;
- params.impactEffect = CONST_ME_MAGIC_BLUE;
-
- CombatDamage reflectDamage;
- reflectDamage.origin = ORIGIN_SPELL;
- reflectDamage.primary.type = combatType;
- reflectDamage.primary.value = std::round(-damage * (reflectPercent / 100.));
-
- Combat::doCombatHealth(this, attacker, reflectDamage, params);
- }
- }
}
uint8_t slots = Item::items[item->getID()].imbuingSlots;
@@ -3036,7 +3032,7 @@ Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** de
return tmpContainer;
}
- n--;
+ --n;
}
for (Item* tmpContainerItem : tmpContainer->getItemList()) {
@@ -3244,7 +3240,7 @@ uint32_t Player::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) con
if (Container* container = item->getContainer()) {
for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) {
- if ((*it)->getID() == itemId) {
+ if ((*it) && (*it)->getID() == itemId) {
count += Item::countByType(*it, subType);
}
}
@@ -3857,9 +3853,17 @@ void Player::onAddCombatCondition(ConditionType_t type)
sendTextMessage(MESSAGE_FAILURE, "You are drunk.");
break;
+ case CONDITION_LESSERHEX:
+ case CONDITION_INTENSEHEX:
+ case CONDITION_GREATERHEX:
+ sendTextMessage(MESSAGE_FAILURE, "You are hexed.");
+ break;
case CONDITION_ROOTED:
sendTextMessage(MESSAGE_FAILURE, "You are rooted.");
break;
+ case CONDITION_FEARED:
+ sendTextMessage(MESSAGE_FAILURE, "You are feared.");
+ break;
case CONDITION_CURSED:
sendTextMessage(MESSAGE_FAILURE, "You are cursed.");
@@ -4620,6 +4624,211 @@ void Player::setTibiaCoins(int32_t v)
coinBalance = v;
}
+int32_t Player::getCleavePercent(bool useCharges) const {
+ int32_t result = cleavePercent;
+ for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) {
+ if (!isItemAbilityEnabled(static_cast(slot))) {
+ continue;
+ }
+
+ Item* item = inventory[slot];
+ if (!item) {
+ continue;
+ }
+
+ const ItemType& it = Item::items[item->getID()];
+ if (it.abilities) {
+ const int32_t& itemValue = it.abilities->cleavePercent;
+ if (itemValue != 0) {
+ result += itemValue;
+ if (useCharges) {
+ uint16_t charges = item->getCharges();
+ if (charges != 0) {
+ g_game.transformItem(item, item->getID(), charges - 1);
+ }
+ }
+ }
+ }
+ }
+ return result;
+}
+
+int32_t Player::getPerfectShotDamage(uint8_t range, bool useCharges) const {
+ int32_t result = 0;
+ auto it = perfectShot.find(range);
+ if (it != perfectShot.end())
+ result = it->second;
+ for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) {
+ if (!isItemAbilityEnabled(static_cast(slot))) {
+ continue;
+ }
+
+ Item* item = inventory[slot];
+ if (!item) {
+ continue;
+ }
+
+ const ItemType& it = Item::items[item->getID()];
+ if (it.abilities) {
+ if (it.abilities->perfectShotRange == range) {
+ result += it.abilities->perfectShotDamage;
+ if (useCharges) {
+ uint16_t charges = item->getCharges();
+ if (charges != 0) {
+ g_game.transformItem(item, item->getID(), charges - 1);
+ }
+ }
+ }
+ }
+ }
+ return result;
+}
+
+int32_t Player::getSpecializedMagicLevel(CombatType_t combat, bool useCharges) const {
+ int32_t result = specializedMagicLevel[combatTypeToIndex(combat)];
+ for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) {
+ if (!isItemAbilityEnabled(static_cast(slot))) {
+ continue;
+ }
+
+ Item* item = inventory[slot];
+ if (!item) {
+ continue;
+ }
+
+ const ItemType& it = Item::items[item->getID()];
+ if (it.abilities) {
+ int32_t itemValue = it.abilities->specializedMagicLevel[combatTypeToIndex(combat)];
+ if (itemValue != 0) {
+ result += itemValue;
+ if (useCharges) {
+ uint16_t charges = item->getCharges();
+ if (charges != 0) {
+ g_game.transformItem(item, item->getID(), charges - 1);
+ }
+ }
+ }
+ }
+ }
+ return result;
+}
+
+int32_t Player::getMagicShieldCapacityFlat(bool useCharges) const {
+ int32_t result = magicShieldCapacityFlat;
+ for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) {
+ if (!isItemAbilityEnabled(static_cast(slot))) {
+ continue;
+ }
+
+ Item* item = inventory[slot];
+ if (!item) {
+ continue;
+ }
+
+ const ItemType& it = Item::items[item->getID()];
+ if (it.abilities) {
+ int32_t itemValue = it.abilities->magicShieldCapacityFlat;
+ if (itemValue != 0) {
+ result += itemValue;
+ if (useCharges) {
+ uint16_t charges = item->getCharges();
+ if (charges != 0) {
+ g_game.transformItem(item, item->getID(), charges - 1);
+ }
+ }
+ }
+ }
+ }
+ return result;
+}
+
+int32_t Player::getMagicShieldCapacityPercent(bool useCharges) const {
+ int32_t result = magicShieldCapacityPercent;
+ for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) {
+ if (!isItemAbilityEnabled(static_cast(slot))) {
+ continue;
+ }
+
+ Item* item = inventory[slot];
+ if (!item) {
+ continue;
+ }
+
+ const ItemType& it = Item::items[item->getID()];
+ if (it.abilities) {
+ int32_t itemValue = it.abilities->magicShieldCapacityPercent;
+ if (itemValue != 0) {
+ result += itemValue;
+ if (useCharges) {
+ uint16_t charges = item->getCharges();
+ if (charges != 0) {
+ g_game.transformItem(item, item->getID(), charges - 1);
+ }
+ }
+ }
+ }
+ }
+ return result;
+}
+
+int32_t Player::getReflectPercent(CombatType_t combat, bool useCharges) const {
+ int32_t result = reflectPercent[combatTypeToIndex(combat)];
+ for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) {
+ if (!isItemAbilityEnabled(static_cast(slot))) {
+ continue;
+ }
+
+ Item* item = inventory[slot];
+ if (!item) {
+ continue;
+ }
+
+ const ItemType& it = Item::items[item->getID()];
+ if (it.abilities) {
+ int32_t itemValue = it.abilities->reflectPercent[combatTypeToIndex(combat)];
+ if (itemValue != 0) {
+ result += itemValue;
+ if (useCharges) {
+ uint16_t charges = item->getCharges();
+ if (charges != 0) {
+ g_game.transformItem(item, item->getID(), charges - 1);
+ }
+ }
+ }
+ }
+ }
+ return result;
+}
+
+int32_t Player::getReflectFlat(CombatType_t combat, bool useCharges) const {
+ int32_t result = reflectFlat[combatTypeToIndex(combat)];
+ for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) {
+ if (!isItemAbilityEnabled(static_cast(slot))) {
+ continue;
+ }
+
+ Item* item = inventory[slot];
+ if (!item) {
+ continue;
+ }
+
+ const ItemType& it = Item::items[item->getID()];
+ if (it.abilities) {
+ int32_t itemValue = it.abilities->reflectFlat[combatTypeToIndex(combat)];
+ if (itemValue != 0) {
+ result += itemValue;
+ if (useCharges) {
+ uint16_t charges = item->getCharges();
+ if (charges != 0) {
+ g_game.transformItem(item, item->getID(), charges - 1);
+ }
+ }
+ }
+ }
+ }
+ return result;
+}
+
PartyShields_t Player::getPartyShield(const Player* player) const
{
if (!player) {
diff --git a/src/creatures/players/player.h b/src/creatures/players/player.h
index 1d8069ba805..5c8c4849cf4 100644
--- a/src/creatures/players/player.h
+++ b/src/creatures/players/player.h
@@ -362,6 +362,47 @@ class Player final : public Creature, public Cylinder
Party* getParty() const {
return party;
}
+
+ int32_t getCleavePercent(bool useCharges = false) const;
+
+ void setCleavePercent(int32_t value) {
+ cleavePercent = std::max(0, cleavePercent + value);
+ }
+
+ int32_t getPerfectShotDamage(uint8_t range, bool useCharges = false) const;
+
+ void setPerfectShotDamage(uint8_t range, int32_t damage) {
+ int32_t actualDamage = getPerfectShotDamage(range);
+ bool aboveZero = (actualDamage != 0);
+ actualDamage += damage;
+ if (actualDamage == 0 && aboveZero)
+ perfectShot.erase(range);
+ else
+ perfectShot[range] = actualDamage;
+ }
+
+ int32_t getSpecializedMagicLevel(CombatType_t combat, bool useCharges = false) const;
+
+ void setSpecializedMagicLevel(CombatType_t combat, int32_t value) {
+ specializedMagicLevel[combatTypeToIndex(combat)] = std::max(0, specializedMagicLevel[combatTypeToIndex(combat)] + value);
+ }
+
+ int32_t getMagicShieldCapacityFlat(bool useCharges = false) const;
+
+ void setMagicShieldCapacityFlat(int32_t value) {
+ magicShieldCapacityFlat += value;
+ }
+
+ int32_t getMagicShieldCapacityPercent(bool useCharges = false) const;
+
+ void setMagicShieldCapacityPercent(int32_t value) {
+ magicShieldCapacityPercent += value;
+ }
+
+ int32_t getReflectPercent(CombatType_t combat, bool useCharges = false) const override;
+
+ int32_t getReflectFlat(CombatType_t combat, bool useCharges = false) const override;
+
PartyShields_t getPartyShield(const Player* player) const;
bool isInviting(const Player* player) const;
bool isPartner(const Player* player) const;
@@ -1000,9 +1041,9 @@ class Player final : public Creature, public Cylinder
client->sendCreatureShield(creature);
}
}
- void sendCreatureType(const Creature* creature, uint8_t creatureType) {
+ void sendCreatureUpdate(const Creature* creature) {
if (client) {
- client->sendCreatureType(creature, creatureType);
+ client->sendCreatureUpdate(creature);
}
}
void sendSpellCooldown(uint8_t spellId, uint32_t time) {
@@ -2116,6 +2157,12 @@ class Player final : public Creature, public Cylinder
bool marketMenu = false; // Menu option 'show in market'
bool exerciseTraining = false;
+ int32_t specializedMagicLevel[COMBAT_COUNT] = { 0 };
+ int32_t cleavePercent = 0;
+ std::map perfectShot;
+ int32_t magicShieldCapacityFlat = 0;
+ int32_t magicShieldCapacityPercent = 0;
+
static uint32_t playerAutoID;
void updateItemsLight(bool internal = false);
diff --git a/src/game/game.cpp b/src/game/game.cpp
index 35c18fb42b9..ba8e632de28 100644
--- a/src/game/game.cpp
+++ b/src/game/game.cpp
@@ -4454,7 +4454,55 @@ void Game::playerLookInBattleList(uint32_t playerId, uint32_t creatureId)
g_events->eventPlayerOnLookInBattleList(player, creature, lookDistance);
}
-void Game::playerQuickLoot(uint32_t playerId, const Position& pos, uint16_t spriteId, uint8_t stackPos, Item* defaultItem)
+void Game::playerLootAllCorpses(Player* player, const Position& pos, bool lootAllCorpses) {
+ if (lootAllCorpses) {
+ Tile *tile = g_game.map.getTile(pos.x, pos.y, pos.z);
+ if (!tile) {
+ player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
+ return;
+ }
+
+ TileItemVector *itemVector = tile->getItemList();
+ uint16_t corpses = 0;
+ for (Item *tileItem: *itemVector) {
+ if (!tileItem) {
+ continue;
+ }
+
+ Container *tileCorpse = tileItem->getContainer();
+ if (!tileCorpse || !tileCorpse->isCorpse() || tileCorpse->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) || tileCorpse->hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) {
+ continue;
+ }
+
+ if (!tileCorpse->isRewardCorpse()) {
+ uint32_t corpseOwner = tileCorpse->getCorpseOwner();
+ if (corpseOwner != 0 && !player->canOpenCorpse(corpseOwner)) {
+ continue;
+ }
+ }
+
+ corpses++;
+ internalQuickLootCorpse(player, tileCorpse);
+ if (corpses >= 10) {
+ break;
+ }
+ }
+
+ if (corpses > 0) {
+ if (corpses > 1) {
+ std::stringstream string;
+ string << "You looted " << corpses << " corpses.";
+ player->sendTextMessage(MESSAGE_LOOT, string.str());
+ }
+
+ return;
+ }
+ }
+
+ browseField = false;
+}
+
+void Game::playerQuickLoot(uint32_t playerId, const Position& pos, uint16_t spriteId, uint8_t stackPos, Item* defaultItem, bool lootAllCorpses)
{
Player* player = getPlayerByID(playerId);
if (!player) {
@@ -4466,7 +4514,7 @@ void Game::playerQuickLoot(uint32_t playerId, const Position& pos, uint16_t spri
SchedulerTask* task = createSchedulerTask(delay, std::bind(
&Game::playerQuickLoot,
this, player->getID(), pos,
- spriteId, stackPos, defaultItem));
+ spriteId, stackPos, defaultItem, lootAllCorpses));
player->setNextActionTask(task);
return;
}
@@ -4480,7 +4528,7 @@ void Game::playerQuickLoot(uint32_t playerId, const Position& pos, uint16_t spri
SchedulerTask* task = createSchedulerTask(0, std::bind(
&Game::playerQuickLoot,
this, player->getID(), pos,
- spriteId, stackPos, defaultItem));
+ spriteId, stackPos, defaultItem, lootAllCorpses));
player->setNextWalkActionTask(task);
} else {
player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
@@ -4513,9 +4561,15 @@ void Game::playerQuickLoot(uint32_t playerId, const Position& pos, uint16_t spri
return;
}
+ playerLootAllCorpses(player, pos, lootAllCorpses);
+
Container* corpse = nullptr;
if (pos.x == 0xffff) {
corpse = item->getParent()->getContainer();
+ if (corpse && corpse->getID() == ITEM_BROWSEFIELD) {
+ corpse = item->getContainer();
+ browseField = true;
+ }
} else {
corpse = item->getContainer();
}
@@ -4533,7 +4587,7 @@ void Game::playerQuickLoot(uint32_t playerId, const Position& pos, uint16_t spri
}
}
- if (pos.x == 0xffff) {
+ if (pos.x == 0xffff && !browseField) {
uint32_t worth = item->getWorth();
ObjectCategory_t category = getObjectCategory(item);
ReturnValue ret = internalQuickLootItem(player, item, category);
@@ -5470,32 +5524,75 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta
bool canHeal = false;
CombatDamage damageHeal;
damageHeal.primary.type = COMBAT_HEALING;
+
+ bool damageAbsorbMessage = false;
+ bool damageIncreaseMessage = false;
bool canReflect = false;
CombatDamage damageReflected;
+ CombatParams damageReflectedParams;
BlockType_t primaryBlockType, secondaryBlockType;
+ Player* targetPlayer = target->getPlayer();
+
if (damage.primary.type != COMBAT_NONE) {
+
// Damage reflection primary
- if (attacker && target->getMonster()) {
- uint32_t primaryReflect = target->getMonster()->getReflectValue(damage.primary.type);
- if (primaryReflect > 0) {
- damageReflected.primary.type = damage.primary.type;
- damageReflected.primary.value = std::ceil((damage.primary.value) * (primaryReflect / 100.));
- damageReflected.extension = true;
- damageReflected.exString = "(damage reflection)";
- canReflect = true;
+ if (!damage.extension && attacker) {
+ if (targetPlayer && attacker->getMonster() && damage.primary.type != COMBAT_HEALING) {
+ // Charm rune (target as player)
+ MonsterType* mType = g_monsters.getMonsterType(attacker->getName());
+ if (mType) {
+ IOBestiary g_bestiary;
+ charmRune_t activeCharm = g_bestiary.getCharmFromTarget(targetPlayer, mType);
+ if (activeCharm == CHARM_PARRY) {
+ Charm* charm = g_bestiary.getBestiaryCharm(activeCharm);
+ if (charm && charm->type == CHARM_DEFENSIVE && (charm->chance > normal_random(0, 100))) {
+ g_bestiary.parseCharmCombat(charm, targetPlayer, attacker, (damage.primary.value + damage.secondary.value));
+ }
+ }
+ }
+ }
+ int32_t primaryReflectPercent = target->getReflectPercent(damage.primary.type, true);
+ int32_t primaryReflectFlat = target->getReflectFlat(damage.primary.type, true);
+ if (primaryReflectPercent > 0 || primaryReflectFlat > 0) {
+ int32_t distanceX = Position::getDistanceX(target->getPosition(), attacker->getPosition());
+ int32_t distanceY = Position::getDistanceY(target->getPosition(), attacker->getPosition());
+ if (target->getMonster() || damage.primary.type != COMBAT_PHYSICALDAMAGE || primaryReflectPercent > 0 || std::max(distanceX, distanceY) < 2) {
+ damageReflected.primary.value = std::ceil(damage.primary.value * primaryReflectPercent / 100.) + std::max(-static_cast(std::ceil(attacker->getMaxHealth() * 0.01)), std::max(damage.primary.value, -(static_cast(primaryReflectFlat))));
+ if (targetPlayer)
+ damageReflected.primary.type = COMBAT_NEUTRALDAMAGE;
+ else
+ damageReflected.primary.type = damage.primary.type;
+ if (!damageReflected.exString.empty()) {
+ damageReflected.exString += ", ";
+ }
+ damageReflected.extension = true;
+ damageReflected.exString += "damage reflection";
+ damageReflectedParams.combatType = damage.primary.type;
+ damageReflectedParams.aggressive = true;
+ canReflect = true;
+ }
}
}
damage.primary.value = -damage.primary.value;
// Damage healing primary
- if (attacker && target->getMonster()) {
- uint32_t primaryHealing = target->getMonster()->getHealingCombatValue(damage.primary.type);
- if (primaryHealing > 0) {
- damageHeal.primary.value = std::ceil((damage.primary.value) * (primaryHealing / 100.));
- canHeal = true;
+ if (attacker) {
+ if (target->getMonster()) {
+ uint32_t primaryHealing = target->getMonster()->getHealingCombatValue(damage.primary.type);
+ if (primaryHealing > 0) {
+ damageHeal.primary.value = std::ceil((damage.primary.value) * (primaryHealing / 100.));
+ canHeal = true;
+ }
}
+ if (targetPlayer && attacker->getAbsorbPercent(damage.primary.type) != 0)
+ damageAbsorbMessage = true;
+ if (attacker->getPlayer() && attacker->getIncreasePercent(damage.primary.type) != 0)
+ damageIncreaseMessage = true;
+ damage.primary.value *= attacker->getBuff(BUFF_DAMAGEDEALT) / 100.;
}
+ damage.primary.value *= target->getBuff(BUFF_DAMAGERECEIVED) / 100.;
+
primaryBlockType = target->blockHit(attacker, damage.primary.type, damage.primary.value, checkDefense, checkArmor, field);
damage.primary.value = -damage.primary.value;
@@ -5506,30 +5603,45 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta
if (damage.secondary.type != COMBAT_NONE) {
// Damage reflection secondary
- if (attacker && target->getMonster()) {
- uint32_t secondaryReflect = target->getMonster()->getReflectValue(damage.secondary.type);
- if (secondaryReflect > 0) {
+ if (!damage.extension && attacker && target->getMonster()) {
+ uint32_t secondaryReflectPercent = target->getReflectPercent(damage.secondary.type, true);
+ uint32_t secondaryReflectFlat = target->getReflectFlat(damage.secondary.type, true);
+ if (secondaryReflectPercent > 0 || secondaryReflectFlat > 0) {
if (!canReflect) {
damageReflected.primary.type = damage.secondary.type;
- damageReflected.primary.value = std::ceil((damage.secondary.value) * (secondaryReflect / 100.));
+ damageReflected.primary.value = std::ceil(damage.secondary.value * secondaryReflectPercent / 100.) + std::max(-static_cast(std::ceil(attacker->getMaxHealth() * 0.01)), std::max(damage.secondary.value, -(static_cast(secondaryReflectFlat))));
+ if (!damageReflected.exString.empty()) {
+ damageReflected.exString += ", ";
+ }
damageReflected.extension = true;
- damageReflected.exString = "(damage reflection)";
+ damageReflected.exString += "damage reflection";
+ damageReflectedParams.combatType = damage.primary.type;
+ damageReflectedParams.aggressive = true;
canReflect = true;
} else {
damageReflected.secondary.type = damage.secondary.type;
- damageReflected.secondary.value = std::ceil((damage.secondary.value) * (secondaryReflect / 100.));
+ damageReflected.primary.value = std::ceil(damage.secondary.value * secondaryReflectPercent / 100.) + std::max(-static_cast(std::ceil(attacker->getMaxHealth() * 0.01)), std::max(damage.secondary.value, -(static_cast(secondaryReflectFlat))));
}
}
}
damage.secondary.value = -damage.secondary.value;
// Damage healing secondary
- if (attacker && target->getMonster()) {
- uint32_t secondaryHealing = target->getMonster()->getHealingCombatValue(damage.secondary.type);
- if (secondaryHealing > 0) {;
- damageHeal.primary.value += std::ceil((damage.secondary.value) * (secondaryHealing / 100.));
- canHeal = true;
+ if (attacker) {
+ if (target->getMonster()) {
+ uint32_t secondaryHealing = target->getMonster()->getHealingCombatValue(damage.secondary.type);
+ if (secondaryHealing > 0) {
+ damageHeal.primary.value = std::ceil((damage.secondary.value) * (secondaryHealing / 100.));
+ canHeal = true;
+ }
}
+ if (targetPlayer && attacker->getAbsorbPercent(damage.secondary.type) != 0)
+ damageAbsorbMessage = true;
+ if (attacker->getPlayer() && attacker->getIncreasePercent(damage.secondary.type) != 0)
+ damageIncreaseMessage = true;
+ damage.secondary.value *= attacker->getBuff(BUFF_DAMAGEDEALT) / 100.;
}
+ damage.secondary.value *= target->getBuff(BUFF_DAMAGERECEIVED) / 100.;
+
secondaryBlockType = target->blockHit(attacker, damage.secondary.type, damage.secondary.value, false, false, field);
damage.secondary.value = -damage.secondary.value;
@@ -5537,8 +5649,26 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta
} else {
secondaryBlockType = BLOCK_NONE;
}
+
+ if (damage.primary.type == COMBAT_HEALING)
+ damage.primary.value *= target->getBuff(BUFF_HEALINGRECEIVED) / 100.;
+
+ if (damageAbsorbMessage) {
+ if (!damage.exString.empty()) {
+ damage.exString += ", ";
+ }
+ damage.exString += "active elemental resiliance";
+ }
+
+ if (damageIncreaseMessage) {
+ if (!damage.exString.empty()) {
+ damage.exString += ", ";
+ }
+ damage.exString += "active elemental amplification";
+ }
+
if (canReflect) {
- combatChangeHealth(target, attacker, damageReflected, false);
+ Combat::doCombatHealth(target, attacker, damageReflected, damageReflectedParams);
}
if (canHeal) {
combatChangeHealth(nullptr, target, damageHeal);
@@ -5578,6 +5708,10 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo
color = TEXTCOLOR_PURPLE;
effect = CONST_ME_ENERGYHIT;
break;
+ case RACE_INK:
+ color = TEXTCOLOR_DARKGREY;
+ effect = CONST_ME_BLACKHIT;
+ break;
default:
color = TEXTCOLOR_NONE;
effect = CONST_ME_NONE;
@@ -5634,6 +5768,11 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo
effect = CONST_ME_MAGIC_RED;
break;
}
+ case COMBAT_NEUTRALDAMAGE: {
+ color = TEXTCOLOR_NEUTRALDAMAGE;
+ effect = CONST_ME_REDSMOKE;
+ break;
+ }
default: {
color = TEXTCOLOR_NONE;
effect = CONST_ME_NONE;
@@ -5673,7 +5812,6 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
return combatChangeHealth(attacker, target, damage);
}
}
-
int32_t realHealthChange = target->getHealth();
target->gainHealth(attacker, damage.primary.value);
realHealthChange = target->getHealth() - realHealthChange;
@@ -5704,12 +5842,6 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
continue;
}
- if (damage.primary.type == COMBAT_HEALING && target && target->getMonster()) {
- if (target != attacker) {
- return false;
- }
- }
-
if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
ss.str({});
ss << "You heal " << target->getNameDescription() << " for " << damageString;
@@ -5772,20 +5904,44 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
damage.primary.value = std::abs(damage.primary.value);
damage.secondary.value = std::abs(damage.secondary.value);
+ if (attacker && attackerPlayer && damage.extension == false && damage.origin == ORIGIN_RANGED && target == attackerPlayer->getAttackedCreature()) {
+ const Position& targetPos = target->getPosition();
+ const Position& attackerPos = attacker->getPosition();
+ if (targetPos.z == attackerPos.z) {
+ int32_t distanceX = Position::getDistanceX(targetPos, attackerPos);
+ int32_t distanceY = Position::getDistanceY(targetPos, attackerPos);
+ int32_t damageX = attackerPlayer->getPerfectShotDamage(distanceX, true);
+ int32_t damageY = attackerPlayer->getPerfectShotDamage(distanceY, true);
+ Item* item = attackerPlayer->getWeapon();
+ if (item && item->getWeaponType() == WEAPON_DISTANCE) {
+ Item* quiver = attackerPlayer->getInventoryItem(CONST_SLOT_RIGHT);
+ if (quiver->getWeaponType() == WEAPON_QUIVER) {
+ if (quiver->getPerfectShotRange() == distanceX)
+ damageX -= quiver->getPerfectShotDamage();
+ else if (quiver->getPerfectShotRange() == distanceY)
+ damageY -= quiver->getPerfectShotDamage();
+ }
+ }
+ if (damageX != 0 || damageY != 0) {
+ int32_t totalDamage = damageX;
+ if (distanceX != distanceY)
+ totalDamage += damageY;
+ damage.primary.value += totalDamage;
+ if (!damage.exString.empty()) {
+ damage.exString += ", ";
+ }
+ damage.exString += "perfect shot";
+ }
+ }
+ }
+
TextMessage message;
message.position = targetPos;
if (!isEvent) {
g_events->eventCreatureOnDrainHealth(target, attacker, damage.primary.type, damage.primary.value, damage.secondary.type, damage.secondary.value, message.primary.color, message.secondary.color);
}
- if (damage.origin != ORIGIN_NONE && attacker && damage.primary.type != COMBAT_HEALING) {
- damage.primary.value *= attacker->getBuff(BUFF_DAMAGEDEALT) / 100.;
- damage.secondary.value *= attacker->getBuff(BUFF_DAMAGEDEALT) / 100.;
- }
- if (damage.origin != ORIGIN_NONE && target && damage.primary.type != COMBAT_HEALING) {
- damage.primary.value *= target->getBuff(BUFF_DAMAGERECEIVED) / 100.;
- damage.secondary.value *= target->getBuff(BUFF_DAMAGERECEIVED) / 100.;
- }
+
int32_t healthChange = damage.primary.value + damage.secondary.value;
if (healthChange == 0) {
return true;
@@ -5804,9 +5960,9 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
if (mType) {
IOBestiary g_bestiary;
charmRune_t activeCharm = g_bestiary.getCharmFromTarget(targetPlayer, mType);
- if (activeCharm != CHARM_NONE && activeCharm != CHARM_CLEANSE) {
+ if (activeCharm != CHARM_NONE && activeCharm != CHARM_CLEANSE && activeCharm != CHARM_PARRY) {
Charm* charm = g_bestiary.getBestiaryCharm(activeCharm);
- if (charm && charm->type == CHARM_DEFENSIVE &&(charm->chance > normal_random(0, 100))) {
+ if (charm && charm->type == CHARM_DEFENSIVE && (charm->chance > normal_random(0, 100))) {
if (g_bestiary.parseCharmCombat(charm, targetPlayer, attacker, (damage.primary.value + damage.secondary.value))) {
return false; // Dodge charm
}
@@ -5815,8 +5971,13 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
}
}
+ std::stringstream ss;
+ ss << (damage.critical ? "critical " : " ") << "attack";
+ std::string attackMsg = ss.str();
+ ss.str({});
+
if (target->hasCondition(CONDITION_MANASHIELD) && damage.primary.type != COMBAT_UNDEFINEDDAMAGE) {
- int32_t manaDamage = std::min(target->getMana(), healthChange);
+ int32_t manaDamage = std::min(target->getMana(), healthChange);
uint16_t manaShield = target->getManaShield();
if (manaShield > 0) {
if (manaShield > manaDamage) {
@@ -5851,8 +6012,6 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY);
- std::stringstream ss;
-
std::string damageString = std::to_string(manaDamage);
std::string spectatorMessage;
@@ -5876,7 +6035,11 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
ss.str({});
- ss << ucfirst(target->getNameDescription()) << " loses " << damageString + " mana due to your attack.";
+ ss << ucfirst(target->getNameDescription()) << " loses " << damageString + " mana due to your " << attackMsg << ".";
+
+ if (!damage.exString.empty()) {
+ ss << " (" << damage.exString << ")";
+ }
message.type = MESSAGE_DAMAGE_DEALT;
message.text = ss.str();
} else if (tmpPlayer == targetPlayer) {
@@ -5885,9 +6048,9 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
if (!attacker) {
ss << '.';
} else if (targetPlayer == attackerPlayer) {
- ss << " due to your own attack.";
+ ss << " due to your own " << attackMsg << ".";
} else {
- ss << " due to an attack by " << attacker->getNameDescription() << '.';
+ ss << " due to an " << attackMsg << " by " << attacker->getNameDescription() << '.';
}
message.type = MESSAGE_DAMAGE_RECEIVED;
message.text = ss.str();
@@ -5898,9 +6061,10 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
if (attacker) {
ss << " due to ";
if (attacker == target) {
- ss << (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack") : "its own attack");
+ ss << (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own " : "his own ") : "its own ");
+ ss << attackMsg;
} else {
- ss << "an attack by " << attacker->getNameDescription();
+ ss << "an " << attackMsg << " by " << attacker->getNameDescription();
}
}
ss << '.';
@@ -5958,9 +6122,6 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
target->drainHealth(attacker, realDamage);
if (realDamage > 0) {
if (Monster* targetMonster = target->getMonster()) {
- if (attackerPlayer && attackerPlayer->getPlayer()) {
- attackerPlayer->updateImpactTracker(damage.secondary.type, damage.secondary.value);
- }
if (targetMonster->israndomStepping()) {
targetMonster->setIgnoreFieldDamage(true);
@@ -5970,10 +6131,10 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
}
// Using real damage
- if (attackerPlayer) {
+ if (attackerPlayer && !damage.extension) {
//life leech
uint16_t lifeChance = attackerPlayer->getSkillLevel(SKILL_LIFE_LEECH_CHANCE);
- uint16_t lifeSkill = attackerPlayer->getSkillLevel(SKILL_LIFE_LEECH_AMOUNT);
+ uint16_t lifeSkill = attackerPlayer->getSkillLevel(SKILL_LIFE_LEECH_AMOUNT);
if (normal_random(0, 100) < lifeChance) {
// Vampiric charm rune
if (target && target->getMonster()) {
@@ -5995,14 +6156,14 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
int affected = damage.affected;
tmpDamage.origin = ORIGIN_SPELL;
tmpDamage.primary.type = COMBAT_HEALING;
- tmpDamage.primary.value = std::round(realDamage * (lifeSkill / 100.) * (0.2 * affected + 0.9)) / affected;
+ tmpDamage.primary.value = std::round((realDamage * (0.9 + (damage.affected / 10.)) * (attackerPlayer->getSkillLevel(SKILL_LIFE_LEECH_AMOUNT) /100.)) / damage.affected);
Combat::doCombatHealth(nullptr, attackerPlayer, tmpDamage, tmpParams);
}
//mana leech
uint16_t manaChance = attackerPlayer->getSkillLevel(SKILL_MANA_LEECH_CHANCE);
- uint16_t manaSkill = attackerPlayer->getSkillLevel(SKILL_MANA_LEECH_AMOUNT);
+ uint16_t manaSkill = attackerPlayer->getSkillLevel(SKILL_MANA_LEECH_AMOUNT);
if (normal_random(0, 100) < manaChance) {
// Void charm rune
if (target && target->getMonster()) {
@@ -6024,21 +6185,24 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
int affected = damage.affected;
tmpDamage.origin = ORIGIN_SPELL;
tmpDamage.primary.type = COMBAT_MANADRAIN;
- tmpDamage.primary.value = std::round(realDamage * (manaSkill / 100.) * (0.1 * affected + 0.9)) / affected;
+ tmpDamage.primary.value = std::round((realDamage * (0.9 + (damage.affected / 10.)) * (attackerPlayer->getSkillLevel(SKILL_MANA_LEECH_AMOUNT) /100.)) / damage.affected);
Combat::doCombatMana(nullptr, attackerPlayer, tmpDamage, tmpParams);
}
-
//Charm rune (attacker as player)
- if (!damage.extension && target && target->getMonster()) {
+ if (target && target->getMonster()) {
MonsterType* mType = g_monsters.getMonsterType(target->getName());
if (mType) {
IOBestiary g_bestiary;
charmRune_t activeCharm = g_bestiary.getCharmFromTarget(attackerPlayer, mType);
if (activeCharm != CHARM_NONE) {
Charm* charm = g_bestiary.getBestiaryCharm(activeCharm);
- if (charm && charm->type == CHARM_OFFENSIVE && (charm->chance >= normal_random(0, 100))) {
- g_bestiary.parseCharmCombat(charm, attackerPlayer, target, realDamage);
+ if (charm && charm->type == CHARM_OFFENSIVE) {
+ int8_t extraChance = (charm->id != CHARM_CRIPPLE ? (attackerPlayer->hasCondition(CONDITION_TIBIADROMEPOTIONS, ITEM_TIBIADROME_POTION_CHARM) ? 5 : 0) : 0);
+ int32_t randomChance = normal_random(0, 100);
+
+ if (charm->chance + extraChance >= randomChance)
+ g_bestiary.parseCharmCombat(charm, attackerPlayer, target, realDamage, (extraChance > 0 && randomChance > charm->chance));
}
}
}
@@ -6083,13 +6247,12 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
}
targetPlayer->updateInputAnalyzer(damage.primary.type, damage.primary.value, cause);
- if (attackerPlayer) {
- if (damage.secondary.type != COMBAT_NONE) {
- attackerPlayer->updateInputAnalyzer(damage.secondary.type, damage.secondary.value, cause);
- }
+ if (damage.secondary.type != COMBAT_NONE) {
+ targetPlayer->updateInputAnalyzer(damage.secondary.type, damage.secondary.value, cause);
}
}
- std::stringstream ss;
+
+ ss.str({});
ss << realDamage << (realDamage != 1 ? " hitpoints" : " hitpoint");
std::string damageString = ss.str();
@@ -6104,9 +6267,9 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
ss.str({});
- ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " due to your attack.";
- if (damage.extension) {
- ss << " " << damage.exString;
+ ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " due to your " << attackMsg << ".";
+ if (!damage.exString.empty()) {
+ ss << " (" << damage.exString << ")";
}
message.type = MESSAGE_DAMAGE_DEALT;
message.text = ss.str();
@@ -6116,12 +6279,12 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
if (!attacker) {
ss << '.';
} else if (targetPlayer == attackerPlayer) {
- ss << " due to your own attack.";
+ ss << " due to your own " << attackMsg << ".";
} else {
- ss << " due to an attack by " << attacker->getNameDescription() << '.';
+ ss << " due to an " << attackMsg << " by " << attacker->getNameDescription() << '.';
}
- if (damage.extension) {
- ss << " " << damage.exString;
+ if (!damage.exString.empty()) {
+ ss << " (" << damage.exString << ")";
}
message.type = MESSAGE_DAMAGE_RECEIVED;
message.text = ss.str();
@@ -6135,17 +6298,18 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
ss << " due to ";
if (attacker == target) {
if (targetPlayer) {
- ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack");
+ ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own " : "his own ");
} else {
- ss << "its own attack";
+ ss << "its own ";
}
+ ss << attackMsg;
} else {
- ss << "an attack by " << attacker->getNameDescription();
+ ss << "an " << attackMsg << " by " << attacker->getNameDescription();
}
}
ss << '.';
- if (damage.extension) {
- ss << " " << damage.exString;
+ if (!damage.exString.empty()) {
+ ss << " (" << damage.exString << ")";
}
spectatorMessage = ss.str();
}
@@ -6589,13 +6753,14 @@ void Game::checkImbuements()
}
const ItemType& itemType = Item::items[item->getID()];
- if (!player->hasCondition(CONDITION_INFIGHT) && !itemType.isContainer()) {
+ if (!player || !player->hasCondition(CONDITION_INFIGHT) && !itemType.isContainer()) {
it++;
continue;
}
bool hasImbue = false;
uint8_t slots = Item::items[item->getID()].imbuingSlots;
+ int32_t index = player ? player->getThingIndex(item) : -1;
for (uint8_t slot = 0; slot < slots; slot++) {
uint32_t info = item->getImbuement(slot);
uint16_t id = info & 0xFF;
@@ -6842,40 +7007,22 @@ void Game::updatePlayerShield(Player* player)
void Game::updateCreatureType(Creature* creature)
{
- if (!creature) {
+ if (!creature || creature->isHealthHidden()) {
return;
}
- const Player* masterPlayer = nullptr;
- CreatureType_t creatureType = creature->getType();
- if (creatureType == CREATURETYPE_MONSTER) {
- const Creature* master = creature->getMaster();
- if (master) {
- masterPlayer = master->getPlayer();
- if (masterPlayer) {
- creatureType = CREATURETYPE_SUMMON_OTHERS;
- }
- }
- }
- if (creature->isHealthHidden()) {
- creatureType = CREATURETYPE_HIDDEN;
- }
-
- //send to clients
+ // Send to clients
SpectatorHashSet spectators;
map.getSpectators(spectators, creature->getPosition(), true, true);
- if (creatureType == CREATURETYPE_SUMMON_OTHERS) {
- for (Creature* spectator : spectators) {
- Player* player = spectator->getPlayer();
- if (masterPlayer == player) {
- player->sendCreatureType(creature, CREATURETYPE_SUMMON_PLAYER);
- } else {
- player->sendCreatureType(creature, creatureType);
- }
+
+ for (Creature* spectator : spectators) {
+ if (!spectator) {
+ continue;
}
- } else {
- for (Creature* spectator : spectators) {
- spectator->getPlayer()->sendCreatureType(creature, creatureType);
+
+ Player* player = spectator->getPlayer();
+ if (player) {
+ player->sendCreatureUpdate(creature);
}
}
}
diff --git a/src/game/game.h b/src/game/game.h
index c22a69ad187..d732366e2f4 100644
--- a/src/game/game.h
+++ b/src/game/game.h
@@ -288,8 +288,13 @@ class Game
void playerSetFightModes(uint32_t playerId, FightMode_t fightMode, bool chaseMode, bool secureMode);
void playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos);
void playerLookInBattleList(uint32_t playerId, uint32_t creatureId);
- void playerQuickLoot(uint32_t playerId, const Position& pos,
- uint16_t spriteId, uint8_t stackPos, Item* defaultItem = nullptr);
+ void playerLootAllCorpses(Player* player, const Position& pos, bool lootAllCorpses);
+ void playerQuickLoot(uint32_t playerId,
+ const Position& pos,
+ uint16_t spriteId,
+ uint8_t stackPos,
+ Item* defaultItem = nullptr,
+ bool lootAllCorpses = false);
void playerSetLootContainer(uint32_t playerId, ObjectCategory_t category,
const Position& pos, uint16_t spriteId, uint8_t stackPos);
void playerClearLootContainer(uint32_t playerId, ObjectCategory_t category);;
@@ -574,6 +579,7 @@ class Game
static constexpr int32_t SUNRISE = 360;
bool isDay = false;
+ bool browseField = false;
GameState_t gameState = GAME_STATE_NORMAL;
WorldType_t worldType = WORLD_TYPE_PVP;
diff --git a/src/game/game_definitions.hpp b/src/game/game_definitions.hpp
index f7e0000484c..bdca1392aab 100644
--- a/src/game/game_definitions.hpp
+++ b/src/game/game_definitions.hpp
@@ -102,7 +102,9 @@ enum Faction_t {
FACTION_PLAYER = 1,
FACTION_LION = 2,
FACTION_LIONUSURPERS = 3,
- FACTION_LAST = FACTION_LIONUSURPERS,
+ FACTION_ISSAVIMONSTER = 4,
+ FACTION_ISSAVIHUMANS = 5,
+ FACTION_LAST = FACTION_ISSAVIHUMANS
};
enum LightState_t {
diff --git a/src/io/iobestiary.cpp b/src/io/iobestiary.cpp
index 23421eed3ca..9a0122b4fe8 100644
--- a/src/io/iobestiary.cpp
+++ b/src/io/iobestiary.cpp
@@ -29,12 +29,11 @@
extern Game g_game;
extern Monsters g_monsters;
-bool IOBestiary::parseCharmCombat(Charm* charm, Player* player, Creature* target, int32_t realDamage)
+bool IOBestiary::parseCharmCombat(Charm* charm, Player* player, Creature* target, int32_t realDamage, bool dueToPotion, bool checkArmor)
{
if (!charm || !player || !target) {
return false;
}
-
CombatParams charmParams;
CombatDamage charmDamage;
if (charm->type == CHARM_OFFENSIVE) {
@@ -44,12 +43,15 @@ bool IOBestiary::parseCharmCombat(Charm* charm, Player* player, Creature* target
target->addCondition(cripple);
player->sendCancelMessage(charm->cancelMsg);
return false;
- }
+ }
int32_t maxHealth = target->getMaxHealth();
charmDamage.primary.type = charm->dmgtype;
charmDamage.primary.value = ((-maxHealth * (charm->percent)) / 100);
charmDamage.extension = true;
- charmDamage.exString = charm->logMsg;
+ if (!charmDamage.exString.empty()) {
+ charmDamage.exString += ", ";
+ }
+ charmDamage.exString += charm->logMsg + (dueToPotion ? " due to active charm upgrade" : "");
charmParams.impactEffect = charm->effect;
charmParams.combatType = charmDamage.primary.type;
@@ -59,14 +61,15 @@ bool IOBestiary::parseCharmCombat(Charm* charm, Player* player, Creature* target
} else if (charm->type == CHARM_DEFENSIVE) {
switch (charm->id) {
case CHARM_PARRY: {
- charmDamage.primary.type = charm->dmgtype;
+ charmDamage.primary.type = COMBAT_NEUTRALDAMAGE;
charmDamage.primary.value = -realDamage;
charmDamage.extension = true;
- charmDamage.exString = charm->logMsg;
-
- charmParams.impactEffect = charm->effect;
- charmParams.combatType = charmDamage.primary.type;
+ if (!charmDamage.exString.empty()) {
+ charmDamage.exString += ", ";
+ }
+ charmDamage.exString += charm->logMsg + (dueToPotion ? " due to active charm upgrade" : "");
charmParams.aggressive = true;
+ charmParams.blockedByArmor = checkArmor;
break;
}
case CHARM_DODGE: {
diff --git a/src/io/iobestiary.h b/src/io/iobestiary.h
index aefd55f6584..38c25090f98 100644
--- a/src/io/iobestiary.h
+++ b/src/io/iobestiary.h
@@ -62,7 +62,7 @@ class IOBestiary
public:
Charm* getBestiaryCharm(charmRune_t activeCharm, bool force = false);
void addBestiaryKill(Player* player, MonsterType* mtype, uint32_t amount = 1);
- bool parseCharmCombat(Charm* charm, Player* player, Creature* target, int32_t realDamage);
+ bool parseCharmCombat(Charm* charm, Player* player, Creature* target, int32_t realDamage, bool dueToPotion = false, bool checkArmor = false);
void addCharmPoints(Player* player, uint16_t amount, bool negative = false);
void sendBuyCharmRune(Player* player, charmRune_t runeID, uint8_t action, uint16_t raceid);
void setCharmRuneCreature(Player* player, Charm* charm, uint16_t raceid);
diff --git a/src/items/functions/item_parse.cpp b/src/items/functions/item_parse.cpp
index 16f592ace40..a1d280f69f6 100644
--- a/src/items/functions/item_parse.cpp
+++ b/src/items/functions/item_parse.cpp
@@ -64,7 +64,7 @@ void ItemParse::initParse(const std::string& tmpStrValue, pugi::xml_node attribu
ItemParse::parseCriticalHit(tmpStrValue, valueAttribute, itemType);
ItemParse::parseLifeAndManaLeech(tmpStrValue, valueAttribute, itemType);
ItemParse::parseMaxHitAndManaPoints(tmpStrValue, valueAttribute, itemType);
- ItemParse::parseMagicPoints(tmpStrValue, valueAttribute, itemType);
+ ItemParse::parseMagicLevelPoint(tmpStrValue, valueAttribute, itemType);
ItemParse::parseFieldAbsorbPercent(tmpStrValue, valueAttribute, itemType);
ItemParse::parseAbsorbPercent(tmpStrValue, valueAttribute, itemType);
ItemParse::parseSupressDrunk(tmpStrValue, valueAttribute, itemType);
@@ -75,6 +75,11 @@ void ItemParse::initParse(const std::string& tmpStrValue, pugi::xml_node attribu
ItemParse::parseElement(tmpStrValue, valueAttribute, itemType);
ItemParse::parseWalk(tmpStrValue, valueAttribute, itemType);
ItemParse::parseAllowDistanceRead(tmpStrValue, valueAttribute, itemType);
+ ItemParse::parseSpecializedMagicLevelPoint(tmpStrValue, valueAttribute, itemType);
+ ItemParse::parseMagicShieldCapacity(tmpStrValue, valueAttribute, itemType);
+ ItemParse::parsePerfecShot(tmpStrValue, valueAttribute, itemType);
+ ItemParse::parseCleavePercent(tmpStrValue, valueAttribute, itemType);
+ ItemParse::parseReflectDamage(tmpStrValue, valueAttribute, itemType);
}
void ItemParse::parseType(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType) {
@@ -510,11 +515,11 @@ void ItemParse::parseMaxHitAndManaPoints(const std::string& tmpStrValue, pugi::x
}
}
-void ItemParse::parseMagicPoints(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType) {
+void ItemParse::parseMagicLevelPoint(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType) {
std::string stringValue = tmpStrValue;
- if (stringValue == "magicpoints") {
+ if (stringValue == "magiclevelpoints") {
itemType.getAbilities().stats[STAT_MAGICPOINTS] = pugi::cast(valueAttribute.value());
- } else if (stringValue == "magicpointspercent") {
+ } else if (stringValue == "magiclevelpointspercent") {
itemType.getAbilities().statsPercent[STAT_MAGICPOINTS] = pugi::cast(valueAttribute.value());
}
}
@@ -814,3 +819,74 @@ void ItemParse::parseAllowDistanceRead(const std::string& tmpStrValue, pugi::xml
itemType.allowDistRead = booleanString(valueAttribute.as_string());
}
}
+
+void ItemParse::parseSpecializedMagicLevelPoint(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType) {
+ std::string stringValue = tmpStrValue;
+ Abilities & abilities = itemType.getAbilities();
+ if (stringValue == "deathmagiclevelpoints") {
+ abilities.specializedMagicLevel[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += pugi::cast(valueAttribute.value());
+ abilities.elementType = COMBAT_DEATHDAMAGE;
+ } else if (stringValue == "energymagiclevelpoints") {
+ abilities.specializedMagicLevel[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value());
+ abilities.elementType = COMBAT_ENERGYDAMAGE;
+ } else if (stringValue == "earthmagiclevelpoints") {
+ abilities.specializedMagicLevel[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value());
+ abilities.elementType = COMBAT_EARTHDAMAGE;
+ } else if (stringValue == "firemagiclevelpoints") {
+ abilities.specializedMagicLevel[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value());
+ abilities.elementType = COMBAT_FIREDAMAGE;
+ } else if (stringValue == "healingmagiclevelpoints") {
+ abilities.specializedMagicLevel[combatTypeToIndex(COMBAT_HEALING)] += pugi::cast(valueAttribute.value());
+ abilities.elementType = COMBAT_HEALING;
+ } else if (stringValue == "holymagiclevelpoints") {
+ abilities.specializedMagicLevel[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += pugi::cast(valueAttribute.value());
+ abilities.elementType = COMBAT_HOLYDAMAGE;
+ } else if (stringValue == "icemagiclevelpoints") {
+ abilities.specializedMagicLevel[combatTypeToIndex(COMBAT_ICEDAMAGE)] += pugi::cast(valueAttribute.value());
+ abilities.elementType = COMBAT_ICEDAMAGE;
+ } else if (stringValue == "physicalmagiclevelpoints") {
+ abilities.specializedMagicLevel[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += pugi::cast(valueAttribute.value());
+ abilities.elementType = COMBAT_PHYSICALDAMAGE;
+ }
+}
+
+void ItemParse::parseMagicShieldCapacity(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType) {
+ std::string stringValue = tmpStrValue;
+ Abilities & abilities = itemType.getAbilities();
+ if (stringValue == "magicshieldcapacitypercent") {
+ abilities.magicShieldCapacityPercent += pugi::cast(valueAttribute.value());
+ } else if (stringValue == "magicshieldcapacityflat") {
+ abilities.magicShieldCapacityFlat += pugi::cast(valueAttribute.value());
+ }
+}
+
+void ItemParse::parsePerfecShot(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType) {
+ std::string stringValue = tmpStrValue;
+ Abilities & abilities = itemType.getAbilities();
+ if (stringValue == "perfectshotdamage") {
+ abilities.perfectShotDamage = pugi::cast(valueAttribute.value());
+ } else if (stringValue == "perfectshotrange") {
+ abilities.perfectShotRange = pugi::cast(valueAttribute.value());
+ }
+}
+
+void ItemParse::parseCleavePercent(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType) {
+ std::string stringValue = tmpStrValue;
+ Abilities & abilities = itemType.getAbilities();
+ if (stringValue == "cleavepercent") {
+ abilities.cleavePercent += pugi::cast(valueAttribute.value());
+ }
+}
+
+void ItemParse::parseReflectDamage(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType) {
+ std::string stringValue = tmpStrValue;
+ Abilities & abilities = itemType.getAbilities();
+ if (stringValue == "reflectdamage") {
+ abilities.reflectFlat[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += pugi::cast(valueAttribute.value());
+ } else if (stringValue == "reflectpercentall") {
+ int32_t value = pugi::cast(valueAttribute.value());
+ for (auto& i : abilities.reflectPercent) {
+ i += value;
+ }
+ }
+}
diff --git a/src/items/functions/item_parse.hpp b/src/items/functions/item_parse.hpp
index a2fc34122bf..0157a421f71 100644
--- a/src/items/functions/item_parse.hpp
+++ b/src/items/functions/item_parse.hpp
@@ -99,8 +99,8 @@ const std::unordered_map ItemParseAttributes
{"maxhitpointspercent", ITEM_PARSE_MAXHITPOINTSPERCENT},
{"maxmanapoints", ITEM_PARSE_MAXMANAPOINTS},
{"maxmanapointspercent", ITEM_PARSE_MAXMANAPOINTSPERCENT},
- {"magicpoints", ITEM_PARSE_MAGICPOINTS},
- {"magicpointspercent", ITEM_PARSE_MAGICPOINTSPERCENT},
+ {"magiclevelpoints", ITEM_PARSE_MAGICLEVELPOINTS},
+ {"magicpointspercent", ITEM_PARSE_MAGICLEVELPOINTSPERCENT},
{"fieldabsorbpercentenergy", ITEM_PARSE_FIELDABSORBPERCENTENERGY},
{"fieldabsorbpercentfire", ITEM_PARSE_FIELDABSORBPERCENTFIRE},
{"fieldabsorbpercentpoison", ITEM_PARSE_FIELDABSORBPERCENTPOISON},
@@ -149,6 +149,22 @@ const std::unordered_map ItemParseAttributes
{"walkstack", ITEM_PARSE_WALKSTACK},
{"blocking", ITEM_PARSE_BLOCK_SOLID},
{"allowdistread", ITEM_PARSE_ALLOWDISTREAD},
+ // news 12.72 modifiers
+ {"deathmagiclevelpoints", ITEM_PARSE_DEATHMAGICLEVELPOINTS},
+ {"energymagiclevelpoints", ITEM_PARSE_ENERGYMAGICLEVELPOINTS},
+ {"earthmagiclevelpoints", ITEM_PARSE_EARTHMAGICLEVELPOINTS},
+ {"firemagiclevelpoints", ITEM_PARSE_EARTHMAGICLEVELPOINTS},
+ {"icemagiclevelpoints", ITEM_PARSE_ICEMAGICLEVELPOINTS},
+ {"holymagiclevelpoints", ITEM_PARSE_HOLYMAGICLEVELPOINTS},
+ {"healingmagiclevelpoints", ITEM_PARSE_HEALINGMAGICLEVELPOINTS},
+ {"physicalmagiclevelpoints", ITEM_PARSE_PHYSICALMAGICLEVELPOINTS},
+ {"magicshieldcapacitypercent", ITEM_PARSE_MAGICSHIELDCAPACITYPERCENT},
+ {"magicshieldcapacityflat", ITEM_PARSE_MAGICSHIELDCAPACITYFLAT},
+ {"perfectshotdamage", ITEM_PARSE_PERFECTSHOTDAMAGE},
+ {"perfectshotrange", ITEM_PARSE_PERFECTSHOTRANGE},
+ {"cleavepercent", ITEM_PARSE_CLEAVEPERCENT},
+ {"reflectdamage", ITEM_PARSE_REFLECTDAMAGE},
+ {"reflectpercentall", ITEM_PARSE_REFLECTPERCENTALL},
};
const std::unordered_map ItemTypesMap = {
@@ -269,7 +285,7 @@ class ItemParse final : public Items
static void parseCriticalHit(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
static void parseLifeAndManaLeech(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
static void parseMaxHitAndManaPoints(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
- static void parseMagicPoints(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
+ static void parseMagicLevelPoint(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
static void parseFieldAbsorbPercent(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
static void parseAbsorbPercent(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
static void parseSupressDrunk(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
@@ -280,6 +296,11 @@ class ItemParse final : public Items
static void parseElement(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
static void parseWalk(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
static void parseAllowDistanceRead(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
+ static void parseSpecializedMagicLevelPoint(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
+ static void parseMagicShieldCapacity(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
+ static void parsePerfecShot(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
+ static void parseCleavePercent(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
+ static void parseReflectDamage(const std::string& tmpStrValue, pugi::xml_attribute valueAttribute, ItemType& itemType);
private:
// Parent of the function: static void parseField
diff --git a/src/items/item.cpp b/src/items/item.cpp
index f6d2c9f2c18..25d099408f6 100644
--- a/src/items/item.cpp
+++ b/src/items/item.cpp
@@ -996,12 +996,47 @@ std::vector>
descriptions.emplace_back("Magic Level", ss.str());
}
+ for (uint8_t i = 1; i <= 11; i++) {
+ if (it.abilities->specializedMagicLevel[i]) {
+ ss.str("");
+
+ ss << std::showpos << it.abilities->specializedMagicLevel[i] << std::noshowpos;
+ std::string combatName = getCombatName(indexToCombatType(i));
+ combatName[0] = toupper(combatName[0]);
+ descriptions.emplace_back(combatName + " Magic Level", ss.str());
+ }
+ }
+
+ if (it.abilities->magicShieldCapacityFlat || it.abilities->magicShieldCapacityPercent) {
+ ss.str("");
+ ss << std::showpos << it.abilities->magicShieldCapacityFlat << std::noshowpos << " and " << it.abilities->magicShieldCapacityPercent << "%";
+ descriptions.emplace_back("Magic Shield Capacity", ss.str());
+ }
+
+ if (it.abilities->perfectShotRange) {
+ ss.str("");
+ ss << std::showpos << it.abilities->perfectShotDamage << std::noshowpos << " at range " << unsigned(it.abilities->perfectShotRange);
+ descriptions.emplace_back("Perfect Shot", ss.str());
+ }
+
+ if (it.abilities->reflectFlat[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)]) {
+ ss.str("");
+ ss << it.abilities->reflectFlat[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)];
+ descriptions.emplace_back("Damage Reflection", ss.str());
+ }
+
if (it.abilities->speed) {
ss.str("");
ss << std::showpos << (it.abilities->speed >> 1) << std::noshowpos;
descriptions.emplace_back("Speed", ss.str());
}
+ if (it.abilities->cleavePercent) {
+ ss.str("");
+ ss << std::showpos << (it.abilities->cleavePercent) << std::noshowpos << "%";
+ descriptions.emplace_back("Cleave", ss.str());
+ }
+
if (hasBitSet(CONDITION_DRUNK, it.abilities->conditionSuppressions)) {
ss.str("");
ss << "Hard Drinking";
@@ -1244,12 +1279,47 @@ std::vector>
descriptions.emplace_back("Magic Level", ss.str());
}
+ for (uint8_t i = 1; i <= 11; i++) {
+ if (it.abilities->specializedMagicLevel[i]) {
+ ss.str("");
+
+ ss << std::showpos << it.abilities->specializedMagicLevel[i] << std::noshowpos;
+ std::string combatName = getCombatName(indexToCombatType(i));
+ combatName[0] = toupper(combatName[0]);
+ descriptions.emplace_back(combatName + " Magic Level", ss.str());
+ }
+ }
+
+ if (it.abilities->magicShieldCapacityFlat || it.abilities->magicShieldCapacityPercent) {
+ ss.str("");
+ ss << std::showpos << it.abilities->magicShieldCapacityFlat << std::noshowpos << " and " << it.abilities->magicShieldCapacityPercent << "%";
+ descriptions.emplace_back("Magic Shield Capacity", ss.str());
+ }
+
+ if (it.abilities->perfectShotRange) {
+ ss.str("");
+ ss << std::showpos << it.abilities->perfectShotDamage << std::noshowpos << " at range " << unsigned(it.abilities->perfectShotRange);
+ descriptions.emplace_back("Perfect Shot", ss.str());
+ }
+
+ if (it.abilities->reflectFlat[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)]) {
+ ss.str("");
+ ss << it.abilities->reflectFlat[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)];
+ descriptions.emplace_back("Damage Reflection", ss.str());
+ }
+
if (it.abilities->speed) {
ss.str("");
ss << std::showpos << (it.abilities->speed >> 1) << std::noshowpos;
descriptions.emplace_back("Speed", ss.str());
}
+ if (it.abilities->cleavePercent) {
+ ss.str("");
+ ss << std::showpos << (it.abilities->cleavePercent) << std::noshowpos << "%";
+ descriptions.emplace_back("Cleave", ss.str());
+ }
+
if (hasBitSet(CONDITION_DRUNK, it.abilities->conditionSuppressions)) {
ss.str("");
ss << "Hard Drinking";
@@ -1388,8 +1458,7 @@ std::vector>
}
std::string Item::getDescription(const ItemType& it, int32_t lookDistance,
- const Item* item /*= nullptr*/,
- int32_t subType /*= -1*/, bool addArticle /*= true*/)
+ const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/)
{
const std::string* text = nullptr;
@@ -1519,6 +1588,52 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance,
s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos;
}
+ for (uint8_t i = 1; i <= 11; i++) {
+ if (it.abilities->specializedMagicLevel[i]) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << getCombatName(indexToCombatType(i)) << " magic level " << std::showpos << it.abilities->specializedMagicLevel[i] << std::noshowpos;
+ }
+ }
+
+ if (it.abilities->magicShieldCapacityFlat || it.abilities->magicShieldCapacityPercent) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << "magic shield capacity " << std::showpos << it.abilities->magicShieldCapacityFlat << std::noshowpos << " and " << it.abilities->magicShieldCapacityPercent << "%";
+ }
+
+ if (it.abilities->perfectShotRange) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << "perfect shot " << std::showpos << it.abilities->perfectShotDamage << std::noshowpos << " at range " << unsigned(it.abilities->perfectShotRange);
+ }
+
+ if (it.abilities->reflectFlat[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)]) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << "damage reflection " << std::showpos << it.abilities->reflectFlat[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] << std::noshowpos;
+ }
+
int16_t show = it.abilities->absorbPercent[0];
if (show != 0) {
for (size_t i = 1; i < COMBAT_COUNT; ++i) {
@@ -1621,6 +1736,17 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance,
s << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos;
}
+
+ if (it.abilities->cleavePercent) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << "cleave " << std::showpos << (it.abilities->cleavePercent) << std::noshowpos << "%";
+ }
}
if (!begin) {
@@ -1705,6 +1831,52 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance,
s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos;
}
+ for (uint8_t i = 1; i <= 11; i++) {
+ if (it.abilities->specializedMagicLevel[i]) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << getCombatName(indexToCombatType(i)) << " magic level " << std::showpos << it.abilities->specializedMagicLevel[i] << std::noshowpos;
+ }
+ }
+
+ if (it.abilities->magicShieldCapacityFlat || it.abilities->magicShieldCapacityPercent) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << "magic shield capacity " << std::showpos << it.abilities->magicShieldCapacityFlat << std::noshowpos << " and " << it.abilities->magicShieldCapacityPercent << "%";
+ }
+
+ if (it.abilities->perfectShotRange) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << "perfect shot " << std::showpos << it.abilities->perfectShotDamage << std::noshowpos << " at range " << unsigned(it.abilities->perfectShotRange);
+ }
+
+ if (it.abilities->reflectFlat[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)]) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << "damage reflection " << std::showpos << it.abilities->reflectFlat[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] << std::noshowpos;
+ }
+
int16_t show = it.abilities->absorbPercent[0];
if (show != 0) {
for (size_t i = 1; i < COMBAT_COUNT; ++i) {
@@ -1807,6 +1979,16 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance,
s << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos;
}
+ if (it.abilities->cleavePercent) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << "cleave " << std::showpos << (it.abilities->cleavePercent) << std::noshowpos << "%";
+ }
}
if (!begin) {
@@ -1865,6 +2047,52 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance,
s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos;
}
+ for (uint8_t i = 1; i <= 11; i++) {
+ if (it.abilities->specializedMagicLevel[i]) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << getCombatName(indexToCombatType(i)) << " magic level " << std::showpos << it.abilities->specializedMagicLevel[i] << std::noshowpos;
+ }
+ }
+
+ if (it.abilities->magicShieldCapacityFlat || it.abilities->magicShieldCapacityPercent) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << "magic shield capacity " << std::showpos << it.abilities->magicShieldCapacityFlat << std::noshowpos << " and " << it.abilities->magicShieldCapacityPercent << "%";
+ }
+
+ if (it.abilities->perfectShotRange) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << "perfect shot " << std::showpos << it.abilities->perfectShotDamage << std::noshowpos << " at range " << unsigned(it.abilities->perfectShotRange);
+ }
+
+ if (it.abilities->reflectFlat[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)]) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << "damage reflection " << std::showpos << it.abilities->reflectFlat[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] << std::noshowpos;
+ }
+
int16_t show = it.abilities->absorbPercent[0];
if (show != 0) {
for (size_t i = 1; i < COMBAT_COUNT; ++i) {
@@ -1966,6 +2194,17 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance,
s << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos;
}
+
+ if (it.abilities->cleavePercent) {
+ if (begin) {
+ begin = false;
+ s << " (";
+ } else {
+ s << ", ";
+ }
+
+ s << "cleave " << std::showpos << (it.abilities->cleavePercent) << std::noshowpos << "%";
+ }
}
if (!begin) {
diff --git a/src/items/item.h b/src/items/item.h
index ffa74a990d4..28047115333 100644
--- a/src/items/item.h
+++ b/src/items/item.h
@@ -792,6 +792,38 @@ class Item : virtual public Thing
}
return items[id].weight;
}
+
+ int32_t getCleavePercent() const {
+ return items[id].abilities->cleavePercent;
+ }
+
+ int32_t getPerfectShotDamage() const {
+ return items[id].abilities->perfectShotDamage;
+ }
+ uint8_t getPerfectShotRange() const {
+ return items[id].abilities->perfectShotRange;
+ }
+
+ int32_t getReflectionFlat(CombatType_t combatType) const {
+ return items[id].abilities->reflectFlat[combatTypeToIndex(combatType)];
+ }
+
+ int32_t getReflectionPercent(CombatType_t combatType) const {
+ return items[id].abilities->reflectPercent[combatTypeToIndex(combatType)];
+ }
+
+ int16_t getMagicShieldCapacityPercent() const {
+ return items[id].abilities->magicShieldCapacityPercent;
+ }
+
+ int32_t getMagicShieldCapacityFlat() const {
+ return items[id].abilities->magicShieldCapacityFlat;
+ }
+
+ int32_t getSpecializedMagicLevel(CombatType_t combat) const {
+ return items[id].abilities->specializedMagicLevel[combatTypeToIndex(combat)];
+ }
+
int32_t getAttack() const {
if (hasAttribute(ITEM_ATTRIBUTE_ATTACK)) {
return getIntAttr(ITEM_ATTRIBUTE_ATTACK);
diff --git a/src/items/items.h b/src/items/items.h
index 392a36f288c..27d457fb3bf 100644
--- a/src/items/items.h
+++ b/src/items/items.h
@@ -49,13 +49,29 @@ struct Abilities {
//damage abilities modifiers
int16_t absorbPercent[COMBAT_COUNT] = { 0 };
- //relfect abilities modifires
+ // Reflect abilities modifires
int16_t reflectPercent[COMBAT_COUNT] = { 0 };
//elemental damage
uint16_t elementDamage = 0;
CombatType_t elementType = COMBAT_NONE;
+ // New 12.7 modifiers
+ // Specialized magic level modifiers
+ int32_t reflectFlat[COMBAT_COUNT] = { 0 };
+ int32_t specializedMagicLevel[COMBAT_COUNT] = { 0 };
+
+ //magic shield capacity
+ int32_t magicShieldCapacityPercent = 0;
+ int32_t magicShieldCapacityFlat = 0;
+
+ //cleave
+ int32_t cleavePercent = 0;
+
+ // perfect shot
+ int32_t perfectShotDamage = 0;
+ uint8_t perfectShotRange = 0;
+
bool manaShield = false;
bool invisible = false;
bool regeneration = false;
diff --git a/src/items/items_definitions.hpp b/src/items/items_definitions.hpp
index 1b6b38e3fc5..618a216a8bb 100644
--- a/src/items/items_definitions.hpp
+++ b/src/items/items_definitions.hpp
@@ -568,8 +568,8 @@ enum ItemParseAttributes_t {
ITEM_PARSE_MAXHITPOINTSPERCENT,
ITEM_PARSE_MAXMANAPOINTS,
ITEM_PARSE_MAXMANAPOINTSPERCENT,
- ITEM_PARSE_MAGICPOINTS,
- ITEM_PARSE_MAGICPOINTSPERCENT,
+ ITEM_PARSE_MAGICLEVELPOINTS,
+ ITEM_PARSE_MAGICLEVELPOINTSPERCENT,
ITEM_PARSE_FIELDABSORBPERCENTENERGY,
ITEM_PARSE_FIELDABSORBPERCENTFIRE,
ITEM_PARSE_FIELDABSORBPERCENTPOISON,
@@ -613,6 +613,22 @@ enum ItemParseAttributes_t {
ITEM_PARSE_WALKSTACK,
ITEM_PARSE_BLOCK_SOLID,
ITEM_PARSE_ALLOWDISTREAD,
+ // 12.72 modifiers
+ ITEM_PARSE_DEATHMAGICLEVELPOINTS,
+ ITEM_PARSE_ENERGYMAGICLEVELPOINTS,
+ ITEM_PARSE_EARTHMAGICLEVELPOINTS,
+ ITEM_PARSE_FIREMAGICLEVELPOINTS,
+ ITEM_PARSE_ICEMAGICLEVELPOINTS,
+ ITEM_PARSE_HEALINGMAGICLEVELPOINTS,
+ ITEM_PARSE_HOLYMAGICLEVELPOINTS,
+ ITEM_PARSE_PHYSICALMAGICLEVELPOINTS,
+ ITEM_PARSE_MAGICSHIELDCAPACITYPERCENT,
+ ITEM_PARSE_MAGICSHIELDCAPACITYFLAT,
+ ITEM_PARSE_PERFECTSHOTDAMAGE,
+ ITEM_PARSE_PERFECTSHOTRANGE,
+ ITEM_PARSE_CLEAVEPERCENT,
+ ITEM_PARSE_REFLECTPERCENTALL,
+ ITEM_PARSE_REFLECTDAMAGE,
};
#endif // SRC_ITEMS_ITEMS_DEFINITIONS_HPP_
diff --git a/src/items/weapons/weapons.cpp b/src/items/weapons/weapons.cpp
index e87f10e74a7..b1d5ab45e85 100644
--- a/src/items/weapons/weapons.cpp
+++ b/src/items/weapons/weapons.cpp
@@ -58,7 +58,7 @@ const Weapon* Weapons::getWeapon(const Item* item) const
void Weapons::clear(bool fromLua)
{
for (auto it = weapons.begin(); it != weapons.end(); ) {
- if (fromLua == it->second->fromLua) {
+ if (it->second && fromLua == it->second->fromLua) {
it = weapons.erase(it);
} else {
++it;
@@ -407,9 +407,13 @@ bool Weapon::useFist(Player* player, Creature* target)
return true;
}
-void Weapon::internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier) const
+void Weapon::internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier, int32_t cleavePercent) const
{
if (scripted) {
+ if (cleavePercent != 0) {
+ return;
+ }
+
LuaVariant var;
var.type = VARIANT_NUMBER;
var.number = target->getID();
@@ -417,7 +421,7 @@ void Weapon::internalUseWeapon(Player* player, Item* item, Creature* target, int
} else {
CombatDamage damage;
WeaponType_t weaponType = item->getWeaponType();
- if (weaponType == WEAPON_AMMO || weaponType == WEAPON_DISTANCE) {
+ if (weaponType == WEAPON_AMMO || weaponType == WEAPON_DISTANCE || weaponType == WEAPON_WAND) {
damage.origin = ORIGIN_RANGED;
} else {
damage.origin = ORIGIN_MELEE;
@@ -426,15 +430,26 @@ void Weapon::internalUseWeapon(Player* player, Item* item, Creature* target, int
damage.primary.type = params.combatType;
damage.secondary.type = getElementType();
- if (damage.secondary.type == COMBAT_NONE) {
- damage.primary.value = (getWeaponDamage(player, target, item) * damageModifier) / 100;
- damage.secondary.value = 0;
- } else {
- damage.primary.value = (getWeaponDamage(player, target, item) * damageModifier) / 100;
- damage.secondary.value = (getElementDamage(player, target, item) * damageModifier) / 100;
- }
+ // Cleave damage
+ uint16_t damagePercent = 100;
+ if (cleavePercent != 0) {
+ damage.extension = true;
+ damagePercent = cleavePercent;
+ if (!damage.exString.empty()) {
+ damage.exString += ", ";
+ }
+ damage.exString += "cleave damage";
+ }
+
+ if (damage.secondary.type == COMBAT_NONE) {
+ damage.primary.value = (getWeaponDamage(player, target, item, false) * damageModifier / 100) * damagePercent / 100;
+ damage.secondary.value = 0;
+ } else {
+ damage.primary.value = (getWeaponDamage(player, target, item, false) * damageModifier / 100) * damagePercent / 100;
+ damage.secondary.value = (getElementDamage(player, target, item) * damageModifier / 100) * damagePercent / 100;
+ }
- Combat::doCombatHealth(player, target, damage, params);
+ Combat::doCombatHealth(player, target, damage, params);
}
onUsedWeapon(player, item, target->getTile());
@@ -600,6 +615,53 @@ bool WeaponMelee::useWeapon(Player* player, Item* item, Creature* target) const
return false;
}
+ int32_t cleavePercent = player->getCleavePercent(true);
+ if (cleavePercent > 0) {
+ const Position& targetPos = target->getPosition();
+ const Position& playerPos = player->getPosition();
+ if (playerPos != targetPos) {
+ Position firstCleaveTargetPos = targetPos;
+ Position secondCleaveTargetPos = targetPos;
+ if (targetPos.x == playerPos.x) {
+ firstCleaveTargetPos.x++;
+ secondCleaveTargetPos.x--;
+ } else if (targetPos.y == playerPos.y) {
+ firstCleaveTargetPos.y++;
+ secondCleaveTargetPos.y--;
+ } else {
+ if (targetPos.x > playerPos.x)
+ firstCleaveTargetPos.x--;
+ else
+ firstCleaveTargetPos.x++;
+
+ if (targetPos.y > playerPos.y)
+ secondCleaveTargetPos.y--;
+ else
+ secondCleaveTargetPos.y++;
+ }
+ Tile* firstTile = g_game.map.getTile(firstCleaveTargetPos.x, firstCleaveTargetPos.y, firstCleaveTargetPos.z);
+ Tile* secondTile = g_game.map.getTile(secondCleaveTargetPos.x, secondCleaveTargetPos.y, secondCleaveTargetPos.z);
+
+ if (firstTile) {
+ if (CreatureVector* tileCreatures = firstTile->getCreatures()) {
+ for (Creature* tileCreature : *tileCreatures) {
+ if (tileCreature->getMonster() || (tileCreature->getPlayer() && !player->hasSecureMode()))
+ internalUseWeapon(player, item, tileCreature, damageModifier, cleavePercent);
+ }
+ }
+ }
+ if (secondTile) {
+ if (CreatureVector* tileCreatures = secondTile->getCreatures()) {
+ for (Creature* tileCreature : *tileCreatures) {
+ if (tileCreature->getMonster() || (tileCreature->getPlayer() && !player->hasSecureMode()))
+ internalUseWeapon(player, item, tileCreature, damageModifier, cleavePercent);
+ }
+ }
+ }
+ }
+
+ }
+
internalUseWeapon(player, item, target, damageModifier);
return true;
}
@@ -720,13 +782,32 @@ bool WeaponDistance::useWeapon(Player* player, Item* item, Creature* target) con
return false;
}
+ bool perfectShot = false;
+ const Position& playerPos = player->getPosition();
+ const Position& targetPos = target->getPosition();
+ int32_t distanceX = Position::getDistanceX(targetPos, playerPos);
+ int32_t distanceY = Position::getDistanceY(targetPos, playerPos);
+ int32_t damageX = player->getPerfectShotDamage(distanceX);
+ int32_t damageY = player->getPerfectShotDamage(distanceY);
+
+ if (it.weaponType == WEAPON_DISTANCE) {
+ Item* quiver = player->getInventoryItem(CONST_SLOT_RIGHT);
+ if (quiver->getWeaponType() == WEAPON_QUIVER) {
+ if (quiver->getPerfectShotRange() == distanceX)
+ damageX -= quiver->getPerfectShotDamage();
+ else if (quiver->getPerfectShotRange() == distanceY)
+ damageY -= quiver->getPerfectShotDamage();
+ }
+ }
+
int32_t chance;
- if (it.hitChance == 0) {
+ if (damageX != 0 || damageY != 0) {
+ chance = 100;
+ perfectShot = true;
+ } else if (it.hitChance == 0) {
//hit chance is based on distance to target and distance skill
uint32_t skill = player->getSkillLevel(SKILL_DISTANCE);
- const Position& playerPos = player->getPosition();
- const Position& targetPos = target->getPosition();
- uint32_t distance = std::max(Position::getDistanceX(playerPos, targetPos), Position::getDistanceY(playerPos, targetPos));
+ uint32_t distance = std::max(distanceX, distanceY);
uint32_t maxHitChance;
if (it.maxHitChance != -1) {
@@ -821,7 +902,7 @@ bool WeaponDistance::useWeapon(Player* player, Item* item, Creature* target) con
chance = it.hitChance;
}
- if (item->getWeaponType() == WEAPON_AMMO) {
+ if (!perfectShot && item->getWeaponType() == WEAPON_AMMO) {
Item* bow = player->getWeapon(true);
if (bow && bow->getHitChance() != 0) {
chance += bow->getHitChance();
diff --git a/src/items/weapons/weapons.h b/src/items/weapons/weapons.h
index c5c2d28a51a..d1fdc6d2775 100644
--- a/src/items/weapons/weapons.h
+++ b/src/items/weapons/weapons.h
@@ -200,7 +200,7 @@ class Weapon : public Event
std::map vocWeaponMap;
protected:
- void internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier) const;
+ void internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier, int32_t cleavePercent = 0) const;
void internalUseWeapon(Player* player, Item* item, Tile* tile) const;
uint16_t id = 0;
diff --git a/src/lua/creature/movement.cpp b/src/lua/creature/movement.cpp
index 7c38a001215..bb00cbd4267 100644
--- a/src/lua/creature/movement.cpp
+++ b/src/lua/creature/movement.cpp
@@ -835,7 +835,7 @@ uint32_t MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, Slots_t
if (it.abilities->regeneration) {
player->removeCondition(CONDITION_REGENERATION, static_cast(slot));
}
-
+
//skill/stats modifiers
bool needUpdate = false;
diff --git a/src/lua/functions/core/game/lua_enums.hpp b/src/lua/functions/core/game/lua_enums.hpp
index b329e4589ff..143c11ff74b 100644
--- a/src/lua/functions/core/game/lua_enums.hpp
+++ b/src/lua/functions/core/game/lua_enums.hpp
@@ -85,6 +85,8 @@ class LuaEnums final : LuaScriptInterface {
registerEnum(L, FACTION_PLAYER)
registerEnum(L, FACTION_LION)
registerEnum(L, FACTION_LIONUSURPERS)
+ registerEnum(L, FACTION_ISSAVIHUMANS)
+ registerEnum(L, FACTION_ISSAVIMONSTER)
registerEnum(L, FACTION_LAST)
registerEnum(L, COMBAT_NONE)
@@ -100,6 +102,7 @@ class LuaEnums final : LuaScriptInterface {
registerEnum(L, COMBAT_ICEDAMAGE)
registerEnum(L, COMBAT_HOLYDAMAGE)
registerEnum(L, COMBAT_DEATHDAMAGE)
+ registerEnum(L, COMBAT_NEUTRALDAMAGE)
registerEnum(L, COMBAT_PARAM_TYPE)
registerEnum(L, COMBAT_PARAM_EFFECT)
@@ -141,7 +144,17 @@ class LuaEnums final : LuaScriptInterface {
registerEnum(L, CONDITION_PACIFIED)
registerEnum(L, CONDITION_SPELLCOOLDOWN)
registerEnum(L, CONDITION_SPELLGROUPCOOLDOWN)
+ registerEnum(L, CONDITION_LESSERHEX)
+ registerEnum(L, CONDITION_INTENSEHEX)
+ registerEnum(L, CONDITION_GREATERHEX)
registerEnum(L, CONDITION_ROOTED)
+ registerEnum(L, CONDITION_FEARED)
+ registerEnum(L, CONDITION_GOSHNAR1)
+ registerEnum(L, CONDITION_GOSHNAR2)
+ registerEnum(L, CONDITION_GOSHNAR3)
+ registerEnum(L, CONDITION_GOSHNAR4)
+ registerEnum(L, CONDITION_GOSHNAR5)
+ registerEnum(L, CONDITION_TIBIADROMEPOTIONS)
registerEnum(L, CONDITIONID_DEFAULT)
registerEnum(L, CONDITIONID_COMBAT)
@@ -209,6 +222,26 @@ class LuaEnums final : LuaScriptInterface {
registerEnum(L, CONDITION_PARAM_MANASHIELD)
registerEnum(L, CONDITION_PARAM_BUFF_DAMAGEDEALT)
registerEnum(L, CONDITION_PARAM_BUFF_DAMAGERECEIVED)
+ registerEnum(L, CONDITION_PARAM_ABSORB_PHYSICALPERCENT)
+ registerEnum(L, CONDITION_PARAM_ABSORB_FIREPERCENT)
+ registerEnum(L, CONDITION_PARAM_ABSORB_ENERGYPERCENT)
+ registerEnum(L, CONDITION_PARAM_ABSORB_ICEPERCENT)
+ registerEnum(L, CONDITION_PARAM_ABSORB_EARTHPERCENT)
+ registerEnum(L, CONDITION_PARAM_ABSORB_DEATHPERCENT)
+ registerEnum(L, CONDITION_PARAM_ABSORB_HOLYPERCENT)
+ registerEnum(L, CONDITION_PARAM_ABSORB_LIFEDRAINPERCENT)
+ registerEnum(L, CONDITION_PARAM_ABSORB_MANADRAINPERCENT)
+ registerEnum(L, CONDITION_PARAM_ABSORB_DROWNPERCENT)
+ registerEnum(L, CONDITION_PARAM_INCREASE_PHYSICALPERCENT)
+ registerEnum(L, CONDITION_PARAM_INCREASE_FIREPERCENT)
+ registerEnum(L, CONDITION_PARAM_INCREASE_ENERGYPERCENT)
+ registerEnum(L, CONDITION_PARAM_INCREASE_ICEPERCENT)
+ registerEnum(L, CONDITION_PARAM_INCREASE_EARTHPERCENT)
+ registerEnum(L, CONDITION_PARAM_INCREASE_DEATHPERCENT)
+ registerEnum(L, CONDITION_PARAM_INCREASE_HOLYPERCENT)
+ registerEnum(L, CONDITION_PARAM_INCREASE_LIFEDRAINPERCENT)
+ registerEnum(L, CONDITION_PARAM_INCREASE_MANADRAINPERCENT)
+ registerEnum(L, CONDITION_PARAM_INCREASE_DROWNPERCENT)
registerEnum(L, CONST_ME_NONE)
registerEnum(L, CONST_ME_DRAWBLOOD)
@@ -316,9 +349,28 @@ class LuaEnums final : LuaScriptInterface {
registerEnum(L, CONST_ME_ORANGE_FIREWORKS)
registerEnum(L, CONST_ME_PINK_FIREWORKS)
registerEnum(L, CONST_ME_BLUE_FIREWORKS)
+ registerEnum(L, CONST_ME_CUBE)
+ registerEnum(L, CONST_ME_BLACKHIT)
+ registerEnum(L, CONST_ME_MAGIC_RAINBOW)
+ registerEnum(L, CONST_ME_THAIAN)
+ registerEnum(L, CONST_ME_THAIANSOUL)
+ registerEnum(L, CONST_ME_GHOST)
+ registerEnum(L, CONST_ME_BLOCK)
+ registerEnum(L, CONST_ME_BLOCKWATER)
registerEnum(L, CONST_ME_ROOTS)
+ registerEnum(L, CONST_ME_CRAWLER)
+ registerEnum(L, CONST_ME_BITE)
+ registerEnum(L, CONST_ME_REDCLAW)
+ registerEnum(L, CONST_ME_BLUETAIL)
+ registerEnum(L, CONST_ME_BIGBITE)
registerEnum(L, CONST_ME_CHIVALRIOUS_CHALLENGE)
registerEnum(L, CONST_ME_DIVINE_DAZZLE)
+ registerEnum(L, CONST_ME_PINK_SPARKS)
+ registerEnum(L, CONST_ME_TELEPORT_PURPLE)
+ registerEnum(L, CONST_ME_TELEPORT_RED)
+ registerEnum(L, CONST_ME_TELEPORT_ORANGE)
+ registerEnum(L, CONST_ME_TELEPORT_WHITE)
+ registerEnum(L, CONST_ME_TELEPORT_LIGHTBLUE)
registerEnum(L, CONST_ANI_NONE)
registerEnum(L, CONST_ANI_SPEAR)
@@ -597,6 +649,11 @@ class LuaEnums final : LuaScriptInterface {
registerEnum(L, ITEM_GOLD_POUCH)
registerEnum(L, ITEM_STORE_INBOX)
+ registerEnum(L, ITEM_TIBIADROME_POTION_START)
+ registerEnum(L, ITEM_TIBIADROME_POTION_END)
+ registerEnum(L, ITEM_TIBIADROME_POTION_CHARM)
+ registerEnum(L, ITEM_TIBIADROME_POTION_LOOT)
+ registerEnum(L, ITEM_TIBIADROME_POTION_BESTIARY)
registerEnum(L, IMBUEMENT_SLOT)
diff --git a/src/lua/functions/creatures/monster/monster_type_functions.cpp b/src/lua/functions/creatures/monster/monster_type_functions.cpp
index 48dde4f162d..606b9e5a1e3 100644
--- a/src/lua/functions/creatures/monster/monster_type_functions.cpp
+++ b/src/lua/functions/creatures/monster/monster_type_functions.cpp
@@ -665,6 +665,9 @@ int MonsterTypeFunctions::luaMonsterTypeCombatImmunities(lua_State* L) {
} else if (immunity == "manadrain") {
monsterType->info.damageImmunities |= COMBAT_MANADRAIN;
pushBoolean(L, true);
+ } else if (immunity == "neutral") {
+ monsterType->info.damageImmunities |= COMBAT_NEUTRALDAMAGE;
+ pushBoolean(L, true);
} else {
SPDLOG_WARN("[MonsterTypeFunctions::luaMonsterTypeCombatImmunities] - "
"Unknown immunity name {} for monster: {}",
@@ -1139,6 +1142,8 @@ int MonsterTypeFunctions::luaMonsterTypeRace(lua_State* L) {
monsterType->info.race = RACE_FIRE;
} else if (race == "energy") {
monsterType->info.race = RACE_ENERGY;
+ } else if (race == "ink") {
+ monsterType->info.race = RACE_INK;
} else {
SPDLOG_WARN("[MonsterTypeFunctions::luaMonsterTypeRace] - "
"Unknown race type {}", race);
diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp
index bf950cf2333..b5289769de4 100644
--- a/src/lua/functions/creatures/player/player_functions.cpp
+++ b/src/lua/functions/creatures/player/player_functions.cpp
@@ -696,6 +696,64 @@ int PlayerFunctions::luaPlayerGetLevel(lua_State* L) {
return 1;
}
+int PlayerFunctions::luaPlayerGetMagicShieldCapacityFlat(lua_State* L)
+{
+ // player:getMagicShieldCapacityFlat(useCharges)
+ Player* player = getUserdata(L, 1);
+ if (player) {
+ lua_pushnumber(L, player->getMagicShieldCapacityFlat(getBoolean(L, 2, false)));
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int PlayerFunctions::luaPlayerGetMagicShieldCapacityPercent(lua_State* L)
+{
+ // player:getMagicShieldCapacityPercent(useCharges)
+ Player* player = getUserdata(L, 1);
+ if (player) {
+ lua_pushnumber(L, player->getMagicShieldCapacityPercent(getBoolean(L, 2, false)));
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int PlayerFunctions::luaPlayerSendSpellCooldown(lua_State* L)
+{
+ // player:sendSpellCooldown(spellId, time)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+ uint8_t spellId = getNumber(L, 2, 1);
+ uint32_t time = getNumber(L, 3, 0);
+
+ player->sendSpellCooldown(spellId, time);
+ pushBoolean(L, true);
+
+ return 1;
+}
+
+int PlayerFunctions::luaPlayerSendSpellGroupCooldown(lua_State* L)
+{
+ // player:sendSpellGroupCooldown(groupId, time)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+ SpellGroup_t groupId = getNumber(L, 2, SPELLGROUP_ATTACK);
+ uint32_t time = getNumber(L, 3, 0);
+
+ player->sendSpellGroupCooldown(groupId, time);
+ pushBoolean(L, true);
+
+ return 1;
+}
+
int PlayerFunctions::luaPlayerGetMagicLevel(lua_State* L) {
// player:getMagicLevel()
Player* player = getUserdata(L, 1);
diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp
index 5152c090571..01dbd6ccdc0 100644
--- a/src/lua/functions/creatures/player/player_functions.hpp
+++ b/src/lua/functions/creatures/player/player_functions.hpp
@@ -89,6 +89,12 @@ class PlayerFunctions final : LuaScriptInterface {
registerMethod(L, "Player", "removeExperience", PlayerFunctions::luaPlayerRemoveExperience);
registerMethod(L, "Player", "getLevel", PlayerFunctions::luaPlayerGetLevel);
+ registerMethod(L, "Player", "getMagicShieldCapacityFlat", PlayerFunctions::luaPlayerGetMagicShieldCapacityFlat);
+ registerMethod(L, "Player", "getMagicShieldCapacityPercent", PlayerFunctions::luaPlayerGetMagicShieldCapacityPercent);
+
+ registerMethod(L, "Player", "sendSpellCooldown", PlayerFunctions::luaPlayerSendSpellCooldown);
+ registerMethod(L, "Player", "sendSpellGroupCooldown", PlayerFunctions::luaPlayerSendSpellGroupCooldown);
+
registerMethod(L, "Player", "getMagicLevel", PlayerFunctions::luaPlayerGetMagicLevel);
registerMethod(L, "Player", "getBaseMagicLevel", PlayerFunctions::luaPlayerGetBaseMagicLevel);
registerMethod(L, "Player", "getMana", PlayerFunctions::luaPlayerGetMana);
@@ -366,6 +372,12 @@ class PlayerFunctions final : LuaScriptInterface {
static int luaPlayerRemoveExperience(lua_State* L);
static int luaPlayerGetLevel(lua_State* L);
+ static int luaPlayerGetMagicShieldCapacityFlat(lua_State* L);
+ static int luaPlayerGetMagicShieldCapacityPercent(lua_State* L);
+
+ static int luaPlayerSendSpellCooldown(lua_State* L);
+ static int luaPlayerSendSpellGroupCooldown(lua_State* L);
+
static int luaPlayerGetMagicLevel(lua_State* L);
static int luaPlayerGetBaseMagicLevel(lua_State* L);
static int luaPlayerGetMana(lua_State* L);
diff --git a/src/lua/functions/events/global_event_functions.cpp b/src/lua/functions/events/global_event_functions.cpp
index 3ed18df982f..a1688998287 100644
--- a/src/lua/functions/events/global_event_functions.cpp
+++ b/src/lua/functions/events/global_event_functions.cpp
@@ -82,6 +82,11 @@ int GlobalEventFunctions::luaGlobalEventRegister(lua_State* L) {
pushBoolean(L, false);
return 1;
}
+ if (globalevent->getEventType() == GLOBALEVENT_NONE && globalevent->getInterval() == 0) {
+ std::cout << "[Error - LuaScriptInterface::luaGlobalEventRegister] No interval for globalevent with name " << globalevent->getName() << std::endl;
+ pushBoolean(L, false);
+ return 1;
+ }
pushBoolean(L, g_globalEvents->registerLuaEvent(globalevent));
} else {
lua_pushnil(L);
diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp
index 79678c5a29e..f023d271e60 100644
--- a/src/server/network/protocol/protocolgame.cpp
+++ b/src/server/network/protocol/protocolgame.cpp
@@ -1278,7 +1278,8 @@ void ProtocolGame::parseQuickLoot(NetworkMessage &msg)
Position pos = msg.getPosition();
uint16_t spriteId = msg.get();
uint8_t stackpos = msg.getByte();
- addGameTask(&Game::playerQuickLoot, player->getID(), pos, spriteId, stackpos, nullptr);
+ bool lootAllCorpses = msg.getByte();
+ addGameTask(&Game::playerQuickLoot, player->getID(), pos, spriteId, stackpos, nullptr, lootAllCorpses);
}
void ProtocolGame::parseLootContainer(NetworkMessage &msg)
@@ -2706,18 +2707,21 @@ void ProtocolGame::sendCreatureLight(const Creature *creature)
void ProtocolGame::sendCreatureIcon(const Creature* creature)
{
if (!creature)
- return;
- CreatureIcon_t icon = creature->getIcon();
- NetworkMessage msg;
- msg.addByte(0x8B);
- msg.add(creature->getID());
- msg.addByte(14); // type 14 for this
- msg.addByte(icon != CREATUREICON_NONE); // 0 = no icon, 1 = we'll send an icon
- if (icon != CREATUREICON_NONE) {
- msg.addByte(icon);
- msg.addByte(1); // ???
- msg.add(0); // used for the life in the new quest
- }
+ {
+ return;
+ }
+
+ CreatureIcon_t icon = creature->getIcon();
+ NetworkMessage msg;
+ msg.addByte(0x8B);
+ msg.add(creature->getID());
+ msg.addByte(14); // type 14 for this
+ msg.addByte(icon != CREATUREICON_NONE); // 0 = no icon, 1 = we'll send an icon
+ if (icon != CREATUREICON_NONE) {
+ msg.addByte(icon);
+ msg.addByte(0); // Creature update
+ msg.add(0); // Used for the life in the new quest
+ }
writeToOutputBuffer(msg);
}
@@ -2784,25 +2788,21 @@ void ProtocolGame::sendCreatureSkull(const Creature *creature)
writeToOutputBuffer(msg);
}
-void ProtocolGame::sendCreatureType(const Creature *creature, uint8_t creatureType)
+
+void ProtocolGame::sendCreatureUpdate(const Creature *creature)
{
- NetworkMessage msg;
- msg.addByte(0x95);
- msg.add(creature->getID());
- if (creatureType == CREATURETYPE_SUMMON_OTHERS) {
- creatureType = CREATURETYPE_SUMMON_PLAYER;
- }
- msg.addByte(creatureType); // type or any byte idk
- if (creatureType == CREATURETYPE_SUMMON_PLAYER) {
- const Creature* master = creature->getMaster();
- if (master) {
- msg.add(master->getID());
- } else {
- msg.add(0);
+ if (creature && !creature->isRemoved())
+ {
+ const Tile* tile = creature->getTile();
+ const Player* player = creature->getPlayer();
+ if (tile)
+ {
+ const Position& position = creature->getPosition();
+ int32_t stackpos = tile->getClientIndexOfCreature(player, creature);
+
+ sendUpdateTileCreature(position, stackpos, creature);
}
}
-
- writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatureSquare(const Creature *creature, SquareColor_t color)
@@ -2860,9 +2860,9 @@ void ProtocolGame::sendCyclopediaCharacterBaseInformation()
msg.add(player->getLevel());
AddOutfit(msg, player->getDefaultOutfit(), false);
- msg.addByte(0x00); // hide stamina
- msg.addByte(0x00); // enable store summary & character titles
- msg.addString(""); // character title
+ msg.addByte(0x01); // hide stamina
+ msg.addByte(0x01); // enable store summary & character titles
+ msg.addString("Loyal"); // character title
writeToOutputBuffer(msg);
}
@@ -2911,10 +2911,10 @@ void ProtocolGame::sendCyclopediaCharacterGeneralStats()
// loyalty bonus
msg.add(player->getBaseMagicLevel());
msg.add(player->getMagicLevelPercent() * 100);
+
+ static const uint8_t HardcodedSkillIds[] = { 11, 9, 8, 10, 7, 6, 13 };
for (uint8_t i = SKILL_FIRST; i < SKILL_CRITICAL_HIT_CHANCE; ++i)
{
- // check if all clients have the same hardcoded skill ids
- static const uint8_t HardcodedSkillIds[] = {11, 9, 8, 10, 7, 6, 13};
msg.addByte(HardcodedSkillIds[i]);
msg.add(std::min(player->getSkillLevel(i), std::numeric_limits::max()));
msg.add(player->getBaseSkill(i));
@@ -2922,6 +2922,19 @@ void ProtocolGame::sendCyclopediaCharacterGeneralStats()
msg.add(player->getBaseSkill(i));
msg.add(player->getSkillPercent(i) * 100);
}
+
+ auto bufferPosition = msg.getBufferPosition();
+ msg.skipBytes(1);
+ uint8_t total = 0;
+ for (size_t i = 0; i < COMBAT_COUNT; i++) {
+ if (player->getSpecializedMagicLevel(indexToCombatType(i)) > 0) {
+ ++total;
+ msg.addByte(getCipbiaElement(indexToCombatType(i)));
+ msg.add(static_cast(player->getSpecializedMagicLevel(indexToCombatType(i))));
+ }
+ }
+ msg.setBufferPosition(bufferPosition);
+ msg.addByte(total);
writeToOutputBuffer(msg);
}
@@ -2936,6 +2949,18 @@ void ProtocolGame::sendCyclopediaCharacterCombatStats()
msg.add(std::min(player->getSkillLevel(i), std::numeric_limits::max()));
msg.add(0);
}
+
+ msg.add(static_cast(player->getCleavePercent())); //cleave %
+ msg.add(static_cast(player->getMagicShieldCapacityFlat())); //direct bonus
+ msg.add(static_cast(player->getMagicShieldCapacityPercent())); //percent bonus
+
+ //at range
+ for (uint8_t range = 1; range <= 5; range++) {
+ msg.add(static_cast(player->getPerfectShotDamage(range)));
+ }
+
+ msg.add(static_cast(player->getReflectFlat(COMBAT_PHYSICALDAMAGE))); //damage deflection
+
uint8_t haveBlesses = 0;
uint8_t blessings = 8;
for (uint8_t i = 1; i < blessings; ++i)
@@ -2986,8 +3011,29 @@ void ProtocolGame::sendCyclopediaCharacterCombatStats()
}
else
{
- msg.addByte(0);
- msg.addByte(CIPBIA_ELEMENTAL_UNDEFINED);
+ bool imbueDmg = false;
+ Item* weaponNC = player->getWeapon(true);
+ if (weaponNC) {
+ uint8_t slots = Item::items[weaponNC->getID()].imbuingSlots;
+ if (slots > 0) {
+ for (uint8_t i = 0; i < slots; i++) {
+ uint32_t info = weaponNC->getImbuement(i);
+ if (info >> 8) {
+ Imbuement* ib = g_imbuements->getImbuement(info & 0xFF);
+ if (ib->combatType != COMBAT_NONE) {
+ msg.addByte(static_cast(ib->elementDamage));
+ msg.addByte(getCipbiaElement(ib->combatType));
+ imbueDmg = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!imbueDmg) {
+ msg.addByte(0);
+ msg.addByte(CIPBIA_ELEMENTAL_UNDEFINED);
+ }
}
}
else
@@ -3009,8 +3055,29 @@ void ProtocolGame::sendCyclopediaCharacterCombatStats()
}
else
{
- msg.addByte(0);
- msg.addByte(CIPBIA_ELEMENTAL_UNDEFINED);
+ bool imbueDmg = false;
+ Item* weaponNC = player->getWeapon();
+ if (weaponNC) {
+ uint8_t slots = Item::items[weaponNC->getID()].imbuingSlots;
+ if (slots > 0) {
+ for (uint8_t i = 0; i < slots; i++) {
+ uint32_t info = weaponNC->getImbuement(i);
+ if (info >> 8) {
+ Imbuement* ib = g_imbuements->getImbuement(info & 0xFF);
+ if (ib->combatType != COMBAT_NONE) {
+ msg.addByte(static_cast(ib->elementDamage));
+ msg.addByte(getCipbiaElement(ib->combatType));
+ imbueDmg = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!imbueDmg) {
+ msg.addByte(0);
+ msg.addByte(CIPBIA_ELEMENTAL_UNDEFINED);
+ }
}
}
}
@@ -3033,7 +3100,7 @@ void ProtocolGame::sendCyclopediaCharacterCombatStats()
auto startCombats = msg.getBufferPosition();
msg.skipBytes(1);
- alignas(16) int16_t absorbs[COMBAT_COUNT] = {};
+ alignas(16) int16_t absorbs[COMBAT_COUNT] = { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 };
for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot)
{
if (!player->isItemAbilityEnabled(static_cast(slot)))
@@ -3053,45 +3120,60 @@ void ProtocolGame::sendCyclopediaCharacterCombatStats()
continue;
}
- if (COMBAT_COUNT == 12)
- {
- absorbs[0] += it.abilities->absorbPercent[0];
- absorbs[1] += it.abilities->absorbPercent[1];
- absorbs[2] += it.abilities->absorbPercent[2];
- absorbs[3] += it.abilities->absorbPercent[3];
- absorbs[4] += it.abilities->absorbPercent[4];
- absorbs[5] += it.abilities->absorbPercent[5];
- absorbs[6] += it.abilities->absorbPercent[6];
- absorbs[7] += it.abilities->absorbPercent[7];
- absorbs[8] += it.abilities->absorbPercent[8];
- absorbs[9] += it.abilities->absorbPercent[9];
- absorbs[10] += it.abilities->absorbPercent[10];
- absorbs[11] += it.abilities->absorbPercent[11];
+ for (uint16_t i = 0; i < COMBAT_COUNT; ++i) {
+ absorbs[i] *= (std::floor(100 - it.abilities->absorbPercent[i]) / 100.);
}
- else
- {
- for (size_t i = 0; i < COMBAT_COUNT; ++i)
- {
- absorbs[i] += it.abilities->absorbPercent[i];
+ uint8_t slots = Item::items[item->getID()].imbuingSlots;
+ if (slots > 0) {
+ for (uint8_t i = 0; i < slots; i++) {
+ uint32_t info = item->getImbuement(i);
+ if (info >> 8) {
+ Imbuement* ib = g_imbuements->getImbuement(info & 0xFF);
+ for (uint16_t i = 0; i < COMBAT_COUNT; ++i) {
+ const int16_t& absorbPercent2 = ib->absorbPercent[i];
+
+ if (absorbPercent2 != 0) {
+ absorbs[i] *= (std::floor(100 - absorbPercent2) / 100.);
+ }
+ }
+ }
}
}
}
-
- static const Cipbia_Elementals_t cipbiaCombats[] = {CIPBIA_ELEMENTAL_PHYSICAL, CIPBIA_ELEMENTAL_ENERGY, CIPBIA_ELEMENTAL_EARTH, CIPBIA_ELEMENTAL_FIRE, CIPBIA_ELEMENTAL_UNDEFINED,
- CIPBIA_ELEMENTAL_LIFEDRAIN, CIPBIA_ELEMENTAL_UNDEFINED, CIPBIA_ELEMENTAL_HEALING, CIPBIA_ELEMENTAL_DROWN, CIPBIA_ELEMENTAL_ICE, CIPBIA_ELEMENTAL_HOLY, CIPBIA_ELEMENTAL_DEATH};
+ int32_t value;
for (size_t i = 0; i < COMBAT_COUNT; ++i)
{
- if (absorbs[i] != 0)
+ if (absorbs[i] != 100)
{
- msg.addByte(cipbiaCombats[i]);
- msg.addByte(std::max(-100, std::min(100, absorbs[i])));
- ++combats;
+ if (value != -1) {
+ msg.addByte(getCipbiaElement(indexToCombatType(i)));
+ msg.addByte(std::max(-100, std::min(100, (100 - absorbs[i]) ) ) );
+ ++combats;
+ }
}
}
+ auto actual = msg.getBufferPosition();
+
msg.setBufferPosition(startCombats);
msg.addByte(combats);
+ msg.setBufferPosition(actual);
+
+ msg.skipBytes(1);
+ uint8_t total = 0;
+ int64_t timeNow = OTSYS_TIME()/1000;
+ for (int itemID = ITEM_TIBIADROME_POTION_START; itemID <= ITEM_TIBIADROME_POTION_END; itemID++) {
+ Condition* condition = player->getCondition(CONDITION_TIBIADROMEPOTIONS, CONDITIONID_DEFAULT, itemID);
+ if (condition && condition->getEndTime()/1000 >= timeNow) {
+ msg.addItemId(itemID);
+ msg.add(static_cast(std::floor(condition->getEndTime()/1000-timeNow)));
+ total++;
+ }
+ }
+ msg.setBufferPosition(actual);
+ msg.addByte(total);
+
writeToOutputBuffer(msg);
}
@@ -3148,11 +3230,66 @@ void ProtocolGame::sendCyclopediaCharacterItemSummary()
msg.addByte(0xDA);
msg.addByte(CYCLOPEDIA_CHARACTERINFO_ITEMSUMMARY);
msg.addByte(0x00);
- msg.add(0);
- msg.add(0);
- msg.add(0);
- msg.add(0);
- msg.add(0);
+
+ // Inventory
+ auto startInventoryItems = msg.getBufferPosition();
+ uint16_t inventoryItems = 0;
+ msg.skipBytes(2);
+
+ for (std::underlying_type::type slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_AMMO; slot++)
+ {
+ Item *inventoryItem = player->getInventoryItem(static_cast(slot));
+ if (inventoryItem)
+ {
+ ++inventoryItems;
+ msg.add(inventoryItem->getClientID());
+ msg.add(inventoryItem->getItemCount());
+ }
+ // TODO show / count items in backpack
+ }
+
+ // Stash
+ // TODO Why does it display items in the store inbox?
+ auto startStashItems = msg.getBufferPosition();
+ StashItemList stashItems = player->getStashItems();
+ msg.skipBytes(2);
+
+ for (auto item : stashItems) {
+ msg.add(item.first);
+ msg.add(item.second);
+ }
+
+ // DepotItems
+ auto startDepotItems = msg.getBufferPosition();
+ uint16_t depotItems = 0;
+ msg.skipBytes(2);
+
+ // StoreInboxItems
+ auto startStoreInboxItems = msg.getBufferPosition();
+ uint16_t storeInboxItems = 0;
+ msg.skipBytes(2);
+
+ // InboxItems
+ auto startInboxItems = msg.getBufferPosition();
+ uint16_t inboxItems = 0;
+ msg.skipBytes(2);
+
+ // execute
+ msg.setBufferPosition(startInventoryItems);
+ msg.add(inventoryItems);
+
+ msg.setBufferPosition(startDepotItems);
+ msg.add(depotItems);
+
+ msg.setBufferPosition(startStashItems);
+ msg.add(stashItems.size());
+
+ msg.setBufferPosition(startInboxItems);
+ msg.add(inboxItems);
+
+ msg.setBufferPosition(startStoreInboxItems);
+ msg.add(storeInboxItems);
+
writeToOutputBuffer(msg);
}
@@ -3270,7 +3407,9 @@ void ProtocolGame::sendCyclopediaCharacterStoreSummary()
msg.addByte(0xDA);
msg.addByte(CYCLOPEDIA_CHARACTERINFO_STORESUMMARY);
msg.addByte(0x00);
- msg.add(0);
+ // Remaining Store Xp Boost Time
+ msg.add(player->getExpBoostStamina());
+ // RemainingDailyRewardXpBoostTime
msg.add(0);
msg.addByte(0x00);
msg.addByte(0x00);
@@ -3346,8 +3485,18 @@ void ProtocolGame::sendCyclopediaCharacterBadges()
msg.addByte(0xDA);
msg.addByte(CYCLOPEDIA_CHARACTERINFO_BADGES);
msg.addByte(0x00);
- // enable badges
+ // ShowAccountInformation
+ msg.addByte(0x01);
+ // if ShowAccountInformation show IsOnline, IsPremium, character title, badges
+ // IsOnline
+ msg.addByte(0x01);
+ // IsPremium (GOD has always 'Premium')
+ msg.addByte(player->isPremium() ? 0x01 : 0x00);
+ // character title
+ msg.addString("");
+ // badges
msg.addByte(0x00);
+ // Todo badges loop
writeToOutputBuffer(msg);
}
@@ -3960,7 +4109,6 @@ void ProtocolGame::sendMarketEnter(uint32_t depotId)
{
continue;
}
-
Container *c = item->getContainer();
if (c && !c->empty())
{
@@ -4439,7 +4587,25 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId)
separator = true;
}
- ss << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos;
+ ss << " magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos;
+ }
+
+ // Version 12.72 (Specialized magic level modifier)
+ for (uint8_t i = 1; i <= 11; i++)
+ {
+ if (it.abilities->specializedMagicLevel[i])
+ {
+ if (separator)
+ {
+ ss << ", ";
+ }
+ else
+ {
+ separator = true;
+ }
+ std::string combatName = getCombatName(indexToCombatType(i));
+ ss << std::showpos << combatName << std::noshowpos << " magic level +" << it.abilities->specializedMagicLevel[i];
+ }
}
if (it.abilities->speed != 0)
@@ -4519,12 +4685,71 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId)
msg.add(0x00);
}
+ // Version 12.70
+
+ //cleave
+ if (it.abilities) {
+
+ std::ostringstream string;
+
+ if (it.abilities->magicShieldCapacityFlat > 0)
+ {
+ string.clear();
+ string << std::showpos << it.abilities->magicShieldCapacityFlat << std::noshowpos << " and " << it.abilities->magicShieldCapacityPercent << "%";
+ msg.addString(string.str());
+ }
+ else {
+ msg.add(0x00);
+ }
+
+ if (it.abilities->cleavePercent > 0) {
+ string.clear();
+ string << it.abilities->cleavePercent << "%";
+ msg.addString(string.str());
+ } else {
+ msg.add(0x00);
+ }
+
+ if (it.abilities->reflectFlat[COMBAT_PHYSICALDAMAGE] > 0) {
+ string.clear();
+ string << it.abilities->reflectFlat[COMBAT_PHYSICALDAMAGE];
+ msg.addString(string.str());
+ } else {
+ msg.add(0x00);
+ }
+
+
+ if (it.abilities->perfectShotDamage > 0) {
+ string.clear();
+ string << std::showpos << it.abilities->perfectShotDamage << std::noshowpos << " at " << it.abilities->perfectShotRange << "%";
+ msg.addString(string.str());
+ } else {
+ msg.add(0x00);
+ }
+
+
+
+ } else {
+ //cleave
+ msg.add(0x00);
+
+ //magic shield capacity
+ msg.add(0x00);
+
+ //perfect shot
+ msg.add(0x00);
+
+ //damage deflect
+ msg.add(0x00);
+ }
+
MarketStatistics *statistics = IOMarket::getInstance().getPurchaseStatistics(itemId);
if (statistics)
{
msg.addByte(0x01);
msg.add(statistics->numTransactions);
msg.add(std::min(std::numeric_limits::max(), statistics->totalPrice));
+ msg.add(0);
msg.add(statistics->highestPrice);
msg.add(statistics->lowestPrice);
}
@@ -4539,6 +4764,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId)
msg.addByte(0x01);
msg.add(statistics->numTransactions);
msg.add(std::min(std::numeric_limits::max(), statistics->totalPrice));
+ msg.add(0);
msg.add(statistics->highestPrice);
msg.add(statistics->lowestPrice);
}
@@ -5081,6 +5307,25 @@ void ProtocolGame::sendRemoveTileThing(const Position &pos, uint32_t stackpos)
RemoveTileThing(msg, pos, stackpos);
writeToOutputBuffer(msg);
}
+void ProtocolGame::sendUpdateTileCreature(const Position& pos, uint32_t stackpos, const Creature* creature)
+{
+ if (!canSee(pos))
+ {
+ return;
+ }
+
+ NetworkMessage msg;
+ msg.addByte(0x6B);
+ msg.addPosition(pos);
+ msg.addByte(stackpos);
+
+ bool known;
+ uint32_t removedKnown;
+ checkCreatureAsKnown(creature->getID(), known, removedKnown);
+ AddCreature(msg, creature, false, removedKnown);
+
+ writeToOutputBuffer(msg);
+}
void ProtocolGame::sendUpdateTile(const Tile *tile, const Position &pos)
{
diff --git a/src/server/network/protocol/protocolgame.h b/src/server/network/protocol/protocolgame.h
index da2c7196699..3eeec07f2fb 100644
--- a/src/server/network/protocol/protocolgame.h
+++ b/src/server/network/protocol/protocolgame.h
@@ -54,7 +54,7 @@ struct TextMessage
struct
{
int32_t value = 0;
- TextColor_t color;
+ TextColor_t color = TEXTCOLOR_NONE;
} primary, secondary;
TextMessage() = default;
@@ -306,7 +306,7 @@ class ProtocolGame final : public Protocol
void sendCreatureWalkthrough(const Creature *creature, bool walkthrough);
void sendCreatureShield(const Creature *creature);
void sendCreatureSkull(const Creature *creature);
- void sendCreatureType(const Creature *creature, uint8_t creatureType);
+ void sendCreatureUpdate(const Creature *creature);
void sendShop(Npc *npc);
void sendCloseShop();
@@ -368,6 +368,7 @@ class ProtocolGame final : public Protocol
void sendAddTileItem(const Position &pos, uint32_t stackpos, const Item *item);
void sendUpdateTileItem(const Position &pos, uint32_t stackpos, const Item *item);
void sendRemoveTileThing(const Position &pos, uint32_t stackpos);
+ void sendUpdateTileCreature(const Position& pos, uint32_t stackpos, const Creature* creature);
void sendUpdateTile(const Tile *tile, const Position &pos);
void sendAddCreature(const Creature *creature, const Position &pos, int32_t stackpos, bool isLogin);
diff --git a/src/utils/tools.cpp b/src/utils/tools.cpp
index 7614e3b591e..0cac91ec656 100644
--- a/src/utils/tools.cpp
+++ b/src/utils/tools.cpp
@@ -520,228 +520,243 @@ using SkullNames = std::unordered_map;
using SpawnTypeNames = std::unordered_map;
MagicEffectNames magicEffectNames = {
- {"assassin", CONST_ME_ASSASSIN},
- {"bluefireworks", CONST_ME_BLUE_FIREWORKS },
- {"bluebubble", CONST_ME_LOSEENERGY},
- {"blackspark", CONST_ME_HITAREA},
- {"blueshimmer", CONST_ME_MAGIC_BLUE},
- {"bluenote", CONST_ME_SOUND_BLUE},
- {"bubbles", CONST_ME_BUBBLES},
- {"bluefirework", CONST_ME_FIREWORK_BLUE},
- {"bigclouds", CONST_ME_BIGCLOUDS},
- {"bigplants", CONST_ME_BIGPLANTS},
- {"bloodysteps", CONST_ME_BLOODYSTEPS},
- {"bats", CONST_ME_BATS},
- {"blueenergyspark", CONST_ME_BLUE_ENERGY_SPARK },
- {"blueghost", CONST_ME_BLUE_GHOST },
- {"blacksmoke", CONST_ME_BLACKSMOKE},
- {"carniphila", CONST_ME_CARNIPHILA},
- {"cake", CONST_ME_CAKE},
- {"confettihorizontal", CONST_ME_CONFETTI_HORIZONTAL},
- {"confettivertical", CONST_ME_CONFETTI_VERTICAL},
- {"criticaldagame", CONST_ME_CRITICAL_DAMAGE},
- {"dice", CONST_ME_CRAPS},
- {"dragonhead", CONST_ME_DRAGONHEAD},
- {"explosionarea", CONST_ME_EXPLOSIONAREA},
- {"explosion", CONST_ME_EXPLOSIONHIT},
- {"energy", CONST_ME_ENERGYHIT},
- {"energyarea", CONST_ME_ENERGYAREA},
- {"earlythunder", CONST_ME_EARLY_THUNDER },
- {"fire", CONST_ME_HITBYFIRE},
- {"firearea", CONST_ME_FIREAREA},
- {"fireattack", CONST_ME_FIREATTACK},
- {"ferumbras", CONST_ME_FERUMBRAS},
- {"greenspark", CONST_ME_HITBYPOISON},
- {"greenbubble", CONST_ME_GREEN_RINGS},
- {"greennote", CONST_ME_SOUND_GREEN},
- {"greenshimmer", CONST_ME_MAGIC_GREEN},
- {"giftwraps", CONST_ME_GIFT_WRAPS},
- {"groundshaker", CONST_ME_GROUNDSHAKER},
- {"giantice", CONST_ME_GIANTICE},
- {"greensmoke", CONST_ME_GREENSMOKE},
- {"greenenergyspark", CONST_ME_GREEN_ENERGY_SPARK },
- {"greenfireworks", CONST_ME_GREEN_FIREWORKS },
- {"hearts", CONST_ME_HEARTS},
- {"holydamage", CONST_ME_HOLYDAMAGE},
- {"holyarea", CONST_ME_HOLYAREA},
- {"icearea", CONST_ME_ICEAREA},
- {"icetornado", CONST_ME_ICETORNADO},
- {"iceattack", CONST_ME_ICEATTACK},
- {"insects", CONST_ME_INSECTS},
- {"mortarea", CONST_ME_MORTAREA},
- {"mirrorhorizontal", CONST_ME_MIRRORHORIZONTAL},
- {"mirrorvertical", CONST_ME_MIRRORVERTICAL},
- {"magicpowder", CONST_ME_MAGIC_POWDER },
- {"orcshaman", CONST_ME_ORCSHAMAN},
- {"orcshamanfire", CONST_ME_ORCSHAMAN_FIRE},
- {"orangeenergyspark", CONST_ME_ORANGE_ENERGY_SPARK },
- {"orangefireworks", CONST_ME_ORANGE_FIREWORKS },
- {"poff", CONST_ME_POFF},
- {"poison", CONST_ME_POISONAREA},
- {"purplenote", CONST_ME_SOUND_PURPLE},
- {"purpleenergy", CONST_ME_PURPLEENERGY},
- {"plantattack", CONST_ME_PLANTATTACK},
- {"plugingfish", CONST_ME_PLUNGING_FISH},
- {"purplesmoke", CONST_ME_PURPLESMOKE},
- {"pixieexplosion", CONST_ME_PIXIE_EXPLOSION },
- {"pixiecoming", CONST_ME_PIXIE_COMING },
- {"pixiegoing", CONST_ME_PIXIE_GOING },
- {"pinkbeam", CONST_ME_PINK_BEAM },
- {"pinkvortex", CONST_ME_PINK_VORTEX },
- {"pinkenergyspark", CONST_ME_PINK_ENERGY_SPARK },
- {"pinkfireworks", CONST_ME_PINK_FIREWORKS },
- {"redspark", CONST_ME_DRAWBLOOD},
- {"redshimmer", CONST_ME_MAGIC_RED},
- {"rednote", CONST_ME_SOUND_RED},
- {"redfirework", CONST_ME_FIREWORK_RED},
- {"redsmoke", CONST_ME_REDSMOKE},
- {"ragiazbonecapsule", CONST_ME_RAGIAZ_BONECAPSULE},
- {"stun", CONST_ME_STUN},
- {"sleep", CONST_ME_SLEEP},
- {"smallclouds", CONST_ME_SMALLCLOUDS},
- {"stones", CONST_ME_STONES},
- {"smallplants", CONST_ME_SMALLPLANTS},
- {"skullhorizontal", CONST_ME_SKULLHORIZONTAL},
- {"skullvertical", CONST_ME_SKULLVERTICAL},
- {"stepshorizontal", CONST_ME_STEPSHORIZONTAL},
- {"stepsvertical", CONST_ME_STEPSVERTICAL},
- {"smoke", CONST_ME_SMOKE},
- {"storm", CONST_ME_STORM },
- {"stonestorm", CONST_ME_STONE_STORM },
- {"teleport", CONST_ME_TELEPORT},
- {"tutorialarrow", CONST_ME_TUTORIALARROW},
- {"tutorialsquare", CONST_ME_TUTORIALSQUARE},
- {"thunder", CONST_ME_THUNDER},
- {"treasuremap", CONST_ME_TREASURE_MAP },
- {"yellowspark", CONST_ME_BLOCKHIT},
- {"yellowbubble", CONST_ME_YELLOW_RINGS},
- {"yellownote", CONST_ME_SOUND_YELLOW},
- {"yellowfirework", CONST_ME_FIREWORK_YELLOW},
- {"yellowenergy", CONST_ME_YELLOWENERGY},
- {"yalaharighost", CONST_ME_YALAHARIGHOST},
- {"yellowsmoke", CONST_ME_YELLOWSMOKE},
- {"yellowenergyspark", CONST_ME_YELLOW_ENERGY_SPARK },
- {"whitenote", CONST_ME_SOUND_WHITE},
- {"watercreature", CONST_ME_WATERCREATURE},
- {"watersplash", CONST_ME_WATERSPLASH},
- {"whiteenergyspark", CONST_ME_WHITE_ENERGY_SPARK },
+ {"assassin", CONST_ME_ASSASSIN},
+ {"bluefireworks", CONST_ME_BLUE_FIREWORKS},
+ {"bluebubble", CONST_ME_LOSEENERGY},
+ {"blackspark", CONST_ME_HITAREA},
+ {"blueshimmer", CONST_ME_MAGIC_BLUE},
+ {"bluenote", CONST_ME_SOUND_BLUE},
+ {"bubbles", CONST_ME_BUBBLES},
+ {"bluefirework", CONST_ME_FIREWORK_BLUE},
+ {"bigclouds", CONST_ME_BIGCLOUDS},
+ {"bigplants", CONST_ME_BIGPLANTS},
+ {"bloodysteps", CONST_ME_BLOODYSTEPS},
+ {"bats", CONST_ME_BATS},
+ {"blueenergyspark", CONST_ME_BLUE_ENERGY_SPARK},
+ {"blueghost", CONST_ME_BLUE_GHOST},
+ {"blacksmoke", CONST_ME_BLACKSMOKE},
+ {"carniphila", CONST_ME_CARNIPHILA},
+ {"cake", CONST_ME_CAKE},
+ {"confettihorizontal", CONST_ME_CONFETTI_HORIZONTAL},
+ {"confettivertical", CONST_ME_CONFETTI_VERTICAL},
+ {"criticaldagame", CONST_ME_CRITICAL_DAMAGE},
+ {"dice", CONST_ME_CRAPS},
+ {"dragonhead", CONST_ME_DRAGONHEAD},
+ {"explosionarea", CONST_ME_EXPLOSIONAREA},
+ {"explosion", CONST_ME_EXPLOSIONHIT},
+ {"energy", CONST_ME_ENERGYHIT},
+ {"energyarea", CONST_ME_ENERGYAREA},
+ {"earlythunder", CONST_ME_EARLY_THUNDER},
+ {"fire", CONST_ME_HITBYFIRE},
+ {"firearea", CONST_ME_FIREAREA},
+ {"fireattack", CONST_ME_FIREATTACK},
+ {"ferumbras", CONST_ME_FERUMBRAS},
+ {"greenspark", CONST_ME_HITBYPOISON},
+ {"greenbubble", CONST_ME_GREEN_RINGS},
+ {"greennote", CONST_ME_SOUND_GREEN},
+ {"greenshimmer", CONST_ME_MAGIC_GREEN},
+ {"giftwraps", CONST_ME_GIFT_WRAPS},
+ {"groundshaker", CONST_ME_GROUNDSHAKER},
+ {"giantice", CONST_ME_GIANTICE},
+ {"greensmoke", CONST_ME_GREENSMOKE},
+ {"greenenergyspark", CONST_ME_GREEN_ENERGY_SPARK},
+ {"greenfireworks", CONST_ME_GREEN_FIREWORKS},
+ {"hearts", CONST_ME_HEARTS},
+ {"holydamage", CONST_ME_HOLYDAMAGE},
+ {"holyarea", CONST_ME_HOLYAREA},
+ {"icearea", CONST_ME_ICEAREA},
+ {"icetornado", CONST_ME_ICETORNADO},
+ {"iceattack", CONST_ME_ICEATTACK},
+ {"insects", CONST_ME_INSECTS},
+ {"mortarea", CONST_ME_MORTAREA},
+ {"mirrorhorizontal", CONST_ME_MIRRORHORIZONTAL},
+ {"mirrorvertical", CONST_ME_MIRRORVERTICAL},
+ {"magicpowder", CONST_ME_MAGIC_POWDER},
+ {"orcshaman", CONST_ME_ORCSHAMAN},
+ {"orcshamanfire", CONST_ME_ORCSHAMAN_FIRE},
+ {"orangeenergyspark", CONST_ME_ORANGE_ENERGY_SPARK},
+ {"orangefireworks", CONST_ME_ORANGE_FIREWORKS},
+ {"poff", CONST_ME_POFF},
+ {"poison", CONST_ME_POISONAREA},
+ {"purplenote", CONST_ME_SOUND_PURPLE},
+ {"purpleenergy", CONST_ME_PURPLEENERGY},
+ {"plantattack", CONST_ME_PLANTATTACK},
+ {"plugingfish", CONST_ME_PLUNGING_FISH},
+ {"purplesmoke", CONST_ME_PURPLESMOKE},
+ {"pixieexplosion", CONST_ME_PIXIE_EXPLOSION},
+ {"pixiecoming", CONST_ME_PIXIE_COMING},
+ {"pixiegoing", CONST_ME_PIXIE_GOING},
+ {"pinkbeam", CONST_ME_PINK_BEAM},
+ {"pinkvortex", CONST_ME_PINK_VORTEX},
+ {"pinkenergyspark", CONST_ME_PINK_ENERGY_SPARK},
+ {"pinkfireworks", CONST_ME_PINK_FIREWORKS},
+ {"redspark", CONST_ME_DRAWBLOOD},
+ {"redshimmer", CONST_ME_MAGIC_RED},
+ {"rednote", CONST_ME_SOUND_RED},
+ {"redfirework", CONST_ME_FIREWORK_RED},
+ {"redsmoke", CONST_ME_REDSMOKE},
+ {"ragiazbonecapsule", CONST_ME_RAGIAZ_BONECAPSULE},
+ {"stun", CONST_ME_STUN},
+ {"sleep", CONST_ME_SLEEP},
+ {"smallclouds", CONST_ME_SMALLCLOUDS},
+ {"stones", CONST_ME_STONES},
+ {"smallplants", CONST_ME_SMALLPLANTS},
+ {"skullhorizontal", CONST_ME_SKULLHORIZONTAL},
+ {"skullvertical", CONST_ME_SKULLVERTICAL},
+ {"stepshorizontal", CONST_ME_STEPSHORIZONTAL},
+ {"stepsvertical", CONST_ME_STEPSVERTICAL},
+ {"smoke", CONST_ME_SMOKE},
+ {"storm", CONST_ME_STORM},
+ {"stonestorm", CONST_ME_STONE_STORM},
+ {"teleport", CONST_ME_TELEPORT},
+ {"tutorialarrow", CONST_ME_TUTORIALARROW},
+ {"tutorialsquare", CONST_ME_TUTORIALSQUARE},
+ {"thunder", CONST_ME_THUNDER},
+ {"treasuremap", CONST_ME_TREASURE_MAP},
+ {"yellowspark", CONST_ME_BLOCKHIT},
+ {"yellowbubble", CONST_ME_YELLOW_RINGS},
+ {"yellownote", CONST_ME_SOUND_YELLOW},
+ {"yellowfirework", CONST_ME_FIREWORK_YELLOW},
+ {"yellowenergy", CONST_ME_YELLOWENERGY},
+ {"yalaharighost", CONST_ME_YALAHARIGHOST},
+ {"yellowsmoke", CONST_ME_YELLOWSMOKE},
+ {"yellowenergyspark", CONST_ME_YELLOW_ENERGY_SPARK},
+ {"whitenote", CONST_ME_SOUND_WHITE},
+ {"watercreature", CONST_ME_WATERCREATURE},
+ {"watersplash", CONST_ME_WATERSPLASH},
+ {"whiteenergyspark", CONST_ME_WHITE_ENERGY_SPARK},
+ {"cube", CONST_ME_CUBE},
+ {"blackhit", CONST_ME_BLACKHIT},
+ {"rainbowshimmer", CONST_ME_MAGIC_RAINBOW},
+ {"thaian", CONST_ME_THAIAN},
+ {"thaiansoul", CONST_ME_THAIANSOUL},
+ {"ghost", CONST_ME_GHOST},
+ {"block", CONST_ME_BLOCK},
+ {"blockwater", CONST_ME_BLOCKWATER},
+ {"crawler", CONST_ME_CRAWLER},
+ {"bite", CONST_ME_BITE},
+ {"redclaw", CONST_ME_REDCLAW},
+ {"bluetail", CONST_ME_BLUETAIL},
+ {"bigbite", CONST_ME_BIGBITE},
+ {"chivalriouschallange", CONST_ME_CHIVALRIOUS_CHALLENGE},
+ {"divinedazzle", CONST_ME_DIVINE_DAZZLE}
};
ShootTypeNames shootTypeNames = {
- {"arrow", CONST_ANI_ARROW},
- {"bolt", CONST_ANI_BOLT},
- {"burstarrow", CONST_ANI_BURSTARROW},
- {"cake", CONST_ANI_CAKE},
- {"crystallinearrow", CONST_ANI_CRYSTALLINEARROW},
- {"drillbolt", CONST_ANI_DRILLBOLT},
- {"death", CONST_ANI_DEATH},
- {"energy", CONST_ANI_ENERGY},
- {"enchantedspear", CONST_ANI_ENCHANTEDSPEAR},
- {"etherealspear", CONST_ANI_ETHEREALSPEAR},
- {"eartharrow", CONST_ANI_EARTHARROW},
- {"explosion", CONST_ANI_EXPLOSION},
- {"earth", CONST_ANI_EARTH},
- {"energyball", CONST_ANI_ENERGYBALL},
- {"envenomedarrow", CONST_ANI_ENVENOMEDARROW},
- {"fire", CONST_ANI_FIRE},
- {"flasharrow", CONST_ANI_FLASHARROW},
- {"flammingarrow", CONST_ANI_FLAMMINGARROW},
- {"greenstar", CONST_ANI_GREENSTAR},
- {"gloothspear", CONST_ANI_GLOOTHSPEAR},
- {"huntingspear", CONST_ANI_HUNTINGSPEAR},
- {"holy", CONST_ANI_HOLY},
- {"infernalbolt", CONST_ANI_INFERNALBOLT},
- {"ice", CONST_ANI_ICE},
- {"largerock", CONST_ANI_LARGEROCK},
- {"leafstar", CONST_ANI_LEAFSTAR},
- {"onyxarrow", CONST_ANI_ONYXARROW},
- {"redstar", CONST_ANI_REDSTAR},
- {"royalspear", CONST_ANI_ROYALSPEAR},
- {"spear", CONST_ANI_SPEAR},
- {"sniperarrow", CONST_ANI_SNIPERARROW},
- {"smallstone", CONST_ANI_SMALLSTONE},
- {"smallice", CONST_ANI_SMALLICE},
- {"smallholy", CONST_ANI_SMALLHOLY},
- {"smallearth", CONST_ANI_SMALLEARTH},
- {"snowball", CONST_ANI_SNOWBALL},
- {"suddendeath", CONST_ANI_SUDDENDEATH},
- {"shiverarrow", CONST_ANI_SHIVERARROW},
- {"simplearrow", CONST_ANI_SIMPLEARROW},
- {"poisonarrow", CONST_ANI_POISONARROW},
- {"powerbolt", CONST_ANI_POWERBOLT},
- {"poison", CONST_ANI_POISON},
- {"prismaticbolt", CONST_ANI_PRISMATICBOLT},
- {"piercingbolt", CONST_ANI_PIERCINGBOLT},
- {"throwingstar", CONST_ANI_THROWINGSTAR},
- {"vortexbolt", CONST_ANI_VORTEXBOLT},
- {"throwingknife", CONST_ANI_THROWINGKNIFE},
- {"tarsalarrow", CONST_ANI_TARSALARROW},
- {"whirlwindsword", CONST_ANI_WHIRLWINDSWORD},
- {"whirlwindaxe", CONST_ANI_WHIRLWINDAXE},
- {"whirlwindclub", CONST_ANI_WHIRLWINDCLUB},
- {"diamondarrow", CONST_ANI_DIAMONDARROW},
- {"spectralbolt", CONST_ANI_SPECTRALBOLT},
- {"royalstar", CONST_ANI_ROYALSTAR},
+ {"arrow", CONST_ANI_ARROW},
+ {"bolt", CONST_ANI_BOLT},
+ {"burstarrow", CONST_ANI_BURSTARROW},
+ {"cake", CONST_ANI_CAKE},
+ {"crystallinearrow", CONST_ANI_CRYSTALLINEARROW},
+ {"drillbolt", CONST_ANI_DRILLBOLT},
+ {"death", CONST_ANI_DEATH},
+ {"energy", CONST_ANI_ENERGY},
+ {"enchantedspear", CONST_ANI_ENCHANTEDSPEAR},
+ {"etherealspear", CONST_ANI_ETHEREALSPEAR},
+ {"eartharrow", CONST_ANI_EARTHARROW},
+ {"explosion", CONST_ANI_EXPLOSION},
+ {"earth", CONST_ANI_EARTH},
+ {"energyball", CONST_ANI_ENERGYBALL},
+ {"envenomedarrow", CONST_ANI_ENVENOMEDARROW},
+ {"fire", CONST_ANI_FIRE},
+ {"flasharrow", CONST_ANI_FLASHARROW},
+ {"flammingarrow", CONST_ANI_FLAMMINGARROW},
+ {"greenstar", CONST_ANI_GREENSTAR},
+ {"gloothspear", CONST_ANI_GLOOTHSPEAR},
+ {"huntingspear", CONST_ANI_HUNTINGSPEAR},
+ {"holy", CONST_ANI_HOLY},
+ {"infernalbolt", CONST_ANI_INFERNALBOLT},
+ {"ice", CONST_ANI_ICE},
+ {"largerock", CONST_ANI_LARGEROCK},
+ {"leafstar", CONST_ANI_LEAFSTAR},
+ {"onyxarrow", CONST_ANI_ONYXARROW},
+ {"redstar", CONST_ANI_REDSTAR},
+ {"royalspear", CONST_ANI_ROYALSPEAR},
+ {"spear", CONST_ANI_SPEAR},
+ {"sniperarrow", CONST_ANI_SNIPERARROW},
+ {"smallstone", CONST_ANI_SMALLSTONE},
+ {"smallice", CONST_ANI_SMALLICE},
+ {"smallholy", CONST_ANI_SMALLHOLY},
+ {"smallearth", CONST_ANI_SMALLEARTH},
+ {"snowball", CONST_ANI_SNOWBALL},
+ {"suddendeath", CONST_ANI_SUDDENDEATH},
+ {"shiverarrow", CONST_ANI_SHIVERARROW},
+ {"simplearrow", CONST_ANI_SIMPLEARROW},
+ {"poisonarrow", CONST_ANI_POISONARROW},
+ {"powerbolt", CONST_ANI_POWERBOLT},
+ {"poison", CONST_ANI_POISON},
+ {"prismaticbolt", CONST_ANI_PRISMATICBOLT},
+ {"piercingbolt", CONST_ANI_PIERCINGBOLT},
+ {"throwingstar", CONST_ANI_THROWINGSTAR},
+ {"vortexbolt", CONST_ANI_VORTEXBOLT},
+ {"throwingknife", CONST_ANI_THROWINGKNIFE},
+ {"tarsalarrow", CONST_ANI_TARSALARROW},
+ {"whirlwindsword", CONST_ANI_WHIRLWINDSWORD},
+ {"whirlwindaxe", CONST_ANI_WHIRLWINDAXE},
+ {"whirlwindclub", CONST_ANI_WHIRLWINDCLUB},
+ {"diamondarrow", CONST_ANI_DIAMONDARROW},
+ {"spectralbolt", CONST_ANI_SPECTRALBOLT},
+ {"royalstar", CONST_ANI_ROYALSTAR},
};
CombatTypeNames combatTypeNames = {
- {COMBAT_DROWNDAMAGE, "drown" },
- {COMBAT_DEATHDAMAGE, "death" },
- {COMBAT_ENERGYDAMAGE, "energy" },
- {COMBAT_EARTHDAMAGE, "earth" },
- {COMBAT_FIREDAMAGE, "fire" },
- {COMBAT_HEALING, "healing" },
- {COMBAT_HOLYDAMAGE, "holy" },
- {COMBAT_ICEDAMAGE, "ice" },
- {COMBAT_UNDEFINEDDAMAGE, "undefined" },
- {COMBAT_LIFEDRAIN, "lifedrain" },
- {COMBAT_MANADRAIN, "manadrain" },
- {COMBAT_PHYSICALDAMAGE, "physical" },
+ {COMBAT_DROWNDAMAGE, "drown"},
+ {COMBAT_DEATHDAMAGE, "death"},
+ {COMBAT_ENERGYDAMAGE, "energy"},
+ {COMBAT_EARTHDAMAGE, "earth"},
+ {COMBAT_FIREDAMAGE, "fire"},
+ {COMBAT_HEALING, "healing"},
+ {COMBAT_HOLYDAMAGE, "holy"},
+ {COMBAT_ICEDAMAGE, "ice"},
+ {COMBAT_UNDEFINEDDAMAGE, "undefined"},
+ {COMBAT_LIFEDRAIN, "lifedrain"},
+ {COMBAT_MANADRAIN, "manadrain"},
+ {COMBAT_PHYSICALDAMAGE, "physical"},
};
AmmoTypeNames ammoTypeNames = {
- {"arrow", AMMO_ARROW},
- {"bolt", AMMO_BOLT},
- {"poisonarrow", AMMO_ARROW},
- {"burstarrow", AMMO_ARROW},
- {"enchantedspear", AMMO_SPEAR},
- {"etherealspear", AMMO_SPEAR},
- {"eartharrow", AMMO_ARROW},
- {"flasharrow", AMMO_ARROW},
- {"flammingarrow", AMMO_ARROW},
- {"huntingspear", AMMO_SPEAR},
- {"infernalbolt", AMMO_BOLT},
- {"largerock", AMMO_STONE},
- {"onyxarrow", AMMO_ARROW},
- {"powerbolt", AMMO_BOLT},
- {"piercingbolt", AMMO_BOLT},
- {"royalspear", AMMO_SPEAR},
- {"snowball", AMMO_SNOWBALL},
- {"smallstone", AMMO_STONE},
- {"spear", AMMO_SPEAR},
- {"sniperarrow", AMMO_ARROW},
- {"shiverarrow", AMMO_ARROW},
- {"throwingstar", AMMO_THROWINGSTAR},
- {"throwingknife", AMMO_THROWINGKNIFE},
- {"diamondarrow", AMMO_ARROW},
- {"spectralbolt", AMMO_BOLT},
+ {"arrow", AMMO_ARROW},
+ {"bolt", AMMO_BOLT},
+ {"poisonarrow", AMMO_ARROW},
+ {"burstarrow", AMMO_ARROW},
+ {"enchantedspear", AMMO_SPEAR},
+ {"etherealspear", AMMO_SPEAR},
+ {"eartharrow", AMMO_ARROW},
+ {"flasharrow", AMMO_ARROW},
+ {"flammingarrow", AMMO_ARROW},
+ {"huntingspear", AMMO_SPEAR},
+ {"infernalbolt", AMMO_BOLT},
+ {"largerock", AMMO_STONE},
+ {"onyxarrow", AMMO_ARROW},
+ {"powerbolt", AMMO_BOLT},
+ {"piercingbolt", AMMO_BOLT},
+ {"royalspear", AMMO_SPEAR},
+ {"snowball", AMMO_SNOWBALL},
+ {"smallstone", AMMO_STONE},
+ {"spear", AMMO_SPEAR},
+ {"sniperarrow", AMMO_ARROW},
+ {"shiverarrow", AMMO_ARROW},
+ {"throwingstar", AMMO_THROWINGSTAR},
+ {"throwingknife", AMMO_THROWINGKNIFE},
+ {"diamondarrow", AMMO_ARROW},
+ {"spectralbolt", AMMO_BOLT},
};
WeaponActionNames weaponActionNames = {
- {"move", WEAPONACTION_MOVE},
- {"removecharge", WEAPONACTION_REMOVECHARGE},
- {"removecount", WEAPONACTION_REMOVECOUNT},
+ {"move", WEAPONACTION_MOVE},
+ {"removecharge", WEAPONACTION_REMOVECHARGE},
+ {"removecount", WEAPONACTION_REMOVECOUNT},
};
SkullNames skullNames = {
- {"black", SKULL_BLACK},
- {"green", SKULL_GREEN},
- {"none", SKULL_NONE},
- {"orange", SKULL_ORANGE},
- {"red", SKULL_RED},
- {"yellow", SKULL_YELLOW},
- {"white", SKULL_WHITE},
+ {"black", SKULL_BLACK},
+ {"green", SKULL_GREEN},
+ {"none", SKULL_NONE},
+ {"orange", SKULL_ORANGE},
+ {"red", SKULL_RED},
+ {"yellow", SKULL_YELLOW},
+ {"white", SKULL_WHITE},
};
/**
@@ -749,11 +764,11 @@ SkullNames skullNames = {
* It will be dropped with monsters. Use RespawnPeriod_t instead.
*/
SpawnTypeNames spawnTypeNames = {
- {"all", RESPAWN_IN_ALL },
- {"day", RESPAWN_IN_DAY },
- {"dayandcave", RESPAWN_IN_DAY_CAVE },
- {"night", RESPAWN_IN_NIGHT },
- {"nightandcave", RESPAWN_IN_NIGHT_CAVE },
+ {"all", RESPAWN_IN_ALL },
+ {"day", RESPAWN_IN_DAY },
+ {"dayandcave", RESPAWN_IN_DAY_CAVE },
+ {"night", RESPAWN_IN_NIGHT },
+ {"nightandcave", RESPAWN_IN_NIGHT_CAVE },
};
MagicEffectClasses getMagicEffect(const std::string& strValue)
@@ -989,6 +1004,8 @@ size_t combatTypeToIndex(CombatType_t combatType)
return 10;
case COMBAT_DEATHDAMAGE:
return 11;
+ case COMBAT_NEUTRALDAMAGE:
+ return 12;
default:
return 0;
}
@@ -1170,7 +1187,7 @@ const char* getReturnMessage(ReturnValue value)
return "You do not have the required magic level to use this rune.";
case RETURNVALUE_YOUAREALREADYTRADING:
- return "You are already trading.";
+ return "You are already trading. Finish this trade first.";
case RETURNVALUE_THISPLAYERISALREADYTRADING:
return "This player is already trading.";
diff --git a/src/utils/tools.h b/src/utils/tools.h
index 0be46a0febd..3ecf214439b 100644
--- a/src/utils/tools.h
+++ b/src/utils/tools.h
@@ -126,6 +126,7 @@ static inline Cipbia_Elementals_t getCipbiaElement(CombatType_t combatType) {
case COMBAT_ICEDAMAGE: return CIPBIA_ELEMENTAL_ICE;
case COMBAT_HOLYDAMAGE: return CIPBIA_ELEMENTAL_HOLY;
case COMBAT_DEATHDAMAGE: return CIPBIA_ELEMENTAL_DEATH;
+ case COMBAT_MANADRAIN: return CIPBIA_ELEMENTAL_MANADRAIN;
default: return CIPBIA_ELEMENTAL_UNDEFINED;
}
}
diff --git a/src/utils/utils_definitions.hpp b/src/utils/utils_definitions.hpp
index 515ee29dd39..61e57ce4f83 100644
--- a/src/utils/utils_definitions.hpp
+++ b/src/utils/utils_definitions.hpp
@@ -64,6 +64,24 @@ enum CreatureIcon_t {
CREATUREICON_HIGHERRECEIVEDDAMAGE = 1,
CREATUREICON_LOWERDEALTDAMAGE = 2,
CREATUREICON_TURNEDMELEE = 3,
+ CREATUREICON_GREENBALL = 4,
+ CREATUREICON_REDBALL = 5,
+ CREATUREICON_GREENSHIELD = 6,
+ CREATUREICON_YELLOWSHIELD = 7,
+ CREATUREICON_BLUESHIELD = 8,
+ CREATUREICON_PURPLESHIELD = 9,
+ CREATUREICON_REDSHIELD = 10,
+ CREATUREICON_PIGEON = 11,
+ CREATUREICON_PURPLESTAR = 12,
+ CREATUREICON_POISONDROP = 13,
+ CREATUREICON_WATERDROP = 14,
+ CREATUREICON_FIREDROP = 15,
+ CREATUREICON_ICEFLOWER = 16,
+ CREATUREICON_ARROWUP = 17,
+ CREATUREICON_ARROWDOWN = 18,
+ CREATUREICON_EXCLAMATIONMARK = 19,
+ CREATUREICON_QUESTIONMARK = 20,
+ CREATUREICON_CANCELMARK = 21,
};
enum ThreadState {
@@ -92,7 +110,8 @@ enum Cipbia_Elementals_t : uint8_t {
CIPBIA_ELEMENTAL_HEALING = 7,
CIPBIA_ELEMENTAL_DROWN = 8,
CIPBIA_ELEMENTAL_LIFEDRAIN = 9,
- CIPBIA_ELEMENTAL_UNDEFINED = 10
+ CIPBIA_ELEMENTAL_MANADRAIN = 10,
+ CIPBIA_ELEMENTAL_UNDEFINED = 11,
};
enum MagicEffectClasses : uint8_t {
@@ -210,12 +229,35 @@ enum MagicEffectClasses : uint8_t {
CONST_ME_ORANGE_FIREWORKS = 197,
CONST_ME_PINK_FIREWORKS = 198,
CONST_ME_BLUE_FIREWORKS = 199,
-
+ // 200 is empty
+ CONST_ME_CUBE = 201,
+ CONST_ME_BLACKHIT = 202,
+ CONST_ME_MAGIC_RAINBOW = 203,
+ CONST_ME_THAIAN = 204,
+ CONST_ME_THAIANSOUL = 205,
+ CONST_ME_GHOST = 206,
+ // 207 is empty
+ CONST_ME_BLOCK = 208,
+ CONST_ME_BLOCKWATER = 209,
CONST_ME_ROOTS = 210,
+ // 211 bugged
+ // 212 bugged
+ CONST_ME_CRAWLER = 213,
+ CONST_ME_BITE = 214,
+ CONST_ME_REDCLAW = 215,
+ CONST_ME_BLUETAIL = 216,
+ CONST_ME_BIGBITE = 217,
+ // 218 is empty
CONST_ME_CHIVALRIOUS_CHALLENGE = 219,
CONST_ME_DIVINE_DAZZLE = 220,
-
- CONST_ME_LAST = CONST_ME_DIVINE_DAZZLE,
+ CONST_ME_PINK_SPARKS = 221,
+ CONST_ME_TELEPORT_PURPLE = 222,
+ CONST_ME_TELEPORT_RED = 223,
+ CONST_ME_TELEPORT_ORANGE = 224,
+ CONST_ME_TELEPORT_WHITE = 225,
+ CONST_ME_TELEPORT_LIGHTBLUE = 226,
+
+ CONST_ME_LAST = CONST_ME_TELEPORT_LIGHTBLUE
};
enum ShootType_t : uint8_t {
@@ -453,8 +495,10 @@ enum TextColor_t : uint8_t {
TEXTCOLOR_BLUE = 5,
TEXTCOLOR_LIGHTGREEN = 30,
TEXTCOLOR_LIGHTBLUE = 35,
+ TEXTCOLOR_DARKGREY = 86,
TEXTCOLOR_MAYABLUE = 95,
TEXTCOLOR_DARKRED = 108,
+ TEXTCOLOR_NEUTRALDAMAGE = 109,
TEXTCOLOR_LIGHTGREY = 129,
TEXTCOLOR_SKYBLUE = 143,
TEXTCOLOR_PURPLE = 154,
@@ -462,6 +506,7 @@ enum TextColor_t : uint8_t {
TEXTCOLOR_RED = 180,
TEXTCOLOR_PASTELRED = 194,
TEXTCOLOR_ORANGE = 198,
+ TEXTCOLOR_LIGHTPURPLE = 199,
TEXTCOLOR_YELLOW = 210,
TEXTCOLOR_WHITE_EXP = 215,
TEXTCOLOR_NONE = 255,
@@ -664,6 +709,12 @@ enum item_t : uint16_t {
ITEM_OLD_DIAMOND_ARROW = 25757,
ITEM_DIAMOND_ARROW = 35901,
+
+ ITEM_TIBIADROME_POTION_START = 36723,
+ ITEM_TIBIADROME_POTION_CHARM = 36726,
+ ITEM_TIBIADROME_POTION_LOOT = 36727,
+ ITEM_TIBIADROME_POTION_BESTIARY = 36728,
+ ITEM_TIBIADROME_POTION_END = 36742,
};
enum PlayerFlags : uint64_t {