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 {