diff --git a/config.lua.dist b/config.lua.dist index 14365b8dbd2..266a70e44f6 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -118,8 +118,10 @@ forgeCostOneSliver = 20 forgeSliverAmount = 3 forgeCoreCost = 50 forgeMaxDust = 225 -forgeFusionCost = 100 -forgeTransferCost = 100 +forgeFusionDustCost = 100 +forgeConvergenceFusionDustCost = 130 +forgeTransferDustCost = 100 +forgeConvergenceTransferCost = 160 forgeBaseSuccessRate = 50 forgeBonusSuccessRate = 15 forgeTierLossReduction = 50 @@ -131,6 +133,24 @@ forgeFiendishLimit = 3 forgeFiendishIntervalType = "hour" forgeFiendishIntervalTime = "1" +ruseChanceFormulaA = 0.0307576 +ruseChanceFormulaB = 0.440697 +ruseChanceFormulaC = 0.026 + +onslaughtChanceFormulaA = 0.05 +onslaughtChanceFormulaB = 0.4 +onslaughtChanceFormulaC = 0.05 + +momentumChanceFormulaA = 0.05 +momentumChanceFormulaB = 1.9 +momentumChanceFormulaC = 0.05 + +transcendanceChanceFormulaA = 0.0127 +transcendanceChanceFormulaB = 0.1070 +transcendanceChanceFormulaC = 0.0073 + +transcendanceAvatarDuration = 7000 + -- Bestiary & Bosstiary system -- NOTE: bestiaryKillMultiplier, multiplier value of monster killed, default 1 -- NOTE: bosstiaryKillMultiplier, multiplier value of boss killed, default 1 @@ -177,6 +197,15 @@ pvpMaxLevelDifference = 0 wheelSystemEnabled = true wheelPointsPerLevel = 1 +-- Gem Atelier +wheelAtelierRotateLesserCost = 125000 +wheelAtelierRotateRegularCost = 250000 +wheelAtelierRotateGreaterCost = 500000 + +wheelAtelierRevealLesserCost = 125000 +wheelAtelierRevealRegularCost = 1000000 +wheelAtelierRevealGreaterCost = 6000000 + -- Familiar system -- NOTE: the time will be divided by 2 to get half the value, the familiar lasts 15 minutes by default and the cooldown of the spell is 30 minutes -- Only change it here if you know what you are doing or to make testing easier with familiars diff --git a/data-canary/scripts/item_classification/item_tiers.lua b/data-canary/scripts/item_classification/item_tiers.lua index 8551624c9a2..2eb0d08bb23 100644 --- a/data-canary/scripts/item_classification/item_tiers.lua +++ b/data-canary/scripts/item_classification/item_tiers.lua @@ -2,33 +2,91 @@ local itemTierClassifications = { -- Upgrade classification 1 [1] = { -- Update tier 0 - [1] = { price = 25000, core = 1 }, + [1] = { + regular = 25000, + core = 1, + }, }, -- Upgrade classification 2 [2] = { -- Update tier 0 - [1] = { price = 750000, core = 1 }, + [1] = { + regular = 750000, + core = 1, + }, -- Update tier 1 - [2] = { price = 5000000, core = 1 }, + [2] = { + regular = 5000000, + core = 1, + }, }, -- Upgrade classification 3 [3] = { - [1] = { price = 4000000, core = 1 }, - [2] = { price = 10000000, core = 1 }, - [3] = { price = 20000000, core = 2 }, + [1] = { + regular = 4000000, + core = 1, + }, + [2] = { + regular = 10000000, + core = 1, + }, + [3] = { + regular = 20000000, + core = 2, + }, }, -- Upgrade classification 4 [4] = { - [1] = { price = 8000000, core = 1 }, - [2] = { price = 20000000, core = 1 }, - [3] = { price = 40000000, core = 2 }, - [4] = { price = 65000000, core = 5 }, - [5] = { price = 100000000, core = 10 }, - [6] = { price = 250000000, core = 15 }, - [7] = { price = 750000000, core = 25 }, - [8] = { price = 2500000000, core = 35 }, - [9] = { price = 8000000000, core = 50 }, - [10] = { price = 15000000000, core = 65 }, + [1] = { + regular = 8000000, + core = 1, + convergence = { fusion = { price = 55000000 }, transfer = { price = 65000000 } }, + }, + [2] = { + regular = 20000000, + core = 2, + convergence = { fusion = { price = 110000000 }, transfer = { price = 165000000 } }, + }, + [3] = { + regular = 40000000, + core = 5, + convergence = { fusion = { price = 170000000 }, transfer = { price = 375000000 } }, + }, + [4] = { + regular = 65000000, + core = 10, + convergence = { fusion = { price = 300000000 }, transfer = { price = 800000000 } }, + }, + [5] = { + regular = 100000000, + core = 15, + convergence = { fusion = { price = 875000000 }, transfer = { price = 2000000000 } }, + }, + [6] = { + regular = 250000000, + core = 25, + convergence = { fusion = { price = 2350000000 }, transfer = { price = 5250000000 } }, + }, + [7] = { + regular = 750000000, + core = 35, + convergence = { fusion = { price = 6950000000 }, transfer = { price = 14500000000 } }, + }, + [8] = { + regular = 2500000000, + core = 50, + convergence = { fusion = { price = 21250000000 }, transfer = { price = 42500000000 } }, + }, + [9] = { + regular = 8000000000, + core = 60, + convergence = { fusion = { price = 50000000000 }, transfer = { price = 100000000000 } }, + }, + [10] = { + regular = 15000000000, + core = 85, + convergence = { fusion = { price = 125000000000 }, transfer = { price = 300000000000 } }, + }, }, } @@ -40,9 +98,13 @@ for classificationId, classificationTable in ipairs(itemTierClassifications) do -- Registers table for register_item_tier.lua interface classification.Upgrades = {} for tierId, tierTable in ipairs(classificationTable) do - if tierId and tierTable.price and tierTable.core ~= nil then - table.insert(classification.Upgrades, { TierId = tierId - 1, Price = tierTable.price, Core = tierTable.core }) - end + table.insert(classification.Upgrades, { + TierId = tierId, + Core = tierTable.core, + RegularPrice = tierTable.regular, + ConvergenceFustionPrice = tierTable.convergence and tierTable.convergence.fusion.price or 0, + ConvergenceTransferPrice = tierTable.convergence and tierTable.convergence.transfer.price or 0, + }) end -- Create item classification and register classification table itemClassification:register(classification) diff --git a/data-otservbr-global/monster/constructs/animated_snowman.lua b/data-otservbr-global/monster/constructs/animated_snowman.lua index 72f7c5c9dc6..6fcf9883043 100644 --- a/data-otservbr-global/monster/constructs/animated_snowman.lua +++ b/data-otservbr-global/monster/constructs/animated_snowman.lua @@ -78,7 +78,7 @@ monster.loot = { { name = "shiver arrow", chance = 7310 }, { name = "ice rapier", chance = 4750 }, { name = "glacier mask", chance = 4570 }, - { name = "snowball", chance = 4000, maxCount = 5 }, + { id = 2992, chance = 4000, maxCount = 5 }, -- snowball { name = "hailstorm rod", chance = 3470 }, { name = "glacier mask", chance = 250 }, { name = "glacier amulet", chance = 3290 }, diff --git a/data-otservbr-global/monster/demons/brachiodemon.lua b/data-otservbr-global/monster/demons/brachiodemon.lua index 88c09c550a6..930ecfe469f 100644 --- a/data-otservbr-global/monster/demons/brachiodemon.lua +++ b/data-otservbr-global/monster/demons/brachiodemon.lua @@ -108,6 +108,7 @@ monster.attacks = { { name = "combat", interval = 2000, chance = 22, type = COMBAT_DEATHDAMAGE, minDamage = -900, maxDamage = -1280, radius = 4, effect = CONST_ME_EXPLOSIONHIT, target = false }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -1150, maxDamage = -1460, range = 7, effect = CONST_ANI_SUDDENDEATH, target = true }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -950, maxDamage = -1100, range = 7, radius = 4, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_MORTAREA, target = true }, + { name = "destroy magic walls", interval = 1000, chance = 30 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/demons/many_faces.lua b/data-otservbr-global/monster/demons/many_faces.lua index e557770d9c8..8b9724a6252 100644 --- a/data-otservbr-global/monster/demons/many_faces.lua +++ b/data-otservbr-global/monster/demons/many_faces.lua @@ -104,6 +104,7 @@ monster.attacks = { { name = "combat", interval = 5000, chance = 44, type = COMBAT_ICEDAMAGE, minDamage = -1000, maxDamage = -1450, range = 7, radius = 5, shootEffect = CONST_ANI_ICE, effect = CONST_ME_ICEAREA, target = true }, { name = "combat", interval = 9500, chance = 59, type = COMBAT_HOLYDAMAGE, minDamage = -1050, maxDamage = -1300, radius = 4, effect = CONST_ME_HOLYAREA, target = false }, { name = "extended holy chain", interval = 10000, chance = 59, minDamage = -1150, maxDamage = -1300, range = 7 }, + { name = "destroy magic walls", interval = 1000, chance = 30 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/event_creatures/grynch_clan_goblin.lua b/data-otservbr-global/monster/event_creatures/grynch_clan_goblin.lua index 3c852794ac8..d13e2a3d487 100644 --- a/data-otservbr-global/monster/event_creatures/grynch_clan_goblin.lua +++ b/data-otservbr-global/monster/event_creatures/grynch_clan_goblin.lua @@ -106,7 +106,7 @@ monster.loot = { { id = 2639, chance = 4000 }, -- picture { id = 2950, chance = 5000 }, -- lute { id = 2983, chance = 500 }, -- flower bowl - { name = "snowball", chance = 7000, maxCount = 5 }, + { id = 2992, chance = 7000, maxCount = 5 }, -- snowball { name = "piggy bank", chance = 1000 }, { name = "gold coin", chance = 22500, maxCount = 22 }, { name = "scarab coin", chance = 500, maxCount = 2 }, diff --git a/data-otservbr-global/monster/mammals/yeti.lua b/data-otservbr-global/monster/mammals/yeti.lua index 7ef601194dc..890751bbc7d 100644 --- a/data-otservbr-global/monster/mammals/yeti.lua +++ b/data-otservbr-global/monster/mammals/yeti.lua @@ -76,7 +76,7 @@ monster.voices = { } monster.loot = { - { name = "snowball", chance = 10000, maxCount = 22 }, + { id = 2992, chance = 10000, maxCount = 22 }, -- snowball { name = "gold coin", chance = 100000, maxCount = 60 }, { name = "gold coin", chance = 100000, maxCount = 40 }, { name = "bunnyslippers", chance = 1333 }, diff --git a/data-otservbr-global/monster/plants/cloak_of_terror.lua b/data-otservbr-global/monster/plants/cloak_of_terror.lua index fd9dbf2a971..184ba93fab2 100644 --- a/data-otservbr-global/monster/plants/cloak_of_terror.lua +++ b/data-otservbr-global/monster/plants/cloak_of_terror.lua @@ -102,6 +102,7 @@ monster.attacks = { { name = "combat", interval = 3000, chance = 20, type = COMBAT_ENERGYDAMAGE, minDamage = -1150, maxDamage = -1300, range = 7, radius = 4, effect = CONST_ME_ENERGYHIT, target = true }, { name = "combat", interval = 2000, chance = 14, type = COMBAT_HOLYDAMAGE, minDamage = -1000, maxDamage = -1300, range = 7, shootEffect = CONST_ANI_SPECTRALBOLT, effect = CONST_ME_HOLYDAMAGE, target = true }, { name = "combat", interval = 2000, chance = 24, type = COMBAT_HOLYDAMAGE, minDamage = -800, maxDamage = -1200, range = 7, radius = 3, shootEffect = CONST_ANI_SMALLHOLY, effect = CONST_ME_YELLOW_ENERGY_SPARK, target = true }, + { name = "destroy magic walls", interval = 1000, chance = 30 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/quests/dangerous_depth/bosses/the_baron_from_below.lua b/data-otservbr-global/monster/quests/dangerous_depth/bosses/the_baron_from_below.lua index e780ac65c8b..a381c3b07d6 100644 --- a/data-otservbr-global/monster/quests/dangerous_depth/bosses/the_baron_from_below.lua +++ b/data-otservbr-global/monster/quests/dangerous_depth/bosses/the_baron_from_below.lua @@ -112,6 +112,8 @@ monster.loot = { { name = "slimy leg", chance = 4170 }, { name = "badger boots", chance = 4170 }, { name = "spellbook of warding", chance = 2080 }, + { name = "gnome sword", chance = 4170 }, + { name = "gnome armor", chance = 3390 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/dangerous_depth/bosses/the_duke_of_the_depths.lua b/data-otservbr-global/monster/quests/dangerous_depth/bosses/the_duke_of_the_depths.lua index 31c354f7667..d654288c405 100644 --- a/data-otservbr-global/monster/quests/dangerous_depth/bosses/the_duke_of_the_depths.lua +++ b/data-otservbr-global/monster/quests/dangerous_depth/bosses/the_duke_of_the_depths.lua @@ -111,6 +111,7 @@ monster.loot = { { name = "gnome sword", chance = 4170 }, { name = "terra mantle", chance = 2080 }, { name = "violet gem", chance = 2080 }, + { name = "gnome legs", chance = 3390 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/the_inquisition/ushuriel.lua b/data-otservbr-global/monster/quests/the_inquisition/ushuriel.lua index 35c7d81b030..6d1bb8f9945 100644 --- a/data-otservbr-global/monster/quests/the_inquisition/ushuriel.lua +++ b/data-otservbr-global/monster/quests/the_inquisition/ushuriel.lua @@ -93,7 +93,7 @@ monster.loot = { { name = "mysterious voodoo skull", chance = 12500 }, { name = "skull helmet", chance = 20000 }, { name = "iron ore", chance = 33333 }, - { name = "spirit container", chance = 4761 }, + { id = 5884, chance = 4761 }, -- spirit container { name = "flask of warrior's sweat", chance = 5555 }, { name = "enchanted chicken wing", chance = 7692 }, { name = "huge chunk of crude iron", chance = 14285 }, diff --git a/data-otservbr-global/monster/reptiles/corrupt_naga.lua b/data-otservbr-global/monster/reptiles/corrupt_naga.lua index bcdd68ee8f6..7292452428e 100644 --- a/data-otservbr-global/monster/reptiles/corrupt_naga.lua +++ b/data-otservbr-global/monster/reptiles/corrupt_naga.lua @@ -5,10 +5,10 @@ monster.description = "a corrupt naga" monster.experience = 4380 monster.outfit = { lookType = 1538, - lookHead = 55, - lookBody = 6, - lookLegs = 0, - lookFeet = 78, + lookHead = 86, + lookBody = 57, + lookLegs = 75, + lookFeet = 94, lookAddons = 3, lookMount = 0, } @@ -16,7 +16,7 @@ monster.outfit = { monster.health = 5990 monster.maxHealth = 5990 monster.race = "blood" -monster.corpse = 0 +monster.corpse = 39217 monster.speed = 182 monster.manaCost = 0 @@ -60,15 +60,14 @@ monster.voices = { } monster.loot = { - { name = "Platinum Coin", chance = 75420, minCount = 1, maxCount = 8 }, - { name = "Violet Crystal Shard", chance = 24580, minCount = 1, maxCount = 2 }, - { name = "Corrupt Naga Scales", chance = 17720 }, + { name = "corrupt naga scales", chance = 17720 }, } monster.attacks = { - { name = "combat", interval = 2000, chance = 100, minDamage = -300, maxDamage = -600, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_PURPLEENERGY, target = true }, - { name = "nagadeath", interval = 6000, chance = 39, target = false, minDamage = -1000, maxDamage = -2200 }, - { name = "nagadeathattack", interval = 3000, chance = 68, target = true, minDamage = -400, maxDamage = -600 }, + { name = "combat", interval = 2000, chance = 100, type = COMBAT_PHYSICALDAMAGE, minDamage = -120, maxDamage = -340, target = true }, -- basic_attack + { name = "combat", interval = 2500, chance = 30, type = COMBAT_PHYSICALDAMAGE, minDamage = -320, maxDamage = -430, effect = CONST_ME_YELLOWSMOKE, range = 3, target = true }, -- eruption_strike + { name = "nagadeathattack", interval = 3000, chance = 35, minDamage = -360, maxDamage = -415, target = true }, -- death_strike + { name = "combat", interval = 3500, chance = 35, type = COMBAT_LIFEDRAIN, minDamage = -360, maxDamage = -386, radius = 4, effect = CONST_ME_DRAWBLOOD, target = false }, -- great_blood_ball } monster.defenses = { diff --git a/data-otservbr-global/monster/reptiles/rogue_naga.lua b/data-otservbr-global/monster/reptiles/rogue_naga.lua index 25ec8c56ccf..e3e7e662fc3 100644 --- a/data-otservbr-global/monster/reptiles/rogue_naga.lua +++ b/data-otservbr-global/monster/reptiles/rogue_naga.lua @@ -5,10 +5,10 @@ monster.description = "a rogue naga" monster.experience = 4510 monster.outfit = { lookType = 1543, - lookHead = 55, - lookBody = 6, - lookLegs = 0, - lookFeet = 78, + lookHead = 75, + lookBody = 13, + lookLegs = 95, + lookFeet = 109, lookAddons = 3, lookMount = 0, } @@ -16,7 +16,7 @@ monster.outfit = { monster.health = 6200 monster.maxHealth = 6200 monster.race = "blood" -monster.corpse = 0 +monster.corpse = 39221 monster.speed = 182 monster.manaCost = 0 @@ -64,15 +64,15 @@ monster.voices = { } monster.loot = { - { name = "Platinum Coin", chance = 85600, minCount = 1, maxCount = 12 }, - { name = "Rogue Naga Scales", chance = 15450 }, - { name = "Green Crystal Shard", chance = 14400, minCount = 1, maxCount = 2 }, + { name = "rogue naga scales", chance = 15450 }, } monster.attacks = { - { name = "combat", interval = 2000, chance = 100, minDamage = -300, maxDamage = -600, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_PURPLEENERGY, target = true }, - { name = "combat", interval = 2000, chance = 47, type = COMBAT_PHYSICALDAMAGE, minDamage = -350, maxDamage = -400, effect = CONST_ME_BIG_SCRATCH, target = true }, - { name = "combat", interval = 1000, chance = 10, type = COMBAT_PHYSICALDAMAGE, minDamage = -380, maxDamage = -470, length = 5, spread = 3, effect = CONST_ME_GROUNDSHAKER, target = false }, + { name = "combat", interval = 2000, chance = 50, type = COMBAT_PHYSICALDAMAGE, minDamage = -95, maxDamage = -390, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_PURPLEENERGY, range = 6, target = true }, -- basic_attack + { name = "nagadeathattack", interval = 2500, chance = 20, minDamage = -430, maxDamage = -505, range = 6, target = true }, -- death_strike + { name = "nagadeath", interval = 3000, chance = 20, minDamage = -380, maxDamage = -470, target = false }, -- short_death_wave + { name = "death chain", interval = 3500, chance = 20, minDamage = -460, maxDamage = -520, range = 6, target = true }, -- death_chain + { name = "combat", interval = 4000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -85, maxDamage = -190, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_PURPLEENERGY, range = 6, target = true }, -- explosion_strike } monster.defenses = { diff --git a/data-otservbr-global/monster/undeads/bony_sea_devil.lua b/data-otservbr-global/monster/undeads/bony_sea_devil.lua index 5e57027cc6b..1199240df4b 100644 --- a/data-otservbr-global/monster/undeads/bony_sea_devil.lua +++ b/data-otservbr-global/monster/undeads/bony_sea_devil.lua @@ -107,6 +107,7 @@ monster.attacks = { { name = "combat", interval = 2000, chance = 25, type = COMBAT_ICEDAMAGE, minDamage = -950, maxDamage = -1100, range = 7, radius = 5, shootEffect = CONST_ANI_ICE, effect = CONST_ME_ICEAREA, target = true }, { name = "ice chain", interval = 2000, chance = 15, minDamage = -1100, maxDamage = -1300, range = 7 }, { name = "soulwars fear", interval = 2000, chance = 1, target = true }, + { name = "destroy magic walls", interval = 1000, chance = 30 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/undeads/souleater.lua b/data-otservbr-global/monster/undeads/souleater.lua index 987e34523bf..dd0912cfd7c 100644 --- a/data-otservbr-global/monster/undeads/souleater.lua +++ b/data-otservbr-global/monster/undeads/souleater.lua @@ -83,7 +83,7 @@ monster.loot = { { name = "platinum coin", chance = 49610, maxCount = 6 }, { name = "necrotic rod", chance = 980 }, { name = "wand of cosmic energy", chance = 910 }, - { name = "spirit container", chance = 140 }, + { id = 5884, chance = 140 }, -- spirit container { id = 6299, chance = 300 }, -- death ring { name = "great mana potion", chance = 8000 }, { name = "ultimate health potion", chance = 9400 }, diff --git a/data-otservbr-global/npc/briasol.lua b/data-otservbr-global/npc/briasol.lua index 1dd3a0060bf..8b0d23061b9 100644 --- a/data-otservbr-global/npc/briasol.lua +++ b/data-otservbr-global/npc/briasol.lua @@ -96,10 +96,11 @@ keywordHandler:addGreetKeyword({ "ashari" }, { npcHandler = npcHandler, text = " keywordHandler:addFarewellKeyword({ "asgha thrazi" }, { npcHandler = npcHandler, text = "Good bye, |PLAYERNAME|." }) npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) + npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "black pearl", clientId = 3027, buy = 560, sell = 280 }, { itemName = "blue crystal shard", clientId = 16119, sell = 1500 }, @@ -121,17 +122,29 @@ npcConfig.shop = { { itemName = "gold nugget", clientId = 3040, sell = 850 }, { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 400 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, { itemName = "prismatic quartz", clientId = 24962, sell = 450 }, { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, { itemName = "small amethyst", clientId = 3033, buy = 400, sell = 200 }, @@ -148,9 +161,10 @@ npcConfig.shop = { { itemName = "unicorn figurine", clientId = 30054, sell = 50000 }, { itemName = "violet crystal shard", clientId = 16120, sell = 1500 }, { itemName = "wedding ring", clientId = 3004, buy = 990 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "white pearl", clientId = 3026, buy = 320 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/chantalle.lua b/data-otservbr-global/npc/chantalle.lua index 5805682e961..1b80f81b8aa 100644 --- a/data-otservbr-global/npc/chantalle.lua +++ b/data-otservbr-global/npc/chantalle.lua @@ -82,9 +82,9 @@ npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "bar of gold", clientId = 14112, sell = 10000 }, { itemName = "black pearl", clientId = 3027, buy = 560, sell = 280 }, @@ -109,19 +109,31 @@ npcConfig.shop = { { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden figurine", clientId = 5799, sell = 3000 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, { itemName = "hexagonal ruby", clientId = 30180, sell = 30000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, { itemName = "moonstone", clientId = 32771, sell = 13000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 500 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, { itemName = "prismatic quartz", clientId = 24962, sell = 450 }, { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "scarab coin", clientId = 3042, sell = 100 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, @@ -140,9 +152,10 @@ npcConfig.shop = { { itemName = "violet crystal shard", clientId = 16120, sell = 1500 }, { itemName = "watermelon tourmaline", clientId = 33780, sell = 230000 }, { itemName = "wedding ring", clientId = 3004, buy = 990 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "white pearl", clientId = 3026, buy = 320 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/edmund.lua b/data-otservbr-global/npc/edmund.lua index 9478a876aeb..687721458ef 100644 --- a/data-otservbr-global/npc/edmund.lua +++ b/data-otservbr-global/npc/edmund.lua @@ -53,9 +53,9 @@ end npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "black pearl", clientId = 3027, buy = 560, sell = 280 }, { itemName = "blue crystal shard", clientId = 16119, sell = 1500 }, @@ -77,17 +77,29 @@ npcConfig.shop = { { itemName = "gold nugget", clientId = 3040, sell = 850 }, { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 400 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, { itemName = "prismatic quartz", clientId = 24962, sell = 450 }, { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, { itemName = "small amethyst", clientId = 3033, buy = 400, sell = 200 }, @@ -104,9 +116,10 @@ npcConfig.shop = { { itemName = "unicorn figurine", clientId = 30054, sell = 50000 }, { itemName = "violet crystal shard", clientId = 16120, sell = 1500 }, { itemName = "wedding ring", clientId = 3004, buy = 990 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "white pearl", clientId = 3026, buy = 320 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/gail.lua b/data-otservbr-global/npc/gail.lua index 5478419dc04..aa65e5f5592 100644 --- a/data-otservbr-global/npc/gail.lua +++ b/data-otservbr-global/npc/gail.lua @@ -94,9 +94,9 @@ npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "black pearl", clientId = 3027, buy = 560, sell = 280 }, { itemName = "blue crystal shard", clientId = 16119, sell = 1500 }, @@ -118,17 +118,29 @@ npcConfig.shop = { { itemName = "gold nugget", clientId = 3040, sell = 850 }, { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 400 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, { itemName = "prismatic quartz", clientId = 24962, sell = 450 }, { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, { itemName = "small amethyst", clientId = 3033, buy = 400, sell = 200 }, @@ -145,9 +157,10 @@ npcConfig.shop = { { itemName = "unicorn figurine", clientId = 30054, sell = 50000 }, { itemName = "violet crystal shard", clientId = 16120, sell = 1500 }, { itemName = "wedding ring", clientId = 3004, buy = 990 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "white pearl", clientId = 3026, buy = 320 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/giri.lua b/data-otservbr-global/npc/giri.lua index 92947372d08..8a243b22f85 100644 --- a/data-otservbr-global/npc/giri.lua +++ b/data-otservbr-global/npc/giri.lua @@ -75,11 +75,22 @@ npcConfig.shop = { { itemName = "gold nugget", clientId = 3040, sell = 850 }, { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 500 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, @@ -88,6 +99,7 @@ npcConfig.shop = { { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "royal almandine", clientId = 39038, sell = 460000 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, { itemName = "small amethyst", clientId = 3033, buy = 400, sell = 200 }, @@ -106,9 +118,10 @@ npcConfig.shop = { { itemName = "watermelon tourmaline piece", clientId = 33779, sell = 30000 }, { itemName = "watermelon tourmaline", clientId = 33780, sell = 230000 }, { itemName = "wedding ring", clientId = 3004, buy = 990, sell = 100 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "white pearl", clientId = 3026, buy = 320, sell = 160 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/hanna.lua b/data-otservbr-global/npc/hanna.lua index 5a062678829..02f89d87631 100644 --- a/data-otservbr-global/npc/hanna.lua +++ b/data-otservbr-global/npc/hanna.lua @@ -131,9 +131,9 @@ npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "black pearl", clientId = 3027, buy = 560, sell = 280 }, { itemName = "blue crystal shard", clientId = 16119, sell = 1500 }, @@ -156,16 +156,28 @@ npcConfig.shop = { { itemName = "gold nugget", clientId = 3040, sell = 850 }, { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 400 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, { itemName = "prismatic quartz", clientId = 24962, sell = 450 }, { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, { itemName = "small amethyst", clientId = 3033, buy = 400, sell = 200 }, @@ -181,10 +193,11 @@ npcConfig.shop = { { itemName = "tiger eye", clientId = 24961, sell = 350 }, { itemName = "unicorn figurine", clientId = 30054, sell = 50000 }, { itemName = "violet crystal shard", clientId = 16120, sell = 1500 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "wedding ring", clientId = 3004, buy = 990, sell = 100 }, { itemName = "white pearl", clientId = 3026, buy = 320, sell = 160 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/ishina.lua b/data-otservbr-global/npc/ishina.lua index c82252356fb..455aed2dda2 100644 --- a/data-otservbr-global/npc/ishina.lua +++ b/data-otservbr-global/npc/ishina.lua @@ -124,9 +124,9 @@ npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "black pearl", clientId = 3027, buy = 560, sell = 280 }, { itemName = "blue crystal shard", clientId = 16119, sell = 1500 }, @@ -148,17 +148,29 @@ npcConfig.shop = { { itemName = "gold nugget", clientId = 3040, sell = 850 }, { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 400 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, { itemName = "prismatic quartz", clientId = 24962, sell = 450 }, { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, { itemName = "small amethyst", clientId = 3033, buy = 400, sell = 200 }, @@ -174,10 +186,11 @@ npcConfig.shop = { { itemName = "tiger eye", clientId = 24961, sell = 350 }, { itemName = "unicorn figurine", clientId = 30054, sell = 50000 }, { itemName = "violet crystal shard", clientId = 16120, sell = 1500 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "wedding ring", clientId = 3004, buy = 990 }, { itemName = "white pearl", clientId = 3026, buy = 320 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/iwan.lua b/data-otservbr-global/npc/iwan.lua index f0f84ea1d65..31f593c7cb0 100644 --- a/data-otservbr-global/npc/iwan.lua +++ b/data-otservbr-global/npc/iwan.lua @@ -63,9 +63,9 @@ npcHandler:setMessage(MESSAGE_SENDTRADE, "Here, take a look.") npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "black pearl", clientId = 3027, buy = 560, sell = 280 }, { itemName = "blue crystal shard", clientId = 16119, sell = 1500 }, @@ -87,17 +87,29 @@ npcConfig.shop = { { itemName = "gold nugget", clientId = 3040, sell = 850 }, { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 400 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, { itemName = "prismatic quartz", clientId = 24962, sell = 450 }, { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, { itemName = "small amethyst", clientId = 3033, buy = 400, sell = 200 }, @@ -113,10 +125,11 @@ npcConfig.shop = { { itemName = "tiger eye", clientId = 24961, sell = 350 }, { itemName = "unicorn figurine", clientId = 30054, sell = 50000 }, { itemName = "violet crystal shard", clientId = 16120, sell = 1500 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "wedding ring", clientId = 3004, buy = 990 }, { itemName = "white pearl", clientId = 3026, buy = 320 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/jessica.lua b/data-otservbr-global/npc/jessica.lua index ff0b8ba0d91..32cd5ac34fd 100644 --- a/data-otservbr-global/npc/jessica.lua +++ b/data-otservbr-global/npc/jessica.lua @@ -81,9 +81,9 @@ npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "bar of gold", clientId = 14112, sell = 10000 }, { itemName = "black pearl", clientId = 3027, buy = 560, sell = 280 }, @@ -108,19 +108,31 @@ npcConfig.shop = { { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden figurine", clientId = 5799, sell = 3000 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, { itemName = "hexagonal ruby", clientId = 30180, sell = 30000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, { itemName = "moonstone", clientId = 32771, sell = 13000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 500 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, { itemName = "prismatic quartz", clientId = 24962, sell = 450 }, { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "scarab coin", clientId = 3042, sell = 100 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, @@ -138,10 +150,11 @@ npcConfig.shop = { { itemName = "unicorn figurine", clientId = 30054, sell = 50000 }, { itemName = "violet crystal shard", clientId = 16120, sell = 1500 }, { itemName = "watermelon tourmaline", clientId = 33780, sell = 230000 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "wedding ring", clientId = 3004, buy = 990 }, { itemName = "white pearl", clientId = 3026, buy = 320 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/odemara.lua b/data-otservbr-global/npc/odemara.lua index fdebc6f7449..8ab4e84152f 100644 --- a/data-otservbr-global/npc/odemara.lua +++ b/data-otservbr-global/npc/odemara.lua @@ -53,9 +53,9 @@ end npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "bar of gold", clientId = 14112, sell = 10000 }, { itemName = "black pearl", clientId = 3027, buy = 560, sell = 280 }, @@ -80,19 +80,31 @@ npcConfig.shop = { { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden figurine", clientId = 5799, sell = 3000 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, { itemName = "hexagonal ruby", clientId = 30180, sell = 30000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, { itemName = "moonstone", clientId = 32771, sell = 13000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 500 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, { itemName = "prismatic quartz", clientId = 24962, sell = 450 }, { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "scarab coin", clientId = 3042, sell = 100 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, @@ -110,10 +122,11 @@ npcConfig.shop = { { itemName = "unicorn figurine", clientId = 30054, sell = 50000 }, { itemName = "violet crystal shard", clientId = 16120, sell = 1500 }, { itemName = "watermelon tourmaline", clientId = 33780, sell = 230000 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "wedding ring", clientId = 3004, buy = 990 }, { itemName = "white pearl", clientId = 3026, buy = 320 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/oiriz.lua b/data-otservbr-global/npc/oiriz.lua index 62817bd0349..38a49116949 100644 --- a/data-otservbr-global/npc/oiriz.lua +++ b/data-otservbr-global/npc/oiriz.lua @@ -53,9 +53,9 @@ end npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "black pearl", clientId = 3027, buy = 560, sell = 280 }, { itemName = "blue crystal shard", clientId = 16119, sell = 1500 }, @@ -77,17 +77,29 @@ npcConfig.shop = { { itemName = "gold nugget", clientId = 3040, sell = 850 }, { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 400 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, { itemName = "prismatic quartz", clientId = 24962, sell = 450 }, { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, { itemName = "small amethyst", clientId = 3033, buy = 400, sell = 200 }, @@ -104,9 +116,10 @@ npcConfig.shop = { { itemName = "unicorn figurine", clientId = 30054, sell = 50000 }, { itemName = "violet crystal shard", clientId = 16120, sell = 1500 }, { itemName = "wedding ring", clientId = 3004, buy = 990 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "white pearl", clientId = 3026, buy = 320 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/talila.lua b/data-otservbr-global/npc/talila.lua index ac14feedbe6..eff8f9b5c54 100644 --- a/data-otservbr-global/npc/talila.lua +++ b/data-otservbr-global/npc/talila.lua @@ -64,9 +64,9 @@ npcHandler:setMessage(MESSAGE_FAREWELL, "May enlightenment be your path, |PLAYER npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "animate dead rune", clientId = 3203, buy = 375 }, { itemName = "avalanche rune", clientId = 3161, buy = 57 }, @@ -121,10 +121,15 @@ npcConfig.shop = { { itemName = "great health potion", clientId = 239, buy = 225 }, { itemName = "great mana potion", clientId = 238, buy = 144 }, { itemName = "great spirit potion", clientId = 7642, buy = 228 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, { itemName = "health potion", clientId = 266, buy = 50 }, { itemName = "heavy magic missile rune", clientId = 3198, buy = 12 }, { itemName = "hexagonal ruby", clientId = 30180, sell = 30000 }, @@ -133,12 +138,18 @@ npcConfig.shop = { { itemName = "icicle rune", clientId = 3158, buy = 30 }, { itemName = "intense healing rune", clientId = 3152, buy = 95 }, { itemName = "leaf star", clientId = 25735, sell = 50 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "light magic missile rune", clientId = 3174, buy = 4 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, { itemName = "magic wall rune", clientId = 3180, buy = 116 }, { itemName = "mana potion", clientId = 268, buy = 56 }, { itemName = "mandrake", clientId = 5014, sell = 5000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, { itemName = "moonstone", clientId = 32771, sell = 13000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 500 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, @@ -153,6 +164,7 @@ npcConfig.shop = { { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "red rose", clientId = 3658, sell = 10 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "shimmering beetles", clientId = 25693, sell = 150 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, @@ -197,6 +209,7 @@ npcConfig.shop = { { itemName = "wood cape", clientId = 3575, sell = 5000 }, { itemName = "wooden spellbook", clientId = 25699, sell = 12000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/tezila.lua b/data-otservbr-global/npc/tezila.lua index f6580c6b602..ada79994ccc 100644 --- a/data-otservbr-global/npc/tezila.lua +++ b/data-otservbr-global/npc/tezila.lua @@ -52,9 +52,9 @@ end npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "black pearl", clientId = 3027, buy = 560, sell = 280 }, { itemName = "blue crystal shard", clientId = 16119, sell = 1500 }, @@ -76,17 +76,29 @@ npcConfig.shop = { { itemName = "gold nugget", clientId = 3040, sell = 850 }, { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 400 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, { itemName = "prismatic quartz", clientId = 24962, sell = 450 }, { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, { itemName = "small amethyst", clientId = 3033, buy = 400, sell = 200 }, @@ -103,9 +115,10 @@ npcConfig.shop = { { itemName = "unicorn figurine", clientId = 30054, sell = 50000 }, { itemName = "violet crystal shard", clientId = 16120, sell = 1500 }, { itemName = "wedding ring", clientId = 3004, buy = 990 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "white pearl", clientId = 3026, buy = 320 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/valindara.lua b/data-otservbr-global/npc/valindara.lua index 52d7762eded..dbb4ab04b7d 100644 --- a/data-otservbr-global/npc/valindara.lua +++ b/data-otservbr-global/npc/valindara.lua @@ -69,9 +69,9 @@ npcHandler:setMessage(MESSAGE_FAREWELL, "May enlightenment be your path, |PLAYER npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "animate dead rune", clientId = 3203, buy = 375 }, { itemName = "avalanche rune", clientId = 3161, buy = 57 }, @@ -126,10 +126,15 @@ npcConfig.shop = { { itemName = "great health potion", clientId = 239, buy = 225 }, { itemName = "great mana potion", clientId = 238, buy = 144 }, { itemName = "great spirit potion", clientId = 7642, buy = 228 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, { itemName = "health potion", clientId = 266, buy = 50 }, { itemName = "heavy magic missile rune", clientId = 3198, buy = 12 }, { itemName = "hexagonal ruby", clientId = 30180, sell = 30000 }, @@ -138,11 +143,17 @@ npcConfig.shop = { { itemName = "icicle rune", clientId = 3158, buy = 30 }, { itemName = "intense healing rune", clientId = 3152, buy = 95 }, { itemName = "leaf star", clientId = 25735, sell = 50 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "light magic missile rune", clientId = 3174, buy = 4 }, { itemName = "magic wall rune", clientId = 3180, buy = 116 }, { itemName = "mana potion", clientId = 268, buy = 56 }, { itemName = "mandrake", clientId = 5014, sell = 5000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, { itemName = "moonstone", clientId = 32771, sell = 13000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 500 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate lion figurine", clientId = 33781, sell = 10000 }, @@ -158,6 +169,7 @@ npcConfig.shop = { { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "red rose", clientId = 3658, sell = 10 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "shimmering beetles", clientId = 25693, sell = 150 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, @@ -202,6 +214,7 @@ npcConfig.shop = { { itemName = "wood cape", clientId = 3575, sell = 5000 }, { itemName = "wooden spellbook", clientId = 25699, sell = 12000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/npc/yonan.lua b/data-otservbr-global/npc/yonan.lua index 1c6101ab842..b499e110254 100644 --- a/data-otservbr-global/npc/yonan.lua +++ b/data-otservbr-global/npc/yonan.lua @@ -24,9 +24,9 @@ npcConfig.flags = { } npcConfig.shop = { - { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "amber with a bug", clientId = 32624, sell = 41000 }, { itemName = "amber with a dragonfly", clientId = 32625, sell = 56000 }, + { itemName = "amber", clientId = 32626, sell = 20000 }, { itemName = "ancient coin", clientId = 24390, sell = 350 }, { itemName = "black pearl", clientId = 3027, buy = 560, sell = 280 }, { itemName = "blue crystal shard", clientId = 16119, sell = 1500 }, @@ -48,17 +48,29 @@ npcConfig.shop = { { itemName = "gold nugget", clientId = 3040, sell = 850 }, { itemName = "golden amulet", clientId = 3013, buy = 6600 }, { itemName = "golden goblet", clientId = 5805, buy = 5000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "lion figurine", clientId = 33781, sell = 10000 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 400 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "ornate locket", clientId = 30056, sell = 18000 }, { itemName = "prismatic quartz", clientId = 24962, sell = 450 }, { itemName = "red crystal fragment", clientId = 16126, sell = 800 }, { itemName = "ruby necklace", clientId = 3016, buy = 3560 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "silver goblet", clientId = 5806, buy = 3000 }, { itemName = "skull coin", clientId = 32583, sell = 12000 }, { itemName = "small amethyst", clientId = 3033, buy = 400, sell = 200 }, @@ -75,9 +87,10 @@ npcConfig.shop = { { itemName = "unicorn figurine", clientId = 30054, sell = 50000 }, { itemName = "violet crystal shard", clientId = 16120, sell = 1500 }, { itemName = "wedding ring", clientId = 3004, buy = 990 }, - { itemName = "white silk flower", clientId = 34008, sell = 9000 }, { itemName = "white pearl", clientId = 3026, buy = 320 }, + { itemName = "white silk flower", clientId = 34008, sell = 9000 }, } + -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) diff --git a/data-otservbr-global/scripts/actions/other/bag_you_covet.lua b/data-otservbr-global/scripts/actions/other/bag_you_covet.lua index e332d9fd3f2..bb62da59372 100644 --- a/data-otservbr-global/scripts/actions/other/bag_you_covet.lua +++ b/data-otservbr-global/scripts/actions/other/bag_you_covet.lua @@ -27,6 +27,13 @@ function bagyouCovet.onUse(player, item, fromPosition, target, toPosition, isHot item:remove(1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You received a " .. rewardItem.name .. ".") + + local text = player:getName() .. " received a " .. rewardItem.name .. " from a " .. item:getName() .. "." + local vocation = player:vocationAbbrev() + Webhook.sendMessage(":game_die: " .. player:getMarkdownLink() .. " received a **" .. rewardItem.name .. "** from a _" .. item:getName() .. "_.") + Broadcast(text, function(targetPlayer) + return targetPlayer ~= player + end) return true end diff --git a/data-otservbr-global/scripts/actions/other/bag_you_desire.lua b/data-otservbr-global/scripts/actions/other/bag_you_desire.lua index be25529c09b..6716d58eef3 100644 --- a/data-otservbr-global/scripts/actions/other/bag_you_desire.lua +++ b/data-otservbr-global/scripts/actions/other/bag_you_desire.lua @@ -33,6 +33,13 @@ function bagyouDesire.onUse(player, item, fromPosition, target, toPosition, isHo item:remove(1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You received a " .. rewardItem.name .. ".") + + local text = player:getName() .. " received a " .. rewardItem.name .. " from a " .. item:getName() .. "." + local vocation = player:vocationAbbrev() + Webhook.sendMessage(":game_die: " .. player:getMarkdownLink() .. " received a **" .. rewardItem.name .. "** from a _" .. item:getName() .. "_.") + Broadcast(text, function(targetPlayer) + return targetPlayer ~= player + end) return true end diff --git a/data-otservbr-global/scripts/actions/other/primal_bag.lua b/data-otservbr-global/scripts/actions/other/primal_bag.lua index 5837caa08ae..ab9f44cdb78 100644 --- a/data-otservbr-global/scripts/actions/other/primal_bag.lua +++ b/data-otservbr-global/scripts/actions/other/primal_bag.lua @@ -26,7 +26,13 @@ function primalBag.onUse(player, item, fromPosition, target, toPosition, isHotke player:addItem(rewardItem.id, 1) item:remove(1) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You received one " .. rewardItem.name .. ".") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You received a " .. rewardItem.name .. ".") + local text = player:getName() .. " received a " .. rewardItem.name .. " from a " .. item:getName() .. "." + local vocation = player:vocationAbbrev() + Webhook.sendMessage(":game_die: " .. player:getMarkdownLink() .. " received a **" .. rewardItem.name .. "** from a _" .. item:getName() .. "_.") + Broadcast(text, function(targetPlayer) + return targetPlayer ~= player + end) return true end diff --git a/data-otservbr-global/scripts/actions/quests/lions_rock/lions_rock.lua b/data-otservbr-global/scripts/actions/quests/lions_rock/lions_rock.lua index 652ba60f1ef..dc2870c1d72 100644 --- a/data-otservbr-global/scripts/actions/quests/lions_rock/lions_rock.lua +++ b/data-otservbr-global/scripts/actions/quests/lions_rock/lions_rock.lua @@ -113,6 +113,9 @@ lionsGetHolyWater:register() local lionsRockFountain = Action() function lionsRockFountain.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item:getId() ~= 6389 then + return false + end if player:getStorageValue(Storage.LionsRock.Time) < os.time() then local reward = "" if player:hasMount(40) then @@ -122,7 +125,8 @@ function lionsRockFountain.onUse(player, item, fromPosition, target, toPosition, else reward = math.random(1, #rewards) end - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Something sparkles in the fountain's water. You draw out a " .. rewards[reward] .. ".") + local iType = ItemType(rewards[reward]) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Something sparkles in the fountain's water. You draw out " .. iType:getArticle() .. " " .. iType:getName() .. ".") player:getPosition():sendMagicEffect(CONST_ME_HOLYAREA) player:addAchievement("Lion's Den Explorer") item:transform(lionsRockSanctuaryRockId) @@ -135,5 +139,5 @@ function lionsRockFountain.onUse(player, item, fromPosition, target, toPosition, return true end -lionsRockFountain:id(6389) +lionsRockFountain:position({ x = 33073, y = 32300, z = 9 }) lionsRockFountain:register() diff --git a/data-otservbr-global/scripts/actions/tibiadrome/concoctions.lua b/data-otservbr-global/scripts/actions/tibiadrome/concoctions.lua index 8e7a23d85ea..fe17dfef06b 100644 --- a/data-otservbr-global/scripts/actions/tibiadrome/concoctions.lua +++ b/data-otservbr-global/scripts/actions/tibiadrome/concoctions.lua @@ -17,7 +17,7 @@ local configs = { player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your spells are no longer on cooldown.") end, }, - [Concoction.Ids.StrikeEnhancement] = { condition = { CONDITION_PARAM_SKILL_CRITICAL_HIT_CHANCE, 5 } }, + [Concoction.Ids.StrikeEnhancement] = { condition = { CONDITION_PARAM_SKILL_CRITICAL_HIT_CHANCE, 500 } }, [Concoction.Ids.CharmUpgrade] = { condition = { CONDITION_PARAM_CHARM_CHANCE_MODIFIER, 5 } }, [Concoction.Ids.WealthDuplex] = { rate = 100 }, [Concoction.Ids.BestiaryBetterment] = { multiplier = 2.0 }, diff --git a/data-otservbr-global/scripts/item_classification/item_tiers.lua b/data-otservbr-global/scripts/item_classification/item_tiers.lua index 8551624c9a2..c199f81a3fe 100644 --- a/data-otservbr-global/scripts/item_classification/item_tiers.lua +++ b/data-otservbr-global/scripts/item_classification/item_tiers.lua @@ -2,33 +2,115 @@ local itemTierClassifications = { -- Upgrade classification 1 [1] = { -- Update tier 0 - [1] = { price = 25000, core = 1 }, + [1] = { + regular = 25000, + core = 1, + }, + [2] = { + regular = 50000, + core = 1, + }, + [3] = { + regular = 100000, + core = 1, + }, }, -- Upgrade classification 2 [2] = { -- Update tier 0 - [1] = { price = 750000, core = 1 }, + [1] = { + regular = 50000, + core = 1, + }, -- Update tier 1 - [2] = { price = 5000000, core = 1 }, + [2] = { + regular = 100000, + core = 1, + }, + [3] = { + regular = 200000, + core = 2, + }, + [4] = { + regular = 400000, + core = 2, + }, }, -- Upgrade classification 3 [3] = { - [1] = { price = 4000000, core = 1 }, - [2] = { price = 10000000, core = 1 }, - [3] = { price = 20000000, core = 2 }, + [1] = { + regular = 200000, + core = 1, + }, + [2] = { + regular = 400000, + core = 2, + }, + [3] = { + regular = 800000, + core = 3, + }, + [4] = { + regular = 1600000, + core = 4, + }, + [5] = { + regular = 3200000, + core = 5, + }, }, -- Upgrade classification 4 [4] = { - [1] = { price = 8000000, core = 1 }, - [2] = { price = 20000000, core = 1 }, - [3] = { price = 40000000, core = 2 }, - [4] = { price = 65000000, core = 5 }, - [5] = { price = 100000000, core = 10 }, - [6] = { price = 250000000, core = 15 }, - [7] = { price = 750000000, core = 25 }, - [8] = { price = 2500000000, core = 35 }, - [9] = { price = 8000000000, core = 50 }, - [10] = { price = 15000000000, core = 65 }, + [1] = { + regular = 1500000, + core = 1, + convergence = { fusion = { price = 6000000 }, transfer = { price = 12000000 } }, + }, + [2] = { + regular = 3000000, + core = 2, + convergence = { fusion = { price = 12000000 }, transfer = { price = 24000000 } }, + }, + [3] = { + regular = 6000000, + core = 5, + convergence = { fusion = { price = 24000000 }, transfer = { price = 48000000 } }, + }, + [4] = { + regular = 15000000, + core = 10, + convergence = { fusion = { price = 48000000 }, transfer = { price = 100000000 } }, + }, + [5] = { + regular = 30000000, + core = 15, + convergence = { fusion = { price = 100000000 }, transfer = { price = 200000000 } }, + }, + [6] = { + regular = 80000000, + core = 25, + convergence = { fusion = { price = 200000000 }, transfer = { price = 400000000 } }, + }, + [7] = { + regular = 200000000, + core = 35, + convergence = { fusion = { price = 400000000 }, transfer = { price = 800000000 } }, + }, + [8] = { + regular = 400000000, + core = 50, + convergence = { fusion = { price = 800000000 }, transfer = { price = 1600000000 } }, + }, + [9] = { + regular = 800000000, + core = 60, + convergence = { fusion = { price = 1600000000 }, transfer = { price = 3200000000 } }, + }, + [10] = { + regular = 1600000000, + core = 85, + convergence = { fusion = { price = 3200000000 }, transfer = { price = 6400000000 } }, + }, }, } @@ -40,9 +122,13 @@ for classificationId, classificationTable in ipairs(itemTierClassifications) do -- Registers table for register_item_tier.lua interface classification.Upgrades = {} for tierId, tierTable in ipairs(classificationTable) do - if tierId and tierTable.price and tierTable.core ~= nil then - table.insert(classification.Upgrades, { TierId = tierId - 1, Price = tierTable.price, Core = tierTable.core }) - end + table.insert(classification.Upgrades, { + TierId = tierId, + Core = tierTable.core, + RegularPrice = tierTable.regular, + ConvergenceFustionPrice = tierTable.convergence and tierTable.convergence.fusion.price or 0, + ConvergenceTransferPrice = tierTable.convergence and tierTable.convergence.transfer.price or 0, + }) end -- Create item classification and register classification table itemClassification:register(classification) diff --git a/data-otservbr-global/scripts/lib/register_item_tier.lua b/data-otservbr-global/scripts/lib/register_item_tier.lua index 51ad62ccc6e..b869dd229a1 100644 --- a/data-otservbr-global/scripts/lib/register_item_tier.lua +++ b/data-otservbr-global/scripts/lib/register_item_tier.lua @@ -14,8 +14,9 @@ end registerItemClassification.Upgrades = function(itemClassification, mask) if mask.Upgrades then for _, value in ipairs(mask.Upgrades) do - if value.TierId and value.Price then - itemClassification:addTier(value.TierId, value.Price, value.Core) + if value.TierId then + logger.debug("Registering tier {}, core {}, regular price {}, fusion price {}, transfer price {}", value.TierId, value.Core, value.RegularPrice, value.ConvergenceFustionPrice, value.ConvergenceTransferPrice) + itemClassification:addTier(value.TierId, value.Core, value.RegularPrice, value.ConvergenceFustionPrice, value.ConvergenceTransferPrice) else logger.warn("[registerItemClassification.Upgrades] - Item classification failed on adquire TierID or Price attribute.") end diff --git a/data-otservbr-global/scripts/lib/shops.lua b/data-otservbr-global/scripts/lib/shops.lua index 84902899489..af19fd80386 100644 --- a/data-otservbr-global/scripts/lib/shops.lua +++ b/data-otservbr-global/scripts/lib/shops.lua @@ -1334,17 +1334,28 @@ LootShopConfigTable = { { itemName = "golden skull", clientId = 35580, sell = 9000 }, { itemName = "golden sun coin", clientId = 43734, sell = 11000 }, { itemName = "golden tiger coin", clientId = 43735, sell = 11000 }, + { itemName = "greater guardian gem", clientId = 44604, sell = 10000 }, + { itemName = "greater marksman gem", clientId = 44607, sell = 10000 }, + { itemName = "greater mystic gem", clientId = 44613, sell = 10000 }, + { itemName = "greater sage gem", clientId = 44610, sell = 10000 }, { itemName = "green crystal fragment", clientId = 16127, sell = 800 }, { itemName = "green crystal shard", clientId = 16121, sell = 1500 }, { itemName = "green crystal splinter", clientId = 16122, sell = 400 }, { itemName = "green gem", clientId = 3038, sell = 5000 }, { itemName = "green giant shimmering pearl", clientId = 281, sell = 3000 }, + { itemName = "guardian gem", clientId = 44603, sell = 5000 }, { itemName = "hexagonal ruby", clientId = 30180, sell = 30000 }, + { itemName = "lesser guardian gem", clientId = 44602, sell = 1000 }, + { itemName = "lesser marksman gem", clientId = 44605, sell = 1000 }, + { itemName = "lesser mystic gem", clientId = 44611, sell = 1000 }, + { itemName = "lesser sage gem", clientId = 44608, sell = 1000 }, { itemName = "life crystal", clientId = 3061, sell = 85 }, { itemName = "magic light wand", clientId = 3046, sell = 35 }, + { itemName = "marksman gem", clientId = 44606, sell = 5000 }, { itemName = "medal of valiance", clientId = 31591, sell = 410000 }, { itemName = "mind stone", clientId = 3062, sell = 100 }, { itemName = "moonstone", clientId = 32771, sell = 13000 }, + { itemName = "mystic gem", clientId = 44612, sell = 5000 }, { itemName = "onyx chip", clientId = 22193, sell = 500 }, { itemName = "opal", clientId = 22194, sell = 500 }, { itemName = "orb", clientId = 3060, sell = 750 }, @@ -1358,6 +1369,7 @@ LootShopConfigTable = { { itemName = "red gem", clientId = 3039, sell = 1000 }, { itemName = "red tome", clientId = 2852, sell = 2000 }, { itemName = "royal almandine", clientId = 39038, sell = 460000 }, + { itemName = "sage gem", clientId = 44609, sell = 5000 }, { itemName = "scarab coin", clientId = 3042, sell = 100 }, { itemName = "sea horse figurine", clientId = 31323, sell = 42000 }, { itemName = "seacrest pearl", clientId = 21747, sell = 400 }, diff --git a/data-otservbr-global/scripts/weapons/unscripted_weapons.lua b/data-otservbr-global/scripts/weapons/unscripted_weapons.lua index 667e242afaf..6feecd6376f 100644 --- a/data-otservbr-global/scripts/weapons/unscripted_weapons.lua +++ b/data-otservbr-global/scripts/weapons/unscripted_weapons.lua @@ -233,7 +233,7 @@ local weapons = { }, { -- broken macuahuitl - itemid = 40530, + itemId = 40530, type = WEAPON_SWORD, }, { @@ -705,7 +705,7 @@ local weapons = { }, { -- phantasmal axe - itemid = 32616, + itemId = 32616, type = WEAPON_AXE, level = 180, unproperly = true, @@ -716,12 +716,12 @@ local weapons = { }, { -- meat hammer - itemid = 32093, + itemId = 32093, type = WEAPON_CLUB, }, { -- tagralt blade - itemid = 31614, + itemId = 31614, type = WEAPON_SWORD, level = 250, unproperly = true, @@ -732,7 +732,7 @@ local weapons = { }, { -- bow of cataclysm - itemid = 31581, + itemId = 31581, type = WEAPON_DISTANCE, level = 250, unproperly = true, @@ -743,7 +743,7 @@ local weapons = { }, { -- mortal mace - itemid = 31580, + itemId = 31580, type = WEAPON_CLUB, level = 220, unproperly = true, @@ -754,7 +754,7 @@ local weapons = { }, { -- cobra rod - itemid = 30400, + itemId = 30400, type = WEAPON_WAND, wandType = "earth", level = 220, @@ -767,7 +767,7 @@ local weapons = { }, { -- cobra wand - itemid = 30399, + itemId = 30399, type = WEAPON_WAND, wandType = "energy", level = 270, @@ -780,7 +780,7 @@ local weapons = { }, { -- cobra sword - itemid = 30398, + itemId = 30398, type = WEAPON_SWORD, level = 220, unproperly = true, @@ -791,7 +791,7 @@ local weapons = { }, { -- cobra axe - itemid = 30396, + itemId = 30396, type = WEAPON_AXE, level = 220, unproperly = true, @@ -802,7 +802,7 @@ local weapons = { }, { -- cobra club - itemid = 30395, + itemId = 30395, type = WEAPON_CLUB, level = 220, unproperly = true, @@ -813,7 +813,7 @@ local weapons = { }, { -- cobra crossbow - itemid = 30393, + itemId = 30393, type = WEAPON_DISTANCE, level = 220, unproperly = true, @@ -824,12 +824,12 @@ local weapons = { }, { -- ice hatchet - itemid = 30283, + itemId = 30283, type = WEAPON_AXE, }, { -- energized limb - itemid = 29425, + itemId = 29425, type = WEAPON_WAND, wandType = "fire", level = 180, @@ -844,7 +844,7 @@ local weapons = { }, { -- winterblade - itemid = 29422, + itemId = 29422, type = WEAPON_SWORD, level = 200, unproperly = true, @@ -855,7 +855,7 @@ local weapons = { }, { -- summerblade - itemid = 29421, + itemId = 29421, type = WEAPON_SWORD, level = 200, unproperly = true, @@ -866,7 +866,7 @@ local weapons = { }, { -- resizer - itemid = 29419, + itemId = 29419, type = WEAPON_CLUB, level = 230, unproperly = true, @@ -877,7 +877,7 @@ local weapons = { }, { -- living vine bow - itemid = 29417, + itemId = 29417, type = WEAPON_DISTANCE, level = 220, unproperly = true, @@ -888,65 +888,65 @@ local weapons = { }, { -- golden axe - itemid = 29286, + itemId = 29286, type = WEAPON_AXE, }, { -- wand of destruction test - itemid = 28479, + itemId = 28479, type = WEAPON_WAND, }, { -- umbral master bow test - itemid = 28478, + itemId = 28478, type = WEAPON_DISTANCE, }, { -- sorcerer test weapon - itemid = 28466, + itemId = 28466, type = WEAPON_WAND, }, { -- bow of destruction test - itemid = 28465, + itemId = 28465, type = WEAPON_DISTANCE, }, { -- test weapon for knights - itemid = 28464, + itemId = 28464, type = WEAPON_SWORD, }, { -- sulphurous demonbone - itemid = 28832, + itemId = 28832, type = WEAPON_CLUB, level = 80, unproperly = true, }, { -- unliving demonbone - itemid = 28831, + itemId = 28831, type = WEAPON_CLUB, level = 80, unproperly = true, }, { -- energized demonbone - itemid = 28830, + itemId = 28830, type = WEAPON_CLUB, level = 80, unproperly = true, }, { -- rotten demonbone - itemid = 28829, + itemId = 28829, type = WEAPON_CLUB, level = 80, unproperly = true, }, { -- deepling fork - itemid = 28826, + itemId = 28826, type = WEAPON_WAND, wandType = "ice", level = 230, @@ -961,7 +961,7 @@ local weapons = { }, { -- deepling ceremonial dagger - itemid = 28825, + itemId = 28825, type = WEAPON_WAND, wandType = "ice", level = 180, @@ -976,7 +976,7 @@ local weapons = { }, { -- falcon mace - itemid = 28725, + itemId = 28725, type = WEAPON_CLUB, level = 300, unproperly = true, @@ -987,7 +987,7 @@ local weapons = { }, { -- falcon battleaxe - itemid = 28724, + itemId = 28724, type = WEAPON_AXE, level = 300, unproperly = true, @@ -998,7 +998,7 @@ local weapons = { }, { -- falcon longsword - itemid = 28723, + itemId = 28723, type = WEAPON_SWORD, level = 300, unproperly = true, @@ -1009,7 +1009,7 @@ local weapons = { }, { -- falcon bow - itemid = 28718, + itemId = 28718, type = WEAPON_DISTANCE, level = 300, unproperly = true, @@ -1020,7 +1020,7 @@ local weapons = { }, { -- falcon wand - itemid = 28717, + itemId = 28717, type = WEAPON_WAND, wandType = "energy", level = 300, @@ -1033,7 +1033,7 @@ local weapons = { }, { -- falcon rod - itemid = 28716, + itemId = 28716, type = WEAPON_WAND, wandType = "earth", level = 300, @@ -1046,7 +1046,7 @@ local weapons = { }, { -- gnome sword - itemid = 27651, + itemId = 27651, type = WEAPON_SWORD, level = 250, unproperly = true, @@ -1057,17 +1057,17 @@ local weapons = { }, { -- mallet handle - itemid = 27525, + itemId = 27525, type = WEAPON_CLUB, }, { -- strange mallet - itemid = 27523, + itemId = 27523, type = WEAPON_CLUB, }, { -- rod of destruction - itemid = 27458, + itemId = 27458, type = WEAPON_WAND, wandType = "ice", level = 200, @@ -1080,7 +1080,7 @@ local weapons = { }, { -- wand of destruction - itemid = 27457, + itemId = 27457, type = WEAPON_WAND, wandType = "energy", level = 200, @@ -1093,7 +1093,7 @@ local weapons = { }, { -- crossbow of destruction - itemid = 27456, + itemId = 27456, type = WEAPON_DISTANCE, level = 200, unproperly = true, @@ -1104,7 +1104,7 @@ local weapons = { }, { -- bow of destruction - itemid = 27455, + itemId = 27455, type = WEAPON_DISTANCE, level = 200, unproperly = true, @@ -1115,7 +1115,7 @@ local weapons = { }, { -- hammer of destruction - itemid = 27454, + itemId = 27454, type = WEAPON_CLUB, level = 200, unproperly = true, @@ -1126,7 +1126,7 @@ local weapons = { }, { -- mace of destruction - itemid = 27453, + itemId = 27453, type = WEAPON_CLUB, level = 200, unproperly = true, @@ -1137,7 +1137,7 @@ local weapons = { }, { -- chopper of destruction - itemid = 27452, + itemId = 27452, type = WEAPON_AXE, level = 200, unproperly = true, @@ -1148,7 +1148,7 @@ local weapons = { }, { -- axe of destruction - itemid = 27451, + itemId = 27451, type = WEAPON_AXE, level = 200, unproperly = true, @@ -1159,7 +1159,7 @@ local weapons = { }, { -- slayer of destruction - itemid = 27450, + itemId = 27450, type = WEAPON_SWORD, level = 200, unproperly = true, @@ -1170,7 +1170,7 @@ local weapons = { }, { -- blade of destruction - itemid = 27449, + itemId = 27449, type = WEAPON_SWORD, level = 200, unproperly = true, @@ -1181,577 +1181,577 @@ local weapons = { }, { -- ornate carving hammer - itemid = 26061, + itemId = 26061, type = WEAPON_CLUB, }, { -- valuable carving hammer - itemid = 26060, + itemId = 26060, type = WEAPON_CLUB, }, { -- plain carving hammer - itemid = 26059, + itemId = 26059, type = WEAPON_CLUB, }, { -- ornate carving mace - itemid = 26058, + itemId = 26058, type = WEAPON_CLUB, }, { -- valuable carving mace - itemid = 26057, + itemId = 26057, type = WEAPON_CLUB, }, { -- plain carving mace - itemid = 26056, + itemId = 26056, type = WEAPON_CLUB, }, { -- ornate carving chopper - itemid = 26055, + itemId = 26055, type = WEAPON_AXE, }, { -- valuable carving chopper - itemid = 26054, + itemId = 26054, type = WEAPON_AXE, }, { -- plain carving chopper - itemid = 26053, + itemId = 26053, type = WEAPON_AXE, }, { -- ornate carving axe - itemid = 26052, + itemId = 26052, type = WEAPON_AXE, }, { -- valuable carving axe - itemid = 26051, + itemId = 26051, type = WEAPON_AXE, }, { -- plain carving axe - itemid = 26050, + itemId = 26050, type = WEAPON_AXE, }, { -- ornate carving slayer - itemid = 26049, + itemId = 26049, type = WEAPON_SWORD, }, { -- valuable carving slayer - itemid = 26048, + itemId = 26048, type = WEAPON_SWORD, }, { -- plain carving slayer - itemid = 26047, + itemId = 26047, type = WEAPON_SWORD, }, { -- ornate carving blade - itemid = 26046, + itemId = 26046, type = WEAPON_SWORD, }, { -- valuable carving blade - itemid = 26045, + itemId = 26045, type = WEAPON_SWORD, }, { -- plain carving blade - itemid = 26044, + itemId = 26044, type = WEAPON_SWORD, }, { -- ornate remedy hammer - itemid = 26031, + itemId = 26031, type = WEAPON_CLUB, }, { -- valuable remedy hammer - itemid = 26030, + itemId = 26030, type = WEAPON_CLUB, }, { -- plain remedy hammer - itemid = 26029, + itemId = 26029, type = WEAPON_CLUB, }, { -- ornate remedy mace - itemid = 26028, + itemId = 26028, type = WEAPON_CLUB, }, { -- valuable remedy mace - itemid = 26027, + itemId = 26027, type = WEAPON_CLUB, }, { -- plain remedy mace - itemid = 26026, + itemId = 26026, type = WEAPON_CLUB, }, { -- ornate remedy chopper - itemid = 26025, + itemId = 26025, type = WEAPON_AXE, }, { -- valuable remedy chopper - itemid = 26024, + itemId = 26024, type = WEAPON_AXE, }, { -- plain remedy chopper - itemid = 26023, + itemId = 26023, type = WEAPON_AXE, }, { -- ornate remedy axe - itemid = 26022, + itemId = 26022, type = WEAPON_AXE, }, { -- valuable remedy axe - itemid = 26021, + itemId = 26021, type = WEAPON_AXE, }, { -- plain remedy axe - itemid = 26020, + itemId = 26020, type = WEAPON_AXE, }, { -- ornate remedy slayer - itemid = 26019, + itemId = 26019, type = WEAPON_SWORD, }, { -- valuable remedy slayer - itemid = 26018, + itemId = 26018, type = WEAPON_SWORD, }, { -- plain remedy slayer - itemid = 26017, + itemId = 26017, type = WEAPON_SWORD, }, { -- ornate remedy blade - itemid = 26016, + itemId = 26016, type = WEAPON_SWORD, }, { -- valuable remedy blade - itemid = 26015, + itemId = 26015, type = WEAPON_SWORD, }, { -- plain remedy blade - itemid = 26014, + itemId = 26014, type = WEAPON_SWORD, }, { -- ornate mayhem hammer - itemid = 26000, + itemId = 26000, type = WEAPON_CLUB, }, { -- valuable mayhem hammer - itemid = 25999, + itemId = 25999, type = WEAPON_CLUB, }, { -- plain mayhem hammer - itemid = 25998, + itemId = 25998, type = WEAPON_CLUB, }, { -- ornate mayhem mace - itemid = 25997, + itemId = 25997, type = WEAPON_CLUB, }, { -- valuable mayhem mace - itemid = 25996, + itemId = 25996, type = WEAPON_CLUB, }, { -- plain mayhem mace - itemid = 25995, + itemId = 25995, type = WEAPON_CLUB, }, { -- ornate mayhem chopper - itemid = 25994, + itemId = 25994, type = WEAPON_AXE, }, { -- valuable mayhem chopper - itemid = 25993, + itemId = 25993, type = WEAPON_AXE, }, { -- plain mayhem chopper - itemid = 25992, + itemId = 25992, type = WEAPON_AXE, }, { -- ornate mayhem axe - itemid = 25991, + itemId = 25991, type = WEAPON_AXE, }, { -- valuable mayhem axe - itemid = 25990, + itemId = 25990, type = WEAPON_AXE, }, { -- plain mayhem axe - itemid = 25989, + itemId = 25989, type = WEAPON_AXE, }, { -- ornate mayhem slayer - itemid = 25988, + itemId = 25988, type = WEAPON_SWORD, }, { -- valuable mayhem slayer - itemid = 25987, + itemId = 25987, type = WEAPON_SWORD, }, { -- plain mayhem slayer - itemid = 25986, + itemId = 25986, type = WEAPON_SWORD, }, { -- ornate mayhem blade - itemid = 25985, + itemId = 25985, type = WEAPON_SWORD, }, { -- valuable mayhem blade - itemid = 25984, + itemId = 25984, type = WEAPON_SWORD, }, { -- plain mayhem blade - itemid = 25983, + itemId = 25983, type = WEAPON_SWORD, }, { -- energy war hammer replica - itemid = 25974, + itemId = 25974, type = WEAPON_CLUB, }, { -- energy orcish maul replica - itemid = 25973, + itemId = 25973, type = WEAPON_CLUB, }, { -- energy basher replica - itemid = 25972, + itemId = 25972, type = WEAPON_CLUB, }, { -- energy crystal mace replica - itemid = 25971, + itemId = 25971, type = WEAPON_CLUB, }, { -- energy clerical mace replica - itemid = 25970, + itemId = 25970, type = WEAPON_CLUB, }, { -- energy war axe replica - itemid = 25969, + itemId = 25969, type = WEAPON_AXE, }, { -- energy headchopper replica - itemid = 25968, + itemId = 25968, type = WEAPON_AXE, }, { -- energy heroic axe replica - itemid = 25967, + itemId = 25967, type = WEAPON_AXE, }, { -- energy knight axe replica - itemid = 25966, + itemId = 25966, type = WEAPON_AXE, }, { -- energy barbarian axe replica - itemid = 25965, + itemId = 25965, type = WEAPON_AXE, }, { -- energy dragon slayer replica - itemid = 25964, + itemId = 25964, type = WEAPON_SWORD, }, { -- energy blacksteel replica - itemid = 25963, + itemId = 25963, type = WEAPON_SWORD, }, { -- energy mystic blade replica - itemid = 25962, + itemId = 25962, type = WEAPON_SWORD, }, { -- energy relic sword replica - itemid = 25961, + itemId = 25961, type = WEAPON_SWORD, }, { -- energy spike sword replica - itemid = 25960, + itemId = 25960, type = WEAPON_SWORD, }, { -- earth war hammer replica - itemid = 25959, + itemId = 25959, type = WEAPON_CLUB, }, { -- earth orcish maul replica - itemid = 25958, + itemId = 25958, type = WEAPON_CLUB, }, { -- earth basher replica - itemid = 25957, + itemId = 25957, type = WEAPON_CLUB, }, { -- earth crystal mace replica - itemid = 25956, + itemId = 25956, type = WEAPON_CLUB, }, { -- earth clerical mace replica - itemid = 25955, + itemId = 25955, type = WEAPON_CLUB, }, { -- earth war axe replica - itemid = 25954, + itemId = 25954, type = WEAPON_AXE, }, { -- earth headchopper replica - itemid = 25953, + itemId = 25953, type = WEAPON_AXE, }, { -- earth heroic axe replica - itemid = 25952, + itemId = 25952, type = WEAPON_AXE, }, { -- earth knight axe replica - itemid = 25951, + itemId = 25951, type = WEAPON_AXE, }, { -- earth barbarian axe replica - itemid = 25950, + itemId = 25950, type = WEAPON_AXE, }, { -- earth dragon slayer replica - itemid = 25949, + itemId = 25949, type = WEAPON_SWORD, }, { -- earth blacksteel replica - itemid = 25948, + itemId = 25948, type = WEAPON_SWORD, }, { -- earth mystic blade replica - itemid = 25947, + itemId = 25947, type = WEAPON_SWORD, }, { -- earth relic sword replica - itemid = 25946, + itemId = 25946, type = WEAPON_SWORD, }, { -- earth spike sword replica - itemid = 25945, + itemId = 25945, type = WEAPON_SWORD, }, { -- icy war hammer replica - itemid = 25944, + itemId = 25944, type = WEAPON_CLUB, }, { -- icy orcish maul replica - itemid = 25943, + itemId = 25943, type = WEAPON_CLUB, }, { -- icy basher replica - itemid = 25942, + itemId = 25942, type = WEAPON_CLUB, }, { -- icy crystal mace replica - itemid = 25941, + itemId = 25941, type = WEAPON_CLUB, }, { -- icy clerical mace replica - itemid = 25940, + itemId = 25940, type = WEAPON_CLUB, }, { -- icy war axe replica - itemid = 25939, + itemId = 25939, type = WEAPON_AXE, }, { -- icy headchopper replica - itemid = 25938, + itemId = 25938, type = WEAPON_AXE, }, { -- icy heroic axe replica - itemid = 25937, + itemId = 25937, type = WEAPON_AXE, }, { -- icy knight axe replica - itemid = 25936, + itemId = 25936, type = WEAPON_AXE, }, { -- icy barbarian axe replica - itemid = 25935, + itemId = 25935, type = WEAPON_AXE, }, { -- icy dragon slayer replica - itemid = 25934, + itemId = 25934, type = WEAPON_SWORD, }, { -- icy blacksteel replica - itemid = 25933, + itemId = 25933, type = WEAPON_SWORD, }, { -- icy mystic blade replica - itemid = 25932, + itemId = 25932, type = WEAPON_SWORD, }, { -- icy relic sword replica - itemid = 25931, + itemId = 25931, type = WEAPON_SWORD, }, { -- icy spike sword replica - itemid = 25930, + itemId = 25930, type = WEAPON_SWORD, }, { -- fiery war hammer replica - itemid = 25929, + itemId = 25929, type = WEAPON_CLUB, }, { -- fiery orcish maul replica - itemid = 25928, + itemId = 25928, type = WEAPON_CLUB, }, { -- fiery basher replica - itemid = 25927, + itemId = 25927, type = WEAPON_CLUB, }, { -- fiery crystal mace replica - itemid = 25926, + itemId = 25926, type = WEAPON_CLUB, }, { -- fiery clerical mace replica - itemid = 25925, + itemId = 25925, type = WEAPON_CLUB, }, { -- fiery war axe replica - itemid = 25924, + itemId = 25924, type = WEAPON_AXE, }, { -- fiery headchopper replica - itemid = 25923, + itemId = 25923, type = WEAPON_AXE, }, { -- fiery heroic axe replica - itemid = 25922, + itemId = 25922, type = WEAPON_AXE, }, { -- fiery knight axe replica - itemid = 25921, + itemId = 25921, type = WEAPON_AXE, }, { -- fiery barbarian axe replica - itemid = 25920, + itemId = 25920, type = WEAPON_AXE, }, { -- fiery dragon slayer replica - itemid = 25919, + itemId = 25919, type = WEAPON_SWORD, }, { -- fiery blacksteel replica - itemid = 25918, + itemId = 25918, type = WEAPON_SWORD, }, { -- fiery mystic blade replica - itemid = 25917, + itemId = 25917, type = WEAPON_SWORD, }, { -- fiery relic sword replica - itemid = 25916, + itemId = 25916, type = WEAPON_SWORD, }, { -- fiery spike sword replica - itemid = 25915, + itemId = 25915, type = WEAPON_SWORD, }, { -- wand of darkness - itemid = 25760, + itemId = 25760, type = WEAPON_WAND, wandType = "death", level = 41, @@ -1772,7 +1772,7 @@ local weapons = { }, { -- spectral bolt - itemid = 25758, + itemId = 25758, type = WEAPON_AMMO, level = 150, unproperly = true, @@ -1788,7 +1788,7 @@ local weapons = { }, { -- dream blossom staff - itemid = 25700, + itemId = 25700, type = WEAPON_WAND, wandType = "energy", level = 80, @@ -1803,7 +1803,7 @@ local weapons = { }, { -- rod of carving - itemid = 23339, + itemId = 23339, type = WEAPON_WAND, wandType = "ice", level = 100, @@ -1816,7 +1816,7 @@ local weapons = { }, { -- wand of carving - itemid = 23335, + itemId = 23335, type = WEAPON_WAND, wandType = "energy", level = 100, @@ -1829,7 +1829,7 @@ local weapons = { }, { -- crossbow of carving - itemid = 23331, + itemId = 23331, type = WEAPON_DISTANCE, level = 100, unproperly = true, @@ -1840,7 +1840,7 @@ local weapons = { }, { -- bow of carving - itemid = 23327, + itemId = 23327, type = WEAPON_DISTANCE, level = 100, unproperly = true, @@ -1851,7 +1851,7 @@ local weapons = { }, { -- hammer of carving - itemid = 23323, + itemId = 23323, type = WEAPON_CLUB, level = 100, unproperly = true, @@ -1862,7 +1862,7 @@ local weapons = { }, { -- mace of carving - itemid = 23319, + itemId = 23319, type = WEAPON_CLUB, level = 100, unproperly = true, @@ -1873,7 +1873,7 @@ local weapons = { }, { -- chopper of carving - itemid = 23315, + itemId = 23315, type = WEAPON_AXE, level = 100, unproperly = true, @@ -1884,7 +1884,7 @@ local weapons = { }, { -- axe of carving - itemid = 23311, + itemId = 23311, type = WEAPON_AXE, level = 100, unproperly = true, @@ -1895,7 +1895,7 @@ local weapons = { }, { -- slayer of carving - itemid = 23307, + itemId = 23307, type = WEAPON_SWORD, level = 100, unproperly = true, @@ -1906,7 +1906,7 @@ local weapons = { }, { -- blade of carving - itemid = 23303, + itemId = 23303, type = WEAPON_SWORD, level = 100, unproperly = true, @@ -1917,7 +1917,7 @@ local weapons = { }, { -- rod of remedy - itemid = 23299, + itemId = 23299, type = WEAPON_WAND, wandType = "ice", level = 100, @@ -1930,7 +1930,7 @@ local weapons = { }, { -- wand of remedy - itemid = 23295, + itemId = 23295, type = WEAPON_WAND, wandType = "energy", level = 100, @@ -1943,7 +1943,7 @@ local weapons = { }, { -- crossbow of remedy - itemid = 23291, + itemId = 23291, type = WEAPON_DISTANCE, level = 100, unproperly = true, @@ -1954,7 +1954,7 @@ local weapons = { }, { -- bow of remedy - itemid = 23287, + itemId = 23287, type = WEAPON_DISTANCE, level = 100, unproperly = true, @@ -1965,7 +1965,7 @@ local weapons = { }, { -- hammer of remedy - itemid = 23283, + itemId = 23283, type = WEAPON_CLUB, level = 100, unproperly = true, @@ -1976,7 +1976,7 @@ local weapons = { }, { -- mace of remedy - itemid = 23279, + itemId = 23279, type = WEAPON_CLUB, level = 100, unproperly = true, @@ -1987,7 +1987,7 @@ local weapons = { }, { -- chopper of remedy - itemid = 23275, + itemId = 23275, type = WEAPON_AXE, level = 100, unproperly = true, @@ -1998,7 +1998,7 @@ local weapons = { }, { -- axe of remedy - itemid = 23271, + itemId = 23271, type = WEAPON_AXE, level = 100, unproperly = true, @@ -2009,7 +2009,7 @@ local weapons = { }, { -- slayer of remedy - itemid = 23267, + itemId = 23267, type = WEAPON_SWORD, level = 100, unproperly = true, @@ -2020,7 +2020,7 @@ local weapons = { }, { -- blade of remedy - itemid = 23263, + itemId = 23263, type = WEAPON_SWORD, level = 100, unproperly = true, @@ -2031,7 +2031,7 @@ local weapons = { }, { -- rod of mayhem - itemid = 23232, + itemId = 23232, type = WEAPON_WAND, wandType = "ice", level = 100, @@ -2044,7 +2044,7 @@ local weapons = { }, { -- wand of mayhem - itemid = 23231, + itemId = 23231, type = WEAPON_WAND, wandType = "energy", level = 100, @@ -2057,7 +2057,7 @@ local weapons = { }, { -- crossbow of mayhem - itemid = 23230, + itemId = 23230, type = WEAPON_DISTANCE, level = 100, unproperly = true, @@ -2068,7 +2068,7 @@ local weapons = { }, { -- bow of mayhem - itemid = 23229, + itemId = 23229, type = WEAPON_DISTANCE, level = 100, unproperly = true, @@ -2079,7 +2079,7 @@ local weapons = { }, { -- hammer of mayhem - itemid = 23228, + itemId = 23228, type = WEAPON_CLUB, level = 100, unproperly = true, @@ -2090,7 +2090,7 @@ local weapons = { }, { -- mace of mayhem - itemid = 23227, + itemId = 23227, type = WEAPON_CLUB, level = 100, unproperly = true, @@ -2101,7 +2101,7 @@ local weapons = { }, { -- chopper of mayhem - itemid = 23226, + itemId = 23226, type = WEAPON_AXE, level = 100, unproperly = true, @@ -2112,7 +2112,7 @@ local weapons = { }, { -- axe of mayhem - itemid = 23225, + itemId = 23225, type = WEAPON_AXE, level = 100, unproperly = true, @@ -2123,7 +2123,7 @@ local weapons = { }, { -- slayer of mayhem - itemid = 23224, + itemId = 23224, type = WEAPON_SWORD, level = 100, unproperly = true, @@ -2134,7 +2134,7 @@ local weapons = { }, { -- blade of mayhem - itemid = 23223, + itemId = 23223, type = WEAPON_SWORD, level = 100, unproperly = true, @@ -2145,7 +2145,7 @@ local weapons = { }, { -- rift crossbow - itemid = 22867, + itemId = 22867, type = WEAPON_DISTANCE, level = 120, unproperly = true, @@ -2156,7 +2156,7 @@ local weapons = { }, { -- rift bow - itemid = 22866, + itemId = 22866, type = WEAPON_DISTANCE, level = 120, unproperly = true, @@ -2167,7 +2167,7 @@ local weapons = { }, { -- ferumbras' staff (enchanted) - itemid = 22766, + itemId = 22766, type = WEAPON_WAND, wandType = "energy", level = 100, @@ -2180,7 +2180,7 @@ local weapons = { }, { -- ferumbras' staff (failed) - itemid = 22765, + itemId = 22765, type = WEAPON_WAND, wandType = "energy", level = 65, @@ -2193,42 +2193,42 @@ local weapons = { }, { -- Ferumbras' staff - itemid = 22764, + itemId = 22764, type = WEAPON_CLUB, level = 100, unproperly = true, }, { -- maimer - itemid = 22762, + itemId = 22762, type = WEAPON_CLUB, level = 150, unproperly = true, }, { -- Impaler of the igniter - itemid = 22760, + itemId = 22760, type = WEAPON_SWORD, level = 150, unproperly = true, }, { -- plague bite - itemid = 22759, + itemId = 22759, type = WEAPON_AXE, level = 150, unproperly = true, }, { -- rift lance - itemid = 22727, + itemId = 22727, type = WEAPON_AXE, level = 70, unproperly = true, }, { -- ogre sceptra - itemid = 22183, + itemId = 22183, type = WEAPON_WAND, wandType = "earth", level = 37, @@ -2241,27 +2241,27 @@ local weapons = { }, { -- ogre choppa - itemid = 22172, + itemId = 22172, type = WEAPON_AXE, level = 25, unproperly = true, }, { -- ogre klubba - itemid = 22171, + itemId = 22171, type = WEAPON_AXE, level = 50, unproperly = true, }, { -- simple arrow - itemid = 21470, + itemId = 21470, type = WEAPON_AMMO, action = "removecount", }, { -- the chiller - itemid = 21350, + itemId = 21350, type = WEAPON_WAND, wandType = "ice", level = 1, @@ -2274,7 +2274,7 @@ local weapons = { }, { -- the scorcher - itemid = 21348, + itemId = 21348, type = WEAPON_WAND, wandType = "fire", level = 1, @@ -2287,14 +2287,14 @@ local weapons = { }, { -- one hit wonder - itemid = 21219, + itemId = 21219, type = WEAPON_CLUB, level = 70, unproperly = true, }, { -- glooth axe - itemid = 21180, + itemId = 21180, type = WEAPON_AXE, level = 75, unproperly = true, @@ -2306,7 +2306,7 @@ local weapons = { }, { -- glooth blade - itemid = 21179, + itemId = 21179, type = WEAPON_SWORD, level = 75, unproperly = true, @@ -2318,7 +2318,7 @@ local weapons = { }, { -- glooth club - itemid = 21178, + itemId = 21178, type = WEAPON_CLUB, level = 75, unproperly = true, @@ -2330,42 +2330,42 @@ local weapons = { }, { -- cowtana - itemid = 21177, + itemId = 21177, type = WEAPON_SWORD, level = 25, unproperly = true, }, { -- execowtioner axe - itemid = 21176, + itemId = 21176, type = WEAPON_AXE, level = 55, unproperly = true, }, { -- mino lance - itemid = 21174, + itemId = 21174, type = WEAPON_AXE, level = 45, unproperly = true, }, { -- moohtant cudgel - itemid = 21173, + itemId = 21173, type = WEAPON_CLUB, level = 60, unproperly = true, }, { -- glooth whip - itemid = 21172, + itemId = 21172, type = WEAPON_CLUB, level = 25, unproperly = true, }, { -- metal bat - itemid = 21171, + itemId = 21171, type = WEAPON_CLUB, level = 55, unproperly = true, @@ -2380,7 +2380,7 @@ local weapons = { }, { -- umbral master crossbow - itemid = 20087, + itemId = 20087, type = WEAPON_DISTANCE, level = 250, unproperly = true, @@ -2391,7 +2391,7 @@ local weapons = { }, { -- umbral crossbow - itemid = 20086, + itemId = 20086, type = WEAPON_DISTANCE, level = 120, unproperly = true, @@ -2402,7 +2402,7 @@ local weapons = { }, { -- crude umbral crossbow - itemid = 20085, + itemId = 20085, type = WEAPON_DISTANCE, level = 75, unproperly = true, @@ -2413,7 +2413,7 @@ local weapons = { }, { -- umbral master bow - itemid = 20084, + itemId = 20084, type = WEAPON_DISTANCE, level = 250, unproperly = true, @@ -2424,7 +2424,7 @@ local weapons = { }, { -- umbral bow - itemid = 20083, + itemId = 20083, type = WEAPON_DISTANCE, level = 120, unproperly = true, @@ -2435,7 +2435,7 @@ local weapons = { }, { -- crude umbral bow - itemid = 20082, + itemId = 20082, type = WEAPON_DISTANCE, level = 75, unproperly = true, @@ -2446,7 +2446,7 @@ local weapons = { }, { -- umbral master hammer - itemid = 20081, + itemId = 20081, type = WEAPON_CLUB, level = 250, unproperly = true, @@ -2457,7 +2457,7 @@ local weapons = { }, { -- umbral hammer - itemid = 20080, + itemId = 20080, type = WEAPON_CLUB, level = 120, unproperly = true, @@ -2468,7 +2468,7 @@ local weapons = { }, { -- crude umbral hammer - itemid = 20079, + itemId = 20079, type = WEAPON_CLUB, level = 75, unproperly = true, @@ -2479,7 +2479,7 @@ local weapons = { }, { -- umbral master mace - itemid = 20078, + itemId = 20078, type = WEAPON_CLUB, level = 250, unproperly = true, @@ -2490,7 +2490,7 @@ local weapons = { }, { -- umbral mace - itemid = 20077, + itemId = 20077, type = WEAPON_CLUB, level = 120, unproperly = true, @@ -2501,7 +2501,7 @@ local weapons = { }, { -- crude umbral mace - itemid = 20076, + itemId = 20076, type = WEAPON_CLUB, level = 75, unproperly = true, @@ -2512,7 +2512,7 @@ local weapons = { }, { -- umbral master chopper - itemid = 20075, + itemId = 20075, type = WEAPON_AXE, level = 250, unproperly = true, @@ -2523,7 +2523,7 @@ local weapons = { }, { -- umbral chopper - itemid = 20074, + itemId = 20074, type = WEAPON_AXE, level = 120, unproperly = true, @@ -2534,7 +2534,7 @@ local weapons = { }, { -- crude umbral chopper - itemid = 20073, + itemId = 20073, type = WEAPON_AXE, level = 75, unproperly = true, @@ -2545,7 +2545,7 @@ local weapons = { }, { -- umbral master axe - itemid = 20072, + itemId = 20072, type = WEAPON_AXE, level = 250, unproperly = true, @@ -2556,7 +2556,7 @@ local weapons = { }, { -- umbral axe - itemid = 20071, + itemId = 20071, type = WEAPON_AXE, level = 120, unproperly = true, @@ -2567,7 +2567,7 @@ local weapons = { }, { -- crude umbral axe - itemid = 20070, + itemId = 20070, type = WEAPON_AXE, level = 75, unproperly = true, @@ -2578,7 +2578,7 @@ local weapons = { }, { -- umbral master slayer - itemid = 20069, + itemId = 20069, type = WEAPON_SWORD, level = 250, unproperly = true, @@ -2589,7 +2589,7 @@ local weapons = { }, { -- umbral slayer - itemid = 20068, + itemId = 20068, type = WEAPON_SWORD, level = 120, unproperly = true, @@ -2600,7 +2600,7 @@ local weapons = { }, { -- crude umbral slayer - itemid = 20067, + itemId = 20067, type = WEAPON_SWORD, level = 75, unproperly = true, @@ -2611,7 +2611,7 @@ local weapons = { }, { -- umbral masterblade - itemid = 20066, + itemId = 20066, type = WEAPON_SWORD, level = 250, unproperly = true, @@ -2622,7 +2622,7 @@ local weapons = { }, { -- umbral blade - itemid = 20065, + itemId = 20065, type = WEAPON_SWORD, level = 120, unproperly = true, @@ -2633,7 +2633,7 @@ local weapons = { }, { -- crude umbral blade - itemid = 20064, + itemId = 20064, type = WEAPON_SWORD, level = 75, unproperly = true, @@ -2644,13 +2644,13 @@ local weapons = { }, { -- icicle bow - itemid = 19362, + itemId = 19362, type = WEAPON_DISTANCE, unproperly = true, }, { -- triple bolt crossbow - itemid = 19356, + itemId = 19356, type = WEAPON_DISTANCE, level = 70, unproperly = true, @@ -2661,14 +2661,14 @@ local weapons = { }, { -- spiky club - itemid = 17859, + itemId = 17859, type = WEAPON_CLUB, level = 20, unproperly = true, }, { -- pair of iron fists - itemid = 17828, + itemId = 17828, type = WEAPON_CLUB, level = 50, unproperly = true, @@ -2679,26 +2679,26 @@ local weapons = { }, { -- swampling club - itemid = 17824, + itemId = 17824, type = WEAPON_CLUB, }, { -- life preserver - itemid = 17813, + itemId = 17813, type = WEAPON_CLUB, level = 15, unproperly = true, }, { -- ratana - itemid = 17812, + itemId = 17812, type = WEAPON_SWORD, level = 15, unproperly = true, }, { -- sorc and druid staff - itemid = 17111, + itemId = 17111, type = WEAPON_WAND, wandType = "energy", level = 1, @@ -2719,7 +2719,7 @@ local weapons = { }, { -- mean knight sword - itemid = 17109, + itemId = 17109, type = WEAPON_SWORD, unproperly = true, vocation = { @@ -2728,14 +2728,14 @@ local weapons = { }, { -- shiny blade - itemid = 16175, + itemId = 16175, type = WEAPON_SWORD, level = 120, unproperly = true, }, { -- mycological bow - itemid = 16164, + itemId = 16164, type = WEAPON_DISTANCE, level = 105, unproperly = true, @@ -2746,7 +2746,7 @@ local weapons = { }, { -- crystal crossbow - itemid = 16163, + itemId = 16163, type = WEAPON_DISTANCE, level = 90, unproperly = true, @@ -2757,21 +2757,21 @@ local weapons = { }, { -- mycological mace - itemid = 16162, + itemId = 16162, type = WEAPON_CLUB, level = 120, unproperly = true, }, { -- crystalline axe - itemid = 16161, + itemId = 16161, type = WEAPON_AXE, level = 120, unproperly = true, }, { -- crystalline sword - itemid = 16160, + itemId = 16160, type = WEAPON_SWORD, level = 62, unproperly = true, @@ -2786,7 +2786,7 @@ local weapons = { }, { -- drill bolt - itemid = 16142, + itemId = 16142, type = WEAPON_AMMO, level = 70, unproperly = true, @@ -2794,7 +2794,7 @@ local weapons = { }, { -- prismatic bolt - itemid = 16141, + itemId = 16141, type = WEAPON_AMMO, level = 90, unproperly = true, @@ -2802,7 +2802,7 @@ local weapons = { }, { -- glacial rod - itemid = 16118, + itemId = 16118, type = WEAPON_WAND, wandType = "ice", level = 65, @@ -2815,7 +2815,7 @@ local weapons = { }, { -- muck rod - itemid = 16117, + itemId = 16117, type = WEAPON_WAND, wandType = "earth", level = 65, @@ -2828,7 +2828,7 @@ local weapons = { }, { -- wand of everblazing - itemid = 16115, + itemId = 16115, type = WEAPON_WAND, wandType = "fire", level = 65, @@ -2841,7 +2841,7 @@ local weapons = { }, { -- wand of defiance - itemid = 16096, + itemId = 16096, type = WEAPON_WAND, wandType = "energy", level = 65, @@ -2862,13 +2862,13 @@ local weapons = { }, { -- crystal bolt - itemid = 15792, + itemId = 15792, type = WEAPON_AMMO, action = "removecount", }, { -- thorn spitter - itemid = 14768, + itemId = 14768, type = WEAPON_DISTANCE, level = 150, unproperly = true, @@ -2879,7 +2879,7 @@ local weapons = { }, { -- vortex bolt - itemid = 14252, + itemId = 14252, type = WEAPON_AMMO, level = 40, unproperly = true, @@ -2895,14 +2895,14 @@ local weapons = { }, { -- deepling squelcher - itemid = 14250, + itemId = 14250, type = WEAPON_CLUB, level = 48, unproperly = true, }, { -- ornate crossbow - itemid = 14247, + itemId = 14247, type = WEAPON_DISTANCE, level = 50, unproperly = true, @@ -2913,7 +2913,7 @@ local weapons = { }, { -- hive bow - itemid = 14246, + itemId = 14246, type = WEAPON_DISTANCE, level = 85, unproperly = true, @@ -2924,49 +2924,49 @@ local weapons = { }, { -- hive scythe - itemid = 14089, + itemId = 14089, type = WEAPON_AXE, level = 70, unproperly = true, }, { -- guardian axe - itemid = 14043, + itemId = 14043, type = WEAPON_AXE, level = 50, unproperly = true, }, { -- warrior's axe - itemid = 14040, + itemId = 14040, type = WEAPON_AXE, level = 40, unproperly = true, }, { -- ornate mace - itemid = 14001, + itemId = 14001, type = WEAPON_CLUB, level = 90, unproperly = true, }, { -- deepling axe - itemid = 13991, + itemId = 13991, type = WEAPON_AXE, level = 80, unproperly = true, }, { -- deepling staff - itemid = 13987, + itemId = 13987, type = WEAPON_CLUB, level = 38, unproperly = true, }, { -- shimmer wand - itemid = 12741, + itemId = 12741, type = WEAPON_WAND, wandType = "energy", level = 40, @@ -2979,7 +2979,7 @@ local weapons = { }, { -- shimmer bow - itemid = 12733, + itemId = 12733, type = WEAPON_DISTANCE, level = 40, unproperly = true, @@ -2990,7 +2990,7 @@ local weapons = { }, { -- shimmer rod - itemid = 12732, + itemId = 12732, type = WEAPON_WAND, wandType = "ice", level = 40, @@ -3003,26 +3003,26 @@ local weapons = { }, { -- shimmer sword - itemid = 12731, + itemId = 12731, type = WEAPON_SWORD, level = 40, unproperly = true, }, { -- heavy trident - itemid = 12683, + itemId = 12683, type = WEAPON_AXE, level = 25, unproperly = true, }, { -- wooden sword - itemid = 12673, + itemId = 12673, type = WEAPON_SWORD, }, { -- wand of dimensions - itemid = 12603, + itemId = 12603, type = WEAPON_WAND, wandType = "death", level = 37, @@ -3035,21 +3035,21 @@ local weapons = { }, { -- blade of corruption - itemid = 11693, + itemId = 11693, type = WEAPON_SWORD, level = 82, unproperly = true, }, { -- snake god's sceptre - itemid = 11692, + itemId = 11692, type = WEAPON_CLUB, level = 82, unproperly = true, }, { -- twiceslicer - itemid = 11657, + itemId = 11657, type = WEAPON_SWORD, level = 58, unproperly = true, @@ -3060,14 +3060,14 @@ local weapons = { }, { -- Zaoan halberd - itemid = 10406, + itemId = 10406, type = WEAPON_AXE, level = 25, unproperly = true, }, { -- twin hooks - itemid = 10392, + itemId = 10392, type = WEAPON_SWORD, level = 20, unproperly = true, @@ -3078,7 +3078,7 @@ local weapons = { }, { -- drachaku - itemid = 10391, + itemId = 10391, type = WEAPON_CLUB, level = 55, unproperly = true, @@ -3089,14 +3089,14 @@ local weapons = { }, { -- Zaoan sword - itemid = 10390, + itemId = 10390, type = WEAPON_SWORD, level = 55, unproperly = true, }, { -- sai - itemid = 10389, + itemId = 10389, type = WEAPON_SWORD, level = 50, unproperly = true, @@ -3107,59 +3107,59 @@ local weapons = { }, { -- drakinata - itemid = 10388, + itemId = 10388, type = WEAPON_AXE, level = 60, unproperly = true, }, { -- incredible mumpiz slayer - itemid = 9396, + itemId = 9396, type = WEAPON_SWORD, }, { -- poet's fencing quill - itemid = 9387, + itemId = 9387, type = WEAPON_SWORD, }, { -- farmer's avenger - itemid = 9386, + itemId = 9386, type = WEAPON_AXE, }, { -- club of the fury - itemid = 9385, + itemId = 9385, type = WEAPON_CLUB, }, { -- scythe of the reaper - itemid = 9384, + itemId = 9384, type = WEAPON_AXE, }, { -- musician's bow - itemid = 9378, + itemId = 9378, type = WEAPON_DISTANCE, }, { -- stale bread of ancientness - itemid = 9376, + itemId = 9376, type = WEAPON_CLUB, }, { -- pointed rabbitslayer - itemid = 9375, + itemId = 9375, type = WEAPON_SWORD, }, { -- glutton's mace - itemid = 9373, + itemId = 9373, type = WEAPON_CLUB, }, { -- the calamity - itemid = 8104, + itemId = 8104, type = WEAPON_SWORD, level = 100, unproperly = true, @@ -3170,21 +3170,21 @@ local weapons = { }, { -- the epiphany - itemid = 8103, + itemId = 8103, type = WEAPON_SWORD, level = 120, unproperly = true, }, { -- emerald sword - itemid = 8102, + itemId = 8102, type = WEAPON_SWORD, level = 100, unproperly = true, }, { -- the stomper - itemid = 8101, + itemId = 8101, type = WEAPON_CLUB, level = 100, unproperly = true, @@ -3195,21 +3195,21 @@ local weapons = { }, { -- obsidian truncheon - itemid = 8100, + itemId = 8100, type = WEAPON_CLUB, level = 100, unproperly = true, }, { -- dark trinity mace - itemid = 8099, + itemId = 8099, type = WEAPON_CLUB, level = 120, unproperly = true, }, { -- demonwing axe - itemid = 8098, + itemId = 8098, type = WEAPON_AXE, level = 120, unproperly = true, @@ -3220,21 +3220,21 @@ local weapons = { }, { -- solar axe - itemid = 8097, + itemId = 8097, type = WEAPON_AXE, level = 130, unproperly = true, }, { -- hellforged axe - itemid = 8096, + itemId = 8096, type = WEAPON_AXE, level = 110, unproperly = true, }, { -- wand of voodoo - itemid = 8094, + itemId = 8094, type = WEAPON_WAND, wandType = "death", level = 42, @@ -3247,7 +3247,7 @@ local weapons = { }, { -- wand of draconia - itemid = 8093, + itemId = 8093, type = WEAPON_WAND, wandType = "fire", level = 22, @@ -3260,7 +3260,7 @@ local weapons = { }, { -- wand of starmstorm - itemid = 8092, + itemId = 8092, type = WEAPON_WAND, wandType = "energy", level = 37, @@ -3273,7 +3273,7 @@ local weapons = { }, { -- springsprout rod - itemid = 8084, + itemId = 8084, type = WEAPON_WAND, wandType = "earth", level = 37, @@ -3286,7 +3286,7 @@ local weapons = { }, { -- northwind rod - itemid = 8083, + itemId = 8083, type = WEAPON_WAND, wandType = "ice", level = 22, @@ -3299,7 +3299,7 @@ local weapons = { }, { -- underworld rod - itemid = 8082, + itemId = 8082, type = WEAPON_WAND, wandType = "death", level = 42, @@ -3312,7 +3312,7 @@ local weapons = { }, { -- elethriel's elemental bow - itemid = 8030, + itemId = 8030, type = WEAPON_DISTANCE, level = 70, unproperly = true, @@ -3323,7 +3323,7 @@ local weapons = { }, { -- silkweaver bow - itemid = 8029, + itemId = 8029, type = WEAPON_DISTANCE, level = 40, unproperly = true, @@ -3334,7 +3334,7 @@ local weapons = { }, { -- yol's bow - itemid = 8028, + itemId = 8028, type = WEAPON_DISTANCE, level = 60, unproperly = true, @@ -3345,7 +3345,7 @@ local weapons = { }, { -- composite hornbow - itemid = 8027, + itemId = 8027, type = WEAPON_DISTANCE, level = 50, unproperly = true, @@ -3356,7 +3356,7 @@ local weapons = { }, { -- warsinger bow - itemid = 8026, + itemId = 8026, type = WEAPON_DISTANCE, level = 80, unproperly = true, @@ -3367,7 +3367,7 @@ local weapons = { }, { -- ironworker - itemid = 8025, + itemId = 8025, type = WEAPON_DISTANCE, level = 80, unproperly = true, @@ -3378,7 +3378,7 @@ local weapons = { }, { -- devileye - itemid = 8024, + itemId = 8024, type = WEAPON_DISTANCE, level = 100, unproperly = true, @@ -3389,7 +3389,7 @@ local weapons = { }, { -- royal crossbow - itemid = 8023, + itemId = 8023, type = WEAPON_DISTANCE, level = 130, unproperly = true, @@ -3400,7 +3400,7 @@ local weapons = { }, { -- chain bolter - itemid = 8022, + itemId = 8022, type = WEAPON_DISTANCE, level = 60, unproperly = true, @@ -3411,7 +3411,7 @@ local weapons = { }, { -- modified crossbow - itemid = 8021, + itemId = 8021, type = WEAPON_DISTANCE, level = 45, unproperly = true, @@ -3422,22 +3422,22 @@ local weapons = { }, { -- jagged sword - itemid = 7774, + itemId = 7774, type = WEAPON_SWORD, }, { -- steel axe - itemid = 7773, + itemId = 7773, type = WEAPON_AXE, }, { -- crimson sword - itemid = 860, + itemId = 860, type = WEAPON_SWORD, }, { -- energy war hammer - itemid = 810, + itemId = 810, type = WEAPON_CLUB, level = 50, unproperly = true, @@ -3449,7 +3449,7 @@ local weapons = { }, { -- energy orcish maul - itemid = 809, + itemId = 809, type = WEAPON_CLUB, level = 35, unproperly = true, @@ -3457,7 +3457,7 @@ local weapons = { }, { -- energy cranial basher - itemid = 808, + itemId = 808, type = WEAPON_CLUB, level = 60, unproperly = true, @@ -3465,7 +3465,7 @@ local weapons = { }, { -- energy crystal mace - itemid = 807, + itemId = 807, type = WEAPON_CLUB, level = 35, unproperly = true, @@ -3473,7 +3473,7 @@ local weapons = { }, { -- energy clerical mace - itemid = 806, + itemId = 806, type = WEAPON_CLUB, level = 20, unproperly = true, @@ -3481,7 +3481,7 @@ local weapons = { }, { -- energy war axe - itemid = 805, + itemId = 805, type = WEAPON_AXE, level = 65, unproperly = true, @@ -3493,7 +3493,7 @@ local weapons = { }, { -- energy headchopper - itemid = 804, + itemId = 804, type = WEAPON_AXE, level = 35, unproperly = true, @@ -3505,7 +3505,7 @@ local weapons = { }, { -- energy heroic axe - itemid = 803, + itemId = 803, type = WEAPON_AXE, level = 60, unproperly = true, @@ -3513,7 +3513,7 @@ local weapons = { }, { -- energy knight axe - itemid = 802, + itemId = 802, type = WEAPON_AXE, level = 25, unproperly = true, @@ -3521,7 +3521,7 @@ local weapons = { }, { -- energy barbarian axe - itemid = 801, + itemId = 801, type = WEAPON_AXE, level = 20, unproperly = true, @@ -3529,7 +3529,7 @@ local weapons = { }, { -- energy dragon slayer - itemid = 798, + itemId = 798, type = WEAPON_SWORD, level = 45, unproperly = true, @@ -3541,7 +3541,7 @@ local weapons = { }, { -- energy blacksteel sword - itemid = 797, + itemId = 797, type = WEAPON_SWORD, level = 35, unproperly = true, @@ -3553,7 +3553,7 @@ local weapons = { }, { -- energy mystic blade - itemid = 796, + itemId = 796, type = WEAPON_SWORD, level = 60, unproperly = true, @@ -3561,7 +3561,7 @@ local weapons = { }, { -- energy relic sword - itemid = 795, + itemId = 795, type = WEAPON_SWORD, level = 50, unproperly = true, @@ -3569,13 +3569,13 @@ local weapons = { }, { -- energy spike sword - itemid = 794, + itemId = 794, type = WEAPON_SWORD, action = "removecharge", }, { -- earth war hammer - itemid = 793, + itemId = 793, type = WEAPON_CLUB, level = 50, unproperly = true, @@ -3587,7 +3587,7 @@ local weapons = { }, { -- earth orcish maul - itemid = 792, + itemId = 792, type = WEAPON_CLUB, level = 35, unproperly = true, @@ -3595,7 +3595,7 @@ local weapons = { }, { -- earth cranial basher - itemid = 791, + itemId = 791, type = WEAPON_CLUB, level = 60, unproperly = true, @@ -3603,7 +3603,7 @@ local weapons = { }, { -- earth crystal mace - itemid = 790, + itemId = 790, type = WEAPON_CLUB, level = 35, unproperly = true, @@ -3611,7 +3611,7 @@ local weapons = { }, { -- earth clerical mace - itemid = 789, + itemId = 789, type = WEAPON_CLUB, level = 20, unproperly = true, @@ -3619,7 +3619,7 @@ local weapons = { }, { -- earth war axe - itemid = 788, + itemId = 788, type = WEAPON_AXE, level = 65, unproperly = true, @@ -3631,7 +3631,7 @@ local weapons = { }, { -- earth headchopper - itemid = 787, + itemId = 787, type = WEAPON_AXE, level = 35, unproperly = true, @@ -3643,7 +3643,7 @@ local weapons = { }, { -- earth heroic axe - itemid = 786, + itemId = 786, type = WEAPON_AXE, level = 60, unproperly = true, @@ -3651,7 +3651,7 @@ local weapons = { }, { -- earth knight axe - itemid = 785, + itemId = 785, type = WEAPON_AXE, level = 25, unproperly = true, @@ -3659,7 +3659,7 @@ local weapons = { }, { -- earth barbarian axe - itemid = 784, + itemId = 784, type = WEAPON_AXE, level = 20, unproperly = true, @@ -3667,7 +3667,7 @@ local weapons = { }, { -- earth dragon slayer - itemid = 783, + itemId = 783, type = WEAPON_SWORD, level = 45, unproperly = true, @@ -3679,7 +3679,7 @@ local weapons = { }, { -- earth blacksteel sword - itemid = 782, + itemId = 782, type = WEAPON_SWORD, level = 35, unproperly = true, @@ -3691,7 +3691,7 @@ local weapons = { }, { -- earth mystic blade - itemid = 781, + itemId = 781, type = WEAPON_SWORD, level = 60, unproperly = true, @@ -3699,7 +3699,7 @@ local weapons = { }, { -- earth relic sword - itemid = 780, + itemId = 780, type = WEAPON_SWORD, level = 50, unproperly = true, @@ -3707,7 +3707,7 @@ local weapons = { }, { -- earth spike sword - itemid = 779, + itemId = 779, type = WEAPON_SWORD, action = "removecharge", }, @@ -3745,7 +3745,7 @@ local weapons = { }, { -- icy war hammer - itemid = 693, + itemId = 693, type = WEAPON_CLUB, level = 50, unproperly = true, @@ -3757,7 +3757,7 @@ local weapons = { }, { -- icy orcish maul - itemid = 692, + itemId = 692, type = WEAPON_CLUB, level = 35, unproperly = true, @@ -3765,7 +3765,7 @@ local weapons = { }, { -- icy cranial basher - itemid = 691, + itemId = 691, type = WEAPON_CLUB, level = 60, unproperly = true, @@ -3773,7 +3773,7 @@ local weapons = { }, { -- icy crystal mace - itemid = 690, + itemId = 690, type = WEAPON_CLUB, level = 35, unproperly = true, @@ -3781,7 +3781,7 @@ local weapons = { }, { -- icy clerical mace - itemid = 689, + itemId = 689, type = WEAPON_CLUB, level = 20, unproperly = true, @@ -3789,7 +3789,7 @@ local weapons = { }, { -- icy war axe - itemid = 688, + itemId = 688, type = WEAPON_AXE, level = 65, unproperly = true, @@ -3801,7 +3801,7 @@ local weapons = { }, { -- icy headchopper - itemid = 687, + itemId = 687, type = WEAPON_AXE, level = 35, unproperly = true, @@ -3813,7 +3813,7 @@ local weapons = { }, { -- icy heroic axe - itemid = 686, + itemId = 686, type = WEAPON_AXE, level = 60, unproperly = true, @@ -3821,7 +3821,7 @@ local weapons = { }, { -- icy knight axe - itemid = 685, + itemId = 685, type = WEAPON_AXE, level = 25, unproperly = true, @@ -3829,7 +3829,7 @@ local weapons = { }, { -- icy barbarian axe - itemid = 684, + itemId = 684, type = WEAPON_AXE, level = 20, unproperly = true, @@ -3837,7 +3837,7 @@ local weapons = { }, { -- icy dragon slayer - itemid = 683, + itemId = 683, type = WEAPON_SWORD, level = 45, unproperly = true, @@ -3849,7 +3849,7 @@ local weapons = { }, { -- icy blacksteel sword - itemid = 682, + itemId = 682, type = WEAPON_SWORD, level = 35, unproperly = true, @@ -3861,7 +3861,7 @@ local weapons = { }, { -- icy mystic blade - itemid = 681, + itemId = 681, type = WEAPON_SWORD, level = 60, unproperly = true, @@ -3869,7 +3869,7 @@ local weapons = { }, { -- icy relic sword - itemid = 680, + itemId = 680, type = WEAPON_SWORD, level = 50, unproperly = true, @@ -3877,13 +3877,13 @@ local weapons = { }, { -- icy spike sword - itemid = 679, + itemId = 679, type = WEAPON_SWORD, action = "removecharge", }, { -- fiery war hammer - itemid = 674, + itemId = 674, type = WEAPON_CLUB, level = 50, unproperly = true, @@ -3895,7 +3895,7 @@ local weapons = { }, { -- fiery orcish maul - itemid = 673, + itemId = 673, type = WEAPON_CLUB, level = 35, unproperly = true, @@ -3903,7 +3903,7 @@ local weapons = { }, { -- fiery cranial basher - itemid = 672, + itemId = 672, type = WEAPON_CLUB, level = 60, unproperly = true, @@ -3911,7 +3911,7 @@ local weapons = { }, { -- fiery crystal mace - itemid = 671, + itemId = 671, type = WEAPON_CLUB, level = 35, unproperly = true, @@ -3919,7 +3919,7 @@ local weapons = { }, { -- fiery clerical mace - itemid = 670, + itemId = 670, type = WEAPON_CLUB, level = 20, unproperly = true, @@ -3927,7 +3927,7 @@ local weapons = { }, { -- fiery war axe - itemid = 669, + itemId = 669, type = WEAPON_AXE, level = 65, unproperly = true, @@ -3939,7 +3939,7 @@ local weapons = { }, { -- fiery headchopper - itemid = 668, + itemId = 668, type = WEAPON_AXE, level = 35, unproperly = true, @@ -3951,7 +3951,7 @@ local weapons = { }, { -- fiery heroic axe - itemid = 667, + itemId = 667, type = WEAPON_AXE, level = 60, unproperly = true, @@ -3959,7 +3959,7 @@ local weapons = { }, { -- fiery knight axe - itemid = 666, + itemId = 666, type = WEAPON_AXE, level = 25, unproperly = true, @@ -3967,7 +3967,7 @@ local weapons = { }, { -- fiery barbarian axe - itemid = 665, + itemId = 665, type = WEAPON_AXE, level = 20, unproperly = true, @@ -3975,7 +3975,7 @@ local weapons = { }, { -- fiery dragon slayer - itemid = 664, + itemId = 664, type = WEAPON_SWORD, level = 45, unproperly = true, @@ -3987,7 +3987,7 @@ local weapons = { }, { -- fiery blacksteel sword - itemid = 663, + itemId = 663, type = WEAPON_SWORD, level = 35, unproperly = true, @@ -3999,7 +3999,7 @@ local weapons = { }, { -- fiery mystic blade - itemid = 662, + itemId = 662, type = WEAPON_SWORD, level = 60, unproperly = true, @@ -4007,7 +4007,7 @@ local weapons = { }, { -- fiery relic sword - itemid = 661, + itemId = 661, type = WEAPON_SWORD, level = 50, unproperly = true, @@ -4015,27 +4015,27 @@ local weapons = { }, { -- fiery spike sword - itemid = 660, + itemId = 660, type = WEAPON_SWORD, action = "removecharge", }, { -- noble axe - itemid = 7456, + itemId = 7456, type = WEAPON_AXE, level = 35, unproperly = true, }, { -- mythril axe - itemid = 7455, + itemId = 7455, type = WEAPON_AXE, level = 80, unproperly = true, }, { -- glorious axe - itemid = 7454, + itemId = 7454, type = WEAPON_AXE, level = 30, unproperly = true, @@ -4046,7 +4046,7 @@ local weapons = { }, { -- executioner - itemid = 7453, + itemId = 7453, type = WEAPON_AXE, level = 85, unproperly = true, @@ -4057,7 +4057,7 @@ local weapons = { }, { -- spiked squelcher - itemid = 7452, + itemId = 7452, type = WEAPON_CLUB, level = 30, unproperly = true, @@ -4068,14 +4068,14 @@ local weapons = { }, { -- shadow sceptre - itemid = 7451, + itemId = 7451, type = WEAPON_CLUB, level = 35, unproperly = true, }, { -- hammer of prophecy - itemid = 7450, + itemId = 7450, type = WEAPON_CLUB, level = 120, unproperly = true, @@ -4086,7 +4086,7 @@ local weapons = { }, { -- crystal sword - itemid = 7449, + itemId = 7449, type = WEAPON_SWORD, level = 25, unproperly = true, @@ -4097,19 +4097,19 @@ local weapons = { }, { -- elvish bow - itemid = 7438, + itemId = 7438, type = WEAPON_DISTANCE, }, { -- sapphire hammer - itemid = 7437, + itemId = 7437, type = WEAPON_CLUB, level = 30, unproperly = true, }, { -- angelic axe - itemid = 7436, + itemId = 7436, type = WEAPON_AXE, level = 45, unproperly = true, @@ -4120,56 +4120,56 @@ local weapons = { }, { -- impaler - itemid = 7435, + itemId = 7435, type = WEAPON_AXE, level = 85, unproperly = true, }, { -- royal axe - itemid = 7434, + itemId = 7434, type = WEAPON_AXE, level = 75, unproperly = true, }, { -- ravenwing - itemid = 7433, + itemId = 7433, type = WEAPON_AXE, level = 65, unproperly = true, }, { -- furry club - itemid = 7432, + itemId = 7432, type = WEAPON_CLUB, level = 20, unproperly = true, }, { -- demonbone - itemid = 7431, + itemId = 7431, type = WEAPON_CLUB, level = 80, unproperly = true, }, { -- dragonbone staff - itemid = 7430, + itemId = 7430, type = WEAPON_CLUB, level = 30, unproperly = true, }, { -- blessed sceptre - itemid = 7429, + itemId = 7429, type = WEAPON_CLUB, level = 75, unproperly = true, }, { -- bonebreaker - itemid = 7428, + itemId = 7428, type = WEAPON_CLUB, level = 55, unproperly = true, @@ -4180,35 +4180,35 @@ local weapons = { }, { -- chaos mace - itemid = 7427, + itemId = 7427, type = WEAPON_CLUB, level = 45, unproperly = true, }, { -- amber staff - itemid = 7426, + itemId = 7426, type = WEAPON_CLUB, level = 40, unproperly = true, }, { -- taurus mace - itemid = 7425, + itemId = 7425, type = WEAPON_CLUB, level = 20, unproperly = true, }, { -- lunar staff - itemid = 7424, + itemId = 7424, type = WEAPON_CLUB, level = 30, unproperly = true, }, { -- skullcrusher - itemid = 7423, + itemId = 7423, type = WEAPON_CLUB, level = 85, unproperly = true, @@ -4219,63 +4219,63 @@ local weapons = { }, { -- jade hammer - itemid = 7422, + itemId = 7422, type = WEAPON_CLUB, level = 70, unproperly = true, }, { -- onyx flail - itemid = 7421, + itemId = 7421, type = WEAPON_CLUB, level = 65, unproperly = true, }, { -- reaper's axe - itemid = 7420, + itemId = 7420, type = WEAPON_AXE, level = 70, unproperly = true, }, { -- dreaded cleaver - itemid = 7419, + itemId = 7419, type = WEAPON_AXE, level = 40, unproperly = true, }, { -- nightmare blade - itemid = 7418, + itemId = 7418, type = WEAPON_SWORD, level = 70, unproperly = true, }, { -- runed sword - itemid = 7417, + itemId = 7417, type = WEAPON_SWORD, level = 65, unproperly = true, }, { -- bloody edge - itemid = 7416, + itemId = 7416, type = WEAPON_SWORD, level = 55, unproperly = true, }, { -- cranial basher - itemid = 7415, + itemId = 7415, type = WEAPON_CLUB, level = 60, unproperly = true, }, { -- abyss hammer - itemid = 7414, + itemId = 7414, type = WEAPON_CLUB, level = 60, unproperly = true, @@ -4286,7 +4286,7 @@ local weapons = { }, { -- titan axe - itemid = 7413, + itemId = 7413, type = WEAPON_AXE, level = 40, unproperly = true, @@ -4297,42 +4297,42 @@ local weapons = { }, { -- butcher's axe - itemid = 7412, + itemId = 7412, type = WEAPON_AXE, level = 45, unproperly = true, }, { -- ornamented axe - itemid = 7411, + itemId = 7411, type = WEAPON_AXE, level = 50, unproperly = true, }, { -- queen's sceptre - itemid = 7410, + itemId = 7410, type = WEAPON_CLUB, level = 55, unproperly = true, }, { -- northern star - itemid = 7409, + itemId = 7409, type = WEAPON_CLUB, level = 50, unproperly = true, }, { -- wyvern fang - itemid = 7408, + itemId = 7408, type = WEAPON_SWORD, level = 25, unproperly = true, }, { -- haunted blade - itemid = 7407, + itemId = 7407, type = WEAPON_SWORD, level = 30, unproperly = true, @@ -4343,7 +4343,7 @@ local weapons = { }, { -- blacksteel sword - itemid = 7406, + itemId = 7406, type = WEAPON_SWORD, level = 35, unproperly = true, @@ -4354,7 +4354,7 @@ local weapons = { }, { -- havoc blade - itemid = 7405, + itemId = 7405, type = WEAPON_SWORD, level = 70, unproperly = true, @@ -4365,14 +4365,14 @@ local weapons = { }, { -- assassin dagger - itemid = 7404, + itemId = 7404, type = WEAPON_SWORD, level = 40, unproperly = true, }, { -- berserker - itemid = 7403, + itemId = 7403, type = WEAPON_SWORD, level = 65, unproperly = true, @@ -4383,7 +4383,7 @@ local weapons = { }, { -- dragon slayer - itemid = 7402, + itemId = 7402, type = WEAPON_SWORD, level = 45, unproperly = true, @@ -4394,14 +4394,14 @@ local weapons = { }, { -- orcish maul - itemid = 7392, + itemId = 7392, type = WEAPON_CLUB, level = 35, unproperly = true, }, { -- thaian sword - itemid = 7391, + itemId = 7391, type = WEAPON_SWORD, level = 50, unproperly = true, @@ -4412,35 +4412,35 @@ local weapons = { }, { -- the justice seeker - itemid = 7390, + itemId = 7390, type = WEAPON_SWORD, level = 75, unproperly = true, }, { -- heroic axe - itemid = 7389, + itemId = 7389, type = WEAPON_AXE, level = 60, unproperly = true, }, { -- vile axe - itemid = 7388, + itemId = 7388, type = WEAPON_AXE, level = 55, unproperly = true, }, { -- diamond sceptre - itemid = 7387, + itemId = 7387, type = WEAPON_CLUB, level = 25, unproperly = true, }, { -- mercenary sword - itemid = 7386, + itemId = 7386, type = WEAPON_SWORD, level = 40, unproperly = true, @@ -4451,28 +4451,28 @@ local weapons = { }, { -- crimson sword - itemid = 7385, + itemId = 7385, type = WEAPON_SWORD, level = 20, unproperly = true, }, { -- mystic blade - itemid = 7384, + itemId = 7384, type = WEAPON_SWORD, level = 60, unproperly = true, }, { -- relic sword - itemid = 7383, + itemId = 7383, type = WEAPON_SWORD, level = 50, unproperly = true, }, { -- demonrage sword - itemid = 7382, + itemId = 7382, type = WEAPON_SWORD, level = 60, unproperly = true, @@ -4483,14 +4483,14 @@ local weapons = { }, { -- mammoth whopper - itemid = 7381, + itemId = 7381, type = WEAPON_CLUB, level = 20, unproperly = true, }, { -- headchopper - itemid = 7380, + itemId = 7380, type = WEAPON_AXE, level = 35, unproperly = true, @@ -4501,7 +4501,7 @@ local weapons = { }, { -- brutetamer's staff - itemid = 7379, + itemId = 7379, type = WEAPON_CLUB, level = 25, unproperly = true, @@ -4548,7 +4548,7 @@ local weapons = { }, { -- piercing bolt - itemid = 7363, + itemId = 7363, type = WEAPON_AMMO, level = 30, unproperly = true, @@ -4556,7 +4556,7 @@ local weapons = { }, { -- ruthless axe - itemid = 6553, + itemId = 6553, type = WEAPON_AXE, level = 75, unproperly = true, @@ -4567,7 +4567,7 @@ local weapons = { }, { -- infernal bolt - itemid = 6528, + itemId = 6528, type = WEAPON_AMMO, level = 110, unproperly = true, @@ -4575,7 +4575,7 @@ local weapons = { }, { -- the avenger - itemid = 6527, + itemId = 6527, type = WEAPON_SWORD, level = 75, unproperly = true, @@ -4586,12 +4586,12 @@ local weapons = { }, { -- Ron the Ripper's sabre - itemid = 6101, + itemId = 6101, type = WEAPON_SWORD, }, { -- arbalest - itemid = 5803, + itemId = 5803, type = WEAPON_DISTANCE, level = 75, unproperly = true, @@ -4602,79 +4602,79 @@ local weapons = { }, { -- banana staff - itemid = 3348, + itemId = 3348, type = WEAPON_CLUB, }, - { - -- hunting spear - itemid = 3347, - type = WEAPON_MISSILE, - level = 20, - unproperly = true, - breakchance = 6, - }, + -- { + -- -- hunting spear + -- itemId = 3347, + -- type = WEAPON_MISSILE, + -- level = 20, + -- unproperly = true, + -- breakchance = 6 + -- }, { -- ripper lance - itemid = 3346, + itemId = 3346, type = WEAPON_AXE, }, { -- templar scytheblade - itemid = 3345, + itemId = 3345, type = WEAPON_SWORD, }, { -- beastslayer axe - itemid = 3344, + itemId = 3344, type = WEAPON_AXE, level = 30, unproperly = true, }, { -- lich staff - itemid = 3343, + itemId = 3343, type = WEAPON_CLUB, level = 40, unproperly = true, }, { -- scythe - itemid = 3453, + itemId = 3453, type = WEAPON_CLUB, }, { -- power bolt - itemid = 3450, + itemId = 3450, type = WEAPON_AMMO, level = 55, unproperly = true, action = "removecount", }, - { - -- arrow - itemid = 3447, - type = WEAPON_AMMO, - action = "removecount", - }, + -- { + -- -- -- arrow + -- -- itemId = 3447, + -- -- type = WEAPON_AMMO, + -- -- -- action = "removecount" + -- }, { -- bolt - itemid = 3446, + itemId = 3446, type = WEAPON_AMMO, - action = "removecount", + -- action = "removecount" }, { -- bow - itemid = 3350, + itemId = 3350, type = WEAPON_DISTANCE, }, { -- crossbow - itemid = 3349, + itemId = 3349, type = WEAPON_DISTANCE, }, { -- war axe - itemid = 3342, + itemId = 3342, type = WEAPON_AXE, level = 65, unproperly = true, @@ -4685,43 +4685,43 @@ local weapons = { }, { -- arcane staff - itemid = 3341, + itemId = 3341, type = WEAPON_CLUB, level = 75, unproperly = true, }, { -- heavy mace - itemid = 3340, + itemId = 3340, type = WEAPON_CLUB, level = 70, unproperly = true, }, { -- djinn blade - itemid = 3339, + itemId = 3339, type = WEAPON_SWORD, level = 35, unproperly = true, }, { -- bone sword - itemid = 3338, + itemId = 3338, type = WEAPON_SWORD, }, { -- bone club - itemid = 3337, + itemId = 3337, type = WEAPON_CLUB, }, { -- studded club - itemid = 3336, + itemId = 3336, type = WEAPON_CLUB, }, { -- twin axe - itemid = 3335, + itemId = 3335, type = WEAPON_AXE, level = 50, unproperly = true, @@ -4732,21 +4732,21 @@ local weapons = { }, { -- pharaoh sword - itemid = 3334, + itemId = 3334, type = WEAPON_SWORD, level = 45, unproperly = true, }, { -- crystal mace - itemid = 3333, + itemId = 3333, type = WEAPON_CLUB, level = 35, unproperly = true, }, { -- hammer of wrath - itemid = 3332, + itemId = 3332, type = WEAPON_CLUB, level = 65, unproperly = true, @@ -4757,24 +4757,24 @@ local weapons = { }, { -- ravager's axe - itemid = 3331, + itemId = 3331, type = WEAPON_AXE, level = 70, unproperly = true, }, { -- heavy machete - itemid = 3330, + itemId = 3330, type = WEAPON_SWORD, }, { -- daramian axe - itemid = 3329, + itemId = 3329, type = WEAPON_AXE, }, { -- daramian waraxe - itemid = 3328, + itemId = 3328, type = WEAPON_AXE, level = 25, unproperly = true, @@ -4785,155 +4785,155 @@ local weapons = { }, { -- daramian mace - itemid = 3327, + itemId = 3327, type = WEAPON_CLUB, }, { -- epee - itemid = 3326, + itemId = 3326, type = WEAPON_SWORD, level = 30, unproperly = true, }, { -- light mace - itemid = 3325, + itemId = 3325, type = WEAPON_CLUB, }, { -- skull staff - itemid = 3324, + itemId = 3324, type = WEAPON_CLUB, level = 30, unproperly = true, }, { -- dwarven axe - itemid = 3323, + itemId = 3323, type = WEAPON_AXE, level = 20, unproperly = true, }, { -- dragon hammer - itemid = 3322, + itemId = 3322, type = WEAPON_CLUB, level = 25, unproperly = true, }, { -- enchanted staff - itemid = 3321, + itemId = 3321, type = WEAPON_CLUB, }, { -- fire axe - itemid = 3320, + itemId = 3320, type = WEAPON_AXE, level = 35, unproperly = true, }, { -- stonecutter axe - itemid = 3319, + itemId = 3319, type = WEAPON_AXE, level = 90, unproperly = true, }, { -- knight axe - itemid = 3318, + itemId = 3318, type = WEAPON_AXE, level = 25, unproperly = true, }, { -- barbarian axe - itemid = 3317, + itemId = 3317, type = WEAPON_AXE, level = 20, unproperly = true, }, { -- orcish axe - itemid = 3316, + itemId = 3316, type = WEAPON_AXE, }, { -- guardian halberd - itemid = 3315, + itemId = 3315, type = WEAPON_AXE, level = 55, unproperly = true, }, { -- naginata - itemid = 3314, + itemId = 3314, type = WEAPON_AXE, level = 25, unproperly = true, }, { -- obsidian lance - itemid = 3313, + itemId = 3313, type = WEAPON_AXE, level = 20, unproperly = true, }, { -- silver mace - itemid = 3312, + itemId = 3312, type = WEAPON_CLUB, level = 45, unproperly = true, }, { -- clerical mace - itemid = 3311, + itemId = 3311, type = WEAPON_CLUB, level = 20, unproperly = true, }, { -- iron hammer - itemid = 3310, + itemId = 3310, type = WEAPON_CLUB, }, { -- thunder hammer - itemid = 3309, + itemId = 3309, type = WEAPON_CLUB, level = 85, unproperly = true, }, { -- machete - itemid = 3308, + itemId = 3308, type = WEAPON_SWORD, }, { -- scimitar - itemid = 3307, + itemId = 3307, type = WEAPON_SWORD, }, { -- golden sickle - itemid = 3306, + itemId = 3306, type = WEAPON_AXE, }, { -- battle hammer - itemid = 3305, + itemId = 3305, type = WEAPON_CLUB, }, { -- crowbar - itemid = 3304, + itemId = 3304, type = WEAPON_CLUB, }, { -- great axe - itemid = 3303, + itemId = 3303, type = WEAPON_AXE, level = 95, unproperly = true, @@ -4944,14 +4944,14 @@ local weapons = { }, { -- dragon lance - itemid = 3302, + itemId = 3302, type = WEAPON_AXE, level = 60, unproperly = true, }, { -- broadsword - itemid = 3301, + itemId = 3301, type = WEAPON_SWORD, vocation = { { "Knight", true }, @@ -4960,7 +4960,7 @@ local weapons = { }, { -- katana - itemid = 3300, + itemId = 3300, type = WEAPON_SWORD, }, { @@ -4976,12 +4976,12 @@ local weapons = { }, { -- serpent sword - itemid = 3297, + itemId = 3297, type = WEAPON_SWORD, }, { -- warlord sword - itemid = 3296, + itemId = 3296, type = WEAPON_SWORD, level = 120, unproperly = true, @@ -4992,83 +4992,81 @@ local weapons = { }, { -- bright sword - itemid = 3295, + itemId = 3295, type = WEAPON_SWORD, - level = 30, - unproperly = true, }, { -- short sword - itemid = 3294, + itemId = 3294, type = WEAPON_SWORD, }, { -- sickle - itemid = 3293, + itemId = 3293, type = WEAPON_AXE, }, { -- combat knife - itemid = 3292, + itemId = 3292, type = WEAPON_SWORD, }, { -- knife - itemid = 3291, + itemId = 3291, type = WEAPON_SWORD, }, { -- silver dagger - itemid = 3290, + itemId = 3290, type = WEAPON_SWORD, }, { -- staff - itemid = 3289, + itemId = 3289, type = WEAPON_CLUB, }, { -- magic sword - itemid = 3288, + itemId = 3288, type = WEAPON_SWORD, level = 80, unproperly = true, }, - { - -- throwing star - itemid = 3287, - type = WEAPON_MISSILE, - breakchance = 10, - }, + -- { + -- -- throwing star + -- itemId = 3287, + -- type = WEAPON_MISSILE, + -- breakchance = 10 + -- }, { -- mace - itemid = 3286, + itemId = 3286, type = WEAPON_CLUB, }, { -- longsword - itemid = 3285, + itemId = 3285, type = WEAPON_SWORD, }, { -- ice rapier - itemid = 3284, + itemId = 3284, type = WEAPON_SWORD, action = "removecharge", }, { -- carlin sword - itemid = 3283, + itemId = 3283, type = WEAPON_SWORD, }, { -- morning star - itemid = 3282, + itemId = 3282, type = WEAPON_CLUB, }, { -- giant sword - itemid = 3281, + itemId = 3281, type = WEAPON_SWORD, level = 55, unproperly = true, @@ -5079,14 +5077,14 @@ local weapons = { }, { -- fire sword - itemid = 3280, + itemId = 3280, type = WEAPON_SWORD, level = 30, unproperly = true, }, { -- war hammer - itemid = 3279, + itemId = 3279, type = WEAPON_CLUB, level = 50, unproperly = true, @@ -5097,7 +5095,7 @@ local weapons = { }, { -- magic longsword - itemid = 3278, + itemId = 3278, type = WEAPON_SWORD, level = 140, unproperly = true, @@ -5106,20 +5104,20 @@ local weapons = { { "Elite Knight" }, }, }, - { - -- spear - itemid = 3277, - type = WEAPON_MISSILE, - breakchance = 3, - }, + -- { + -- -- spear + -- itemId = 3277, + -- type = WEAPON_MISSILE, + -- -- breakchance = 3 + -- }, { -- hatchet - itemid = 3276, + itemId = 3276, type = WEAPON_AXE, }, { -- double axe - itemid = 3275, + itemId = 3275, type = WEAPON_AXE, level = 25, unproperly = true, @@ -5130,49 +5128,49 @@ local weapons = { }, { -- axe - itemid = 3274, + itemId = 3274, type = WEAPON_AXE, }, { -- sabre - itemid = 3273, + itemId = 3273, type = WEAPON_SWORD, }, { -- rapier - itemid = 3272, + itemId = 3272, type = WEAPON_SWORD, }, { -- spike sword - itemid = 3271, + itemId = 3271, type = WEAPON_SWORD, }, { -- club - itemid = 3270, + itemId = 3270, type = WEAPON_CLUB, }, { -- halberd - itemid = 3269, + itemId = 3269, type = WEAPON_AXE, level = 25, unproperly = true, }, { -- hand axe - itemid = 3268, + itemId = 3268, type = WEAPON_AXE, }, { -- dagger - itemid = 3267, + itemId = 3267, type = WEAPON_SWORD, }, { -- battle axe - itemid = 3266, + itemId = 3266, type = WEAPON_AXE, unproperly = true, vocation = { @@ -5182,7 +5180,7 @@ local weapons = { }, { -- two handed sword - itemid = 3265, + itemId = 3265, type = WEAPON_SWORD, level = 20, unproperly = true, @@ -5193,17 +5191,17 @@ local weapons = { }, { -- sword - itemid = 3264, + itemId = 3264, type = WEAPON_SWORD, }, { -- giant smithhammer - itemid = 3208, + itemId = 3208, type = WEAPON_CLUB, }, { -- wand of dragonbreath - itemid = 3075, + itemId = 3075, type = WEAPON_WAND, wandType = "fire", level = 13, @@ -5216,7 +5214,7 @@ local weapons = { }, { -- wand of vortex - itemid = 3074, + itemId = 3074, type = WEAPON_WAND, wandType = "energy", level = 6, @@ -5229,7 +5227,7 @@ local weapons = { }, { -- wand of cosmic energy - itemid = 3073, + itemId = 3073, type = WEAPON_WAND, wandType = "energy", level = 26, @@ -5242,7 +5240,7 @@ local weapons = { }, { -- wand of decay - itemid = 3072, + itemId = 3072, type = WEAPON_WAND, wandType = "death", level = 19, @@ -5255,7 +5253,7 @@ local weapons = { }, { -- wand of inferno - itemid = 3071, + itemId = 3071, type = WEAPON_WAND, wandType = "fire", level = 33, @@ -5268,7 +5266,7 @@ local weapons = { }, { -- moonlight rod - itemid = 3070, + itemId = 3070, type = WEAPON_WAND, wandType = "ice", level = 13, @@ -5281,7 +5279,7 @@ local weapons = { }, { -- necrotic rod - itemid = 3069, + itemId = 3069, type = WEAPON_WAND, wandType = "death", level = 19, @@ -5294,7 +5292,7 @@ local weapons = { }, { -- hailstorm rod - itemid = 3067, + itemId = 3067, type = WEAPON_WAND, wandType = "ice", level = 33, @@ -5307,7 +5305,7 @@ local weapons = { }, { -- snakebit rod - itemid = 3066, + itemId = 3066, type = WEAPON_WAND, wandType = "earth", level = 6, @@ -5320,7 +5318,7 @@ local weapons = { }, { -- terra rod - itemid = 3065, + itemId = 3065, type = WEAPON_WAND, wandType = "earth", level = 26, diff --git a/data/XML/imbuements.xml b/data/XML/imbuements.xml index 867bab66f52..74a06dbd9f8 100644 --- a/data/XML/imbuements.xml +++ b/data/XML/imbuements.xml @@ -155,18 +155,18 @@ - + - + - + diff --git a/data/XML/vocations.xml b/data/XML/vocations.xml index fd5ae77a31e..8cbf8e4ecd8 100644 --- a/data/XML/vocations.xml +++ b/data/XML/vocations.xml @@ -12,7 +12,7 @@ - + @@ -24,7 +24,7 @@ - + @@ -36,7 +36,7 @@ - + @@ -48,7 +48,7 @@ - + @@ -60,7 +60,7 @@ - + @@ -71,8 +71,11 @@ + + + - + @@ -83,8 +86,11 @@ + + + - + @@ -95,8 +101,11 @@ + + + - + @@ -107,5 +116,8 @@ + + + diff --git a/data/global.lua b/data/global.lua index 3508d7fbd44..2d5ffc31cf0 100644 --- a/data/global.lua +++ b/data/global.lua @@ -202,7 +202,7 @@ function addStamina(playerId, ...) local regen = configManager.getNumber(configKeys.STAMINA_PZ_GAIN) player:setStamina(player:getStamina() + regen) - player:sendTextMessage(MESSAGE_STATUS, string.format("%i minute%s of stamina has been refilled.", regen, regen == 1 and "" or "s")) + player:sendTextMessage(MESSAGE_FAILURE, string.format("%i minute%s of stamina has been refilled.", regen, regen == 1 and "" or "s")) staminaBonus.eventsPz[localPlayerId] = addEvent(addStamina, delay, nil, localPlayerId, delay) return true end diff --git a/data/items/appearances.dat b/data/items/appearances.dat index 9f0de0ece0e..0c609c42b9c 100644 Binary files a/data/items/appearances.dat and b/data/items/appearances.dat differ diff --git a/data/items/items.xml b/data/items/items.xml index abbe27c060c..c57bafaf745 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -12136,21 +12136,18 @@ - - - @@ -12158,7 +12155,6 @@ - @@ -12166,7 +12162,6 @@ - @@ -12215,7 +12210,6 @@ - @@ -15581,8 +15575,8 @@ - - + + @@ -21338,22 +21332,22 @@ - + - + - + - + @@ -24998,7 +24992,10 @@ - + + + + @@ -36642,8 +36639,8 @@ - - + + @@ -37181,10 +37178,10 @@ - + - + @@ -37360,18 +37357,18 @@ - + - + - - + + - - + + @@ -37425,18 +37422,18 @@ - + - + - - + + - - + + @@ -38282,7 +38279,6 @@ - @@ -38735,19 +38731,19 @@ - + - + - + - + @@ -38802,7 +38798,7 @@ - + @@ -40182,7 +40178,7 @@ - + @@ -40633,7 +40629,7 @@ - + @@ -41020,7 +41016,7 @@ - + @@ -44514,8 +44510,8 @@ - - + + @@ -49514,8 +49510,8 @@ - - + + @@ -51966,8 +51962,8 @@ - - + + @@ -55309,8 +55305,8 @@ - - + + @@ -55351,7 +55347,7 @@ - + @@ -58332,8 +58328,8 @@ - - + + @@ -58370,8 +58366,8 @@ - - + + @@ -58408,8 +58404,8 @@ - - + + @@ -58427,8 +58423,8 @@ - - + + @@ -58449,8 +58445,8 @@ - - + + @@ -58469,8 +58465,8 @@ - - + + @@ -58818,8 +58814,8 @@ - - + + @@ -60214,7 +60210,7 @@ - + @@ -61664,7 +61660,7 @@ - + @@ -61683,7 +61679,7 @@ - + @@ -65139,6 +65135,9 @@ + + + @@ -65333,7 +65332,7 @@ - + @@ -65366,7 +65365,7 @@ - + @@ -67594,6 +67593,11 @@ + + + + + @@ -68532,6 +68536,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -68544,6 +68615,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -68645,8 +68883,8 @@ - - + + @@ -68663,8 +68901,8 @@ - - + + @@ -68681,8 +68919,8 @@ - - + + @@ -68699,8 +68937,8 @@ - - + + @@ -68717,8 +68955,8 @@ - - + + @@ -68735,8 +68973,8 @@ - - + + @@ -68762,8 +69000,8 @@ - - + + @@ -68783,8 +69021,8 @@ - - + + @@ -68804,8 +69042,8 @@ - - + + @@ -68825,8 +69063,8 @@ - - + + @@ -68859,8 +69097,8 @@ - - + + @@ -68876,8 +69114,8 @@ - - + + @@ -68906,8 +69144,8 @@ - - + + @@ -68923,8 +69161,8 @@ - - + + @@ -68949,6 +69187,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -68973,4 +69249,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/libs/features_lib.lua b/data/libs/features_lib.lua new file mode 100644 index 00000000000..3b8f283e89a --- /dev/null +++ b/data/libs/features_lib.lua @@ -0,0 +1,36 @@ +Features = { + AutoLoot = "autoloot", +} + +local function validateFeature(feature) + local found = false + for _, v in pairs(Features) do + if v == feature then + found = true + end + end + if not found then + error("Invalid feature: " .. feature) + end +end + +function Player:hasFeature(feature) + validateFeature(feature) + local kv = self:kv():scoped("features") + if kv:get(feature) then + return true + end + return false +end + +function Player:getFeature(feature) + validateFeature(feature) + local kv = self:kv():scoped("features") + return kv:get(feature) +end + +function Player:setFeature(feature, value) + validateFeature(feature) + local kv = self:kv():scoped("features") + kv:set(feature, value) +end diff --git a/data/libs/forge_lib.lua b/data/libs/forge_lib.lua index e232c60fe2d..986d9bf4a92 100644 --- a/data/libs/forge_lib.lua +++ b/data/libs/forge_lib.lua @@ -45,7 +45,7 @@ function ForgeMonster:onDeath(creature, corpse, killer, mostDamageKiller, unjust return true end - local forgeAmountMultiplier = (configManager.getNumber(configKeys.FORGE_AMOUNT_MULTIPLIER) or 3) + local forgeAmountMultiplier = (configManager.getFloat(configKeys.FORGE_AMOUNT_MULTIPLIER) or 3) local stack = creature:getForgeStack() if stack > 0 then diff --git a/data/libs/functions/gematelier.lua b/data/libs/functions/gematelier.lua new file mode 100644 index 00000000000..23b3f8b8f73 --- /dev/null +++ b/data/libs/functions/gematelier.lua @@ -0,0 +1,91 @@ +local config = { + lesser = { + names = { + "lesser guardian gem", + "lesser marksman gem", + "lesser sage gem", + "lesser mystic gem", + }, + chance = { + influenced = 9000, + fiendish = 3000, + archfoe = 0, + }, + maxCount = 2, + }, + regular = { + names = { + "guardian gem", + "marksman gem", + "sage gem", + "mystic gem", + }, + chance = { + influenced = 0, + fiendish = 3000, + archfoe = 9000, + }, + maxCount = 2, + }, + greater = { + names = { + "greater guardian gem", + "greater marksman gem", + "greater sage gem", + "greater mystic gem", + }, + chance = { + influenced = 0, + fiendish = 9000, + archfoe = 3000, + }, + maxCount = 1, + }, +} + +function Monster:generateGemAtelierLoot() + local mType = self:getType() + if not mType then + return {} + end + local category = "none" + local forgeClassification = self:getMonsterForgeClassification() + if forgeClassification == FORGE_INFLUENCED_MONSTER then + category = "influenced" + elseif forgeClassification == FORGE_FIENDISH_MONSTER then + category = "fiendish" + elseif (mType:bossRace() or ""):lower() == "archfoe" then + category = "archfoe" + end + if category == "none" then + return {} + end + + local loot = {} + for _, gemConfig in pairs(config) do + local chance = gemConfig.chance[category] or 0 + local names = gemConfig.names + local maxCount = gemConfig.maxCount + if chance > 0 then + for i = 1, maxCount do + local roll = math.random(1, 100000) + if roll > chance then + goto continue + end + + local name = names[math.random(1, #names)] + local itemType = ItemType(name) + if not itemType then + goto continue + end + if loot[itemType:getId()] then + loot[itemType:getId()].count = loot[itemType:getId()].count + 1 + else + loot[itemType:getId()] = { count = 1 } + end + end + end + ::continue:: + end + return loot +end diff --git a/data/libs/functions/load.lua b/data/libs/functions/load.lua index bedbbe84fe1..ef939a35fa7 100644 --- a/data/libs/functions/load.lua +++ b/data/libs/functions/load.lua @@ -5,6 +5,7 @@ dofile(CORE_DIRECTORY .. "/libs/functions/constants.lua") dofile(CORE_DIRECTORY .. "/libs/functions/container.lua") dofile(CORE_DIRECTORY .. "/libs/functions/creature.lua") dofile(CORE_DIRECTORY .. "/libs/functions/functions.lua") +dofile(CORE_DIRECTORY .. "/libs/functions/gematelier.lua") dofile(CORE_DIRECTORY .. "/libs/functions/fs.lua") dofile(CORE_DIRECTORY .. "/libs/functions/game.lua") dofile(CORE_DIRECTORY .. "/libs/functions/item.lua") diff --git a/data/libs/functions/player.lua b/data/libs/functions/player.lua index 94e5f848603..f9940570c1d 100644 --- a/data/libs/functions/player.lua +++ b/data/libs/functions/player.lua @@ -1,10 +1,6 @@ -- Functions from The Forgotten Server local foodCondition = Condition(CONDITION_REGENERATION, CONDITIONID_DEFAULT) -local function firstToUpper(str) - return (str:gsub("^%l", string.upper)) -end - function Player.feed(self, food) local condition = self:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT) if condition then @@ -322,7 +318,7 @@ function Player.getMarriageDescription(thing) if self == thing then descr = descr .. " You are " else - descr = descr .. " " .. firstToUpper(thing:getSubjectPronoun()) .. " " .. thing:getSubjectVerb() .. " " + descr = descr .. " " .. thing:getSubjectPronoun():titleCase() .. " " .. thing:getSubjectVerb() .. " " end descr = descr .. "married to " .. getPlayerNameById(playerSpouse) .. "." end @@ -538,19 +534,17 @@ function Player.updateHazard(self) return true end + self:setHazardSystemPoints(0) for _, zone in pairs(zones) do local hazard = Hazard.getByName(zone:getName()) - if not hazard then - self:setHazardSystemPoints(0) + if hazard then + if self:getParty() then + self:getParty():refreshHazard() + else + self:setHazardSystemPoints(hazard:getPlayerCurrentLevel(self)) + end return true end - - if self:getParty() then - self:getParty():refreshHazard() - else - self:setHazardSystemPoints(hazard:getPlayerCurrentLevel(self)) - end - return true end return true end diff --git a/data/libs/functions/tables.lua b/data/libs/functions/tables.lua index 1c798ecb2e0..13fdb3e271f 100644 --- a/data/libs/functions/tables.lua +++ b/data/libs/functions/tables.lua @@ -102,6 +102,14 @@ function table.unserialize(str) return loadstring("return " .. str)() end +function table.shallowCopy(oldTable) + local newTable = {} + for k, v in pairs(oldTable) do + newTable[k] = v + end + return newTable +end + function pairsByKeys(t, f) local a = {} for n in pairs(t) do diff --git a/data/libs/hazard_lib.lua b/data/libs/hazard_lib.lua index f9338299e79..a1501b2d1fd 100644 --- a/data/libs/hazard_lib.lua +++ b/data/libs/hazard_lib.lua @@ -100,16 +100,7 @@ function Hazard:setPlayerCurrentLevel(player, level) if not zones then return true end - for _, zone in ipairs(zones) do - local hazard = Hazard.getByName(zone:getName()) - if hazard then - if hazard == self then - player:setHazardSystemPoints(level) - else - player:setHazardSystemPoints(0) - end - end - end + player:updateHazard() return true end diff --git a/data/libs/libs.lua b/data/libs/libs.lua index bd6164111de..a3f9a3933e3 100644 --- a/data/libs/libs.lua +++ b/data/libs/libs.lua @@ -33,3 +33,4 @@ dofile(CORE_DIRECTORY .. "/libs/encounters_lib.lua") dofile(CORE_DIRECTORY .. "/libs/raids_lib.lua") dofile(CORE_DIRECTORY .. "/libs/concoctions_lib.lua") dofile(CORE_DIRECTORY .. "/libs/kill_lib.lua") +dofile(CORE_DIRECTORY .. "/libs/features_lib.lua") diff --git a/data/scripts/discord_webhook/discord_webhook.lua b/data/scripts/discord_webhook/discord_webhook.lua index 0aed824b278..f763ed90d10 100644 --- a/data/scripts/discord_webhook/discord_webhook.lua +++ b/data/scripts/discord_webhook/discord_webhook.lua @@ -2,11 +2,15 @@ -- The URL layout is https://discord.com/api/webhooks/:id/:token -- Leave empty if you wish to disable. -announcementChannels = { - ["serverAnnouncements"] = "", -- Used for an announcement channel on your discord - ["raids"] = "", -- Used to isolate raids on your discord - ["player-kills"] = "", -- Self-explaining -} +if not announcementChannels then + announcementChannels = { + ["serverAnnouncements"] = "", -- Used for an announcement channel on your discord + ["raids"] = "", -- Used to isolate raids on your discord + ["player-kills"] = "", -- Self-explaining + ["player-levels"] = "", -- Self-explaining + ["reports"] = "", + } +end --[[ Example of notification (After you do the config): diff --git a/data/scripts/talkactions/gm/broadcast.lua b/data/scripts/talkactions/gm/broadcast.lua index ca8171e3635..021f218ebe3 100644 --- a/data/scripts/talkactions/gm/broadcast.lua +++ b/data/scripts/talkactions/gm/broadcast.lua @@ -1,5 +1,15 @@ local broadcast = TalkAction("/b") +function Broadcast(text, filter) + for _, targetPlayer in ipairs(Game.getPlayers()) do + if filter and not filter(targetPlayer) then + goto continue + end + targetPlayer:sendTextMessage(MESSAGE_ADMINISTRADOR, text) + ::continue:: + end +end + function broadcast.onSay(player, words, param) -- create log logCommand(player, words, param) @@ -11,10 +21,7 @@ function broadcast.onSay(player, words, param) local text = player:getName() .. " broadcasted: " .. param logger.info(text) - Webhook.sendMessage("Broadcast", text, WEBHOOK_COLOR_WARNING, announcementChannels["serverAnnouncements"]) - for _, targetPlayer in ipairs(Game.getPlayers()) do - targetPlayer:sendPrivateMessage(player, param, TALKTYPE_BROADCAST) - end + Broadcast(param) return true end diff --git a/data/scripts/talkactions/god/forge_functions.lua b/data/scripts/talkactions/god/forge_functions.lua index b4194380214..5ed33b41dd3 100644 --- a/data/scripts/talkactions/god/forge_functions.lua +++ b/data/scripts/talkactions/god/forge_functions.lua @@ -267,3 +267,54 @@ end forge:groupType("god") forge:register() + +---------------- // ---------------- +-- Add dust level +local addDustLevel = TalkAction("/adddustlevel") + +function addDustLevel.onSay(player, words, param) + -- create log + logCommand(player, words, param) + + -- Check the first param (player name) exists + if param == "" then + player:sendCancelMessage("Player name param required.") + -- Distro log + logger.error("[addDustLevel.onSay] - Player name param not found.") + return true + end + + local split = param:split(",") + local name = split[1] + + -- Check if player is online + local targetPlayer = Player(name) + if not targetPlayer then + player:sendCancelMessage("Player " .. string.titleCase(name) .. " is not online.") + -- Distro log + logger.error("[addDustLevel.onSay] - Player {} is not online.", string.titleCase(name)) + return true + end + + local dustLevel = nil + if split[2] then + dustLevel = tonumber(split[2]) + end + + -- Check if the dustAmount is valid + if dustLevel <= 0 or dustLevel == nil then + player:sendCancelMessage("Invalid dust level.") + return true + end + + targetPlayer:addForgeDustLevel(dustLevel) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("Successful added %d dust level for the %s player.", dustLevel, targetPlayer:getName())) + targetPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("%s adds %d dust level to your character.", player:getName(), dustLevel)) + -- Distro log + logger.info("{} added {} dust level to {} player.", player:getName(), dustLevel, targetPlayer:getName()) + return true +end + +addDustLevel:separator(" ") +addDustLevel:groupType("god") +addDustLevel:register() diff --git a/data/scripts/talkactions/god/test_send_message.lua b/data/scripts/talkactions/god/test_send_message.lua new file mode 100644 index 00000000000..2db0ebcf3e6 --- /dev/null +++ b/data/scripts/talkactions/god/test_send_message.lua @@ -0,0 +1,25 @@ +local sendMessage = TalkAction("/testmessage") + +function sendMessage.onSay(player, words, param) + -- create log + logCommand(player, words, param) + + if param == "" or param == nil then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Message type is missing, please enter a message type.") + return + end + + local split = param:split(",") + local messageType = tonumber(split[1]) + local textCollor = TEXTCOLOR_WHITE_EXP + if split[2] then + textCollor = tonumber(split[2]) + end + + player:sendTextMessage(messageType, "Testing message type.", player:getPosition(), 500, textCollor) + return true +end + +sendMessage:separator(" ") +sendMessage:groupType("god") +sendMessage:register() diff --git a/data/scripts/talkactions/player/auto_loot.lua b/data/scripts/talkactions/player/auto_loot.lua index a5541d51438..927a5b100a1 100644 --- a/data/scripts/talkactions/player/auto_loot.lua +++ b/data/scripts/talkactions/player/auto_loot.lua @@ -1,6 +1,12 @@ -local autoLoot = TalkAction("!autoloot") +local feature = TalkAction("!autoloot") -function autoLoot.onSay(player, words, param) +local validValues = { + -- "all", + "on", + "off", +} + +function feature.onSay(player, words, param) if not configManager.getBoolean(configKeys.AUTOLOOT) then return true end @@ -8,20 +14,25 @@ function autoLoot.onSay(player, words, param) player:sendCancelMessage("You need to be VIP to use this command!") return true end - if param == "" then - player:sendCancelMessage("You need to specify on/off param.") + if not table.contains(validValues, param) then + local validValuesStr = table.concat(validValues, "/") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Invalid param specified. Usage: !feature [" .. validValuesStr .. "]") return true end - if param == "on" then - player:setStorageValue(STORAGEVALUE_AUTO_LOOT, 1) - player:sendTextMessage(MESSAGE_LOOK, "You have successfully enabled your automatic looting!") + + if param == "all" then + player:setFeature(Features.AutoLoot, 2) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "AutoLoot is now enabled for all kills (including bosses).") + elseif param == "on" then + player:setFeature(Features.AutoLoot, 1) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "AutoLoot is now enabled for all regular kills (no bosses).") elseif param == "off" then - player:setStorageValue(STORAGEVALUE_AUTO_LOOT, -1) - player:sendTextMessage(MESSAGE_LOOK, "You have successfully disabled your automatic looting!") + player:setFeature(Features.AutoLoot, 0) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "AutoLoot is now disabled.") end return true end -autoLoot:separator(" ") -autoLoot:groupType("normal") -autoLoot:register() +feature:separator(" ") +feature:groupType("normal") +feature:register() diff --git a/metrics/prometheus/prometheus.yml b/metrics/prometheus/prometheus.yml index 97a21bc19f2..068e64503d3 100644 --- a/metrics/prometheus/prometheus.yml +++ b/metrics/prometheus/prometheus.yml @@ -1,8 +1,8 @@ --- global: - scrape_interval: 5s - scrape_timeout: 2s - evaluation_interval: 5s + scrape_interval: 30s + scrape_timeout: 25s + evaluation_interval: 30s scrape_configs: - job_name: canary static_configs: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3a771b55dd6..abd35c09c36 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,24 @@ if(MSVC) target_sources(${PROJECT_NAME} PRIVATE ../cmake/canary.rc) endif() +if (UNIX) + + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_options(${PROJECT_NAME}_lib + PRIVATE + -Wall -Wextra -Wpedantic + ) + endif() + + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_options(${PROJECT_NAME} + PRIVATE + -Wall -Wextra -Wpedantic + ) + endif() + +endif (UNIX) + setup_target(${PROJECT_NAME}) set_output_directory(${PROJECT_NAME}) target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_lib) diff --git a/src/canary_server.cpp b/src/canary_server.cpp index a3a27bfdef4..9e13e8b05fd 100644 --- a/src/canary_server.cpp +++ b/src/canary_server.cpp @@ -383,7 +383,7 @@ void CanaryServer::modulesLoadHelper(bool loaded, std::string moduleName) { } void CanaryServer::shutdown() { - inject().shutdown(); g_dispatcher().shutdown(); g_metrics().shutdown(); + inject().shutdown(); } diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp index 31aa4659e06..b3f1e51a739 100644 --- a/src/config/config_definitions.hpp +++ b/src/config/config_definitions.hpp @@ -63,12 +63,15 @@ enum ConfigKey_t : uint16_t { FORGE_AMOUNT_MULTIPLIER, FORGE_BASE_SUCCESS_RATE, FORGE_BONUS_SUCCESS_RATE, + FORGE_FUSION_DUST_COST, + FORGE_CONVERGENCE_FUSION_DUST_COST, + FORGE_TRANSFER_DUST_COST, + FORGE_CONVERGENCE_TRANSFER_DUST_COST, FORGE_CORE_COST, FORGE_COST_ONE_SLIVER, FORGE_FIENDISH_CREATURES_LIMIT, FORGE_FIENDISH_INTERVAL_TIME, FORGE_FIENDISH_INTERVAL_TYPE, - FORGE_FUSION_DUST_COST, FORGE_INFLUENCED_CREATURES_LIMIT, FORGE_MAX_DUST, FORGE_MAX_ITEM_TIER, @@ -76,7 +79,6 @@ enum ConfigKey_t : uint16_t { FORGE_MIN_SLIVERS, FORGE_SLIVER_AMOUNT, FORGE_TIER_LOSS_REDUCTION, - FORGE_TRANSFER_DUST_COST, FRAG_TIME, FREE_DEPOT_LIMIT, FREE_PREMIUM, @@ -146,6 +148,9 @@ enum ConfigKey_t : uint16_t { METRICS_OSTREAM_INTERVAL, METRICS_PROMETHEUS_ADDRESS, MIN_ELEMENTAL_RESISTANCE, + MOMENTUM_CHANCE_FORMULA_A, + MOMENTUM_CHANCE_FORMULA_B, + MOMENTUM_CHANCE_FORMULA_C, MONTH_KILLS_TO_RED, MULTIPLIER_ATTACKONFIST, MYSQL_DB, @@ -158,6 +163,9 @@ enum ConfigKey_t : uint16_t { ONE_PLAYER_ON_ACCOUNT, ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, ONLY_PREMIUM_ACCOUNT, + ONSLAUGHT_CHANCE_FORMULA_A, + ONSLAUGHT_CHANCE_FORMULA_B, + ONSLAUGHT_CHANCE_FORMULA_C, OPTIMIZE_DATABASE, ORANGE_SKULL_DURATION, OWNER_EMAIL, @@ -221,6 +229,9 @@ enum ConfigKey_t : uint16_t { RESET_SESSIONS_ON_STARTUP, REWARD_CHEST_COLLECT_ENABLED, REWARD_CHEST_MAX_COLLECT_ITEMS, + RUSE_CHANCE_FORMULA_A, + RUSE_CHANCE_FORMULA_B, + RUSE_CHANCE_FORMULA_C, SAVE_INTERVAL_TIME, SAVE_INTERVAL_TYPE, SCRIPTS_CONSOLE_LOGS, @@ -274,6 +285,10 @@ enum ConfigKey_t : uint16_t { TOGGLE_SERVER_IS_RETRO, TOGGLE_TRAVELS_FREE, TOGGLE_WHEELSYSTEM, + TRANSCENDANCE_AVATAR_DURATION, + TRANSCENDANCE_CHANCE_FORMULA_A, + TRANSCENDANCE_CHANCE_FORMULA_B, + TRANSCENDANCE_CHANCE_FORMULA_C, T_CONST, URL, USE_ANY_DATAPACK_FOLDER, @@ -289,6 +304,12 @@ enum ConfigKey_t : uint16_t { WEATHER_RAIN, WEATHER_THUNDER, WEEK_KILLS_TO_RED, + WHEEL_ATELIER_REVEAL_GREATER_COST, + WHEEL_ATELIER_REVEAL_LESSER_COST, + WHEEL_ATELIER_REVEAL_REGULAR_COST, + WHEEL_ATELIER_ROTATE_GREATER_COST, + WHEEL_ATELIER_ROTATE_LESSER_COST, + WHEEL_ATELIER_ROTATE_REGULAR_COST, WHEEL_POINTS_PER_LEVEL, WHITE_SKULL_TIME, WORLD_TYPE, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index 1afbc0295ae..2287a3ed0a4 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -227,16 +227,37 @@ bool ConfigManager::load() { loadIntConfig(L, FORGE_SLIVER_AMOUNT, "forgeSliverAmount", 3); loadIntConfig(L, FORGE_CORE_COST, "forgeCoreCost", 50); loadIntConfig(L, FORGE_MAX_DUST, "forgeMaxDust", 225); - loadIntConfig(L, FORGE_FUSION_DUST_COST, "forgeFusionCost", 100); - loadIntConfig(L, FORGE_TRANSFER_DUST_COST, "forgeTransferCost", 100); + loadIntConfig(L, FORGE_FUSION_DUST_COST, "forgeFusionDustCost", 100); + loadIntConfig(L, FORGE_CONVERGENCE_FUSION_DUST_COST, "forgeConvergenceFusionCost", 130); + loadIntConfig(L, FORGE_TRANSFER_DUST_COST, "forgeTransferDustCost", 100); + loadIntConfig(L, FORGE_CONVERGENCE_TRANSFER_DUST_COST, "forgeConvergenceTransferCost", 160); loadIntConfig(L, FORGE_BASE_SUCCESS_RATE, "forgeBaseSuccessRate", 50); loadIntConfig(L, FORGE_BONUS_SUCCESS_RATE, "forgeBonusSuccessRate", 15); loadIntConfig(L, FORGE_TIER_LOSS_REDUCTION, "forgeTierLossReduction", 50); - loadIntConfig(L, FORGE_AMOUNT_MULTIPLIER, "forgeAmountMultiplier", 3); + loadFloatConfig(L, FORGE_AMOUNT_MULTIPLIER, "forgeAmountMultiplier", 3.0); loadIntConfig(L, FORGE_MIN_SLIVERS, "forgeMinSlivers", 3); loadIntConfig(L, FORGE_MAX_SLIVERS, "forgeMaxSlivers", 7); loadIntConfig(L, FORGE_INFLUENCED_CREATURES_LIMIT, "forgeInfluencedLimit", 300); loadIntConfig(L, FORGE_FIENDISH_CREATURES_LIMIT, "forgeFiendishLimit", 3); + + loadFloatConfig(L, RUSE_CHANCE_FORMULA_A, "ruseChanceFormulaA", 0.0307576); + loadFloatConfig(L, RUSE_CHANCE_FORMULA_B, "ruseChanceFormulaB", 0.440697); + loadFloatConfig(L, RUSE_CHANCE_FORMULA_C, "ruseChanceFormulaC", 0.026); + + loadFloatConfig(L, ONSLAUGHT_CHANCE_FORMULA_A, "onslaughtChanceFormulaA", 0.05); + loadFloatConfig(L, ONSLAUGHT_CHANCE_FORMULA_B, "onslaughtChanceFormulaB", 0.4); + loadFloatConfig(L, ONSLAUGHT_CHANCE_FORMULA_C, "onslaughtChanceFormulaC", 0.05); + + loadFloatConfig(L, MOMENTUM_CHANCE_FORMULA_A, "momentumChanceFormulaA", 0.05); + loadFloatConfig(L, MOMENTUM_CHANCE_FORMULA_B, "momentumChanceFormulaB", 1.9); + loadFloatConfig(L, MOMENTUM_CHANCE_FORMULA_C, "momentumChanceFormulaC", 0.05); + + loadFloatConfig(L, TRANSCENDANCE_CHANCE_FORMULA_A, "transcendanceChanceFormulaA", 0.0127); + loadFloatConfig(L, TRANSCENDANCE_CHANCE_FORMULA_B, "transcendanceChanceFormulaB", 0.1070); + loadFloatConfig(L, TRANSCENDANCE_CHANCE_FORMULA_C, "transcendanceChanceFormulaC", 0.0073); + + loadIntConfig(L, TRANSCENDANCE_AVATAR_DURATION, "transcendanceAvatarDuration", 7000); + loadIntConfig(L, DISCORD_WEBHOOK_DELAY_MS, "discordWebhookDelayMs", Webhook::DEFAULT_DELAY_MS); loadFloatConfig(L, BESTIARY_RATE_CHARM_SHOP_PRICE, "bestiaryRateCharmShopPrice", 1.0); @@ -311,7 +332,7 @@ bool ConfigManager::load() { loadIntConfig(L, HAZARD_PODS_DROP_MULTIPLIER, "hazardPodsDropMultiplier", 87); loadIntConfig(L, HAZARD_PODS_TIME_TO_DAMAGE, "hazardPodsTimeToDamage", 2000); loadIntConfig(L, HAZARD_PODS_TIME_TO_SPAWN, "hazardPodsTimeToSpawn", 4000); - loadIntConfig(L, HAZARD_EXP_BONUS_MULTIPLIER, "hazardExpBonusMultiplier", 2); + loadFloatConfig(L, HAZARD_EXP_BONUS_MULTIPLIER, "hazardExpBonusMultiplier", 2.0); loadIntConfig(L, HAZARD_LOOT_BONUS_MULTIPLIER, "hazardLootBonusMultiplier", 2); loadIntConfig(L, HAZARD_PODS_DAMAGE, "hazardPodsDamage", 5); loadIntConfig(L, HAZARD_SPAWN_PLUNDER_MULTIPLIER, "hazardSpawnPlunderMultiplier", 25); @@ -326,6 +347,14 @@ bool ConfigManager::load() { loadBoolConfig(L, TOGGLE_WHEELSYSTEM, "wheelSystemEnabled", true); loadIntConfig(L, WHEEL_POINTS_PER_LEVEL, "wheelPointsPerLevel", 1); + loadIntConfig(L, WHEEL_ATELIER_ROTATE_LESSER_COST, "wheelAtelierRotateLesserCost", 125000); + loadIntConfig(L, WHEEL_ATELIER_ROTATE_REGULAR_COST, "wheelAtelierRotateRegularCost", 250000); + loadIntConfig(L, WHEEL_ATELIER_ROTATE_GREATER_COST, "wheelAtelierRotateGreaterCost", 500000); + + loadIntConfig(L, WHEEL_ATELIER_REVEAL_LESSER_COST, "wheelAtelierRevealLesserCost", 125000); + loadIntConfig(L, WHEEL_ATELIER_REVEAL_REGULAR_COST, "wheelAtelierRevealRegularCost", 1000000); + loadIntConfig(L, WHEEL_ATELIER_REVEAL_GREATER_COST, "wheelAtelierRevealGreaterCost", 6000000); + loadBoolConfig(L, PARTY_AUTO_SHARE_EXPERIENCE, "partyAutoShareExperience", true); loadBoolConfig(L, PARTY_SHARE_LOOT_BOOSTS, "partyShareLootBoosts", true); loadFloatConfig(L, PARTY_SHARE_LOOT_BOOSTS_DIMINISHING_FACTOR, "partyShareLootBoostsDimishingFactor", 0.7f); diff --git a/src/core.hpp b/src/core.hpp index 67262257a32..8ca52d107da 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -15,7 +15,7 @@ static constexpr auto AUTHENTICATOR_PERIOD = 30U; // SERVER_MAJOR_VERSION is the actual full version of the server, including minor and patch numbers. // This is intended for internal use to identify the exact state of the server (release) software. static constexpr auto SERVER_RELEASE_VERSION = "3.1.2"; -static constexpr auto CLIENT_VERSION = 1321; +static constexpr auto CLIENT_VERSION = 1332; #define CLIENT_VERSION_UPPER (CLIENT_VERSION / 100) #define CLIENT_VERSION_LOWER (CLIENT_VERSION % 100) diff --git a/src/creatures/CMakeLists.txt b/src/creatures/CMakeLists.txt index 3c055d508cb..d5884fdc871 100644 --- a/src/creatures/CMakeLists.txt +++ b/src/creatures/CMakeLists.txt @@ -22,5 +22,6 @@ target_sources(${PROJECT_NAME}_lib PRIVATE players/storages/storages.cpp players/player.cpp players/wheel/player_wheel.cpp + players/wheel/wheel_gems.cpp players/vocations/vocation.cpp ) diff --git a/src/creatures/appearance/outfit/outfit.cpp b/src/creatures/appearance/outfit/outfit.cpp index 779ba64c95c..5425f7fed31 100644 --- a/src/creatures/appearance/outfit/outfit.cpp +++ b/src/creatures/appearance/outfit/outfit.cpp @@ -49,8 +49,8 @@ bool Outfits::loadFromXml() { if (uint16_t lookType = pugi::cast(lookTypeAttribute.value()); g_configManager().getBoolean(WARN_UNSAFE_SCRIPTS, __FUNCTION__) && lookType != 0 && !g_game().isLookTypeRegistered(lookType)) { - g_logger().warn("[Outfits::loadFromXml] An unregistered creature looktype type with id '{}' was blocked to prevent client crash.", lookType); - return false; + g_logger().warn("[Outfits::loadFromXml] An unregistered creature looktype type with id '{}' was ignored to prevent client crash.", lookType); + continue; } outfits[type].emplace_back(std::make_shared( diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index e3211a1f52e..3e0af9ea444 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -1324,6 +1324,7 @@ void Combat::setRuneSpellName(const std::string &value) { } std::vector>> Combat::pickChainTargets(std::shared_ptr caster, const CombatParams ¶ms, uint8_t chainDistance, uint8_t maxTargets, bool backtracking, bool aggressive, std::shared_ptr initialTarget /* = nullptr */) { + Benchmark bm_pickChain; metrics::method_latency measure(__METHOD_NAME__); if (!caster) { return {}; @@ -1342,8 +1343,8 @@ std::vector>> Combat::pickChainTargets maxTargets++; } - const int maxBacktrackingAttempts = 10; // Can be adjusted as needed - while (!targets.empty() && targets.size() <= maxTargets) { + int backtrackingAttempts = 10; + while (!targets.empty() && targets.size() <= maxTargets && backtrackingAttempts > 0) { auto currentTarget = targets.back(); auto spectators = Spectators().find(currentTarget->getPosition(), false, chainDistance, chainDistance, chainDistance, chainDistance); g_logger().debug("Combat::pickChainTargets: currentTarget: {}, spectators: {}", currentTarget->getName(), spectators.size()); @@ -1367,7 +1368,7 @@ std::vector>> Combat::pickChainTargets } if (closestSpectator) { - g_logger().debug("Combat::pickChainTargets: closestSpectator: {}", closestSpectator->getName()); + g_logger().trace("[{}] closestSpectator: {}", __METHOD_NAME__, closestSpectator->getName()); bool found = false; for (auto &[pos, vec] : resultMap) { @@ -1385,14 +1386,15 @@ std::vector>> Combat::pickChainTargets visited.insert(closestSpectator->getID()); continue; } else if (backtracking) { + g_logger().debug("[{}] backtracking", __METHOD_NAME__); targets.pop_back(); - if (targets.size() <= maxBacktrackingAttempts) { - continue; - } + backtrackingAttempts--; + continue; } break; } + g_logger().debug("[{}] resultMap: {} in {} ms", __METHOD_NAME__, resultMap.size(), bm_pickChain.duration()); return resultMap; } @@ -2069,7 +2071,7 @@ void Combat::applyExtensions(std::shared_ptr caster, std::shared_ptr(bonus) / 100; + double multiplier = 1.0 + static_cast(bonus) / 10000; chance += (uint16_t)damage.criticalChance; if (chance != 0 && uniform_random(1, 10000) <= chance) { diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index 4181ca39410..f1e2ec6e6d1 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -2051,7 +2051,7 @@ bool ConditionFeared::executeCondition(std::shared_ptr creature, int32 void ConditionFeared::endCondition(std::shared_ptr creature) { creature->stopEventWalk(); /* - * After a player is feared there's a 10 seconds before he can feared again. + * After a player is feared there's a 10 seconds before they can can feared again. */ std::shared_ptr player = creature->getPlayer(); if (player) { diff --git a/src/creatures/combat/spells.cpp b/src/creatures/combat/spells.cpp index 152bc37c496..013d1b4a1ff 100644 --- a/src/creatures/combat/spells.cpp +++ b/src/creatures/combat/spells.cpp @@ -639,6 +639,8 @@ void Spell::applyCooldownConditions(std::shared_ptr player) const { if (isUpgraded) { spellCooldown -= getWheelOfDestinyBoost(WheelSpellBoost_t::COOLDOWN, spellGrade); } + g_logger().debug("[{}] spell name: {}, spellCooldown: {}, bonus: {}", __FUNCTION__, name, spellCooldown, player->wheel()->getSpellBonus(name, WheelSpellBoost_t::COOLDOWN)); + spellCooldown -= player->wheel()->getSpellBonus(name, WheelSpellBoost_t::COOLDOWN); if (spellCooldown > 0) { std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, spellCooldown / rateCooldown, 0, false, m_spellId); player->addCondition(condition); @@ -676,6 +678,7 @@ void Spell::postCastSpell(std::shared_ptr player, bool finishedCast /*= if (aggressive) { player->addInFightTicks(); + player->updateLastAggressiveAction(); } if (player && soundCastEffect != SoundEffect_t::SILENCE) { @@ -702,28 +705,26 @@ void Spell::postCastSpell(std::shared_ptr player, uint32_t manaCost, uin } uint32_t Spell::getManaCost(std::shared_ptr player) const { + WheelSpellGrade_t spellGrade = player->wheel()->getSpellUpgrade(getName()); + uint32_t manaRedution = 0; + if (getWheelOfDestinyUpgraded() && static_cast(spellGrade) > 0) { + manaRedution += getWheelOfDestinyBoost(WheelSpellBoost_t::MANA, spellGrade); + } + manaRedution += player->wheel()->getSpellBonus(name, WheelSpellBoost_t::MANA); + if (mana != 0) { - WheelSpellGrade_t spellGrade = player->wheel()->getSpellUpgrade(getName()); - if (getWheelOfDestinyUpgraded() && static_cast(spellGrade) > 0) { - if (getWheelOfDestinyBoost(WheelSpellBoost_t::MANA, spellGrade) >= mana) { - return 0; - } else { - return (mana - getWheelOfDestinyBoost(WheelSpellBoost_t::MANA, spellGrade)); - } + if (manaRedution > mana) { + return 0; } - return mana; + return mana - manaRedution; } if (manaPercent != 0) { uint32_t maxMana = player->getMaxMana(); uint32_t manaCost = (maxMana * manaPercent) / 100; WheelSpellGrade_t spellGrade = player->wheel()->getSpellUpgrade(getName()); - if (getWheelOfDestinyUpgraded() && static_cast(spellGrade) > 0) { - if (getWheelOfDestinyBoost(WheelSpellBoost_t::MANA, spellGrade) >= manaCost) { - return 0; - } else { - return (manaCost - getWheelOfDestinyBoost(WheelSpellBoost_t::MANA, spellGrade)); - } + if (manaRedution > manaCost) { + return 0; } return manaCost; } @@ -842,6 +843,7 @@ bool InstantSpell::playerCastInstant(std::shared_ptr player, std::string auto worldType = g_game().getWorldType(); if (pzLocked && (worldType == WORLD_TYPE_PVP || worldType == WORLD_TYPE_PVP_ENFORCED)) { player->addInFightTicks(true); + player->updateLastAggressiveAction(); } bool result = executeCastSpell(player, var); @@ -1014,6 +1016,7 @@ bool RuneSpell::executeUse(std::shared_ptr player, std::shared_ptr auto worldType = g_game().getWorldType(); if (pzLocked && (worldType == WORLD_TYPE_PVP || worldType == WORLD_TYPE_PVP_ENFORCED)) { player->addInFightTicks(true); + player->updateLastAggressiveAction(); } return true; diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index 8e2e748c004..efbf3ea5cf7 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -798,10 +798,10 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared g_game().internalAddItem(tile, corpse, INDEX_WHEREEVER, FLAG_NOLIMIT); dropLoot(corpse->getContainer(), lastHitCreature); corpse->startDecaying(); - bool corpses = corpse->isRewardCorpse() || (corpse->getID() == ITEM_MALE_CORPSE || corpse->getID() == ITEM_FEMALE_CORPSE); + bool disallowedCorpses = corpse->isRewardCorpse() || (corpse->getID() == ITEM_MALE_CORPSE || corpse->getID() == ITEM_FEMALE_CORPSE); const auto player = mostDamageCreature ? mostDamageCreature->getPlayer() : nullptr; auto corpseContainer = corpse->getContainer(); - if (corpseContainer && player && !corpses) { + if (corpseContainer && player && !disallowedCorpses) { auto monster = getMonster(); if (monster && !monster->isRewardBoss()) { std::ostringstream lootMessage; @@ -813,9 +813,9 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared player->sendLootMessage(lootMessage.str()); } - if (player->checkAutoLoot() && corpseContainer && mostDamageCreature->getPlayer()) { + if (player->checkAutoLoot(monster->isRewardBoss()) && corpseContainer && mostDamageCreature->getPlayer()) { g_dispatcher().addEvent( - std::bind(&Game::playerQuickLootCorpse, &g_game(), player, corpseContainer), + std::bind(&Game::playerQuickLootCorpse, &g_game(), player, corpseContainer, corpse->getPosition()), "Game::playerQuickLootCorpse" ); } diff --git a/src/creatures/creatures_definitions.hpp b/src/creatures/creatures_definitions.hpp index 39ada8745a5..ee1f390b9a2 100644 --- a/src/creatures/creatures_definitions.hpp +++ b/src/creatures/creatures_definitions.hpp @@ -321,7 +321,6 @@ enum ObjectCategory_t { OBJECTCATEGORY_PREMIUMSCROLLS = 22, // not used in quickloot OBJECTCATEGORY_TIBIACOINS = 23, // not used in quickloot OBJECTCATEGORY_CREATUREPRODUCTS = 24, - OBJECTCATEGORY_STASHRETRIEVE = 27, OBJECTCATEGORY_GOLD = 30, OBJECTCATEGORY_DEFAULT = 31, // unassigned loot @@ -329,6 +328,39 @@ enum ObjectCategory_t { OBJECTCATEGORY_LAST = OBJECTCATEGORY_DEFAULT, }; +static bool isValidObjectCategory(uint8_t category) { + static std::unordered_set valid = { + OBJECTCATEGORY_NONE, + OBJECTCATEGORY_ARMORS, + OBJECTCATEGORY_NECKLACES, + OBJECTCATEGORY_BOOTS, + OBJECTCATEGORY_CONTAINERS, + OBJECTCATEGORY_DECORATION, + OBJECTCATEGORY_FOOD, + OBJECTCATEGORY_HELMETS, + OBJECTCATEGORY_LEGS, + OBJECTCATEGORY_OTHERS, + OBJECTCATEGORY_POTIONS, + OBJECTCATEGORY_RINGS, + OBJECTCATEGORY_RUNES, + OBJECTCATEGORY_SHIELDS, + OBJECTCATEGORY_TOOLS, + OBJECTCATEGORY_VALUABLES, + OBJECTCATEGORY_AMMO, + OBJECTCATEGORY_AXES, + OBJECTCATEGORY_CLUBS, + OBJECTCATEGORY_DISTANCEWEAPONS, + OBJECTCATEGORY_SWORDS, + OBJECTCATEGORY_WANDS, + OBJECTCATEGORY_PREMIUMSCROLLS, + OBJECTCATEGORY_TIBIACOINS, + OBJECTCATEGORY_CREATUREPRODUCTS, + OBJECTCATEGORY_GOLD, + OBJECTCATEGORY_DEFAULT, + }; + return valid.contains(category); +} + enum RespawnPeriod_t { RESPAWNPERIOD_ALL, RESPAWNPERIOD_DAY, diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index b4b3337bbed..2d6c27558e7 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -336,12 +336,15 @@ class Monster final : public Creature { float getAttackMultiplier() const { float multiplier = mType->getAttackMultiplier(); - return multiplier * std::pow(1.03f, getForgeStack()); + if (auto stacks = getForgeStack(); stacks > 0) { + multiplier *= (1.35 + (stacks - 1) * 0.1); + } + return multiplier; } float getDefenseMultiplier() const { float multiplier = mType->getDefenseMultiplier(); - return multiplier * std::pow(1.01f, getForgeStack()); + return multiplier * std::pow(1.02f, getForgeStack()); } private: diff --git a/src/creatures/monsters/spawns/spawn_monster.hpp b/src/creatures/monsters/spawns/spawn_monster.hpp index 7e7f7cf3f49..bf83e5dec0a 100644 --- a/src/creatures/monsters/spawns/spawn_monster.hpp +++ b/src/creatures/monsters/spawns/spawn_monster.hpp @@ -59,10 +59,10 @@ class SpawnMonster { private: // map of the spawned creatures - phmap::parallel_flat_hash_map_m> spawnedMonsterMap; + std::map> spawnedMonsterMap; // map of creatures in the spawn - phmap::parallel_flat_hash_map_m spawnMonsterMap; + std::map spawnMonsterMap; Position centerPos; int32_t radius; diff --git a/src/creatures/players/imbuements/imbuements.cpp b/src/creatures/players/imbuements/imbuements.cpp index 995ea3197a4..093eae3c956 100644 --- a/src/creatures/players/imbuements/imbuements.cpp +++ b/src/creatures/players/imbuements/imbuements.cpp @@ -240,7 +240,7 @@ bool Imbuements::loadFromXml(bool /* reloading */) { imbuement.skills[skillId] = bonus; int32_t chance = 100; if ((attr = childNode.attribute("chance"))) { - chance = std::min(100, pugi::cast(attr.value())); + chance = std::min(10000, pugi::cast(attr.value())); } imbuement.skills[skillId - 1] = chance; diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 0a310061fc5..46528c79241 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -339,7 +339,7 @@ int32_t Player::getWeaponSkill(std::shared_ptr item) const { int32_t Player::getArmor() const { int32_t armor = 0; - static const Slots_t armorSlots[] = { CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING }; + static const Slots_t armorSlots[] = { CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING, CONST_SLOT_AMMO }; for (Slots_t slot : armorSlots) { std::shared_ptr inventoryItem = inventory[slot]; if (inventoryItem) { @@ -1024,79 +1024,114 @@ void Player::onReceiveMail() { } } -std::shared_ptr Player::setLootContainer(ObjectCategory_t category, std::shared_ptr container, bool loading /* = false*/) { +std::shared_ptr Player::refreshManagedContainer(ObjectCategory_t category, std::shared_ptr container, bool isLootContainer, bool loading /* = false*/) { std::shared_ptr previousContainer = nullptr; - if (auto it = quickLootContainers.find(category); - it != quickLootContainers.end() && !loading) { - previousContainer = (*it).second; - auto flags = previousContainer->getAttribute(ItemAttribute_t::QUICKLOOTCONTAINER); - flags &= ~(1 << category); - if (flags == 0) { - previousContainer->removeAttribute(ItemAttribute_t::QUICKLOOTCONTAINER); + auto toSetAttribute = isLootContainer ? ItemAttribute_t::QUICKLOOTCONTAINER : ItemAttribute_t::OBTAINCONTAINER; + if (auto it = m_managedContainers.find(category); it != m_managedContainers.end() && !loading) { + previousContainer = isLootContainer ? it->second.first : it->second.second; + if (previousContainer) { + auto flags = previousContainer->getAttribute(toSetAttribute); + flags &= ~(1 << category); + if (flags == 0) { + previousContainer->removeAttribute(toSetAttribute); + } else { + previousContainer->setAttribute(toSetAttribute, flags); + } + } + + if (isLootContainer) { + it->second.first = nullptr; } else { - previousContainer->setAttribute(ItemAttribute_t::QUICKLOOTCONTAINER, flags); + it->second.second = nullptr; } - quickLootContainers.erase(it); + if (!it->second.first && !it->second.second) { + m_managedContainers.erase(it); + } } + if (container) { previousContainer = container; - quickLootContainers[category] = container; + if (m_managedContainers.find(category) != m_managedContainers.end()) { + if (isLootContainer) { + m_managedContainers[category].first = container; + } else { + m_managedContainers[category].second = container; + } + } else { + std::pair, std::shared_ptr> newPair; + if (isLootContainer) { + newPair.first = container; + newPair.second = nullptr; + } else { + newPair.first = nullptr; + newPair.second = container; + } + m_managedContainers[category] = newPair; + } if (!loading) { - auto flags = container->getAttribute(ItemAttribute_t::QUICKLOOTCONTAINER); - auto sendAttribute = flags | 1 << category; - container->setAttribute(ItemAttribute_t::QUICKLOOTCONTAINER, sendAttribute); + auto flags = container->getAttribute(toSetAttribute); + auto sendAttribute = flags | (1 << category); + container->setAttribute(toSetAttribute, sendAttribute); } - return previousContainer; } - return nullptr; + return previousContainer; } -std::shared_ptr Player::getLootContainer(ObjectCategory_t category) const { +std::shared_ptr Player::getManagedContainer(ObjectCategory_t category, bool isLootContainer) const { if (category != OBJECTCATEGORY_DEFAULT && !isPremium()) { category = OBJECTCATEGORY_DEFAULT; } - auto it = quickLootContainers.find(category); - if (it != quickLootContainers.end()) { - return (*it).second; + auto it = m_managedContainers.find(category); + std::shared_ptr container = nullptr; + if (it != m_managedContainers.end()) { + container = isLootContainer ? it->second.first : it->second.second; } - if (category != OBJECTCATEGORY_DEFAULT) { + if (!container && category != OBJECTCATEGORY_DEFAULT) { // firstly, fallback to default - return getLootContainer(OBJECTCATEGORY_DEFAULT); + container = getManagedContainer(OBJECTCATEGORY_DEFAULT, isLootContainer); } - return nullptr; + return container; } -void Player::checkLootContainers(std::shared_ptr item) { - if (!item) { - return; - } - - std::shared_ptr container = item->getContainer(); +void Player::checkLootContainers(std::shared_ptr container) { if (!container) { return; } bool shouldSend = false; + for (auto it = m_managedContainers.begin(); it != m_managedContainers.end();) { + std::shared_ptr &lootContainer = it->second.first; + std::shared_ptr &obtainContainer = it->second.second; + bool removeLoot = false; + bool removeObtain = false; + if (lootContainer && container->getHoldingPlayer() != getPlayer() && (container == lootContainer || container->isHoldingItem(lootContainer))) { + removeLoot = true; + shouldSend = true; + lootContainer->removeAttribute(ItemAttribute_t::QUICKLOOTCONTAINER); + } - auto it = quickLootContainers.begin(); - while (it != quickLootContainers.end()) { - std::shared_ptr lootContainer = (*it).second; + if (obtainContainer && container->getHoldingPlayer() != getPlayer() && (container == obtainContainer || container->isHoldingItem(obtainContainer))) { + removeObtain = true; + shouldSend = true; + obtainContainer->removeAttribute(ItemAttribute_t::OBTAINCONTAINER); + } - bool remove = false; - if (item->getHoldingPlayer() != getPlayer() && (item == lootContainer || container->isHoldingItem(lootContainer))) { - remove = true; + if (removeLoot) { + lootContainer.reset(); } - if (remove) { - shouldSend = true; - it = quickLootContainers.erase(it); - lootContainer->removeAttribute(ItemAttribute_t::QUICKLOOTCONTAINER); + if (removeObtain) { + obtainContainer.reset(); + } + + if (!lootContainer && !obtainContainer) { + it = m_managedContainers.erase(it); } else { ++it; } @@ -1107,6 +1142,27 @@ void Player::checkLootContainers(std::shared_ptr item) { } } +void Player::setMainBackpackUnassigned(std::shared_ptr container) { + if (!container) { + return; + } + + // Update containers + bool toSendInventoryUpdate = false; + for (bool isLootContainer : { true, false }) { + std::shared_ptr managedContainer = getManagedContainer(OBJECTCATEGORY_DEFAULT, isLootContainer); + if (!managedContainer) { + refreshManagedContainer(OBJECTCATEGORY_DEFAULT, container, isLootContainer); + toSendInventoryUpdate = true; + } + } + + if (toSendInventoryUpdate) { + sendInventoryItem(CONST_SLOT_BACKPACK, container); + sendLootContainers(); + } +} + void Player::sendLootStats(std::shared_ptr item, uint8_t count) { uint64_t value = 0; if (item->getID() == ITEM_GOLD_COIN || item->getID() == ITEM_PLATINUM_COIN || item->getID() == ITEM_CRYSTAL_COIN) { @@ -1621,7 +1677,7 @@ void Player::onRemoveTileItem(std::shared_ptr fromTile, const Position &po } } - checkLootContainers(item); + checkLootContainers(item->getContainer()); } void Player::onCreatureAppear(std::shared_ptr creature, bool isLogin) { @@ -1942,7 +1998,7 @@ void Player::onRemoveContainerItem(std::shared_ptr container, std::sh } } - checkLootContainers(item); + checkLootContainers(item->getContainer()); } void Player::onCloseContainer(std::shared_ptr container) { @@ -1994,7 +2050,7 @@ void Player::onRemoveInventoryItem(std::shared_ptr item) { } } - checkLootContainers(item); + checkLootContainers(item->getContainer()); } void Player::checkTradeState(std::shared_ptr item) { @@ -2108,6 +2164,8 @@ void Player::onThink(uint32_t interval) { addMessageBuffer(); } + // Transcendance (avatar trigger) + triggerTranscendance(); // Momentum (cooldown resets) triggerMomentum(); auto playerTile = getTile(); @@ -2287,7 +2345,7 @@ void Player::addExperience(std::shared_ptr target, uint64_t exp, bool std::shared_ptr monster = target && target->getMonster() ? target->getMonster() : nullptr; bool handleHazardExperience = monster && monster->getHazard() && getHazardSystemPoints() > 0; if (handleHazardExperience) { - exp += (exp * (1.75 * getHazardSystemPoints() * g_configManager().getNumber(HAZARD_EXP_BONUS_MULTIPLIER, __FUNCTION__))) / 100.; + exp += (exp * (1.75 * getHazardSystemPoints() * g_configManager().getFloat(HAZARD_EXP_BONUS_MULTIPLIER, __FUNCTION__))) / 100.; } experience += exp; @@ -4312,7 +4370,8 @@ void Player::doAttacking(uint32_t) { } if (result) { - lastAttack = OTSYS_TIME(); + updateLastAggressiveAction(); + updateLastAttack(); } } } @@ -6519,7 +6578,7 @@ void Player::triggerMomentum() { } double_t chance = item->getMomentumChance(); - double_t randomChance = uniform_random(0, 10000) / 100; + double_t randomChance = uniform_random(0, 10000) / 100.; if (getZoneType() != ZONE_PROTECTION && hasCondition(CONDITION_INFIGHT) && ((OTSYS_TIME() / 1000) % 2) == 0 && chance > 0 && randomChance < chance) { bool triggered = false; auto it = conditions.begin(); @@ -6561,6 +6620,32 @@ void Player::clearCooldowns() { } } +void Player::triggerTranscendance() { + auto item = getInventoryItem(CONST_SLOT_LEGS); + if (item == nullptr) { + return; + } + + double_t chance = item->getTranscendenceChance(); + double_t randomChance = uniform_random(0, 10000) / 100.; + if (getZoneType() != ZONE_PROTECTION && checkLastAggressiveActionWithin(2000) && ((OTSYS_TIME() / 1000) % 2) == 0 && chance > 0 && randomChance < chance) { + int64_t duration = g_configManager().getNumber(TRANSCENDANCE_AVATAR_DURATION, __FUNCTION__); + auto outfitCondition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)->static_self_cast(); + Outfit_t outfit; + outfit.lookType = getVocation()->getAvatarLookType(); + outfitCondition->setOutfit(outfit); + addCondition(outfitCondition); + wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR, OTSYS_TIME() + duration); + g_game().addMagicEffect(getPosition(), CONST_ME_AVATAR_APPEAR); + sendTextMessage(MESSAGE_ATTENTION, "Transcendance was triggered."); + sendSkills(); + sendStats(); + sendBasicData(); + wheel()->sendGiftOfLifeCooldown(); + g_game().reloadCreature(getPlayer()); + } +} + /******************************************************************************* * Depot search system ******************************************************************************/ @@ -6917,41 +7002,35 @@ bool Player::saySpell( } // Forge system -void Player::forgeFuseItems(uint16_t itemId, uint8_t tier, bool success, bool reduceTierLoss, uint8_t bonus, uint8_t coreCount) { - if (this->getFreeBackpackSlots() < 1) { - sendCancelMessage("You have no slots in your backpack."); - sendForgeError(RETURNVALUE_NOTENOUGHROOM); - return; - } - +void Player::forgeFuseItems(ForgeAction_t actionType, uint16_t firstItemId, uint8_t tier, uint16_t secondItemId, bool success, bool reduceTierLoss, bool convergence, uint8_t bonus, uint8_t coreCount) { ForgeHistory history; - history.actionType = ForgeConversion_t::FORGE_ACTION_FUSION; + history.actionType = actionType; history.tier = tier; history.success = success; history.tierLoss = reduceTierLoss; - auto firstForgingItem = getForgeItemFromId(itemId, tier); + auto firstForgingItem = getForgeItemFromId(firstItemId, tier); if (!firstForgingItem) { - g_logger().error("[Log 1] Player with name {} failed to fuse item with id {}", getName(), itemId); + g_logger().error("[Log 1] Player with name {} failed to fuse item with id {}", getName(), firstItemId); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } auto returnValue = g_game().internalRemoveItem(firstForgingItem, 1); if (returnValue != RETURNVALUE_NOERROR) { - g_logger().error("[Log 1] Failed to remove forge item {} from player with name {}", itemId, getName()); + g_logger().error("[Log 1] Failed to remove forge item {} from player with name {}", firstItemId, getName()); sendCancelMessage(getReturnMessage(returnValue)); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } - auto secondForgingItem = getForgeItemFromId(itemId, tier); + auto secondForgingItem = getForgeItemFromId(secondItemId, tier); if (!secondForgingItem) { - g_logger().error("[Log 2] Player with name {} failed to fuse item with id {}", getName(), itemId); + g_logger().error("[Log 2] Player with name {} failed to fuse item with id {}", getName(), secondItemId); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } if (returnValue = g_game().internalRemoveItem(secondForgingItem, 1); returnValue != RETURNVALUE_NOERROR) { - g_logger().error("[Log 2] Failed to remove forge item {} from player with name {}", itemId, getName()); + g_logger().error("[Log 2] Failed to remove forge item {} from player with name {}", secondItemId, getName()); sendCancelMessage(getReturnMessage(returnValue)); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; @@ -6970,151 +7049,179 @@ void Player::forgeFuseItems(uint16_t itemId, uint8_t tier, bool success, bool re return; } - std::shared_ptr firstForgedItem = Item::CreateItem(itemId, 1); + std::shared_ptr firstForgedItem = Item::CreateItem(firstItemId, 1); if (!firstForgedItem) { - g_logger().error("[Log 3] Player with name {} failed to fuse item with id {}", getName(), itemId); + g_logger().error("[Log 3] Player with name {} failed to fuse item with id {}", getName(), firstItemId); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } - firstForgedItem->setTier(tier); returnValue = g_game().internalAddItem(exaltationContainer, firstForgedItem, INDEX_WHEREEVER); if (returnValue != RETURNVALUE_NOERROR) { - g_logger().error("[Log 1] Failed to add forge item {} from player with name {}", itemId, getName()); + g_logger().error("[Log 1] Failed to add forge item {} from player with name {}", firstItemId, getName()); sendCancelMessage(getReturnMessage(returnValue)); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } - std::shared_ptr secondForgedItem = Item::CreateItem(itemId, 1); - if (!secondForgedItem) { - g_logger().error("[Log 4] Player with name {} failed to fuse item with id {}", getName(), itemId); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; - } + auto configKey = convergence ? FORGE_CONVERGENCE_FUSION_DUST_COST : FORGE_FUSION_DUST_COST; + auto dustCost = static_cast(g_configManager().getNumber(configKey, __FUNCTION__)); + if (convergence) { + firstForgedItem->setTier(tier + 1); + history.dustCost = dustCost; + setForgeDusts(getForgeDusts() - dustCost); - secondForgedItem->setTier(tier); - returnValue = g_game().internalAddItem(exaltationContainer, secondForgedItem, INDEX_WHEREEVER); - if (returnValue != RETURNVALUE_NOERROR) { - g_logger().error("[Log 2] Failed to add forge item {} from player with name {}", itemId, getName()); - sendCancelMessage(getReturnMessage(returnValue)); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; - } + uint64_t cost = 0; + for (const auto* itemClassification : g_game().getItemsClassifications()) { + if (itemClassification->id != firstForgingItem->getClassification()) { + continue; + } - auto dustCost = static_cast(g_configManager().getNumber(FORGE_FUSION_DUST_COST, __FUNCTION__)); - if (success) { - firstForgedItem->setTier(tier + 1); + for (const auto &[mapTier, mapPrice] : itemClassification->tiers) { + if (mapTier == firstForgingItem->getTier()) { + cost = mapPrice.convergenceFusionPrice; + break; + } + } + break; + } + if (!g_game().removeMoney(static_self_cast(), cost, 0, true)) { + g_logger().error("[{}] Failed to remove {} gold from player with name {}", __FUNCTION__, cost, getName()); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); + return; + } + g_metrics().addCounter("balance_decrease", cost, { { "player", getName() }, { "context", "forge_convergence_fuse" } }); + history.cost = cost; + } else { + firstForgedItem->setTier(tier); + std::shared_ptr secondForgedItem = Item::CreateItem(secondItemId, 1); + if (!secondForgedItem) { + g_logger().error("[Log 4] Player with name {} failed to fuse item with id {}", getName(), secondItemId); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); + return; + } - if (bonus != 1) { - history.dustCost = dustCost; - setForgeDusts(getForgeDusts() - dustCost); + secondForgedItem->setTier(tier); + returnValue = g_game().internalAddItem(exaltationContainer, secondForgedItem, INDEX_WHEREEVER); + if (returnValue != RETURNVALUE_NOERROR) { + g_logger().error("[Log 2] Failed to add forge item {} from player with name {}", secondItemId, getName()); + sendCancelMessage(getReturnMessage(returnValue)); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); + return; } - if (bonus != 2) { - if (coreCount != 0 && !removeItemCountById(ITEM_FORGE_CORE, coreCount)) { - g_logger().error("[{}][Log 1] Failed to remove item 'id :{} count: {}' from player {}", __FUNCTION__, fmt::underlying(ITEM_FORGE_CORE), coreCount, getName()); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; + + if (success) { + firstForgedItem->setTier(tier + 1); + + if (bonus != 1) { + history.dustCost = dustCost; + setForgeDusts(getForgeDusts() - dustCost); } - history.coresCost = coreCount; - } - if (bonus != 3) { - uint64_t cost = 0; - for (const auto* itemClassification : g_game().getItemsClassifications()) { - if (itemClassification->id != firstForgingItem->getClassification()) { - continue; + if (bonus != 2) { + if (coreCount != 0 && !removeItemCountById(ITEM_FORGE_CORE, coreCount)) { + g_logger().error("[{}][Log 1] Failed to remove item 'id :{} count: {}' from player {}", __FUNCTION__, fmt::underlying(ITEM_FORGE_CORE), coreCount, getName()); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); + return; } - - for (const auto &[mapTier, mapPrice] : itemClassification->tiers) { - if (mapTier == firstForgingItem->getTier()) { - cost = mapPrice.priceToUpgrade; + history.coresCost = coreCount; + } + if (bonus != 3) { + uint64_t cost = 0; + for (const auto* itemClassification : g_game().getItemsClassifications()) { + if (itemClassification->id != firstForgedItem->getClassification()) { + continue; + } + if (!itemClassification->tiers.contains(firstForgedItem->getTier())) { + g_logger().error("[{}] Failed to find tier {} for item {} in classification {}", __FUNCTION__, firstForgedItem->getTier(), firstForgedItem->getClassification(), itemClassification->id); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); break; } + cost = itemClassification->tiers.at(firstForgedItem->getTier()).regularPrice; + break; } - break; - } - if (!g_game().removeMoney(static_self_cast(), cost, 0, true)) { - g_logger().error("[{}] Failed to remove {} gold from player with name {}", __FUNCTION__, cost, getName()); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; + if (!g_game().removeMoney(static_self_cast(), cost, 0, true)) { + g_logger().error("[{}] Failed to remove {} gold from player with name {}", __FUNCTION__, cost, getName()); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); + return; + } + g_metrics().addCounter("balance_decrease", cost, { { "player", getName() }, { "context", "forge_fuse" } }); + history.cost = cost; } - g_metrics().addCounter("balance_decrease", cost, { { "player", getName() }, { "context", "forge_fuse" } }); - history.cost = cost; - } - if (bonus == 4) { - if (tier > 0) { - secondForgedItem->setTier(tier - 1); + if (bonus == 4) { + if (tier > 0) { + secondForgedItem->setTier(tier - 1); + } + } else if (bonus == 6) { + secondForgedItem->setTier(tier + 1); + } else if (bonus == 7 && tier + 2 <= firstForgedItem->getClassification()) { + firstForgedItem->setTier(tier + 2); } - } else if (bonus == 6) { - secondForgedItem->setTier(tier + 1); - } else if (bonus == 7 && tier + 2 <= firstForgedItem->getClassification()) { - firstForgedItem->setTier(tier + 2); - } - if (bonus != 4 && bonus != 5 && bonus != 6 && bonus != 8) { - returnValue = g_game().internalRemoveItem(secondForgedItem, 1); - if (returnValue != RETURNVALUE_NOERROR) { - g_logger().error("[Log 6] Failed to remove forge item {} from player with name {}", itemId, getName()); - sendCancelMessage(getReturnMessage(returnValue)); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; - } - } - } else { - auto isTierLost = uniform_random(1, 100) <= (reduceTierLoss ? g_configManager().getNumber(FORGE_TIER_LOSS_REDUCTION, __FUNCTION__) : 100); - if (isTierLost) { - if (secondForgedItem->getTier() >= 1) { - secondForgedItem->setTier(tier - 1); - } else { + if (bonus != 4 && bonus != 5 && bonus != 6 && bonus != 8) { returnValue = g_game().internalRemoveItem(secondForgedItem, 1); if (returnValue != RETURNVALUE_NOERROR) { - g_logger().error("[Log 7] Failed to remove forge item {} from player with name {}", itemId, getName()); + g_logger().error("[Log 6] Failed to remove forge item {} from player with name {}", secondItemId, getName()); sendCancelMessage(getReturnMessage(returnValue)); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } } - } - bonus = (isTierLost ? 0 : 8); - history.coresCost = coreCount; - - if (getForgeDusts() < dustCost) { - g_logger().error("[Log 7] Failed to remove fuse dusts from player with name {}", getName()); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; } else { - setForgeDusts(getForgeDusts() - dustCost); - } + auto isTierLost = uniform_random(1, 100) <= (reduceTierLoss ? g_configManager().getNumber(FORGE_TIER_LOSS_REDUCTION, __FUNCTION__) : 100); + if (isTierLost) { + if (secondForgedItem->getTier() >= 1) { + secondForgedItem->setTier(tier - 1); + } else { + returnValue = g_game().internalRemoveItem(secondForgedItem, 1); + if (returnValue != RETURNVALUE_NOERROR) { + g_logger().error("[Log 7] Failed to remove forge item {} from player with name {}", secondItemId, getName()); + sendCancelMessage(getReturnMessage(returnValue)); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); + return; + } + } + } + bonus = (isTierLost ? 0 : 8); + history.coresCost = coreCount; - if (coreCount != 0 && !removeItemCountById(ITEM_FORGE_CORE, coreCount)) { - g_logger().error("[{}][Log 2] Failed to remove item 'id: {}, count: {}' from player {}", __FUNCTION__, fmt::underlying(ITEM_FORGE_CORE), coreCount, getName()); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; - } + if (getForgeDusts() < dustCost) { + g_logger().error("[Log 7] Failed to remove fuse dusts from player with name {}", getName()); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); + return; + } else { + setForgeDusts(getForgeDusts() - dustCost); + } - uint64_t cost = 0; - for (const auto* itemClassification : g_game().getItemsClassifications()) { - if (itemClassification->id != firstForgingItem->getClassification()) { - continue; + if (coreCount != 0 && !removeItemCountById(ITEM_FORGE_CORE, coreCount)) { + g_logger().error("[{}][Log 2] Failed to remove item 'id: {}, count: {}' from player {}", __FUNCTION__, fmt::underlying(ITEM_FORGE_CORE), coreCount, getName()); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); + return; } - for (const auto &[mapTier, mapPrice] : itemClassification->tiers) { - if (mapTier == firstForgingItem->getTier()) { - cost = mapPrice.priceToUpgrade; + uint64_t cost = 0; + for (const auto* itemClassification : g_game().getItemsClassifications()) { + if (itemClassification->id != firstForgingItem->getClassification()) { + continue; + } + if (!itemClassification->tiers.contains(firstForgingItem->getTier() + 1)) { + g_logger().error("[{}] Failed to find tier {} for item {} in classification {}", __FUNCTION__, firstForgingItem->getTier() + 1, firstForgingItem->getClassification(), itemClassification->id); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); break; } + cost = itemClassification->tiers.at(firstForgingItem->getTier() + 1).regularPrice; + break; } - break; - } - if (!g_game().removeMoney(static_self_cast(), cost, 0, true)) { - g_logger().error("[{}] Failed to remove {} gold from player with name {}", __FUNCTION__, cost, getName()); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; - } - g_metrics().addCounter("balance_decrease", cost, { { "player", getName() }, { "context", "forge_fuse" } }); + if (!g_game().removeMoney(static_self_cast(), cost, 0, true)) { + g_logger().error("[{}] Failed to remove {} gold from player with name {}", __FUNCTION__, cost, getName()); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); + return; + } + g_metrics().addCounter("balance_decrease", cost, { { "player", getName() }, { "context", "forge_fuse" } }); - history.cost = cost; + history.cost = cost; + } } + returnValue = g_game().internalAddItem(static_self_cast(), exaltationContainer, INDEX_WHEREEVER); if (returnValue != RETURNVALUE_NOERROR) { g_logger().error("Failed to add exaltation chest to player with name {}", fmt::underlying(ITEM_EXALTATION_CHEST), getName()); @@ -7124,22 +7231,18 @@ void Player::forgeFuseItems(uint16_t itemId, uint8_t tier, bool success, bool re } history.firstItemName = firstForgingItem->getName(); + history.secondItemName = secondForgingItem->getName(); history.bonus = bonus; history.createdAt = getTimeNow(); + history.convergence = convergence; registerForgeHistoryDescription(history); - sendForgeFusionItem(itemId, tier, success, bonus, coreCount); + sendForgeResult(actionType, firstItemId, tier, secondItemId, tier + 1, success, bonus, coreCount, convergence); } -void Player::forgeTransferItemTier(uint16_t donorItemId, uint8_t tier, uint16_t receiveItemId) { - if (this->getFreeBackpackSlots() < 1) { - sendCancelMessage("You have no slots in your backpack."); - sendForgeError(RETURNVALUE_NOTENOUGHROOM); - return; - } - +void Player::forgeTransferItemTier(ForgeAction_t actionType, uint16_t donorItemId, uint8_t tier, uint16_t receiveItemId, bool convergence) { ForgeHistory history; - history.actionType = ForgeConversion_t::FORGE_ACTION_TRANSFER; + history.actionType = actionType; history.tier = tier; history.success = true; @@ -7184,27 +7287,27 @@ void Player::forgeTransferItemTier(uint16_t donorItemId, uint8_t tier, uint16_t return; } - std::shared_ptr newDonorItem = Item::CreateItem(donorItemId, 1); - if (!newDonorItem) { - g_logger().error("[Log 4] Player with name {} failed to transfer item with id {}", getName(), donorItemId); + std::shared_ptr newReceiveItem = Item::CreateItem(receiveItemId, 1); + if (!newReceiveItem) { + g_logger().error("[Log 6] Player with name {} failed to fuse item with id {}", getName(), receiveItemId); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } - returnValue = g_game().internalAddItem(exaltationContainer, newDonorItem, INDEX_WHEREEVER); - if (returnValue != RETURNVALUE_NOERROR) { - g_logger().error("[Log 5] Failed to add forge item {} from player with name {}", donorItemId, getName()); - sendCancelMessage(getReturnMessage(returnValue)); + + auto configKey = convergence ? FORGE_CONVERGENCE_TRANSFER_DUST_COST : FORGE_TRANSFER_DUST_COST; + if (getForgeDusts() < g_configManager().getNumber(configKey, __FUNCTION__)) { + g_logger().error("[Log 8] Failed to remove transfer dusts from player with name {}", getName()); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; + } else { + setForgeDusts(getForgeDusts() - g_configManager().getNumber(configKey, __FUNCTION__)); } - std::shared_ptr newReceiveItem = Item::CreateItem(receiveItemId, 1); - if (!newReceiveItem) { - g_logger().error("[Log 6] Player with name {} failed to fuse item with id {}", getName(), receiveItemId); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; + if (convergence) { + newReceiveItem->setTier(tier); + } else { + newReceiveItem->setTier(tier - 1); } - newReceiveItem->setTier(tier - 1); returnValue = g_game().internalAddItem(exaltationContainer, newReceiveItem, INDEX_WHEREEVER); if (returnValue != RETURNVALUE_NOERROR) { g_logger().error("[Log 7] Failed to add forge item {} from player with name {}", receiveItemId, getName()); @@ -7213,28 +7316,21 @@ void Player::forgeTransferItemTier(uint16_t donorItemId, uint8_t tier, uint16_t return; } - if (getForgeDusts() < g_configManager().getNumber(FORGE_TRANSFER_DUST_COST, __FUNCTION__)) { - g_logger().error("[Log 8] Failed to remove transfer dusts from player with name {}", getName()); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; - } else { - setForgeDusts(getForgeDusts() - g_configManager().getNumber(FORGE_TRANSFER_DUST_COST, __FUNCTION__)); - } - uint8_t coresAmount = 0; uint64_t cost = 0; for (const auto &itemClassification : g_game().getItemsClassifications()) { if (itemClassification->id != donorItem->getClassification()) { continue; } - - for (const auto &[mapTier, mapPrice] : itemClassification->tiers) { - if (mapTier == donorItem->getTier() - 1) { - cost = mapPrice.priceToUpgrade; - coresAmount = mapPrice.corePriceToFuse; - break; - } + if (!itemClassification->tiers.contains(donorItem->getTier())) { + g_logger().error("[{}] Failed to find tier {} for item {} in classification {}", __FUNCTION__, donorItem->getTier(), donorItem->getClassification(), itemClassification->id); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); + break; } + auto tierPriecs = itemClassification->tiers.at(donorItem->getTier()); + cost = convergence ? tierPriecs.convergenceTransferPrice : tierPriecs.regularPrice; + coresAmount = tierPriecs.corePrice; + break; } if (!removeItemCountById(ITEM_FORGE_CORE, coresAmount)) { @@ -7259,22 +7355,22 @@ void Player::forgeTransferItemTier(uint16_t donorItemId, uint8_t tier, uint16_t return; } - history.firstItemName = newDonorItem->getName(); + history.firstItemName = Item::items[donorItemId].name; history.secondItemName = newReceiveItem->getName(); history.createdAt = getTimeNow(); + history.convergence = convergence; registerForgeHistoryDescription(history); - sendTransferItemTier(donorItemId, tier, receiveItemId); + sendForgeResult(actionType, donorItemId, tier, receiveItemId, convergence ? tier : tier - 1, true, 0, 0, convergence); } -void Player::forgeResourceConversion(uint8_t action) { - auto actionEnum = magic_enum::enum_value(action); +void Player::forgeResourceConversion(ForgeAction_t actionType) { ForgeHistory history; - history.actionType = actionEnum; + history.actionType = actionType; history.success = true; ReturnValue returnValue = RETURNVALUE_NOERROR; - if (actionEnum == ForgeConversion_t::FORGE_ACTION_DUSTTOSLIVERS) { + if (actionType == ForgeAction_t::DUSTTOSLIVERS) { auto dusts = getForgeDusts(); auto cost = static_cast(g_configManager().getNumber(FORGE_COST_ONE_SLIVER, __FUNCTION__) * g_configManager().getNumber(FORGE_SLIVER_AMOUNT, __FUNCTION__)); if (cost > dusts) { @@ -7295,7 +7391,7 @@ void Player::forgeResourceConversion(uint8_t action) { history.cost = cost; history.gained = 3; setForgeDusts(dusts - cost); - } else if (actionEnum == ForgeConversion_t::FORGE_ACTION_SLIVERSTOCORES) { + } else if (actionType == ForgeAction_t::SLIVERSTOCORES) { auto [sliverCount, coreCount] = getForgeSliversAndCores(); auto cost = static_cast(g_configManager().getNumber(FORGE_CORE_COST, __FUNCTION__)); if (cost > sliverCount) { @@ -7361,10 +7457,10 @@ void Player::registerForgeHistoryDescription(ForgeHistory history) { std::stringstream detailsResponse; auto itemId = Item::items.getItemIdByName(history.firstItemName); const ItemType &itemType = Item::items[itemId]; - if (history.actionType == ForgeConversion_t::FORGE_ACTION_FUSION) { + if (history.actionType == ForgeAction_t::FUSION) { if (history.success) { detailsResponse << fmt::format( - "{:s}

" + "{:s}{:s}

" "Fusion partners:" "
    " "
  • " @@ -7398,6 +7494,7 @@ void Player::registerForgeHistoryDescription(ForgeHistory history) { "
  • " "
", successfulString, + history.convergence ? " (convergence)" : "", itemType.article, itemType.name, std::to_string(history.tier), itemType.article, itemType.name, std::to_string(history.tier), history.bonus == 8 ? "unchanged" : "consumed", @@ -7405,7 +7502,7 @@ void Player::registerForgeHistoryDescription(ForgeHistory history) { ); } else { detailsResponse << fmt::format( - "{:s}

" + "{:s}{:s}

" "Fusion partners:" "
    " "
  • " @@ -7439,15 +7536,16 @@ void Player::registerForgeHistoryDescription(ForgeHistory history) { "
  • " "
", successfulString, + history.convergence ? " (convergence)" : "", itemType.article, itemType.name, std::to_string(history.tier), itemType.article, itemType.name, std::to_string(history.tier), history.bonus == 8 ? "unchanged" : historyTierString, history.coresCost, price ); } - } else if (history.actionType == ForgeConversion_t::FORGE_ACTION_TRANSFER) { + } else if (history.actionType == ForgeAction_t::TRANSFER) { detailsResponse << fmt::format( - "{:s}

" + "{:s}{:s}

" "Transfer partners:" "
    " "
  • " @@ -7481,19 +7579,20 @@ void Player::registerForgeHistoryDescription(ForgeHistory history) { "
  • " "
", successfulString, + history.convergence ? " (convergence)" : "", itemType.article, itemType.name, std::to_string(history.tier), itemType.article, itemType.name, std::to_string(history.tier), itemType.article, itemType.name, std::to_string(history.tier), itemType.article, itemType.name, std::to_string(history.tier), price ); - } else if (history.actionType == ForgeConversion_t::FORGE_ACTION_DUSTTOSLIVERS) { + } else if (history.actionType == ForgeAction_t::DUSTTOSLIVERS) { detailsResponse << fmt::format("Converted {:d} dust to {:d} slivers.", history.cost, history.gained); - } else if (history.actionType == ForgeConversion_t::FORGE_ACTION_SLIVERSTOCORES) { - history.actionType = ForgeConversion_t::FORGE_ACTION_DUSTTOSLIVERS; + } else if (history.actionType == ForgeAction_t::SLIVERSTOCORES) { + history.actionType = ForgeAction_t::DUSTTOSLIVERS; detailsResponse << fmt::format("Converted {:d} slivers to {:d} exalted core.", history.cost, history.gained); - } else if (history.actionType == ForgeConversion_t::FORGE_ACTION_INCREASELIMIT) { - history.actionType = ForgeConversion_t::FORGE_ACTION_DUSTTOSLIVERS; + } else if (history.actionType == ForgeAction_t::INCREASELIMIT) { + history.actionType = ForgeAction_t::DUSTTOSLIVERS; detailsResponse << fmt::format("Spent {:d} dust to increase the dust limit to {:d}.", history.cost, history.gained + 1); } else { detailsResponse << "(unknown)"; @@ -7844,6 +7943,18 @@ bool Player::hasPermittedConditionInPZ() const { return hasPermittedCondition; } +uint16_t Player::getDodgeChance() const { + uint16_t chance = 0; + if (auto playerArmor = getInventoryItem(CONST_SLOT_ARMOR); + playerArmor != nullptr && playerArmor->getTier()) { + chance += static_cast(playerArmor->getDodgeChance() * 100); + } + + chance += m_wheelPlayer->getStat(WheelStat_t::DODGE); + + return chance; +} + void Player::checkAndShowBlessingMessage() { auto adventurerBlessingLevel = g_configManager().getNumber(ADVENTURERSBLESSING_LEVEL, __FUNCTION__); auto willNotLoseBless = getLevel() < adventurerBlessingLevel && getVocationId() > VOCATION_NONE; diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 521399425e9..ecf489b89ec 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -17,6 +17,7 @@ #include "items/containers/depot/depotchest.hpp" #include "items/containers/depot/depotlocker.hpp" #include "grouping/familiars.hpp" +#include "enums/forge_conversion.hpp" #include "grouping/groups.hpp" #include "grouping/guild.hpp" #include "imbuements/imbuements.hpp" @@ -49,16 +50,8 @@ class Spell; class PlayerWheel; class Spectators; -enum class ForgeConversion_t : uint8_t { - FORGE_ACTION_FUSION = 0, - FORGE_ACTION_TRANSFER = 1, - FORGE_ACTION_DUSTTOSLIVERS = 2, - FORGE_ACTION_SLIVERSTOCORES = 3, - FORGE_ACTION_INCREASELIMIT = 4 -}; - struct ForgeHistory { - ForgeConversion_t actionType = ForgeConversion_t::FORGE_ACTION_FUSION; + ForgeAction_t actionType = ForgeAction_t::FUSION; uint8_t tier = 0; uint8_t bonus = 0; @@ -75,6 +68,7 @@ struct ForgeHistory { bool tierLoss = false; bool successCore = false; bool tierCore = false; + bool convergence = false; std::string description; std::string firstItemName; @@ -172,6 +166,7 @@ class Player final : public Creature, public Cylinder, public Bankable { bool hasAnyMount() const; uint8_t getRandomMountId() const; void dismount(); + uint16_t getDodgeChance() const; uint8_t isRandomMounted() const { return randomMount; @@ -782,8 +777,9 @@ class Player final : public Creature, public Cylinder, public Bankable { void onReceiveMail(); bool isNearDepotBox(); - std::shared_ptr setLootContainer(ObjectCategory_t category, std::shared_ptr container, bool loading = false); - std::shared_ptr getLootContainer(ObjectCategory_t category) const; + std::shared_ptr refreshManagedContainer(ObjectCategory_t category, std::shared_ptr container, bool isLootContainer, bool loading = false); + std::shared_ptr getManagedContainer(ObjectCategory_t category, bool isLootContainer) const; + void setMainBackpackUnassigned(std::shared_ptr container); bool canSee(const Position &pos) override; bool canSeeCreature(std::shared_ptr creature) const override; @@ -880,7 +876,7 @@ class Player final : public Creature, public Cylinder, public Bankable { BlockType_t blockHit(std::shared_ptr attacker, CombatType_t combatType, int32_t &damage, bool checkDefense = false, bool checkArmor = false, bool field = false) override; void doAttacking(uint32_t interval) override; bool hasExtraSwing() override { - return lastAttack > 0 && ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()); + return lastAttack > 0 && !checkLastAttackWithin(getAttackSpeed()); } uint16_t getSkillLevel(skills_t skill) const; @@ -895,10 +891,48 @@ class Player final : public Creature, public Cylinder, public Bankable { bool getAddAttackSkill() const { return addAttackSkillPoint; } + BlockType_t getLastAttackBlockType() const { return lastAttackBlockType; } + uint64_t getLastAttack() const { + return lastAttack; + } + + bool checkLastAttackWithin(uint32_t interval) const { + return lastAttack > 0 && ((OTSYS_TIME() - lastAttack) < interval); + } + + void updateLastAttack() { + if (lastAttack == 0) { + lastAttack = OTSYS_TIME() - getAttackSpeed() - 1; + return; + } + lastAttack = OTSYS_TIME(); + } + + uint64_t getLastAggressiveAction() const { + return lastAggressiveAction; + } + + bool checkLastAggressiveActionWithin(uint32_t interval) const { + return lastAggressiveAction > 0 && ((OTSYS_TIME() - lastAggressiveAction) < interval); + } + + void updateLastAggressiveAction() { + lastAggressiveAction = OTSYS_TIME(); + } + + uint64_t getLastFocusLost() const { + return lastFocusLost; + } + void setLastFocusLost(uint64_t time) { + lastFocusLost = time; + } + + std::unordered_set getNPCSkips(); + std::shared_ptr getWeapon(Slots_t slot, bool ignoreAmmo) const; std::shared_ptr getWeapon(bool ignoreAmmo = false) const; WeaponType_t getWeaponType() const; @@ -2330,9 +2364,9 @@ class Player final : public Creature, public Cylinder, public Bankable { ); // Forge system - void forgeFuseItems(uint16_t itemid, uint8_t tier, bool success, bool reduceTierLoss, uint8_t bonus, uint8_t coreCount); - void forgeTransferItemTier(uint16_t donorItemId, uint8_t tier, uint16_t receiveItemId); - void forgeResourceConversion(uint8_t action); + void forgeFuseItems(ForgeAction_t actionType, uint16_t firstItemid, uint8_t tier, uint16_t secondItemId, bool success, bool reduceTierLoss, bool convergence, uint8_t bonus, uint8_t coreCount); + void forgeTransferItemTier(ForgeAction_t actionType, uint16_t donorItemId, uint8_t tier, uint16_t receiveItemId, bool convergence); + void forgeResourceConversion(ForgeAction_t actionType); void forgeHistory(uint8_t page) const; void sendOpenForge() const { @@ -2345,14 +2379,9 @@ class Player final : public Creature, public Cylinder, public Bankable { client->sendForgeError(returnValue); } } - void sendForgeFusionItem(uint16_t itemId, uint8_t tier, bool success, uint8_t bonus, uint8_t coreCount) const { + void sendForgeResult(ForgeAction_t actionType, uint16_t leftItemId, uint8_t leftTier, uint16_t rightItemId, uint8_t rightTier, bool success, uint8_t bonus, uint8_t coreCount, bool convergence) const { if (client) { - client->sendForgeFusionItem(itemId, tier, success, bonus, coreCount); - } - } - void sendTransferItemTier(uint16_t firstItem, uint8_t tier, uint16_t secondItem) const { - if (client) { - client->sendTransferItemTier(firstItem, tier, secondItem); + client->sendForgeResult(actionType, leftItemId, leftTier, rightItemId, rightTier, success, bonus, coreCount, convergence); } } void sendForgeHistory(uint8_t page) const { @@ -2518,12 +2547,32 @@ class Player final : public Creature, public Cylinder, public Bankable { return timeLeft > 0; } - bool checkAutoLoot() const { - const bool autoLoot = g_configManager().getBoolean(AUTOLOOT, __FUNCTION__) && getStorageValue(STORAGEVALUE_AUTO_LOOT) != 0; - if (g_configManager().getBoolean(VIP_SYSTEM_ENABLED, __FUNCTION__) && g_configManager().getBoolean(VIP_AUTOLOOT_VIP_ONLY, __FUNCTION__)) { - return autoLoot && isVip(); + bool checkAutoLoot(bool isBoss) const { + const bool autoLoot = g_configManager().getBoolean(AUTOLOOT, __FUNCTION__); + if (!autoLoot) { + return false; + } + if (g_configManager().getBoolean(VIP_SYSTEM_ENABLED, __FUNCTION__) && g_configManager().getBoolean(VIP_AUTOLOOT_VIP_ONLY, __FUNCTION__) && !isVip()) { + return false; } - return autoLoot; + + auto featureKV = kv()->scoped("features")->get("autoloot"); + if (featureKV.has_value()) { + auto value = featureKV->getNumber(); + if (value == 2) { + return true; + } else if (value == 1) { + return !isBoss; + } else if (value == 0) { + return false; + } + } + + return true; + } + + QuickLootFilter_t getQuickLootFilter() const { + return quickLootFilter; } // Get specific inventory item from itemid @@ -2566,7 +2615,7 @@ class Player final : public Creature, public Cylinder, public Bankable { void checkTradeState(std::shared_ptr item); bool hasCapacity(std::shared_ptr item, uint32_t count) const; - void checkLootContainers(std::shared_ptr item); + void checkLootContainers(std::shared_ptr item); void gainExperience(uint64_t exp, std::shared_ptr target); void addExperience(std::shared_ptr target, uint64_t exp, bool sendText = false); @@ -2640,12 +2689,12 @@ class Player final : public Creature, public Cylinder, public Bankable { std::map maxValuePerSkill = { { SKILL_LIFE_LEECH_CHANCE, 100 }, { SKILL_MANA_LEECH_CHANCE, 100 }, - { SKILL_CRITICAL_HIT_CHANCE, g_configManager().getNumber(CRITICALCHANCE, "std::map::maxValuePerSkill") } + { SKILL_CRITICAL_HIT_CHANCE, 100 * g_configManager().getNumber(CRITICALCHANCE, "std::map::maxValuePerSkill") } }; std::map> rewardMap; - std::map> quickLootContainers; + std::map, std::shared_ptr>> m_managedContainers; std::vector forgeHistoryVector; std::vector quickLootListItemIds; @@ -2682,6 +2731,8 @@ class Player final : public Creature, public Cylinder, public Bankable { uint64_t experience = 0; uint64_t manaSpent = 0; uint64_t lastAttack = 0; + uint64_t lastAggressiveAction = 0; + uint64_t lastFocusLost = 0; uint64_t bankBalance = 0; uint64_t lastQuestlogUpdate = 0; uint64_t preyCards = 0; @@ -2917,6 +2968,7 @@ class Player final : public Creature, public Cylinder, public Bankable { void triggerMomentum(); void clearCooldowns(); + void triggerTranscendance(); friend class Game; friend class SaveManager; diff --git a/src/creatures/players/vocations/vocation.cpp b/src/creatures/players/vocations/vocation.cpp index a27413b61f6..fc07cea986f 100644 --- a/src/creatures/players/vocations/vocation.cpp +++ b/src/creatures/players/vocations/vocation.cpp @@ -111,6 +111,10 @@ bool Vocations::loadFromXml() { voc.combat = attr.as_bool(); } + if ((attr = vocationNode.attribute("avatarlooktype"))) { + voc.avatarLookType = pugi::cast(attr.value()); + } + for (auto childNode : vocationNode.children()) { if (strcasecmp(childNode.name(), "skill") == 0) { pugi::xml_attribute skillIdAttribute = childNode.attribute("id"); @@ -173,6 +177,12 @@ bool Vocations::loadFromXml() { if (pvpDamageDealtMultiplier) { voc.pvpDamageDealtMultiplier = pugi::cast(pvpDamageDealtMultiplier.value()); } + } else if (strcasecmp(childNode.name(), "gem") == 0) { + pugi::xml_attribute qualityAttr = childNode.attribute("quality"); + pugi::xml_attribute nameAttr = childNode.attribute("name"); + auto quality = pugi::cast(qualityAttr.value()); + auto name = nameAttr.as_string(); + voc.wheelGems[static_cast(quality)] = name; } } } @@ -272,3 +282,22 @@ uint64_t Vocation::getReqMana(uint32_t magLevel) { cacheMana[magLevel] = reqMana; return reqMana; } + +std::vector Vocation::getSupremeGemModifiers() { + if (!m_supremeGemModifiers.empty()) { + return m_supremeGemModifiers; + } + auto baseVocation = g_vocations().getVocation(getBaseId()); + auto vocationName = asLowerCaseString(baseVocation->getVocName()); + auto allModifiers = magic_enum::enum_entries(); + g_logger().debug("Loading supreme gem modifiers for vocation: {}", vocationName); + for (const auto &[value, modifierName] : allModifiers) { + std::string targetVocation(modifierName.substr(0, modifierName.find("_"))); + toLowerCaseString(targetVocation); + g_logger().debug("Checking supreme gem modifier: {}, targetVocation: {}", modifierName, targetVocation); + if (targetVocation == "general" || targetVocation.find(vocationName) != std::string::npos) { + m_supremeGemModifiers.push_back(value); + } + } + return m_supremeGemModifiers; +} diff --git a/src/creatures/players/vocations/vocation.hpp b/src/creatures/players/vocations/vocation.hpp index 45f5d29d361..8f81e3805e3 100644 --- a/src/creatures/players/vocations/vocation.hpp +++ b/src/creatures/players/vocations/vocation.hpp @@ -12,6 +12,7 @@ #include "declarations.hpp" #include "items/item.hpp" #include "lib/di/container.hpp" +#include "creatures/players/wheel/wheel_gems.hpp" class Vocation { public: @@ -41,6 +42,10 @@ class Vocation { return baseId; } + uint16_t getAvatarLookType() const { + return avatarLookType; + } + uint32_t getHPGain() const { return gainHP; } @@ -110,6 +115,16 @@ class Vocation { float pvpDamageReceivedMultiplier = 1.0f; float pvpDamageDealtMultiplier = 1.0f; + std::vector getSupremeGemModifiers(); + + uint16_t getWheelGemId(WheelGemQuality_t quality) { + if (!wheelGems.contains(quality)) { + return 0; + } + const auto &name = wheelGems[quality]; + return Item::items.getItemIdByName(name); + } + private: friend class Vocations; @@ -117,6 +132,7 @@ class Vocation { std::map cacheManaTotal; std::map cacheSkill[SKILL_LAST + 1]; std::map cacheSkillTotal[SKILL_LAST + 1]; + std::map wheelGems; std::string name = "none"; std::string description; @@ -144,6 +160,9 @@ class Vocation { uint8_t soulMax = 100; uint8_t clientId = 0; uint8_t baseId = 0; + uint16_t avatarLookType = 0; + + std::vector m_supremeGemModifiers; static uint32_t skillBase[SKILL_LAST + 1]; }; diff --git a/src/creatures/players/wheel/player_wheel.cpp b/src/creatures/players/wheel/player_wheel.cpp index a60d8124a76..dfd685bd83f 100644 --- a/src/creatures/players/wheel/player_wheel.cpp +++ b/src/creatures/players/wheel/player_wheel.cpp @@ -17,6 +17,21 @@ #include "creatures/players/player.hpp" #include "creatures/combat/spells.hpp" +const static std::vector wheelGemBasicSlot1Allowed = { + WheelGemBasicModifier_t::General_FireResistance, + WheelGemBasicModifier_t::General_IceResistance, + WheelGemBasicModifier_t::General_EnergyResistance, + WheelGemBasicModifier_t::General_EarthResistance, + WheelGemBasicModifier_t::General_MitigationMultiplier, + WheelGemBasicModifier_t::Vocation_Health, + WheelGemBasicModifier_t::Vocation_Mana, + WheelGemBasicModifier_t::Vocation_Capacity, + WheelGemBasicModifier_t::Vocation_Health_FireResistance, + WheelGemBasicModifier_t::Vocation_Health_IceResistance, + WheelGemBasicModifier_t::Vocation_Health_EnergyResistance, + WheelGemBasicModifier_t::Vocation_Health_EarthResistance, +}; + // To avoid conflict in other files that might use a function with the same name // Here are built-in helper functions namespace { @@ -703,6 +718,211 @@ void PlayerWheel::addPromotionScrolls(NetworkMessage &msg) const { } } +std::shared_ptr PlayerWheel::gemsKV() const { + return m_player.kv()->scoped("wheel-of-destiny")->scoped("gems"); +} + +std::vector PlayerWheel::getRevealedGems() const { + std::vector unlockedGems; + auto unlockedGemUUIDs = gemsKV()->scoped("revealed")->keys(); + if (unlockedGemUUIDs.empty()) { + return unlockedGems; + } + std::vector sortedUnlockedGemGUIDs; + for (const auto &uuid : unlockedGemUUIDs) { + sortedUnlockedGemGUIDs.push_back(uuid); + } + std::sort(sortedUnlockedGemGUIDs.begin(), sortedUnlockedGemGUIDs.end(), [](const std::string &a, const std::string &b) { + return std::stoull(a) < std::stoull(b); + }); + + for (const auto &uuid : sortedUnlockedGemGUIDs) { + auto gem = PlayerWheelGem::load(gemsKV(), uuid); + if (gem.uuid.empty()) { + continue; + } + unlockedGems.push_back(gem); + } + return unlockedGems; +} + +std::vector PlayerWheel::getActiveGems() const { + std::vector activeGems; + for (auto affinity : magic_enum::enum_values()) { + std::string key(magic_enum::enum_name(affinity)); + auto uuidKV = gemsKV()->scoped("active")->get(key); + if (!uuidKV.has_value()) { + continue; + } + + auto uuid = uuidKV->get(); + if (uuid.empty()) { + continue; + } + auto gem = PlayerWheelGem::load(gemsKV(), uuid); + if (gem.uuid.empty()) { + continue; + } + activeGems.push_back(gem); + } + return activeGems; +} + +void PlayerWheel::revealGem(WheelGemQuality_t quality) { + uint16_t gemId = m_player.getVocation()->getWheelGemId(quality); + if (gemId == 0) { + g_logger().error("[{}] Failed to get gem id for quality {} and vocation {}", __FUNCTION__, fmt::underlying(quality), m_player.getVocation()->getVocName()); + return; + } + if (!m_player.hasItemCountById(gemId, 1, false)) { + g_logger().error("[{}] Player {} does not have gem with id {}", __FUNCTION__, m_player.getName(), gemId); + return; + } + auto goldCost = getGemRevealCost(quality); + if (!g_game().removeMoney(m_player.getPlayer(), goldCost, 0, true)) { + g_logger().error("[{}] Failed to remove {} gold from player with name {}", __FUNCTION__, goldCost, m_player.getName()); + return; + } + if (!m_player.removeItemCountById(gemId, 1, false)) { + g_logger().error("[{}] Failed to remove gem with id {} from player with name {}", __FUNCTION__, gemId, m_player.getName()); + return; + } + auto supremeModifiers = m_player.getVocation()->getSupremeGemModifiers(); + PlayerWheelGem gem; + gem.uuid = KV::generateUUID(); + gem.locked = false; + gem.affinity = static_cast(uniform_random(0, 3)); + gem.quality = quality; + gem.basicModifier1 = wheelGemBasicSlot1Allowed[uniform_random(0, wheelGemBasicSlot1Allowed.size() - 1)]; + gem.basicModifier2 = {}; + gem.supremeModifier = {}; + if (quality >= WheelGemQuality_t::Regular) { + gem.basicModifier2 = static_cast(uniform_random(0, magic_enum::enum_count() - 1)); + } + if (quality >= WheelGemQuality_t::Greater && !supremeModifiers.empty()) { + gem.supremeModifier = supremeModifiers[uniform_random(0, supremeModifiers.size() - 1)]; + } + g_logger().debug("[{}] {}", __FUNCTION__, gem.toString()); + gem.save(gemsKV()); + sendOpenWheelWindow(m_player.getID()); +} + +PlayerWheelGem PlayerWheel::getGem(uint8_t index) const { + auto gems = getRevealedGems(); + if (gems.size() <= index) { + g_logger().error("[{}] Player {} trying to get gem with index {} but has only {} gems", __FUNCTION__, m_player.getName(), index, gems.size()); + return {}; + } + return gems[index]; +} + +PlayerWheelGem PlayerWheel::getGem(const std::string &uuid) const { + auto gem = PlayerWheelGem::load(gemsKV(), uuid); + if (gem.uuid.empty()) { + g_logger().error("[{}] Failed to load gem with uuid {}", __FUNCTION__, uuid); + return {}; + } + return gem; +} + +uint8_t PlayerWheel::getGemIndex(const std::string &uuid) const { + auto gems = getRevealedGems(); + for (uint8_t i = 0; i < gems.size(); ++i) { + if (gems[i].uuid == uuid) { + return i; + } + } + g_logger().error("[{}] Failed to find gem with uuid {}", __FUNCTION__, uuid); + return 0xFF; +} + +void PlayerWheel::destroyGem(uint8_t index) { + auto gem = getGem(index); + if (gem.locked) { + g_logger().error("[{}] Player {} trying to destroy locked gem with index {}", __FUNCTION__, m_player.getName(), index); + return; + } + gem.remove(gemsKV()); + sendOpenWheelWindow(m_player.getID()); +} + +void PlayerWheel::switchGemDomain(uint8_t index) { + auto gem = getGem(index); + if (gem.locked) { + g_logger().error("[{}] Player {} trying to destroy locked gem with index {}", __FUNCTION__, m_player.getName(), index); + return; + } + auto goldCost = getGemRotateCost(gem.quality); + if (!g_game().removeMoney(m_player.getPlayer(), goldCost, 0, true)) { + g_logger().error("[{}] Failed to remove {} gold from player with name {}", __FUNCTION__, goldCost, m_player.getName()); + return; + } + + auto gemAffinity = convertWheelGemAffinityToDomain(static_cast(gem.affinity)); + gem.affinity = static_cast(gemAffinity); + gem.save(gemsKV()); + sendOpenWheelWindow(m_player.getID()); +} + +void PlayerWheel::toggleGemLock(uint8_t index) { + auto gem = getGem(index); + gem.locked = !gem.locked; + gem.save(gemsKV()); + sendOpenWheelWindow(m_player.getID()); +} + +void PlayerWheel::setActiveGem(WheelGemAffinity_t affinity, uint8_t index) { + auto gem = getGem(index); + if (gem.uuid.empty()) { + g_logger().error("[{}] Failed to load gem with index {}", __FUNCTION__, index); + return; + } + if (gem.affinity != affinity) { + g_logger().error("[{}] Gem with index {} has affinity {} but trying to set it to {}", __FUNCTION__, index, fmt::underlying(gem.affinity), fmt::underlying(affinity)); + return; + } + std::string key(magic_enum::enum_name(affinity)); + gemsKV()->scoped("active")->set(key, gem.uuid); +} + +void PlayerWheel::removeActiveGem(WheelGemAffinity_t affinity) { + std::string key(magic_enum::enum_name(affinity)); + gemsKV()->scoped("active")->remove(key); +} + +void PlayerWheel::addGems(NetworkMessage &msg) const { + auto activeGems = getActiveGems(); + msg.addByte(activeGems.size()); + g_logger().debug("[{}] Player {} has {} active gems", __FUNCTION__, m_player.getName(), activeGems.size()); + for (const auto &gem : activeGems) { + auto index = getGemIndex(gem.uuid); + g_logger().debug("[{}] Adding active gem: {} with index {}", __FUNCTION__, gem.toString(), index); + msg.addByte(getGemIndex(gem.uuid)); + } + + auto revealedGems = getRevealedGems(); + if (revealedGems.size() > 225) { + g_logger().error("[{}] Player {} has more than 225 gems unlocked", __FUNCTION__, m_player.getName()); + revealedGems.resize(225); + } + msg.addByte(revealedGems.size()); + int index = 0; + for (const auto &gem : revealedGems) { + g_logger().debug("[{}] Adding revealed gem: {}", __FUNCTION__, gem.toString()); + msg.addByte(index++); + msg.addByte(gem.locked); + msg.addByte(static_cast(gem.affinity)); + msg.addByte(static_cast(gem.quality)); + msg.addByte(static_cast(gem.basicModifier1)); + if (gem.quality >= WheelGemQuality_t::Regular) { + msg.addByte(static_cast(gem.basicModifier2)); + } + if (gem.quality >= WheelGemQuality_t::Greater) { + msg.addByte(static_cast(gem.supremeModifier)); + } + } +} + void PlayerWheel::sendOpenWheelWindow(NetworkMessage &msg, uint32_t ownerId) const { if (m_player.client && m_player.client->oldProtocol) { return; @@ -725,6 +945,14 @@ void PlayerWheel::sendOpenWheelWindow(NetworkMessage &msg, uint32_t ownerId) con msg.add(getPointsBySlotType(i)); } addPromotionScrolls(msg); + addGems(msg); + // TODO: read items from inventory + auto voc = m_player.getVocation(); + m_player.client->sendResourceBalance(RESOURCE_BANK, m_player.getBankBalance()); + m_player.client->sendResourceBalance(RESOURCE_INVENTORY, m_player.getMoney()); + m_player.client->sendResourceBalance(RESOURCE_LESSER_GEMS, m_player.getItemTypeCount(voc->getWheelGemId(WheelGemQuality_t::Lesser))); + m_player.client->sendResourceBalance(RESOURCE_REGULAR_GEMS, m_player.getItemTypeCount(voc->getWheelGemId(WheelGemQuality_t::Regular))); + m_player.client->sendResourceBalance(RESOURCE_GREATER_GEMS, m_player.getItemTypeCount(voc->getWheelGemId(WheelGemQuality_t::Greater))); } void PlayerWheel::sendGiftOfLifeCooldown() const { @@ -844,6 +1072,17 @@ void PlayerWheel::saveSlotPointsOnPressSaveButton(NetworkMessage &msg) { g_logger().error("[parseSaveWheel] Player '{}' tried to select a slot without the valid requirements", m_player.getName()); } + // Gem Vessels + for (auto affinity : magic_enum::enum_values()) { + bool hasGem = msg.getByte(); + if (!hasGem) { + removeActiveGem(affinity); + continue; + } + uint8_t gemIndex = msg.getByte(); + setActiveGem(affinity, gemIndex); + } + // Player's bonus data is loaded, initialized, and registered, and the function logs loadPlayerBonusData(); initializePlayerData(); @@ -1051,15 +1290,15 @@ void PlayerWheel::initializePlayerData() { void PlayerWheel::setPlayerCombatStats(CombatType_t type, int32_t leechAmount) { if (type == COMBAT_LIFEDRAIN) { if (leechAmount > 0) { - setStat(WheelStat_t::LIFE_LEECH, leechAmount); + addStat(WheelStat_t::LIFE_LEECH, leechAmount); } else { - setStat(WheelStat_t::LIFE_LEECH, 0); + addStat(WheelStat_t::LIFE_LEECH, 0); } } else if (type == COMBAT_MANADRAIN) { if (leechAmount > 0) { - setStat(WheelStat_t::MANA_LEECH, leechAmount); + addStat(WheelStat_t::MANA_LEECH, leechAmount); } else { - setStat(WheelStat_t::MANA_LEECH, 0); + addStat(WheelStat_t::MANA_LEECH, 0); } } } @@ -1078,27 +1317,49 @@ void PlayerWheel::reloadPlayerData() { } void PlayerWheel::registerPlayerBonusData() { - // Reset stages and spell data resetUpgradedSpells(); - // Reset resistance resetResistance(); - // Stats - setStat(WheelStat_t::HEALTH, m_playerBonusData.stats.health); - setStat(WheelStat_t::MANA, m_playerBonusData.stats.mana); - setStat(WheelStat_t::CAPACITY, m_playerBonusData.stats.capacity * 100); - setStat(WheelStat_t::MITIGATION, m_playerBonusData.mitigation * 100); - setStat(WheelStat_t::DAMAGE, m_playerBonusData.stats.damage); - setStat(WheelStat_t::HEALING, m_playerBonusData.stats.healing); - - // Resistance - for (uint16_t i = 0; i < COMBAT_COUNT; ++i) { - setResistance(indexToCombatType(i), m_playerBonusData.resistance[i]); - } + resetStats(); + resetRevelationBonus(); + if (!m_modifierContext) { + m_modifierContext = std::make_unique(*this, static_cast(m_player.getVocation()->getBaseId())); + } + m_modifierContext->resetStrategies(); + m_spellsBonuses.clear(); + + addStat(WheelStat_t::HEALTH, m_playerBonusData.stats.health); + addStat(WheelStat_t::MANA, m_playerBonusData.stats.mana); + addStat(WheelStat_t::CAPACITY, m_playerBonusData.stats.capacity * 100); + addStat(WheelStat_t::MITIGATION, m_playerBonusData.mitigation * 100); + addStat(WheelStat_t::DAMAGE, m_playerBonusData.stats.damage); + addStat(WheelStat_t::HEALING, m_playerBonusData.stats.healing); + + auto activeGems = getActiveGems(); + std::string playerName = m_player.getName(); + for (const auto &gem : activeGems) { + auto count = m_playerBonusData.unlockedVesselResonances[static_cast(gem.affinity)]; + if (count >= 1) { + std::string modifierName(magic_enum::enum_name(gem.basicModifier1)); + g_logger().debug("[{}] Adding basic modifier 1 {} to player {} from {} gem affinity {}", __FUNCTION__, modifierName, playerName, magic_enum::enum_name(gem.quality), magic_enum::enum_name(gem.affinity)); + m_modifierContext->addStrategies(gem.basicModifier1); + } + if (count >= 2 && gem.quality >= WheelGemQuality_t::Regular) { + std::string modifierName(magic_enum::enum_name(gem.basicModifier2)); + g_logger().debug("[{}] Adding basic modifier 2 {} to player {} from {} gem affinity {}", __FUNCTION__, modifierName, playerName, magic_enum::enum_name(gem.quality), magic_enum::enum_name(gem.affinity)); + m_modifierContext->addStrategies(gem.basicModifier2); + } + if (count >= 3 && gem.quality >= WheelGemQuality_t::Greater) { + std::string modifierName(magic_enum::enum_name(gem.supremeModifier)); + g_logger().debug("[{}] Adding supreme modifier {} to player {} from {} gem affinity {}", __FUNCTION__, modifierName, playerName, magic_enum::enum_name(gem.quality), magic_enum::enum_name(gem.affinity)); + m_modifierContext->addStrategies(gem.supremeModifier); + } + } + m_modifierContext->executeStrategies(); // Skills - setStat(WheelStat_t::MELEE, m_playerBonusData.skills.melee); - setStat(WheelStat_t::DISTANCE, m_playerBonusData.skills.distance); - setStat(WheelStat_t::MAGIC, m_playerBonusData.skills.magic); + addStat(WheelStat_t::MELEE, m_playerBonusData.skills.melee); + addStat(WheelStat_t::DISTANCE, m_playerBonusData.skills.distance); + addStat(WheelStat_t::MAGIC, m_playerBonusData.skills.magic); // Leech setPlayerCombatStats(COMBAT_LIFEDRAIN, m_playerBonusData.leech.lifeLeech * 100); @@ -1142,6 +1403,16 @@ void PlayerWheel::registerPlayerBonusData() { for (int i = 0; i < m_playerBonusData.stages.divineEmpowerment; ++i) { setSpellInstant("Divine Empowerment", true); } + if (m_playerBonusData.stages.divineEmpowerment >= 2) { + WheelSpells::Bonus bonus; + bonus.decrease.cooldown = 4000; + addSpellBonus("Divine Empowerment", bonus); + } + if (m_playerBonusData.stages.divineEmpowerment >= 3) { + WheelSpells::Bonus bonus; + bonus.decrease.cooldown = 4000; + addSpellBonus("Divine Empowerment", bonus); + } } else { setSpellInstant("Divine Empowerment", false); } @@ -1150,6 +1421,16 @@ void PlayerWheel::registerPlayerBonusData() { for (int i = 0; i < m_playerBonusData.stages.divineGrenade; ++i) { setSpellInstant("Divine Grenade", true); } + if (m_playerBonusData.stages.divineGrenade >= 2) { + WheelSpells::Bonus bonus; + bonus.decrease.cooldown = 4000; + addSpellBonus("Divine Grenade", bonus); + } + if (m_playerBonusData.stages.divineGrenade >= 3) { + WheelSpells::Bonus bonus; + bonus.decrease.cooldown = 6000; + addSpellBonus("Divine Grenade", bonus); + } } else { setSpellInstant("Divine Grenade", false); } @@ -1190,6 +1471,16 @@ void PlayerWheel::registerPlayerBonusData() { for (int i = 0; i < m_playerBonusData.avatar.light; ++i) { setSpellInstant("Avatar of Light", true); } + if (m_playerBonusData.avatar.light >= 2) { + WheelSpells::Bonus bonus; + bonus.decrease.cooldown = 3 * 60 * 1000; + addSpellBonus("Avatar of Light", bonus); + } + if (m_playerBonusData.avatar.light >= 3) { + WheelSpells::Bonus bonus; + bonus.decrease.cooldown = 2 * 60 * 1000; + addSpellBonus("Avatar of Light", bonus); + } } else { setSpellInstant("Avatar of Light", false); } @@ -1198,6 +1489,16 @@ void PlayerWheel::registerPlayerBonusData() { for (int i = 0; i < m_playerBonusData.avatar.nature; ++i) { setSpellInstant("Avatar of Nature", true); } + if (m_playerBonusData.avatar.nature >= 2) { + WheelSpells::Bonus bonus; + bonus.decrease.cooldown = 3 * 60 * 1000; + addSpellBonus("Avatar of Nature", bonus); + } + if (m_playerBonusData.avatar.nature >= 3) { + WheelSpells::Bonus bonus; + bonus.decrease.cooldown = 2 * 60 * 1000; + addSpellBonus("Avatar of Nature", bonus); + } } else { setSpellInstant("Avatar of Nature", false); } @@ -1206,6 +1507,16 @@ void PlayerWheel::registerPlayerBonusData() { for (int i = 0; i < m_playerBonusData.avatar.steel; ++i) { setSpellInstant("Avatar of Steel", true); } + if (m_playerBonusData.avatar.steel >= 2) { + WheelSpells::Bonus bonus; + bonus.decrease.cooldown = 3 * 60 * 1000; + addSpellBonus("Avatar of Steel", bonus); + } + if (m_playerBonusData.avatar.steel >= 3) { + WheelSpells::Bonus bonus; + bonus.decrease.cooldown = 2 * 60 * 1000; + addSpellBonus("Avatar of Steel", bonus); + } } else { setSpellInstant("Avatar of Steel", false); } @@ -1214,6 +1525,16 @@ void PlayerWheel::registerPlayerBonusData() { for (int i = 0; i < m_playerBonusData.avatar.storm; ++i) { setSpellInstant("Avatar of Storm", true); } + if (m_playerBonusData.avatar.storm >= 2) { + WheelSpells::Bonus bonus; + bonus.decrease.cooldown = 3 * 60 * 1000; + addSpellBonus("Avatar of Storm", bonus); + } + if (m_playerBonusData.avatar.storm >= 3) { + WheelSpells::Bonus bonus; + bonus.decrease.cooldown = 2 * 60 * 1000; + addSpellBonus("Avatar of Storm", bonus); + } } else { setSpellInstant("Avatar of Storm", false); } @@ -1270,18 +1591,16 @@ void PlayerWheel::printPlayerWheelMethodsBonusData(const PlayerWheelMethodsBonus g_logger().debug(" healing: {}", bonusData.stats.healing); } - g_logger().debug("Resistance:"); - for (size_t i = 0; i < bonusData.resistance.size(); ++i) { - auto combatValue = bonusData.resistance[i]; - if (combatValue == 0) { + g_logger().debug("Vessel Resonance:"); + for (size_t i = 0; i < bonusData.unlockedVesselResonances.size(); ++i) { + auto count = bonusData.unlockedVesselResonances[i]; + if (count == 0) { continue; } - CombatType_t combatType = indexToCombatType(i); - std::string combatTypeStr = getCombatName(combatType); - // Convert to percentage - float percentage = bonusData.resistance[i] / 100.0f; - g_logger().debug(" combatName: {} value: {} ({}%)", combatTypeStr, bonusData.resistance[i], percentage); + WheelGemAffinity_t affinity = static_cast(i); + std::string affinityName(magic_enum::enum_name(affinity)); + g_logger().debug(" Affinity: {} count: {}", affinityName, bonusData.unlockedVesselResonances[i]); } g_logger().debug("Skills:"); @@ -1513,7 +1832,9 @@ void PlayerWheel::loadRevelationPerks() { WheelStageEnum_t PlayerWheel::getPlayerSliceStage(const std::string &color) const { std::vector slots; + WheelGemAffinity_t affinity = WheelGemAffinity_t::Green; if (color == "green") { + affinity = WheelGemAffinity_t::Green; slots = { WheelSlots_t::SLOT_GREEN_50, WheelSlots_t::SLOT_GREEN_TOP_75, @@ -1526,6 +1847,7 @@ WheelStageEnum_t PlayerWheel::getPlayerSliceStage(const std::string &color) cons WheelSlots_t::SLOT_GREEN_200 }; } else if (color == "red") { + affinity = WheelGemAffinity_t::Red; slots = { WheelSlots_t::SLOT_RED_50, WheelSlots_t::SLOT_RED_TOP_75, @@ -1538,6 +1860,7 @@ WheelStageEnum_t PlayerWheel::getPlayerSliceStage(const std::string &color) cons WheelSlots_t::SLOT_RED_200 }; } else if (color == "purple") { + affinity = WheelGemAffinity_t::Purple; slots = { WheelSlots_t::SLOT_PURPLE_50, WheelSlots_t::SLOT_PURPLE_TOP_75, @@ -1550,6 +1873,7 @@ WheelStageEnum_t PlayerWheel::getPlayerSliceStage(const std::string &color) cons WheelSlots_t::SLOT_PURPLE_200 }; } else if (color == "blue") { + affinity = WheelGemAffinity_t::Blue; slots = { WheelSlots_t::SLOT_BLUE_50, WheelSlots_t::SLOT_BLUE_TOP_75, @@ -1569,6 +1893,8 @@ WheelStageEnum_t PlayerWheel::getPlayerSliceStage(const std::string &color) cons for (const auto &slot : slots) { totalPoints += getPointsBySlotType(slot); } + totalPoints += m_bonusRevelationPoints[static_cast(affinity)]; + if (totalPoints >= static_cast(WheelStagePointsEnum_t::THREE)) { return WheelStageEnum_t::THREE; } else if (totalPoints >= static_cast(WheelStagePointsEnum_t::TWO)) { @@ -1711,7 +2037,7 @@ bool PlayerWheel::checkPositionalTatics() { bool PlayerWheel::checkBallisticMastery() { setOnThinkTimer(WheelOnThink_t::BALLISTIC_MASTERY, OTSYS_TIME() + 2000); bool updateClient = false; - int32_t newCritical = 10; + int32_t newCritical = 1000; uint16_t newHolyBonus = 2; // 2% uint16_t newPhysicalBonus = 2; // 2% @@ -1760,11 +2086,11 @@ bool PlayerWheel::checkCombatMastery() { if (item && item->getSlotPosition() & SLOTP_TWO_HAND) { int32_t criticalSkill = 0; if (stage >= 3) { - criticalSkill = 12; + criticalSkill = 1200; } else if (stage >= 2) { - criticalSkill = 8; + criticalSkill = 800; } else if (stage >= 1) { - criticalSkill = 4; + criticalSkill = 400; } if (getMajorStat(WheelMajor_t::CRITICAL_DMG_2) != criticalSkill) { @@ -1823,13 +2149,14 @@ bool PlayerWheel::checkDivineEmpowerment() { if (isOwner) { uint8_t stage = getStage(WheelStage_t::DIVINE_EMPOWERMENT); if (stage >= 3) { - damageBonus = 12; + damageBonus = 7; } else if (stage >= 2) { - damageBonus = 10; + damageBonus = 5; } else if (stage >= 1) { - damageBonus = 8; + damageBonus = 3; } } + if (damageBonus != getMajorStat(WheelMajor_t::DAMAGE)) { setMajorStat(WheelMajor_t::DAMAGE, damageBonus); updateClient = true; @@ -2030,14 +2357,14 @@ int32_t PlayerWheel::checkAvatarSkill(WheelAvatarSkill_t skill) const { return 5; } } else if (skill == WheelAvatarSkill_t::CRITICAL_CHANCE) { - return 100; + return 10000; } else if (skill == WheelAvatarSkill_t::CRITICAL_DAMAGE) { if (stage >= 3) { - return 15; + return 1500; } else if (stage >= 2) { - return 10; + return 1000; } else if (stage >= 1) { - return 5; + return 500; } } @@ -2064,6 +2391,10 @@ int32_t PlayerWheel::checkElementSensitiveReduction(CombatType_t type) const { void PlayerWheel::onThink(bool force /* = false*/) { bool updateClient = false; m_creaturesNearby = 0; + // Gift of life (Cooldown) + if (getGiftOfCooldown() > 0 /*getInstant("Gift of Life")*/ && getOnThinkTimer(WheelOnThink_t::GIFT_OF_LIFE) <= OTSYS_TIME()) { + decreaseGiftOfCooldown(1); + } if (!m_player.hasCondition(CONDITION_INFIGHT) || m_player.getZoneType() == ZONE_PROTECTION || (!getInstant("Battle Instinct") && !getInstant("Positional Tatics") && !getInstant("Ballistic Mastery") && !getInstant("Gift of Life") && !getInstant("Combat Mastery") && !getInstant("Divine Empowerment") && getGiftOfCooldown() == 0)) { bool mustReset = false; for (int i = 0; i < static_cast(WheelMajor_t::TOTAL_COUNT); i++) { @@ -2097,10 +2428,6 @@ void PlayerWheel::onThink(bool force /* = false*/) { if (getInstant("Ballistic Mastery") && (force || getOnThinkTimer(WheelOnThink_t::BALLISTIC_MASTERY) < OTSYS_TIME()) && checkBallisticMastery()) { updateClient = true; } - // Gift of life (Cooldown) - if (getGiftOfCooldown() > 0 /*getInstant("Gift of Life")*/ && getOnThinkTimer(WheelOnThink_t::GIFT_OF_LIFE) <= OTSYS_TIME()) { - decreaseGiftOfCooldown(1); - } // Combat Mastery if (getInstant("Combat Mastery") && (force || getOnThinkTimer(WheelOnThink_t::COMBAT_MASTERY) < OTSYS_TIME()) && checkCombatMastery()) { updateClient = true; @@ -2188,15 +2515,15 @@ std::shared_ptr PlayerWheel::getCombatDataSpell(CombatDamage &damage) { } if (spell->getWheelOfDestinyUpgraded()) { - damage.criticalDamage += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::CRITICAL_DAMAGE, spellGrade); - damage.criticalChance += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::CRITICAL_CHANCE, spellGrade); - damage.damageMultiplier += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::DAMAGE, spellGrade); - damage.damageReductionMultiplier += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::DAMAGE_REDUCTION, spellGrade); - damage.healingMultiplier += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::HEAL, spellGrade); - damage.manaLeech += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::MANA_LEECH, spellGrade); - damage.manaLeechChance += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::LIFE_LEECH_CHANCE, spellGrade); - damage.lifeLeech += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::LIFE_LEECH, spellGrade); - damage.lifeLeechChance += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::LIFE_LEECH_CHANCE, spellGrade); + damage.criticalDamage += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::CRITICAL_DAMAGE, spellGrade) + getSpellBonus(spell->getName(), WheelSpellBoost_t::CRITICAL_DAMAGE); + damage.criticalChance += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::CRITICAL_CHANCE, spellGrade) + getSpellBonus(spell->getName(), WheelSpellBoost_t::CRITICAL_CHANCE); + damage.damageMultiplier += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::DAMAGE, spellGrade) + getSpellBonus(spell->getName(), WheelSpellBoost_t::DAMAGE); + damage.damageReductionMultiplier += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::DAMAGE_REDUCTION, spellGrade) + getSpellBonus(spell->getName(), WheelSpellBoost_t::DAMAGE_REDUCTION); + damage.healingMultiplier += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::HEAL, spellGrade) + getSpellBonus(spell->getName(), WheelSpellBoost_t::HEAL); + damage.manaLeech += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::MANA_LEECH, spellGrade) + getSpellBonus(spell->getName(), WheelSpellBoost_t::MANA_LEECH); + damage.manaLeechChance += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::LIFE_LEECH_CHANCE, spellGrade) + getSpellBonus(spell->getName(), WheelSpellBoost_t::LIFE_LEECH_CHANCE); + damage.lifeLeech += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::LIFE_LEECH, spellGrade) + getSpellBonus(spell->getName(), WheelSpellBoost_t::LIFE_LEECH); + damage.lifeLeechChance += spell->getWheelOfDestinyBoost(WheelSpellBoost_t::LIFE_LEECH_CHANCE, spellGrade) + getSpellBonus(spell->getName(), WheelSpellBoost_t::LIFE_LEECH_CHANCE); } } @@ -2240,21 +2567,22 @@ void PlayerWheel::setInstant(WheelInstant_t type, bool toggle) { } } -void PlayerWheel::setStat(WheelStat_t type, int32_t value) { +void PlayerWheel::addStat(WheelStat_t type, int32_t value) { auto enumValue = static_cast(type); - try { - m_stats.at(enumValue) = value; - } catch (const std::out_of_range &e) { - g_logger().error("[{}]. Type {} is out of range, value {}. Error message: {}", __FUNCTION__, enumValue, value, e.what()); + if (enumValue >= static_cast(WheelStat_t::TOTAL_COUNT)) { + g_logger().error("[{}]. Type {} is out of range, value {}. Error message: {}", __FUNCTION__, enumValue, value, "Enum value is out of range"); + return; } + m_stats[enumValue] += value; } -void PlayerWheel::setResistance(CombatType_t type, int32_t value) { - try { - m_resistance.at(combatTypeToIndex(type)) = value; - } catch (const std::out_of_range &e) { - g_logger().error("[{}]. Type {} is out of range, value {}. Error message: {}", __FUNCTION__, combatTypeToIndex(type), value, e.what()); +void PlayerWheel::addResistance(CombatType_t type, int32_t value) { + auto index = combatTypeToIndex(type); + if (index >= static_cast(WheelStat_t::TOTAL_COUNT)) { + g_logger().error("[{}]. Type {} is out of range, value {}. Error message: {}", __FUNCTION__, index, value, "Enum value is out of range"); + return; } + m_resistance[index] += value; } void PlayerWheel::setSpellInstant(const std::string &name, bool value) { @@ -2375,6 +2703,12 @@ void PlayerWheel::resetResistance() { } } +void PlayerWheel::resetStats() { + for (int32_t i = 0; i < static_cast(WheelStat_t::TOTAL_COUNT); i++) { + m_stats[i] = 0; + } +} + // Wheel of destiny - Header get: bool PlayerWheel::getInstant(WheelInstant_t type) const { auto enumValue = static_cast(type); diff --git a/src/creatures/players/wheel/player_wheel.hpp b/src/creatures/players/wheel/player_wheel.hpp index 70e55ff28d5..42c4f0d35c5 100644 --- a/src/creatures/players/wheel/player_wheel.hpp +++ b/src/creatures/players/wheel/player_wheel.hpp @@ -10,6 +10,11 @@ #pragma once #include "io/io_wheel.hpp" +#include "utils/utils_definitions.hpp" +#include "config/config_definitions.hpp" +#include "config/configmanager.hpp" +#include "kv/kv.hpp" +#include "wheel_gems.hpp" class Spell; class Player; @@ -17,6 +22,65 @@ class Creature; class NetworkMessage; class IOWheel; +struct PlayerWheelGem { + std::string uuid; + bool locked; + WheelGemAffinity_t affinity; + WheelGemQuality_t quality; + WheelGemBasicModifier_t basicModifier1; + WheelGemBasicModifier_t basicModifier2; + WheelGemSupremeModifier_t supremeModifier; + + std::string toString() const { + return fmt::format("[PlayerWheelGem] uuid: {}, locked: {}, affinity: {}, quality: {}, basicModifier1: {}, basicModifier2: {}, supremeModifier: {}", uuid, locked, static_cast(affinity), static_cast(quality), static_cast(basicModifier1), static_cast(basicModifier2), static_cast(supremeModifier)); + } + + void save(const std::shared_ptr &kv) const { + kv->scoped("revealed")->set(uuid, serialize()); + } + + void remove(const std::shared_ptr &kv) const { + kv->scoped("revealed")->remove(uuid); + } + + static PlayerWheelGem load(const std::shared_ptr &kv, const std::string &uuid) { + auto val = kv->scoped("revealed")->get(uuid); + if (!val || !val.has_value()) { + return {}; + } + return deserialize(uuid, val.value()); + } + +private: + ValueWrapper serialize() const { + return { + { "uuid", uuid }, + { "locked", locked }, + { "affinity", static_cast(affinity) }, + { "quality", static_cast(quality) }, + { "basicModifier1", static_cast(basicModifier1) }, + { "basicModifier2", static_cast(basicModifier2) }, + { "supremeModifier", static_cast(supremeModifier) } + }; + } + + static PlayerWheelGem deserialize(const std::string &uuid, const ValueWrapper &val) { + auto map = val.get(); + if (map.empty()) { + return {}; + } + return { + uuid, + map["locked"]->get(), + static_cast(map["affinity"]->get()), + static_cast(map["quality"]->get()), + static_cast(map["basicModifier1"]->get()), + static_cast(map["basicModifier2"]->get()), + static_cast(map["supremeModifier"]->get()) + }; + } +}; + class PlayerWheel { public: explicit PlayerWheel(Player &initPlayer); @@ -51,6 +115,7 @@ class PlayerWheel { */ void saveSlotPointsOnPressSaveButton(NetworkMessage &msg); void addPromotionScrolls(NetworkMessage &msg) const; + void addGems(NetworkMessage &msg) const; void sendOpenWheelWindow(NetworkMessage &msg, uint32_t ownerId) const; void sendGiftOfLifeCooldown() const; @@ -144,6 +209,47 @@ class PlayerWheel { uint8_t getOptions(uint32_t ownerId) const; uint8_t getPlayerVocationEnum() const; + std::shared_ptr gemsKV() const; + + std::vector getRevealedGems() const; + std::vector getActiveGems() const; + + static uint64_t getGemRotateCost(WheelGemQuality_t quality) { + ConfigKey_t key; + switch (quality) { + case WheelGemQuality_t::Lesser: + key = WHEEL_ATELIER_ROTATE_LESSER_COST; + break; + case WheelGemQuality_t::Regular: + key = WHEEL_ATELIER_ROTATE_REGULAR_COST; + break; + case WheelGemQuality_t::Greater: + key = WHEEL_ATELIER_ROTATE_GREATER_COST; + break; + default: + return 0; + } + return static_cast(g_configManager().getNumber(key, __FUNCTION__)); + } + + static uint64_t getGemRevealCost(WheelGemQuality_t quality) { + ConfigKey_t key; + switch (quality) { + case WheelGemQuality_t::Lesser: + key = WHEEL_ATELIER_REVEAL_LESSER_COST; + break; + case WheelGemQuality_t::Regular: + key = WHEEL_ATELIER_REVEAL_REGULAR_COST; + break; + case WheelGemQuality_t::Greater: + key = WHEEL_ATELIER_REVEAL_GREATER_COST; + break; + default: + return 0; + } + return static_cast(g_configManager().getNumber(key, __FUNCTION__)); + } + // Members variables const uint16_t m_minLevelToStartCountPoints = 50; uint16_t m_pointsPerLevel = 1; @@ -216,24 +322,24 @@ class PlayerWheel { void setInstant(WheelInstant_t type, bool toggle); /** - * @brief Sets the value of a specific stat in the Wheel of Destiny. + * @brief Adds the value of a specific stat in the Wheel of Destiny. * * This function sets the value of the specified stat in the Wheel of Destiny to the provided value. * * @param type The type of the stat to set. * @param value The value to set for the stat. */ - void setStat(WheelStat_t type, int32_t value); + void addStat(WheelStat_t type, int32_t value); /** - * @brief Sets the value of a specific resistance in the Wheel of Destiny. + * @brief Adds the value of a specific resistance in the Wheel of Destiny. * * This function sets the value of the specified resistance in the Wheel of Destiny to the provided value. * * @param type The type of the resistance to set. * @param value The value to set for the resistance. */ - void setResistance(CombatType_t type, int32_t value); + void addResistance(CombatType_t type, int32_t value); /** * @brief Sets the value of a specific instant in the Wheel of Destiny based on its spell name. @@ -246,6 +352,7 @@ class PlayerWheel { */ void setSpellInstant(const std::string &name, bool value); void resetResistance(); + void resetStats(); // Wheel of destiny - Header get: bool getInstant(WheelInstant_t type) const; @@ -314,6 +421,72 @@ class PlayerWheel { * @return The calculated mitigation value. */ float calculateMitigation() const; + PlayerWheelGem getGem(uint8_t index) const; + PlayerWheelGem getGem(const std::string &uuid) const; + uint8_t getGemIndex(const std::string &uuid) const; + void revealGem(WheelGemQuality_t quality); + void destroyGem(uint8_t index); + void switchGemDomain(uint8_t index); + void toggleGemLock(uint8_t index); + void setActiveGem(WheelGemAffinity_t affinity, uint8_t index); + void removeActiveGem(WheelGemAffinity_t affinity); + void addRevelationBonus(WheelGemAffinity_t affinity, uint16_t points) { + m_bonusRevelationPoints[static_cast(affinity)] += points; + } + void resetRevelationBonus() { + m_bonusRevelationPoints = { 0, 0, 0, 0 }; + } + + void addSpellBonus(const std::string &spellName, WheelSpells::Bonus bonus) { + if (m_spellsBonuses.contains(spellName)) { + m_spellsBonuses[spellName].decrease.cooldown += bonus.decrease.cooldown; + m_spellsBonuses[spellName].decrease.manaCost += bonus.decrease.manaCost; + m_spellsBonuses[spellName].decrease.secondaryGroupCooldown += bonus.decrease.secondaryGroupCooldown; + m_spellsBonuses[spellName].increase.aditionalTarget += bonus.increase.aditionalTarget; + m_spellsBonuses[spellName].increase.area = bonus.increase.area; + m_spellsBonuses[spellName].increase.criticalChance += bonus.increase.criticalChance; + m_spellsBonuses[spellName].increase.criticalDamage += bonus.increase.criticalDamage; + m_spellsBonuses[spellName].increase.damage += bonus.increase.damage; + m_spellsBonuses[spellName].increase.damageReduction += bonus.increase.damageReduction; + m_spellsBonuses[spellName].increase.duration += bonus.increase.duration; + m_spellsBonuses[spellName].increase.heal += bonus.increase.heal; + m_spellsBonuses[spellName].leech.life += bonus.leech.life; + m_spellsBonuses[spellName].leech.mana += bonus.leech.mana; + return; + } + m_spellsBonuses[spellName] = bonus; + } + + int32_t getSpellBonus(const std::string &spellName, WheelSpellBoost_t boost) const { + if (!m_spellsBonuses.contains(spellName)) { + return 0; + } + auto bonus = m_spellsBonuses.at(spellName); + switch (boost) { + case WheelSpellBoost_t::COOLDOWN: + return bonus.decrease.cooldown; + case WheelSpellBoost_t::MANA: + return bonus.decrease.manaCost; + case WheelSpellBoost_t::SECONDARY_GROUP_COOLDOWN: + return bonus.decrease.secondaryGroupCooldown; + case WheelSpellBoost_t::CRITICAL_CHANCE: + return bonus.increase.criticalChance; + case WheelSpellBoost_t::CRITICAL_DAMAGE: + return bonus.increase.criticalDamage; + case WheelSpellBoost_t::DAMAGE: + return bonus.increase.damage; + case WheelSpellBoost_t::DAMAGE_REDUCTION: + return bonus.increase.damageReduction; + case WheelSpellBoost_t::HEAL: + return bonus.increase.heal; + case WheelSpellBoost_t::LIFE_LEECH: + return bonus.leech.life; + case WheelSpellBoost_t::MANA_LEECH: + return bonus.leech.mana; + default: + return 0; + } + } private: friend class Player; @@ -322,8 +495,10 @@ class PlayerWheel { // Starting count in 1 (1-37), slot enums are from 1 to 36, but the index always starts at 0 in c++ std::array m_wheelSlots = {}; + std::array m_bonusRevelationPoints = { 0, 0, 0, 0 }; PlayerWheelMethodsBonusData m_playerBonusData; + std::unique_ptr m_modifierContext; std::array(WheelStage_t::TOTAL_COUNT)> m_stages = { 0 }; std::array(WheelOnThink_t::TOTAL_COUNT)> m_onThink = { 0 }; @@ -335,4 +510,5 @@ class PlayerWheel { int32_t m_creaturesNearby = 0; std::map m_spellsSelected; std::vector m_learnedSpellsSelected; + std::unordered_map m_spellsBonuses; }; diff --git a/src/creatures/players/wheel/wheel_definitions.hpp b/src/creatures/players/wheel/wheel_definitions.hpp index 91666a8f1be..0f40d32bf96 100644 --- a/src/creatures/players/wheel/wheel_definitions.hpp +++ b/src/creatures/players/wheel/wheel_definitions.hpp @@ -124,8 +124,10 @@ enum class WheelStat_t : uint8_t { DAMAGE = 10, LIFE_LEECH_CHANCE = 11, MANA_LEECH_CHANCE = 12, + DODGE = 13, + CRITICAL_DAMAGE = 14, - TOTAL_COUNT = 13 + TOTAL_COUNT = 15 }; enum class WheelMajor_t : uint8_t { @@ -160,7 +162,7 @@ enum class WheelAvatarSkill_t : uint8_t { NONE = 0, DAMAGE_REDUCTION = 1, CRITICAL_CHANCE = 2, - CRITICAL_DAMAGE = 3 + CRITICAL_DAMAGE = 3, }; enum class WheelSpellGrade_t : uint8_t { @@ -198,7 +200,7 @@ struct PlayerWheelMethodsBonusData { int healing = 0; }; // value * 100. Example: 1% == 100 - std::array resistance = {}; + std::array unlockedVesselResonances = {}; // Raw value. Example: 1 == 1 struct Skills { @@ -264,3 +266,33 @@ struct SlotInfo { uint8_t slot; ///< The slot index. uint16_t points; ///< The points for the slot. }; + +namespace WheelSpells { + struct Increase { + bool area = false; + int damage = 0; + int heal = 0; + int aditionalTarget = 0; + int damageReduction = 0; + int duration = 0; + int criticalDamage = 0; + int criticalChance = 0; + }; + + struct Decrease { + int cooldown = 0; + int manaCost = 0; + uint8_t secondaryGroupCooldown = 0; + }; + + struct Leech { + int mana = 0; + int life = 0; + }; + + struct Bonus { + Leech leech; + Increase increase; + Decrease decrease; + }; +} diff --git a/src/creatures/players/wheel/wheel_gems.cpp b/src/creatures/players/wheel/wheel_gems.cpp new file mode 100644 index 00000000000..d7957a9bb05 --- /dev/null +++ b/src/creatures/players/wheel/wheel_gems.cpp @@ -0,0 +1,524 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.org/ + */ + +#include "pch.hpp" + +#include "creatures/players/wheel/player_wheel.hpp" + +void GemModifierResistanceStrategy::execute() { + m_wheel.addResistance(m_combatType, m_resistance); +} + +void GemModifierStatStrategy::execute() { + m_wheel.addStat(m_stat, m_value); +} + +void GemModifierRevelationStrategy::execute() { + m_wheel.addRevelationBonus(m_affinity, m_value); +} + +void GemModifierSpellBonusStrategy::execute() { + m_wheel.addSpellBonus(m_spellName, m_bonus); +} + +void WheelModifierContext::addStrategies(WheelGemBasicModifier_t modifier) { + switch (modifier) { + case WheelGemBasicModifier_t::General_PhysicalResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_PHYSICALDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::General_HolyResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_HOLYDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::General_DeathResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_DEATHDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::General_FireResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, 200)); + break; + case WheelGemBasicModifier_t::General_EarthResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, 200)); + break; + case WheelGemBasicModifier_t::General_IceResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, 200)); + break; + case WheelGemBasicModifier_t::General_EnergyResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, 200)); + break; + case WheelGemBasicModifier_t::General_HolyResistance_DeathWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_HOLYDAMAGE, 150)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_DEATHDAMAGE, -100)); + break; + case WheelGemBasicModifier_t::General_DeathResistance_HolyWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_DEATHDAMAGE, 150)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_HOLYDAMAGE, -100)); + break; + case WheelGemBasicModifier_t::General_FireResistance_EarthResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, 100)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::General_FireResistance_IceResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, 100)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::General_FireResistance_EnergyResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, 100)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::General_EarthResistance_IceResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, 100)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::General_EarthResistance_EnergyResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, 100)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::General_IceResistance_EnergyResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, 100)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::General_FireResistance_EarthWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, 300)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, -200)); + break; + case WheelGemBasicModifier_t::General_FireResistance_IceWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, 300)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, -200)); + break; + case WheelGemBasicModifier_t::General_FireResistance_EnergyWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, 300)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, -200)); + break; + case WheelGemBasicModifier_t::General_EarthResistance_FireWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, 300)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, -200)); + break; + case WheelGemBasicModifier_t::General_EarthResistance_IceWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, 300)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, -200)); + break; + case WheelGemBasicModifier_t::General_EarthResistance_EnergyWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, 300)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, -200)); + break; + case WheelGemBasicModifier_t::General_IceResistance_EarthWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, 300)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, -200)); + break; + case WheelGemBasicModifier_t::General_IceResistance_FireWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, 300)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, -200)); + break; + case WheelGemBasicModifier_t::General_IceResistance_EnergyWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, 300)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, -200)); + break; + case WheelGemBasicModifier_t::General_EnergyResistance_EarthWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, 300)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, -200)); + break; + case WheelGemBasicModifier_t::General_EnergyResistance_IceWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, 300)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, -200)); + break; + case WheelGemBasicModifier_t::General_EnergyResistance_FireWeakness: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, 300)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, -200)); + break; + case WheelGemBasicModifier_t::General_ManaDrainResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_MANADRAIN, 300)); + break; + case WheelGemBasicModifier_t::General_LifeDrainResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_LIFEDRAIN, 300)); + break; + case WheelGemBasicModifier_t::General_ManaDrainResistance_LifeDrainResistance: + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_MANADRAIN, 150)); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_LIFEDRAIN, 150)); + break; + case WheelGemBasicModifier_t::General_MitigationMultiplier: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::MITIGATION, 500)); + break; + + case WheelGemBasicModifier_t::Vocation_Health: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::HEALTH, getHealthValue(m_vocation, modifier))); + break; + case WheelGemBasicModifier_t::Vocation_Mana_FireResistance: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::MANA, getManaValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::Vocation_Mana_EnergyResistance: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::MANA, getManaValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::Vocation_Mana_Earth_Resistance: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::MANA, getManaValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::Vocation_Mana_Ice_Resistance: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::MANA, getManaValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::Vocation_Mana: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::MANA, getManaValue(m_vocation, modifier))); + break; + case WheelGemBasicModifier_t::Vocation_Health_FireResistance: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::HEALTH, getHealthValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::Vocation_Health_EnergyResistance: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::HEALTH, getHealthValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::Vocation_Health_EarthResistance: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::HEALTH, getHealthValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::Vocation_Health_IceResistance: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::HEALTH, getHealthValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::Vocation_Mixed: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::HEALTH, getHealthValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::MANA, getManaValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::CAPACITY, getCapacityValue(m_vocation, modifier))); + break; + case WheelGemBasicModifier_t::Vocation_Capacity_FireResistance: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::CAPACITY, getCapacityValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_FIREDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::Vocation_Capacity_EnergyResistance: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::CAPACITY, getCapacityValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ENERGYDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::Vocation_Capacity_EarthResistance: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::CAPACITY, getCapacityValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_EARTHDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::Vocation_Capacity_IceResistance: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::CAPACITY, getCapacityValue(m_vocation, modifier))); + m_strategies.push_back(std::make_unique(m_wheel, CombatType_t::COMBAT_ICEDAMAGE, 100)); + break; + case WheelGemBasicModifier_t::Vocation_Capacity: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::CAPACITY, getCapacityValue(m_vocation, modifier))); + break; + + default: + g_logger().error("WheelModifierContext::setStrategy: Invalid basic modifier: {}", static_cast(modifier)); + } +} + +void WheelModifierContext::addStrategies(WheelGemSupremeModifier_t modifier) { + WheelSpells::Bonus bonus; + + switch (modifier) { + case WheelGemSupremeModifier_t::General_Dodge: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::DODGE, 25)); + break; + case WheelGemSupremeModifier_t::General_LifeLeech: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::LIFE_LEECH, 120)); + break; + case WheelGemSupremeModifier_t::General_ManaLeech: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::MANA_LEECH, 40)); + break; + case WheelGemSupremeModifier_t::General_CriticalDamage: + m_strategies.push_back(std::make_unique(m_wheel, WheelStat_t::CRITICAL_DAMAGE, 150)); + break; + case WheelGemSupremeModifier_t::General_RevelationMastery_GiftOfLife: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Green, 150)); + break; + + case WheelGemSupremeModifier_t::SorcererDruid_UltimateHealing: + bonus.increase.heal = 10; + m_strategies.push_back(std::make_unique(m_wheel, "Ultimate Healing", bonus)); + break; + + case WheelGemSupremeModifier_t::Knight_RevelationMastery_ExecutionersThrow: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Red, 150)); + break; + case WheelGemSupremeModifier_t::Knight_RevelationMastery_AvatarOfSteel: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Purple, 150)); + break; + case WheelGemSupremeModifier_t::Knight_RevelationMastery_CombatMastery: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Blue, 150)); + break; + + case WheelGemSupremeModifier_t::Paladin_RevelationMastery_DivineGrenade: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Red, 150)); + break; + case WheelGemSupremeModifier_t::Paladin_RevelationMastery_AvatarOfLight: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Purple, 150)); + break; + case WheelGemSupremeModifier_t::Paladin_RevelationMastery_DivineEmpowerment: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Blue, 150)); + break; + + case WheelGemSupremeModifier_t::Druid_RevelationMastery_BlessingOfTheGrove: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Red, 150)); + break; + case WheelGemSupremeModifier_t::Druid_RevelationMastery_AvatarOfNature: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Purple, 150)); + break; + case WheelGemSupremeModifier_t::Druid_RevelationMastery_TwinBursts: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Blue, 150)); + break; + + case WheelGemSupremeModifier_t::Sorcerer_RevelationMastery_BeamMastery: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Red, 150)); + break; + case WheelGemSupremeModifier_t::Sorcerer_RevelationMastery_AvatarOfStorm: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Purple, 150)); + break; + case WheelGemSupremeModifier_t::Sorcerer_RevelationMastery_DrainBody: + m_strategies.push_back(std::make_unique(m_wheel, WheelGemAffinity_t::Blue, 150)); + break; + + case WheelGemSupremeModifier_t::Knight_AvatarOfSteel_Cooldown: + bonus.decrease.cooldown = 300 * 1000; + m_strategies.push_back(std::make_unique(m_wheel, "Avatar of Steel", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_ExecutionersThrow_Cooldown: + bonus.decrease.cooldown = 1 * 1000; + m_strategies.push_back(std::make_unique(m_wheel, "Executioner's Throw", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_ExecutionersThrow_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Executioner's Throw", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_ExecutionersThrow_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Executioner's Throw", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_Fierce_Berserk_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Fierce Berserk", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_Fierce_Berserk_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Fierce Berserk", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_Berserk_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Berserk", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_Berserk_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Berserk", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_Front_Sweep_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Front Sweep", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_Front_Sweep_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Front Sweep", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_Groundshaker_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Groundshaker", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_Groundshaker_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Groundshaker", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_Annihilation_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Annihilation", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_Annihilation_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Annihilation", bonus)); + break; + case WheelGemSupremeModifier_t::Knight_FairWoundCleansing_HealingIncrease: + bonus.increase.heal = 10; + m_strategies.push_back(std::make_unique(m_wheel, "Fair Wound Cleansing", bonus)); + break; + + case WheelGemSupremeModifier_t::Paladin_AvatarOfLight_Cooldown: + bonus.decrease.cooldown = 300 * 1000; + m_strategies.push_back(std::make_unique(m_wheel, "Avatar of Light", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_DivineDazzle_Cooldown: + bonus.decrease.cooldown = 2 * 1000; + m_strategies.push_back(std::make_unique(m_wheel, "Divine Dazzle", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_DivineGrenade_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Divine Grenade", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_DivineGrenade_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Divine Grenade", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_DivineCaldera_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Divine Caldera", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_DivineCaldera_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Divine Caldera", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_DivineMissile_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Divine Missile", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_DivineMissile_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Divine Missile", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_EtherealSpear_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Ethereal Spear", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_EtherealSpear_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Ethereal Spear", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_StrongEtherealSpear_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Strong Ethereal Spear", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_StrongEtherealSpear_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Strong Ethereal Spear", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_DivineEmpowerment_Cooldown: + bonus.decrease.cooldown = 3 * 1000; + m_strategies.push_back(std::make_unique(m_wheel, "Divine Empowerment", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_DivineGrenade_Cooldown: + bonus.decrease.cooldown = 1 * 1000; + m_strategies.push_back(std::make_unique(m_wheel, "Divine Grenade", bonus)); + break; + case WheelGemSupremeModifier_t::Paladin_Salvation_HealingIncrease: + bonus.increase.heal = 10; + m_strategies.push_back(std::make_unique(m_wheel, "Salvation", bonus)); + break; + + case WheelGemSupremeModifier_t::Sorcerer_AvatarOfStorm_Cooldown: + bonus.decrease.cooldown = 300 * 1000; + m_strategies.push_back(std::make_unique(m_wheel, "Avatar of Storm", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_EnergyWave_Cooldown: + bonus.decrease.cooldown = 1 * 1000; + m_strategies.push_back(std::make_unique(m_wheel, "Energy Wave", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_GreatDeathBeam_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Great Death Beam", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_GreatDeathBeam_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Great Death Beam", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_HellsCore_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Hell's Core", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_HellsCore_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Hell's Core", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_EnergyWave_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Energy Wave", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_EnergyWave_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Energy Wave", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_GreatFireWave_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Great Fire Wave", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_GreatFireWave_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Great Fire Wave", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_RageOfTheSkies_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Rage of the Skies", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_RageOfTheSkies_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Rage of the Skies", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_GreatEnergyBeam_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Great Energy Beam", bonus)); + break; + case WheelGemSupremeModifier_t::Sorcerer_GreatEnergyBeam_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Great Energy Beam", bonus)); + break; + + case WheelGemSupremeModifier_t::Druid_AvatarOfNature_Cooldown: + bonus.decrease.cooldown = 300 * 1000; + m_strategies.push_back(std::make_unique(m_wheel, "Avatar of Nature", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_NaturesEmbrace_Cooldown: + bonus.decrease.cooldown = 5 * 1000; + m_strategies.push_back(std::make_unique(m_wheel, "Nature's Embrace", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_TerraBurst_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Terra Burst", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_TerraBurst_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Terra Burst", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_IceBurst_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Ice Burst", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_IceBurst_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Ice Burst", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_EternalWinter_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Eternal Winter", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_EternalWinter_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Eternal Winter", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_TerraWave_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Terra Wave", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_TerraWave_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Terra Wave", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_StrongIceWave_DamageIncrease: + bonus.increase.damage = 25; + m_strategies.push_back(std::make_unique(m_wheel, "Strong Ice Wave", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_StrongIceWave_CriticalExtraDamage: + bonus.increase.criticalDamage = 8; + m_strategies.push_back(std::make_unique(m_wheel, "Strong Ice Wave", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_HealFriend_HealingIncrease: + bonus.increase.heal = 10; + m_strategies.push_back(std::make_unique(m_wheel, "Heal Friend", bonus)); + break; + case WheelGemSupremeModifier_t::Druid_MassHealing_HealingIncrease: + bonus.increase.heal = 10; + m_strategies.push_back(std::make_unique(m_wheel, "Mass Healing", bonus)); + break; + default: + g_logger().error("WheelModifierContext::setStrategy: Invalid supreme modifier: {}", static_cast(modifier)); + } +} + +void WheelModifierContext::executeStrategies() { + for (auto &strategy : m_strategies) { + strategy->execute(); + } +} diff --git a/src/creatures/players/wheel/wheel_gems.hpp b/src/creatures/players/wheel/wheel_gems.hpp new file mode 100644 index 00000000000..fa47fe12ad2 --- /dev/null +++ b/src/creatures/players/wheel/wheel_gems.hpp @@ -0,0 +1,484 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.org/ + */ + +#pragma once + +#include "wheel_definitions.hpp" + +class PlayerWheel; + +enum class WheelGemAction_t : uint8_t { + Destroy, + Reveal, + SwitchDomain, + ToggleLock, +}; + +enum class WheelGemAffinity_t : uint8_t { + Green, + Red, + Blue, + Purple, +}; + +enum class WheelGemQuality_t : uint8_t { + Lesser, + Regular, + Greater, +}; + +enum class WheelGemBasicModifier_t : uint8_t { + General_PhysicalResistance, + General_HolyResistance, + General_DeathResistance, + General_FireResistance, + General_EarthResistance, + General_IceResistance, + General_EnergyResistance, + + General_HolyResistance_DeathWeakness, + General_DeathResistance_HolyWeakness, + General_FireResistance_EarthResistance, + General_FireResistance_IceResistance, + General_FireResistance_EnergyResistance, + General_EarthResistance_IceResistance, + General_EarthResistance_EnergyResistance, + General_IceResistance_EnergyResistance, + + General_FireResistance_EarthWeakness, + General_FireResistance_IceWeakness, + General_FireResistance_EnergyWeakness, + General_EarthResistance_FireWeakness, + General_EarthResistance_IceWeakness, + General_EarthResistance_EnergyWeakness, + General_IceResistance_EarthWeakness, + General_IceResistance_FireWeakness, + General_IceResistance_EnergyWeakness, + General_EnergyResistance_EarthWeakness, + General_EnergyResistance_IceWeakness, + General_EnergyResistance_FireWeakness, + General_ManaDrainResistance, + General_LifeDrainResistance, + General_ManaDrainResistance_LifeDrainResistance, + General_MitigationMultiplier, + + Vocation_Health, + Vocation_Capacity, + Vocation_Mana_FireResistance, + Vocation_Mana_EnergyResistance, + Vocation_Mana_Earth_Resistance, + Vocation_Mana_Ice_Resistance, + Vocation_Mana, + Vocation_Health_FireResistance, + Vocation_Health_EnergyResistance, + Vocation_Health_EarthResistance, + Vocation_Health_IceResistance, + Vocation_Mixed, + Vocation_Mixed2, + Vocation_Capacity_FireResistance, + Vocation_Capacity_EnergyResistance, + Vocation_Capacity_EarthResistance, + Vocation_Capacity_IceResistance, +}; + +enum class WheelGemSupremeModifier_t : uint8_t { + General_Dodge, + General_CriticalDamage, + General_LifeLeech, + General_ManaLeech, + SorcererDruid_UltimateHealing, + General_RevelationMastery_GiftOfLife, + + Knight_AvatarOfSteel_Cooldown, + Knight_ExecutionersThrow_Cooldown, + Knight_ExecutionersThrow_DamageIncrease, + Knight_ExecutionersThrow_CriticalExtraDamage, + Knight_Fierce_Berserk_DamageIncrease, + Knight_Fierce_Berserk_CriticalExtraDamage, + Knight_Berserk_DamageIncrease, + Knight_Berserk_CriticalExtraDamage, + Knight_Front_Sweep_CriticalExtraDamage, + Knight_Front_Sweep_DamageIncrease, + Knight_Groundshaker_DamageIncrease, + Knight_Groundshaker_CriticalExtraDamage, + Knight_Annihilation_CriticalExtraDamage, + Knight_Annihilation_DamageIncrease, + Knight_FairWoundCleansing_HealingIncrease, + Knight_RevelationMastery_AvatarOfSteel, + Knight_RevelationMastery_ExecutionersThrow, + Knight_RevelationMastery_CombatMastery, + + Paladin_AvatarOfLight_Cooldown, + Paladin_DivineDazzle_Cooldown, + Paladin_DivineGrenade_DamageIncrease, + Paladin_DivineGrenade_CriticalExtraDamage, + Paladin_DivineCaldera_DamageIncrease, + Paladin_DivineCaldera_CriticalExtraDamage, + Paladin_DivineMissile_DamageIncrease, + Paladin_DivineMissile_CriticalExtraDamage, + Paladin_EtherealSpear_DamageIncrease, + Paladin_EtherealSpear_CriticalExtraDamage, + Paladin_StrongEtherealSpear_DamageIncrease, + Paladin_StrongEtherealSpear_CriticalExtraDamage, + Paladin_DivineEmpowerment_Cooldown, + Paladin_DivineGrenade_Cooldown, + Paladin_Salvation_HealingIncrease, + Paladin_RevelationMastery_AvatarOfLight, + Paladin_RevelationMastery_DivineGrenade, + Paladin_RevelationMastery_DivineEmpowerment, + + Sorcerer_AvatarOfStorm_Cooldown, + Sorcerer_EnergyWave_Cooldown, + Sorcerer_GreatDeathBeam_DamageIncrease, + Sorcerer_GreatDeathBeam_CriticalExtraDamage, + Sorcerer_HellsCore_DamageIncrease, + Sorcerer_HellsCore_CriticalExtraDamage, + Sorcerer_EnergyWave_DamageIncrease, + Sorcerer_EnergyWave_CriticalExtraDamage, + Sorcerer_GreatFireWave_DamageIncrease, + Sorcerer_GreatFireWave_CriticalExtraDamage, + Sorcerer_RageOfTheSkies_DamageIncrease, + Sorcerer_RageOfTheSkies_CriticalExtraDamage, + Sorcerer_GreatEnergyBeam_DamageIncrease, + Sorcerer_GreatEnergyBeam_CriticalExtraDamage, + Sorcerer_RevelationMastery_AvatarOfStorm, + Sorcerer_RevelationMastery_BeamMastery, + Sorcerer_RevelationMastery_DrainBody, + + Druid_AvatarOfNature_Cooldown, + Druid_NaturesEmbrace_Cooldown, + Druid_TerraBurst_DamageIncrease, + Druid_TerraBurst_CriticalExtraDamage, + Druid_IceBurst_DamageIncrease, + Druid_IceBurst_CriticalExtraDamage, + Druid_EternalWinter_CriticalExtraDamage, + Druid_EternalWinter_DamageIncrease, + Druid_TerraWave_DamageIncrease, + Druid_TerraWave_CriticalExtraDamage, + Druid_StrongIceWave_DamageIncrease, + Druid_StrongIceWave_CriticalExtraDamage, + Druid_HealFriend_HealingIncrease, + Druid_MassHealing_HealingIncrease, + Druid_RevelationMastery_AvatarOfNature, + Druid_RevelationMastery_BlessingOfTheGrove, + Druid_RevelationMastery_TwinBursts, +}; + +class GemModifierStrategy { +public: + explicit GemModifierStrategy(PlayerWheel &wheel) : + m_wheel(wheel) { } + virtual ~GemModifierStrategy() { } + virtual void execute() = 0; + +protected: + PlayerWheel &m_wheel; +}; + +class GemModifierResistanceStrategy : public GemModifierStrategy { +public: + explicit GemModifierResistanceStrategy(PlayerWheel &wheel, CombatType_t combatType, int32_t resistance) : + GemModifierStrategy(wheel), + m_combatType(combatType), + m_resistance(resistance) { } + + void execute() override; + +private: + CombatType_t m_combatType; + int32_t m_resistance; +}; + +class GemModifierStatStrategy : public GemModifierStrategy { +public: + explicit GemModifierStatStrategy(PlayerWheel &wheel, WheelStat_t stat, int32_t value) : + GemModifierStrategy(wheel), + m_stat(stat), + m_value(value) { } + + void execute() override; + +private: + WheelStat_t m_stat; + int32_t m_value; +}; + +class GemModifierRevelationStrategy : public GemModifierStrategy { +public: + explicit GemModifierRevelationStrategy(PlayerWheel &wheel, WheelGemAffinity_t affinity, uint16_t value) : + GemModifierStrategy(wheel), + m_affinity(affinity) { } + + void execute() override; + +private: + WheelGemAffinity_t m_affinity; + uint16_t m_value; +}; + +class GemModifierSpellBonusStrategy : public GemModifierStrategy { +public: + explicit GemModifierSpellBonusStrategy(PlayerWheel &wheel, const std::string &spellName, WheelSpells::Bonus bonus) : + GemModifierStrategy(wheel), + m_spellName(spellName), + m_bonus(bonus) { } + + void execute() override; + +private: + std::string m_spellName; + WheelSpells::Bonus m_bonus; +}; + +class WheelModifierContext { +public: + explicit WheelModifierContext(PlayerWheel &wheel, Vocation_t vocation) : + m_wheel(wheel), m_vocation(vocation) { } + + void addStrategies(WheelGemBasicModifier_t modifier); + void addStrategies(WheelGemSupremeModifier_t modifier); + + void resetStrategies() { + m_strategies.clear(); + } + + void executeStrategies(); + +private: + std::vector> m_strategies; + PlayerWheel &m_wheel; + Vocation_t m_vocation; +}; + +static int32_t getHealthValue(Vocation_t vocation, WheelGemBasicModifier_t modifier) { + static const std::unordered_map> stats = { + { + WheelGemBasicModifier_t::Vocation_Health, + { + { Vocation_t::VOCATION_KNIGHT, 300 }, + { Vocation_t::VOCATION_PALADIN, 200 }, + { Vocation_t::VOCATION_SORCERER, 100 }, + { Vocation_t::VOCATION_DRUID, 100 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Health_FireResistance, + { + { Vocation_t::VOCATION_KNIGHT, 150 }, + { Vocation_t::VOCATION_PALADIN, 100 }, + { Vocation_t::VOCATION_SORCERER, 50 }, + { Vocation_t::VOCATION_DRUID, 50 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Health_EnergyResistance, + { + { Vocation_t::VOCATION_KNIGHT, 150 }, + { Vocation_t::VOCATION_PALADIN, 100 }, + { Vocation_t::VOCATION_SORCERER, 50 }, + { Vocation_t::VOCATION_DRUID, 50 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Health_EarthResistance, + { + { Vocation_t::VOCATION_KNIGHT, 150 }, + { Vocation_t::VOCATION_PALADIN, 100 }, + { Vocation_t::VOCATION_SORCERER, 50 }, + { Vocation_t::VOCATION_DRUID, 50 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Health_IceResistance, + { + { Vocation_t::VOCATION_KNIGHT, 150 }, + { Vocation_t::VOCATION_PALADIN, 100 }, + { Vocation_t::VOCATION_SORCERER, 50 }, + { Vocation_t::VOCATION_DRUID, 50 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Mixed, + { + { Vocation_t::VOCATION_KNIGHT, 150 }, + { Vocation_t::VOCATION_PALADIN, 100 }, + { Vocation_t::VOCATION_SORCERER, 50 }, + { Vocation_t::VOCATION_DRUID, 50 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Mixed2, + { + { Vocation_t::VOCATION_KNIGHT, 150 }, + { Vocation_t::VOCATION_PALADIN, 100 }, + { Vocation_t::VOCATION_SORCERER, 50 }, + { Vocation_t::VOCATION_DRUID, 50 }, + }, + }, + }; + + auto modifierIt = stats.find(modifier); + if (modifierIt != stats.end()) { + auto vocationIt = modifierIt->second.find(vocation); + if (vocationIt != modifierIt->second.end()) { + return vocationIt->second; + } + } + return 0; +} + +static int32_t getManaValue(Vocation_t vocation, WheelGemBasicModifier_t modifier) { + static const std::unordered_map> stats = { + { + WheelGemBasicModifier_t::Vocation_Mana_FireResistance, + { + { Vocation_t::VOCATION_KNIGHT, 50 }, + { Vocation_t::VOCATION_PALADIN, 150 }, + { Vocation_t::VOCATION_SORCERER, 300 }, + { Vocation_t::VOCATION_DRUID, 300 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Mana_EnergyResistance, + { + { Vocation_t::VOCATION_KNIGHT, 50 }, + { Vocation_t::VOCATION_PALADIN, 150 }, + { Vocation_t::VOCATION_SORCERER, 300 }, + { Vocation_t::VOCATION_DRUID, 300 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Mana_Earth_Resistance, + { + { Vocation_t::VOCATION_KNIGHT, 50 }, + { Vocation_t::VOCATION_PALADIN, 150 }, + { Vocation_t::VOCATION_SORCERER, 300 }, + { Vocation_t::VOCATION_DRUID, 300 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Mana_Ice_Resistance, + { + { Vocation_t::VOCATION_KNIGHT, 50 }, + { Vocation_t::VOCATION_PALADIN, 150 }, + { Vocation_t::VOCATION_SORCERER, 300 }, + { Vocation_t::VOCATION_DRUID, 300 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Mana, + { + { Vocation_t::VOCATION_KNIGHT, 100 }, + { Vocation_t::VOCATION_PALADIN, 300 }, + { Vocation_t::VOCATION_SORCERER, 600 }, + { Vocation_t::VOCATION_DRUID, 600 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Mixed, + { + { Vocation_t::VOCATION_PALADIN, 100 }, + { Vocation_t::VOCATION_SORCERER, 150 }, + { Vocation_t::VOCATION_DRUID, 150 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Capacity, + { + { Vocation_t::VOCATION_KNIGHT, 50 }, + { Vocation_t::VOCATION_PALADIN, 150 }, + { Vocation_t::VOCATION_SORCERER, 300 }, + { Vocation_t::VOCATION_DRUID, 300 }, + }, + } + }; + + auto modifierIt = stats.find(modifier); + if (modifierIt != stats.end()) { + auto vocationIt = modifierIt->second.find(vocation); + if (vocationIt != modifierIt->second.end()) { + return vocationIt->second; + } + } + return 0; +} + +static int32_t getCapacityValue(Vocation_t vocation, WheelGemBasicModifier_t modifier) { + static const std::unordered_map> stats = { + { + WheelGemBasicModifier_t::Vocation_Capacity_FireResistance, + { + { Vocation_t::VOCATION_KNIGHT, 250 }, + { Vocation_t::VOCATION_PALADIN, 200 }, + { Vocation_t::VOCATION_SORCERER, 100 }, + { Vocation_t::VOCATION_DRUID, 100 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Capacity_EnergyResistance, + { + { Vocation_t::VOCATION_KNIGHT, 250 }, + { Vocation_t::VOCATION_PALADIN, 200 }, + { Vocation_t::VOCATION_SORCERER, 100 }, + { Vocation_t::VOCATION_DRUID, 100 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Capacity_EarthResistance, + { + { Vocation_t::VOCATION_KNIGHT, 250 }, + { Vocation_t::VOCATION_PALADIN, 200 }, + { Vocation_t::VOCATION_SORCERER, 100 }, + { Vocation_t::VOCATION_DRUID, 100 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Capacity_IceResistance, + { + { Vocation_t::VOCATION_KNIGHT, 250 }, + { Vocation_t::VOCATION_PALADIN, 200 }, + { Vocation_t::VOCATION_SORCERER, 100 }, + { Vocation_t::VOCATION_DRUID, 100 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Capacity, + { + { Vocation_t::VOCATION_KNIGHT, 250 }, + { Vocation_t::VOCATION_PALADIN, 200 }, + { Vocation_t::VOCATION_SORCERER, 100 }, + { Vocation_t::VOCATION_DRUID, 100 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Mixed, + { + { Vocation_t::VOCATION_KNIGHT, 125 }, + }, + }, + { + WheelGemBasicModifier_t::Vocation_Mixed2, + { + { Vocation_t::VOCATION_KNIGHT, 250 }, + { Vocation_t::VOCATION_PALADIN, 200 }, + { Vocation_t::VOCATION_SORCERER, 100 }, + { Vocation_t::VOCATION_DRUID, 100 }, + }, + } + }; + + auto modifierIt = stats.find(modifier); + if (modifierIt != stats.end()) { + auto vocationIt = modifierIt->second.find(vocation); + if (vocationIt != modifierIt->second.end()) { + return vocationIt->second; + } + } + return 0; +} diff --git a/src/enums/forge_conversion.hpp b/src/enums/forge_conversion.hpp new file mode 100644 index 00000000000..8f84a453bf2 --- /dev/null +++ b/src/enums/forge_conversion.hpp @@ -0,0 +1,18 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#pragma once + +enum class ForgeAction_t : uint8_t { + FUSION = 0, + TRANSFER = 1, + DUSTTOSLIVERS = 2, + SLIVERSTOCORES = 3, + INCREASELIMIT = 4 +}; diff --git a/src/enums/item_attribute.hpp b/src/enums/item_attribute.hpp index a245f3d5eb9..1f6bccf1671 100644 --- a/src/enums/item_attribute.hpp +++ b/src/enums/item_attribute.hpp @@ -45,6 +45,7 @@ enum ItemAttribute_t : uint64_t { CUSTOM = 32, LOOTMESSAGE_SUFFIX = 33, STORE_INBOX_CATEGORY = 34, + OBTAINCONTAINER = 35, }; enum ItemDecayState_t : uint8_t { diff --git a/src/game/game.cpp b/src/game/game.cpp index 4a573d31f94..732b589e488 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -136,7 +136,7 @@ namespace InternalGame { auto targetItem = targetThing ? targetThing->getItem() : nullptr; uint16_t targetId = targetItem ? targetItem->getID() : 0; auto invitedCheckUseWith = house && item->getRealParent() && item->getRealParent() != player && (!house->isInvited(player) || house->getHouseAccessLevel(player) == HOUSE_GUEST); - if (targetId != 0 && targetItem && invitedCheckUseWith && !item->canBeUsedByGuests()) { + if (targetId != 0 && targetItem && !targetItem->isDummy() && invitedCheckUseWith && !item->canBeUsedByGuests()) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return false; } @@ -2055,6 +2055,7 @@ ReturnValue Game::internalRemoveItem(std::shared_ptr item, int32_t count / if (count == -1) { count = item->getItemCount(); } + ReturnValue ret = cylinder->queryRemove(item, count, flags | FLAG_IGNORENOTMOVABLE); if (!force && ret != RETURNVALUE_NOERROR) { g_logger().debug("{} - Failed to execute query remove", __FUNCTION__); @@ -2096,13 +2097,28 @@ ReturnValue Game::internalRemoveItem(std::shared_ptr item, int32_t count / } std::tuple Game::addItemBatch(const std::shared_ptr &toCylinder, const std::vector> &items, uint32_t flags /* = 0 */, bool dropOnMap /* = true */, uint32_t autoContainerId /* = 0 */) { - metrics::method_latency measure(__METHOD_NAME__); - const auto player = toCylinder->getPlayer(); - bool dropping = false; - ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; uint32_t totalAdded = 0; uint32_t containersCreated = 0; + ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; + if (dropOnMap) { + for (const auto &item : items) { + auto returnError = internalAddItem(toCylinder->getTile(), item, INDEX_WHEREEVER, FLAG_NOLIMIT); + if (returnError == RETURNVALUE_NOERROR) { + if (item->getContainer()) { + containersCreated++; + } + totalAdded++; + } + + ret = returnError; + } + return std::make_tuple(ret, totalAdded, containersCreated); + } + + metrics::method_latency measure(__METHOD_NAME__); + const auto player = toCylinder->getPlayer(); + bool dropping = false; auto setupDestination = [&]() -> std::shared_ptr { if (autoContainerId == 0) { return toCylinder; @@ -2135,7 +2151,11 @@ std::tuple Game::addItemBatch(const std::shared } if (!dropping) { uint32_t remainderCount = 0; - ret = internalAddItem(destination, item, CONST_SLOT_WHEREEVER, flags, false, remainderCount); + ret = internalCollectManagedItems(player, item, g_game().getObjectCategory(item), false); + // If cannot place it in the obtain containers, will add it normally + if (ret != RETURNVALUE_NOERROR) { + ret = internalAddItem(destination, item, CONST_SLOT_WHEREEVER, flags, false, remainderCount); + } if (remainderCount != 0) { std::shared_ptr remainderItem = Item::CreateItem(item->getID(), remainderCount); ReturnValue remaindRet = internalAddItem(destination->getTile(), remainderItem, INDEX_WHEREEVER, FLAG_NOLIMIT); @@ -2200,7 +2220,16 @@ std::tuple Game::createItem(const std::shared_p ReturnValue Game::internalPlayerAddItem(std::shared_ptr player, std::shared_ptr item, bool dropOnMap /*= true*/, Slots_t slot /*= CONST_SLOT_WHEREEVER*/) { metrics::method_latency measure(__METHOD_NAME__); uint32_t remainderCount = 0; - ReturnValue ret = internalAddItem(player, item, static_cast(slot), 0, false, remainderCount); + ReturnValue ret; + if (slot == CONST_SLOT_WHEREEVER) { + ret = internalCollectManagedItems(player, item, getObjectCategory(item), false); + // If cannot place it in the obtain containers, will add it normally + if (ret != RETURNVALUE_NOERROR) { + ret = internalAddItem(player, item, slot, 0, false, remainderCount); + } + } else { + ret = internalAddItem(player, item, slot, 0, false, remainderCount); + } if (remainderCount != 0) { std::shared_ptr remainderItem = Item::CreateItem(item->getID(), remainderCount); ReturnValue remaindRet = internalAddItem(player->getTile(), remainderItem, INDEX_WHEREEVER, FLAG_NOLIMIT); @@ -2560,7 +2589,7 @@ ReturnValue Game::internalTeleport(const std::shared_ptr &thing, const Po return RETURNVALUE_NOTPOSSIBLE; } -void Game::playerQuickLootCorpse(std::shared_ptr player, std::shared_ptr corpse) { +void Game::playerQuickLootCorpse(std::shared_ptr player, std::shared_ptr corpse, const Position &position) { if (!player || !corpse) { return; } @@ -2596,7 +2625,7 @@ void Game::playerQuickLootCorpse(std::shared_ptr player, std::shared_ptr uint16_t baseCount = item->getItemCount(); ObjectCategory_t category = getObjectCategory(item); - ReturnValue ret = internalCollectLootItems(player, item, category); + ReturnValue ret = internalCollectManagedItems(player, item, category); if (ret == RETURNVALUE_NOTENOUGHCAPACITY) { shouldNotifyCapacity = true; } else if (ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) { @@ -2677,10 +2706,6 @@ void Game::playerQuickLootCorpse(std::shared_ptr player, std::shared_ptr } else { ss << "No loot"; } - - if (player->checkAutoLoot()) { - ss << " (automatic looting)"; - } ss << "."; player->sendTextMessage(MESSAGE_STATUS, ss.str()); @@ -2703,14 +2728,14 @@ void Game::playerQuickLootCorpse(std::shared_ptr player, std::shared_ptr player->lastQuickLootNotification = OTSYS_TIME(); } -std::shared_ptr Game::findLootContainer(std::shared_ptr player, bool &fallbackConsumed, ObjectCategory_t category) { - auto lootContainer = player->getLootContainer(category); +std::shared_ptr Game::findManagedContainer(std::shared_ptr player, bool &fallbackConsumed, ObjectCategory_t category, bool isLootContainer) { + auto lootContainer = player->getManagedContainer(category, isLootContainer); if (!lootContainer && player->quickLootFallbackToMainContainer && !fallbackConsumed) { auto fallbackItem = player->getInventoryItem(CONST_SLOT_BACKPACK); auto mainBackpack = fallbackItem ? fallbackItem->getContainer() : nullptr; if (mainBackpack) { - player->setLootContainer(OBJECTCATEGORY_DEFAULT, mainBackpack); + player->refreshManagedContainer(OBJECTCATEGORY_DEFAULT, mainBackpack, isLootContainer); player->sendInventoryItem(CONST_SLOT_BACKPACK, player->getInventoryItem(CONST_SLOT_BACKPACK)); lootContainer = mainBackpack; fallbackConsumed = true; @@ -2796,7 +2821,7 @@ ReturnValue Game::processLootItems(std::shared_ptr player, std::shared_p return ret; } -ReturnValue Game::internalCollectLootItems(std::shared_ptr player, std::shared_ptr item, ObjectCategory_t category /* = OBJECTCATEGORY_DEFAULT*/) { +ReturnValue Game::internalCollectManagedItems(std::shared_ptr player, std::shared_ptr item, ObjectCategory_t category /* = OBJECTCATEGORY_DEFAULT*/, bool isLootContainer /* = true*/) { if (!player || !item) { return RETURNVALUE_NOTPOSSIBLE; } @@ -2826,7 +2851,7 @@ ReturnValue Game::internalCollectLootItems(std::shared_ptr player, std:: } bool fallbackConsumed = false; - std::shared_ptr lootContainer = findLootContainer(player, fallbackConsumed, category); + std::shared_ptr lootContainer = findManagedContainer(player, fallbackConsumed, category, isLootContainer); if (!lootContainer) { return RETURNVALUE_NOTPOSSIBLE; } @@ -2862,7 +2887,7 @@ ReturnValue Game::collectRewardChestItems(std::shared_ptr player, uint32 } ObjectCategory_t category = getObjectCategory(item); - if (internalCollectLootItems(player, item, category) == RETURNVALUE_NOERROR) { + if (internalCollectManagedItems(player, item, category) == RETURNVALUE_NOERROR) { movedRewardItems++; } } @@ -2886,7 +2911,16 @@ ObjectCategory_t Game::getObjectCategory(std::shared_ptr item) { const ItemType &it = Item::items[item->getID()]; if (item->getWorth() != 0) { category = OBJECTCATEGORY_GOLD; - } else if (it.weaponType != WEAPON_NONE) { + } else { + category = getObjectCategory(it); + } + + return category; +} + +ObjectCategory_t Game::getObjectCategory(const ItemType &it) { + ObjectCategory_t category = OBJECTCATEGORY_DEFAULT; + if (it.weaponType != WEAPON_NONE) { switch (it.weaponType) { case WEAPON_SWORD: category = OBJECTCATEGORY_SWORDS; @@ -3302,14 +3336,13 @@ void Game::playerUseItemEx(uint32_t playerId, const Position &fromPos, uint8_t f return; } - if (g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__) && !InternalGame::playerCanUseItemWithOnHouseTile(player, item, toPos, toStackPos, toItemId)) { - player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); - return; - } - - if (item->hasOwner() && !item->isOwner(player)) { + bool canUseHouseItem = !g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__) || InternalGame::playerCanUseItemOnHouseTile(player, item); + if (!canUseHouseItem && item->hasOwner() && !item->isOwner(player)) { player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS); return; + } else if (!canUseHouseItem) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return; } Position walkToPos = fromPos; @@ -3437,12 +3470,11 @@ void Game::playerUseItem(uint32_t playerId, const Position &pos, uint8_t stackPo return; } - if (item->hasOwner() && !item->isOwner(player)) { + bool canUseHouseItem = !g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__) || InternalGame::playerCanUseItemOnHouseTile(player, item); + if (!canUseHouseItem && item->hasOwner() && !item->isOwner(player)) { player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS); return; - } - - if (g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__) && !InternalGame::playerCanUseItemOnHouseTile(player, item)) { + } else if (!canUseHouseItem) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return; } @@ -4280,7 +4312,7 @@ void Game::playerStashWithdraw(uint32_t playerId, uint16_t itemId, uint32_t coun } uint16_t freeSlots = player->getFreeBackpackSlots(); - auto stashContainer = player->getLootContainer(OBJECTCATEGORY_STASHRETRIEVE); + auto stashContainer = player->getManagedContainer(getObjectCategory(it), false); if (stashContainer && !(player->quickLootFallbackToMainContainer)) { freeSlots = stashContainer->getFreeSlots(); } @@ -5060,7 +5092,7 @@ void Game::playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t item if (pos.x == 0xffff && !browseField && !corpse->isRewardCorpse()) { uint32_t worth = item->getWorth(); ObjectCategory_t category = getObjectCategory(item); - ReturnValue ret = internalCollectLootItems(player, item, category); + ReturnValue ret = internalCollectManagedItems(player, item, category); std::stringstream ss; if (ret == RETURNVALUE_NOTENOUGHCAPACITY) { @@ -5097,11 +5129,11 @@ void Game::playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t item auto rewardId = corpse->getAttribute(ItemAttribute_t::DATE); auto reward = player->getReward(rewardId, false); if (reward) { - playerQuickLootCorpse(player, reward->getContainer()); + playerQuickLootCorpse(player, reward->getContainer(), corpse->getPosition()); } } else { if (!lootAllCorpses) { - playerQuickLootCorpse(player, corpse); + playerQuickLootCorpse(player, corpse, corpse->getPosition()); } else { playerLootAllCorpses(player, pos, lootAllCorpses); } @@ -5138,7 +5170,7 @@ void Game::playerLootAllCorpses(std::shared_ptr player, const Position & } corpses++; - playerQuickLootCorpse(player, tileCorpse); + playerQuickLootCorpse(player, tileCorpse, tileCorpse->getPosition()); if (corpses >= 30) { break; } @@ -5158,7 +5190,7 @@ void Game::playerLootAllCorpses(std::shared_ptr player, const Position & browseField = false; } -void Game::playerSetLootContainer(uint32_t playerId, ObjectCategory_t category, const Position &pos, uint16_t itemId, uint8_t stackPos) { +void Game::playerSetManagedContainer(uint32_t playerId, ObjectCategory_t category, const Position &pos, uint16_t itemId, uint8_t stackPos, bool isLootContainer) { std::shared_ptr player = getPlayerByID(playerId); if (!player || pos.x != 0xffff) { return; @@ -5181,7 +5213,7 @@ void Game::playerSetLootContainer(uint32_t playerId, ObjectCategory_t category, return; } - std::shared_ptr previousContainer = player->setLootContainer(category, container); + std::shared_ptr previousContainer = player->refreshManagedContainer(category, container, isLootContainer); player->sendLootContainers(); std::shared_ptr parent = container->getParent(); @@ -5197,13 +5229,13 @@ void Game::playerSetLootContainer(uint32_t playerId, ObjectCategory_t category, } } -void Game::playerClearLootContainer(uint32_t playerId, ObjectCategory_t category) { +void Game::playerClearManagedContainer(uint32_t playerId, ObjectCategory_t category, bool isLootContainer) { std::shared_ptr player = getPlayerByID(playerId); if (!player) { return; } - std::shared_ptr previousContainer = player->setLootContainer(category, nullptr); + std::shared_ptr previousContainer = player->refreshManagedContainer(category, nullptr, isLootContainer); player->sendLootContainers(); if (previousContainer != nullptr) { @@ -5214,13 +5246,13 @@ void Game::playerClearLootContainer(uint32_t playerId, ObjectCategory_t category } } -void Game::playerOpenLootContainer(uint32_t playerId, ObjectCategory_t category) { +void Game::playerOpenManagedContainer(uint32_t playerId, ObjectCategory_t category, bool isLootContainer) { std::shared_ptr player = getPlayerByID(playerId); if (!player) { return; } - std::shared_ptr container = player->getLootContainer(category); + std::shared_ptr container = player->getManagedContainer(category, isLootContainer); if (!container) { return; } @@ -5706,12 +5738,7 @@ bool Game::playerSaySpell(std::shared_ptr player, SpeakClasses type, con if (!g_configManager().getBoolean(PUSH_WHEN_ATTACKING, __FUNCTION__)) { player->cancelPush(); } - - if (g_configManager().getBoolean(EMOTE_SPELLS, __FUNCTION__) && player->getStorageValue(STORAGEVALUE_EMOTE) == 1) { - return internalCreatureSay(player, TALKTYPE_MONSTER_SAY, words, false); - } else { - return player->saySpell(type, words, false); - } + return player->saySpell(type, words, false); } else if (result == TALKACTION_FAILED) { return true; } @@ -6095,15 +6122,11 @@ bool Game::combatBlockHit(CombatDamage &damage, std::shared_ptr attack // Skill dodge (ruse) if (std::shared_ptr targetPlayer = target->getPlayer()) { - if (auto playerArmor = targetPlayer->getInventoryItem(CONST_SLOT_ARMOR); - playerArmor != nullptr && playerArmor->getTier()) { - double_t chance = playerArmor->getDodgeChance(); - double_t randomChance = uniform_random(0, 10000) / 100; - if (chance > 0 && randomChance < chance) { - InternalGame::sendBlockEffect(BLOCK_DODGE, damage.primary.type, target->getPosition(), attacker); - targetPlayer->sendTextMessage(MESSAGE_ATTENTION, "You dodged an attack. (Ruse)"); - return true; - } + auto chance = targetPlayer->getDodgeChance(); + if (chance > 0 && uniform_random(0, 10000) < chance) { + InternalGame::sendBlockEffect(BLOCK_DODGE, damage.primary.type, target->getPosition(), attacker); + targetPlayer->sendTextMessage(MESSAGE_ATTENTION, "You dodged an attack."); + return true; } } @@ -6162,13 +6185,16 @@ bool Game::combatBlockHit(CombatDamage &damage, std::shared_ptr attack } } } - int32_t primaryReflectPercent = target->getReflectPercent(damage.primary.type, true); + double_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)))); + int32_t reflectFlat = -static_cast(primaryReflectFlat); + int32_t reflectPercent = std::ceil(damage.primary.value * primaryReflectPercent / 100.); + int32_t reflectLimit = std::ceil(attacker->getMaxHealth() * 0.01); + damageReflected.primary.value = std::max(-reflectLimit, reflectFlat + reflectPercent); if (targetPlayer) { damageReflected.primary.type = COMBAT_NEUTRALDAMAGE; } else { @@ -6178,7 +6204,7 @@ bool Game::combatBlockHit(CombatDamage &damage, std::shared_ptr attack damageReflected.exString += ", "; } damageReflected.extension = true; - damageReflected.exString += "damage reflection"; + damageReflected.exString += " (damage reflection)"; damageReflectedParams.combatType = damage.primary.type; damageReflectedParams.aggressive = true; canReflect = true; @@ -6190,13 +6216,11 @@ bool Game::combatBlockHit(CombatDamage &damage, std::shared_ptr attack } if (damage.secondary.type != COMBAT_NONE) { - 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; } @@ -6220,13 +6244,16 @@ bool Game::combatBlockHit(CombatDamage &damage, std::shared_ptr attack int32_t secondaryReflectFlat = target->getReflectFlat(damage.secondary.type, true); if (secondaryReflectPercent > 0 || secondaryReflectFlat > 0) { if (!canReflect) { + int32_t reflectFlat = -static_cast(secondaryReflectFlat); + int32_t reflectPercent = std::ceil(damage.primary.value * secondaryReflectPercent / 100.); + int32_t reflectLimit = std::ceil(attacker->getMaxHealth() * 0.01); + damageReflected.primary.value = std::max(-reflectLimit, reflectFlat + reflectPercent); damageReflected.primary.type = damage.secondary.type; - 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; @@ -9055,7 +9082,7 @@ void Game::playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, ui } } -void Game::playerForgeFuseItems(uint32_t playerId, uint16_t itemId, uint8_t tier, bool usedCore, bool reduceTierLoss) { +void Game::playerForgeFuseItems(uint32_t playerId, ForgeAction_t actionType, uint16_t firstItemId, uint8_t tier, uint16_t secondItemId, bool usedCore, bool reduceTierLoss, bool convergence) { metrics::method_latency measure(__METHOD_NAME__); std::shared_ptr player = getPlayerByID(playerId); if (!player) { @@ -9071,26 +9098,34 @@ void Game::playerForgeFuseItems(uint32_t playerId, uint16_t itemId, uint8_t tier uint8_t coreCount = (usedCore ? 1 : 0) + (reduceTierLoss ? 1 : 0); auto baseSuccess = static_cast(g_configManager().getNumber(FORGE_BASE_SUCCESS_RATE, __FUNCTION__)); - auto bonusSuccess = static_cast(g_configManager().getNumber(FORGE_BASE_SUCCESS_RATE, __FUNCTION__) + g_configManager().getNumber(FORGE_BONUS_SUCCESS_RATE, __FUNCTION__)); - auto roll = static_cast(uniform_random(1, 100)) <= (usedCore ? bonusSuccess : baseSuccess); + auto coreSuccess = usedCore ? g_configManager().getNumber(FORGE_BONUS_SUCCESS_RATE, __FUNCTION__) : 0; + auto finalRate = baseSuccess + coreSuccess; + auto roll = static_cast(uniform_random(1, 100)) <= finalRate; + bool success = roll ? true : false; auto chance = uniform_random(0, 10000); - uint8_t bonus = forgeBonus(chance); + uint8_t bonus = convergence ? 0 : forgeBonus(chance); - player->forgeFuseItems(itemId, tier, success, reduceTierLoss, bonus, coreCount); + player->forgeFuseItems(actionType, firstItemId, tier, secondItemId, success, reduceTierLoss, convergence, bonus, coreCount); } -void Game::playerForgeTransferItemTier(uint32_t playerId, uint16_t donorItemId, uint8_t tier, uint16_t receiveItemId) { +void Game::playerForgeTransferItemTier(uint32_t playerId, ForgeAction_t actionType, uint16_t donorItemId, uint8_t tier, uint16_t receiveItemId, bool convergence) { std::shared_ptr player = getPlayerByID(playerId); if (!player) { return; } - player->forgeTransferItemTier(donorItemId, tier, receiveItemId); + if (player->isUIExhausted()) { + player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); + return; + } + + player->updateUIExhausted(); + player->forgeTransferItemTier(actionType, donorItemId, tier, receiveItemId, convergence); } -void Game::playerForgeResourceConversion(uint32_t playerId, uint8_t action) { +void Game::playerForgeResourceConversion(uint32_t playerId, ForgeAction_t actionType) { std::shared_ptr player = getPlayerByID(playerId); if (!player) { return; @@ -9102,7 +9137,7 @@ void Game::playerForgeResourceConversion(uint32_t playerId, uint8_t action) { } player->updateUIExhausted(); - player->forgeResourceConversion(action); + player->forgeResourceConversion(actionType); } void Game::playerBrowseForgeHistory(uint32_t playerId, uint8_t page) { @@ -9403,6 +9438,40 @@ void Game::playerSaveWheel(uint32_t playerId, NetworkMessage &msg) { player->updateUIExhausted(); } +void Game::playerWheelGemAction(uint32_t playerId, NetworkMessage &msg) { + std::shared_ptr player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (player->isUIExhausted()) { + player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); + return; + } + + auto action = msg.get(); + auto param = msg.get(); + + switch (static_cast(action)) { + case WheelGemAction_t::Destroy: + player->wheel()->destroyGem(param); + break; + case WheelGemAction_t::Reveal: + player->wheel()->revealGem(static_cast(param)); + break; + case WheelGemAction_t::SwitchDomain: + player->wheel()->switchGemDomain(param); + break; + case WheelGemAction_t::ToggleLock: + player->wheel()->toggleGemLock(param); + break; + default: + g_logger().error("[{}] player {} is trying to do invalid action {} on wheel", __FUNCTION__, player->getName(), action); + break; + } + player->updateUIExhausted(); +} + /* Player Methods end ********************/ @@ -9546,7 +9615,7 @@ void Game::removeUniqueItem(uint16_t uniqueId) { } bool Game::hasEffect(uint16_t effectId) { - for (uint16_t i = CONST_ME_NONE; i <= CONST_ME_LAST; i++) { + for (uint16_t i = CONST_ME_NONE; i < CONST_ME_LAST; i++) { MagicEffectClasses effect = static_cast(i); if (effect == effectId) { return true; @@ -10056,7 +10125,8 @@ void Game::playerRewardChestCollect(uint32_t playerId, const Position &pos, uint } bool Game::tryRetrieveStashItems(std::shared_ptr player, std::shared_ptr item) { - return internalCollectLootItems(player, item, OBJECTCATEGORY_STASHRETRIEVE) == RETURNVALUE_NOERROR; + ObjectCategory_t category = getObjectCategory(item); + return internalCollectManagedItems(player, item, category, false) == RETURNVALUE_NOERROR; } std::unique_ptr &Game::getIOWheel() { diff --git a/src/game/game.hpp b/src/game/game.hpp index 9fb56f8b88c..aff723fd780 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -241,6 +241,7 @@ class Game { bool internalCreatureSay(std::shared_ptr creature, SpeakClasses type, const std::string &text, bool ghostMode, Spectators* spectatorsPtr = nullptr, const Position* pos = nullptr); ObjectCategory_t getObjectCategory(const std::shared_ptr item); + ObjectCategory_t getObjectCategory(const ItemType &it); uint64_t getItemMarketPrice(const std::map &itemMap, bool buyPrice) const; @@ -260,18 +261,23 @@ class Game { void playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, uint8_t button, uint8_t choice); void playerForgeFuseItems( uint32_t playerId, - uint16_t itemId, + ForgeAction_t actionType, + uint16_t firstItemId, uint8_t tier, + uint16_t secondItemId, bool usedCore, - bool reduceTierLoss + bool reduceTierLoss, + bool convergence ); void playerForgeTransferItemTier( uint32_t playerId, + ForgeAction_t actionType, uint16_t donorItemId, uint8_t tier, - uint16_t receiveItemId + uint16_t receiveItemId, + bool convergence ); - void playerForgeResourceConversion(uint32_t playerId, uint8_t action); + void playerForgeResourceConversion(uint32_t playerId, ForgeAction_t actionType); void playerBrowseForgeHistory(uint32_t playerId, uint8_t page); void playerBosstiarySlot(uint32_t playerId, uint8_t slotId, uint32_t selectedBossId); @@ -350,13 +356,13 @@ class Game { void playerSetFightModes(uint32_t playerId, FightMode_t fightMode, bool chaseMode, bool secureMode); void playerLookAt(uint32_t playerId, uint16_t itemId, const Position &pos, uint8_t stackPos); void playerLookInBattleList(uint32_t playerId, uint32_t creatureId); - void playerQuickLootCorpse(std::shared_ptr player, std::shared_ptr corpse); + void playerQuickLootCorpse(std::shared_ptr player, std::shared_ptr corpse, const Position &position); void playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t itemId, uint8_t stackPos, std::shared_ptr defaultItem = nullptr, bool lootAllCorpses = false, bool autoLoot = false); void playerLootAllCorpses(std::shared_ptr player, const Position &pos, bool lootAllCorpses); - void playerSetLootContainer(uint32_t playerId, ObjectCategory_t category, const Position &pos, uint16_t itemId, uint8_t stackPos); - void playerClearLootContainer(uint32_t playerId, ObjectCategory_t category); + void playerSetManagedContainer(uint32_t playerId, ObjectCategory_t category, const Position &pos, uint16_t itemId, uint8_t stackPos, bool isLootContainer); + void playerClearManagedContainer(uint32_t playerId, ObjectCategory_t category, bool isLootContainer); ; - void playerOpenLootContainer(uint32_t playerId, ObjectCategory_t category); + void playerOpenManagedContainer(uint32_t playerId, ObjectCategory_t category, bool isLootContainer); void playerSetQuickLootFallback(uint32_t playerId, bool fallback); void playerQuickLootBlackWhitelist(uint32_t playerId, QuickLootFilter_t filter, const std::vector itemIds); @@ -398,6 +404,7 @@ class Game { void playerOpenWheel(uint32_t playerId, uint32_t ownerId); void playerSaveWheel(uint32_t playerId, NetworkMessage &msg); + void playerWheelGemAction(uint32_t playerId, NetworkMessage &msg); void updatePlayerHelpers(std::shared_ptr player); @@ -480,6 +487,7 @@ class Game { void addPlayerMana(const std::shared_ptr target); void addPlayerVocation(const std::shared_ptr target); void addMagicEffect(const Position &pos, uint16_t effect); + static void addMagicEffect(const std::vector> &players, const Position &pos, uint16_t effect); static void addMagicEffect(const CreatureVector &spectators, const Position &pos, uint16_t effect); void removeMagicEffect(const Position &pos, uint16_t effect); static void removeMagicEffect(const CreatureVector &spectators, const Position &pos, uint16_t effect); @@ -668,7 +676,7 @@ class Game { /** * @brief Attemtps to retrieve an item from the stash. * - * @details This function leverages the internalCollectLootItems function with the OBJECTCATEGORY_STASHRETRIEVE category + * @details This function leverages the internalCollectManagedItems function with the OBJECTCATEGORY_STASHRETRIEVE category * to determine if the player is capable of retrieving the stash items. * * @param player Pointer to the player object. @@ -699,18 +707,18 @@ class Game { std::shared_ptr createPlayerTask(uint32_t delay, std::function f, std::string context) const; /** - * @brief Finds the container for loot based on the given parameters. + * @brief Finds the managed container for loot or obtain based on the given parameters. * * @param player Pointer to the player object. * @param fallbackConsumed Reference to a boolean flag indicating whether a fallback has been consumed. * @param category The category of the object. * * @note If it's enabled in config.lua to use the gold pouch to store any item, then the system will check whether the player has a loot pouch. - * @note If the player does have one, the loot pouch will be used instead of the loot containers. + * @note If the player does have one, the loot pouch will be used instead of the managed containers. * - * @return Pointer to the loot container or nullptr if not found. + * @return Pointer to the managed container or nullptr if not found. */ - std::shared_ptr findLootContainer(std::shared_ptr player, bool &fallbackConsumed, ObjectCategory_t category); + std::shared_ptr findManagedContainer(std::shared_ptr player, bool &fallbackConsumed, ObjectCategory_t category, bool isLootContainer); /** * @brief Finds the next available sub-container within a container. @@ -756,14 +764,14 @@ class Game { ReturnValue processLootItems(std::shared_ptr player, std::shared_ptr lootContainer, std::shared_ptr item, bool &fallbackConsumed); /** - * @brief Internally collects loot items from a given item and places them into the loot container. + * @brief Internally collects loot or obtain items from a given item and places them into the managed container. * * @param player Pointer to the player object. - * @param item Pointer to the item being looted. + * @param item Pointer to the item being collected. * @param category Category of the item (default is OBJECTCATEGORY_DEFAULT). * @return Return value indicating success or error. */ - ReturnValue internalCollectLootItems(std::shared_ptr player, std::shared_ptr item, ObjectCategory_t category = OBJECTCATEGORY_DEFAULT); + ReturnValue internalCollectManagedItems(std::shared_ptr player, std::shared_ptr item, ObjectCategory_t category = OBJECTCATEGORY_DEFAULT, bool isLootContainer = true); /** * @brief Collects items from the reward chest. diff --git a/src/game/scheduling/task.hpp b/src/game/scheduling/task.hpp index e02a1ce4056..dbb6d658a20 100644 --- a/src/game/scheduling/task.hpp +++ b/src/game/scheduling/task.hpp @@ -100,6 +100,7 @@ class Task { "SpawnNpc::checkSpawnNpc", "Webhook::run", "Protocol::sendRecvMessageCallback", + "sendRecvMessageCallback", }); return tasksContext.contains(context); diff --git a/src/game/zones/zone.hpp b/src/game/zones/zone.hpp index 91fa6f25e29..99adf9d985d 100644 --- a/src/game/zones/zone.hpp +++ b/src/game/zones/zone.hpp @@ -210,7 +210,7 @@ class Zone { static bool loadFromXML(const std::string &fileName, uint16_t shiftID = 0); -private: +protected: bool contains(const Position &position) const; Position removeDestination = Position(); diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index 33a8b0a8ff7..2c50938a37f 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -516,11 +516,15 @@ void IOLoginDataLoad::loadPlayerInventoryItems(std::shared_ptr player, D openContainersList.emplace_back(std::make_pair(cid, itemContainer)); } } - if (item->hasAttribute(ItemAttribute_t::QUICKLOOTCONTAINER)) { - auto flags = item->getAttribute(ItemAttribute_t::QUICKLOOTCONTAINER); - for (uint8_t category = OBJECTCATEGORY_FIRST; category <= OBJECTCATEGORY_LAST; category++) { - if (hasBitSet(1 << category, static_cast(flags))) { - player->setLootContainer(static_cast(category), itemContainer, true); + for (bool isLootContainer : { true, false }) { + auto checkAttribute = isLootContainer ? ItemAttribute_t::QUICKLOOTCONTAINER : ItemAttribute_t::OBTAINCONTAINER; + if (item->hasAttribute(checkAttribute)) { + auto flags = item->getAttribute(checkAttribute); + + for (uint8_t category = OBJECTCATEGORY_FIRST; category <= OBJECTCATEGORY_LAST; category++) { + if (hasBitSet(1 << category, flags)) { + player->refreshManagedContainer(static_cast(category), itemContainer, isLootContainer, true); + } } } } @@ -786,7 +790,7 @@ void IOLoginDataLoad::loadPlayerForgeHistory(std::shared_ptr player, DBR query << "SELECT * FROM `forge_history` WHERE `player_id` = " << player->getGUID(); if (result = Database::getInstance().storeQuery(query.str())) { do { - auto actionEnum = magic_enum::enum_value(result->getNumber("action_type")); + auto actionEnum = magic_enum::enum_value(result->getNumber("action_type")); ForgeHistory history; history.actionType = actionEnum; history.description = result->getString("description"); diff --git a/src/io/io_bosstiary.cpp b/src/io/io_bosstiary.cpp index 10c4f2f088c..eea2c7f3547 100644 --- a/src/io/io_bosstiary.cpp +++ b/src/io/io_bosstiary.cpp @@ -15,6 +15,7 @@ #include "creatures/players/player.hpp" #include "game/game.hpp" #include "utils/tools.hpp" +#include "items/item.hpp" void IOBosstiary::loadBoostedBoss() { Database &database = Database::getInstance(); diff --git a/src/io/io_wheel.cpp b/src/io/io_wheel.cpp index 3a8d69d7dac..d0f3c5c1e28 100644 --- a/src/io/io_wheel.cpp +++ b/src/io/io_wheel.cpp @@ -17,6 +17,10 @@ #include "utils/tools.hpp" +#define MITIGATION_INCREASE 0.03 +#define MANA_LEECH_INCREASE 0.25 +#define HEALTH_LEECH_INCREASE 0.75 + /** * @brief This namespace groups together variables, functions, and class definitions within a specific scope. * @brief Utilizing namespaces in C++ is a strategic approach to mitigate the need for file inclusions in header files (.hpp). @@ -332,9 +336,9 @@ void IOWheel::addSpell(const std::shared_ptr &player, PlayerWheelMethods } } -void IOWheel::increaseResistance(const std::shared_ptr &player, PlayerWheelMethodsBonusData &bonusData, WheelSlots_t slotType, uint16_t points, CombatType_t combat, int16_t value) const { +void IOWheel::addVesselResonance(const std::shared_ptr &player, PlayerWheelMethodsBonusData &bonusData, WheelSlots_t slotType, WheelGemAffinity_t affinity, uint16_t points) const { if (isMaxPointAddedToSlot(player, points, slotType)) { - bonusData.resistance[combatTypeToIndex(combat)] += value; + bonusData.unlockedVesselResonances[static_cast(affinity)]++; } } @@ -411,8 +415,10 @@ void IOWheel::slotGreen200(const std::shared_ptr &player, uint16_t point // SLOT_GREEN_TOP_150 = 2 void IOWheel::slotGreenTop150(const std::shared_ptr &player, uint16_t points, uint8_t, PlayerWheelMethodsBonusData &bonusData) const { - bonusData.mitigation += 0.03 * points; - increaseResistance(player, bonusData, WheelSlots_t::SLOT_GREEN_TOP_150, points, COMBAT_ICEDAMAGE, 200); + bonusData.mitigation += MITIGATION_INCREASE * points; + if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_GREEN_BOTTOM_150)) { + bonusData.leech.manaLeech += MANA_LEECH_INCREASE; + } } // SLOT_GREEN_TOP_100 = 3 @@ -424,9 +430,7 @@ void IOWheel::slotGreenTop100(const std::shared_ptr &player, uint16_t po } else { bonusData.stats.health += 1 * points; } - if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_GREEN_TOP_100)) { - bonusData.leech.lifeLeech += 0.75; - } + addVesselResonance(player, bonusData, WheelSlots_t::SLOT_GREEN_TOP_100, WheelGemAffinity_t::Green, points); } // SLOT_RED_TOP_100 = 4 @@ -459,10 +463,7 @@ void IOWheel::slotRedTop150(const std::shared_ptr &player, uint16_t poin } else { bonusData.stats.health += 1 * points; } - - if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_RED_TOP_150)) { - bonusData.leech.manaLeech += 0.25; // 0,25% - } + addVesselResonance(player, bonusData, WheelSlots_t::SLOT_RED_TOP_150, WheelGemAffinity_t::Red, points); } // SLOT_RED_200 = 6 @@ -490,10 +491,8 @@ void IOWheel::slotRed200(const std::shared_ptr &player, uint16_t points, // SLOT_GREEN_BOTTOM_150 = 7 void IOWheel::slotGreenBottom150(const std::shared_ptr &player, uint16_t points, uint8_t, PlayerWheelMethodsBonusData &bonusData) const { - bonusData.mitigation += 0.03 * points; // 0,03% - if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_GREEN_BOTTOM_150)) { - bonusData.leech.manaLeech += 0.25; // 0,25% - } + bonusData.mitigation += MITIGATION_INCREASE * points; + addVesselResonance(player, bonusData, WheelSlots_t::SLOT_GREEN_BOTTOM_150, WheelGemAffinity_t::Green, points); } // SLOT_GREEN_MIDDLE_100 = 8 @@ -523,9 +522,9 @@ void IOWheel::slotGreenTop75(const std::shared_ptr &player, uint16_t poi } else { bonusData.stats.mana += 6 * points; } - // 1% - increaseResistance(player, bonusData, WheelSlots_t::SLOT_GREEN_TOP_75, points, COMBAT_HOLYDAMAGE, 100); - increaseResistance(player, bonusData, WheelSlots_t::SLOT_GREEN_TOP_75, points, COMBAT_DEATHDAMAGE, 100); + if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_GREEN_TOP_100)) { + bonusData.leech.lifeLeech += HEALTH_LEECH_INCREASE; + } } // SLOT_RED_TOP_75 = 10 @@ -537,8 +536,7 @@ void IOWheel::slotRedTop75(const std::shared_ptr &player, uint16_t point } else { bonusData.stats.capacity += 2 * points; } - // 2% - increaseResistance(player, bonusData, WheelSlots_t::SLOT_RED_TOP_75, points, COMBAT_ENERGYDAMAGE, 200); + addVesselResonance(player, bonusData, WheelSlots_t::SLOT_RED_TOP_75, WheelGemAffinity_t::Red, points); } // SLOT_RED_MIDDLE_100 = 11 @@ -568,9 +566,9 @@ void IOWheel::slotRedBottom150(const std::shared_ptr &player, uint16_t p } else { bonusData.stats.health += 1 * points; } - // 1% - increaseResistance(player, bonusData, WheelSlots_t::SLOT_RED_BOTTOM_150, points, COMBAT_HOLYDAMAGE, 100); - increaseResistance(player, bonusData, WheelSlots_t::SLOT_RED_BOTTOM_150, points, COMBAT_DEATHDAMAGE, 100); + if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_RED_TOP_150)) { + bonusData.leech.manaLeech += MANA_LEECH_INCREASE; + } } // SLOT_GREEN_BOTTOM_100 = 13 @@ -621,13 +619,12 @@ void IOWheel::slotGreen50(const std::shared_ptr &player, uint16_t points } else { bonusData.stats.capacity += 2 * points; } - // 2% of resistance - increaseResistance(player, bonusData, WheelSlots_t::SLOT_GREEN_50, points, COMBAT_EARTHDAMAGE, 200); + addVesselResonance(player, bonusData, WheelSlots_t::SLOT_GREEN_50, WheelGemAffinity_t::Green, points); } // SLOT_RED_50 = 16 void IOWheel::slotRed50(const std::shared_ptr &player, uint16_t points, uint8_t vocationCipId, PlayerWheelMethodsBonusData &bonusData) const { - bonusData.mitigation += 0.03 * points; // 0,03% + bonusData.mitigation += MITIGATION_INCREASE * points; if (isKnight(vocationCipId)) { addSpell(player, bonusData, WheelSlots_t::SLOT_RED_50, points, "Fierce Berserk"); } else if (isPaladin(vocationCipId)) { @@ -649,7 +646,7 @@ void IOWheel::slotRedBottom75(const std::shared_ptr &player, uint16_t po bonusData.stats.capacity += 2 * points; } if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_RED_BOTTOM_75)) { - bonusData.leech.lifeLeech += 0.75; // 0,75% + bonusData.leech.lifeLeech += HEALTH_LEECH_INCREASE; } } @@ -662,14 +659,13 @@ void IOWheel::slotRedBottom100(const std::shared_ptr &player, uint16_t p } else { bonusData.stats.mana += 6 * points; } - // Increase 2% of fire elemental damage resistance - increaseResistance(player, bonusData, WheelSlots_t::SLOT_RED_BOTTOM_100, points, COMBAT_FIREDAMAGE, 200); + addVesselResonance(player, bonusData, WheelSlots_t::SLOT_RED_BOTTOM_100, WheelGemAffinity_t::Red, points); } // SLOT_BLUE_TOP_100 = 19 void IOWheel::slotBlueTop100(const std::shared_ptr &player, uint16_t points, uint8_t, PlayerWheelMethodsBonusData &bonusData) const { - bonusData.mitigation += 0.03 * points; // 0,03% - increaseResistance(player, bonusData, WheelSlots_t::SLOT_BLUE_TOP_100, points, COMBAT_ENERGYDAMAGE, 200); + bonusData.mitigation += MITIGATION_INCREASE * points; + addVesselResonance(player, bonusData, WheelSlots_t::SLOT_BLUE_TOP_100, WheelGemAffinity_t::Blue, points); } // SLOT_BLUE_TOP_75 = 20 @@ -682,7 +678,7 @@ void IOWheel::slotBlueTop75(const std::shared_ptr &player, uint16_t poin bonusData.stats.health += 1 * points; } if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_BLUE_TOP_75)) { - bonusData.leech.manaLeech += 0.25; // 0,25% + bonusData.leech.manaLeech += MANA_LEECH_INCREASE; } } @@ -715,13 +711,12 @@ void IOWheel::slotPurple50(const std::shared_ptr &player, uint16_t point } else { bonusData.stats.health += 1 * points; } - // Increase 2% of resistance - increaseResistance(player, bonusData, WheelSlots_t::SLOT_PURPLE_50, points, COMBAT_ICEDAMAGE, 200); + addVesselResonance(player, bonusData, WheelSlots_t::SLOT_PURPLE_50, WheelGemAffinity_t::Purple, points); } // SLOT_PURPLE_TOP_75 = 23 void IOWheel::slotPurpleTop75(const std::shared_ptr &player, uint16_t points, uint8_t vocationCipId, PlayerWheelMethodsBonusData &bonusData) const { - bonusData.mitigation += 0.03 * points; // 0,03% + bonusData.mitigation += MITIGATION_INCREASE * points; auto pointsInSlot = isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_PURPLE_TOP_75); if (isKnight(vocationCipId)) { if (pointsInSlot) { @@ -765,15 +760,14 @@ void IOWheel::slotBlueTop150(const std::shared_ptr &player, uint16_t poi } else { bonusData.stats.capacity += 2 * points; } - // Increase 1% of resistance for holy - increaseResistance(player, bonusData, WheelSlots_t::SLOT_BLUE_TOP_150, points, COMBAT_HOLYDAMAGE, 100); - // Increase 1% of resistance for death - increaseResistance(player, bonusData, WheelSlots_t::SLOT_BLUE_TOP_150, points, COMBAT_DEATHDAMAGE, 100); + if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_BLUE_BOTTOM_150)) { + bonusData.leech.lifeLeech += HEALTH_LEECH_INCREASE; + } } // SLOT_BLUE_MIDDLE_100 = 26 void IOWheel::slotBlueMiddle100(const std::shared_ptr &player, uint16_t points, uint8_t vocationCipId, PlayerWheelMethodsBonusData &bonusData) const { - bonusData.mitigation += 0.03 * points; // 0,03% + bonusData.mitigation += MITIGATION_INCREASE * points; if (isKnight(vocationCipId)) { addSpell(player, bonusData, WheelSlots_t::SLOT_BLUE_MIDDLE_100, points, "Chivalrous Challenge"); } else if (isPaladin(vocationCipId)) { @@ -796,15 +790,15 @@ void IOWheel::slotBlueBottom75(const std::shared_ptr &player, uint16_t p } else { bonusData.stats.health += 1 * points; } - // Increase 2% resistance of fire damage - increaseResistance(player, bonusData, WheelSlots_t::SLOT_BLUE_BOTTOM_75, points, COMBAT_FIREDAMAGE, 200); + addVesselResonance(player, bonusData, WheelSlots_t::SLOT_BLUE_BOTTOM_75, WheelGemAffinity_t::Blue, points); } // SLOT_PURPLE_BOTTOM_75 = 28 void IOWheel::slotPurpleBottom75(const std::shared_ptr &player, uint16_t points, uint8_t, PlayerWheelMethodsBonusData &bonusData) const { - bonusData.mitigation += 0.03 * points; // 0,03% - increaseResistance(player, bonusData, WheelSlots_t::SLOT_PURPLE_BOTTOM_75, points, COMBAT_HOLYDAMAGE, 100); - increaseResistance(player, bonusData, WheelSlots_t::SLOT_PURPLE_BOTTOM_75, points, COMBAT_DEATHDAMAGE, 100); + bonusData.mitigation += MITIGATION_INCREASE * points; + if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_PURPLE_BOTTOM_100)) { + bonusData.leech.manaLeech += MANA_LEECH_INCREASE; + } } // SLOT_PURPLE_MIDDLE_100 = 29 @@ -834,9 +828,7 @@ void IOWheel::slotPurpleTop150(const std::shared_ptr &player, uint16_t p } else { bonusData.stats.mana += 6 * points; } - if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_PURPLE_TOP_150)) { - bonusData.leech.lifeLeech += 0.75; // 0,75% - } + addVesselResonance(player, bonusData, WheelSlots_t::SLOT_PURPLE_TOP_150, WheelGemAffinity_t::Purple, points); } // SLOT_BLUE_200 = 31 @@ -869,14 +861,12 @@ void IOWheel::slotBlueBottom150(const std::shared_ptr &player, uint16_t } else { bonusData.stats.capacity += 2 * points; } - if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_BLUE_BOTTOM_150)) { - bonusData.leech.lifeLeech += 0.75; // 0,75% - } + addVesselResonance(player, bonusData, WheelSlots_t::SLOT_BLUE_BOTTOM_150, WheelGemAffinity_t::Blue, points); } // SLOT_BLUE_BOTTOM_100 = 33 void IOWheel::slotBlueBottom100(const std::shared_ptr &player, uint16_t points, uint8_t vocationCipId, PlayerWheelMethodsBonusData &bonusData) const { - bonusData.mitigation += 0.03 * points; // 0,03% + bonusData.mitigation += MITIGATION_INCREASE * points; bool onSlot = isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_BLUE_BOTTOM_100); if (isKnight(vocationCipId) && onSlot) { bonusData.skills.melee += 1; @@ -896,9 +886,7 @@ void IOWheel::slotPurpleBottom100(const std::shared_ptr &player, uint16_ } else { bonusData.stats.capacity += 2 * points; } - if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_PURPLE_BOTTOM_100)) { - bonusData.leech.manaLeech += 0.25; // 0,25% - } + addVesselResonance(player, bonusData, WheelSlots_t::SLOT_PURPLE_BOTTOM_100, WheelGemAffinity_t::Purple, points); } // SLOT_PURPLE_BOTTOM_150 = 35 @@ -910,8 +898,9 @@ void IOWheel::slotPurpleBottom150(const std::shared_ptr &player, uint16_ } else { bonusData.stats.mana += 6 * points; } - // Increase 2% of earth resistance - increaseResistance(player, bonusData, WheelSlots_t::SLOT_PURPLE_BOTTOM_150, points, COMBAT_EARTHDAMAGE, 200); + if (isMaxPointAddedToSlot(player, points, WheelSlots_t::SLOT_PURPLE_TOP_150)) { + bonusData.leech.lifeLeech += HEALTH_LEECH_INCREASE; + } } // SLOT_PURPLE_200 = 36 diff --git a/src/io/io_wheel.hpp b/src/io/io_wheel.hpp index f1b95f20073..1bc56428b08 100644 --- a/src/io/io_wheel.hpp +++ b/src/io/io_wheel.hpp @@ -11,6 +11,7 @@ // Definitions of wheel of destiny enums #include "creatures/players/wheel/wheel_definitions.hpp" +#include "creatures/players/wheel/wheel_gems.hpp" #include "creatures/creatures_definitions.hpp" @@ -46,51 +47,24 @@ class IOWheelBonusData { }; }; - struct Increase { - bool area = false; - int damage = 0; - int heal = 0; - int aditionalTarget = 0; - int damageReduction = 0; - int duration = 0; - int criticalDamage = 0; - int criticalChance = 0; - }; - struct Decrease { - int cooldown = 0; - int manaCost = 0; - uint8_t secondaryGroupCooldown = 0; - }; - - struct Leech { - int mana = 0; - int life = 0; - }; - struct Spells { - struct Grade { - Leech leech; - Increase increase; - Decrease decrease; - }; - struct Druid { - std::array grade; + std::array grade; std::string name; }; struct Knight { - std::array grade; + std::array grade; std::string name; }; struct Paladin { - std::array grade; + std::array grade; std::string name; }; struct Sorcerer { - std::array grade; + std::array grade; std::string name; }; @@ -299,15 +273,13 @@ class IOWheel : public IOWheelBonusData { void addSpell(const std::shared_ptr &player, PlayerWheelMethodsBonusData &bonusData, WheelSlots_t slotType, uint16_t points, const std::string &spellName) const; /** - * @brief Increases the resistance value of the specified combat type for the player's bonus data if the number of points is equal to the player's points in the specified slot type. - * @param player The player whose resistance will be increased. + * @brief Unlock a vessel resonance if the number of points is equal to the player's points in the specified slot type. + * @param player The player to receive the vessel resonance. * @param bonusData The bonus data to update. * @param slotType The slot type to check the points against. - * @param points The number of points required to increase the resistance. - * @param combat The combat type to increase the resistance for. - * @param value The value to increase the resistance by. + * @param points The number of points required to add the vessel resonance. */ - void increaseResistance(const std::shared_ptr &player, PlayerWheelMethodsBonusData &bonusData, WheelSlots_t slotType, uint16_t points, CombatType_t combat, int16_t value) const; + void addVesselResonance(const std::shared_ptr &player, PlayerWheelMethodsBonusData &bonusData, WheelSlots_t slotType, WheelGemAffinity_t affinity, uint16_t points) const; /** * @brief Initialize the wheel map functions. diff --git a/src/io/iomap.cpp b/src/io/iomap.cpp index 457f3eed6bf..a84365bedf1 100644 --- a/src/io/iomap.cpp +++ b/src/io/iomap.cpp @@ -165,7 +165,7 @@ void IOMap::parseTileArea(FileStream &stream, Map &map, const Position &pos) { const uint16_t id = stream.getU16(); const auto &iType = Item::items[id]; - if (!tile->isHouse() || !iType.isBed()) { + if (!tile->isHouse() || (!iType.isBed() && !iType.isTrashHolder())) { if (iType.blockSolid) { tileIsStatic = true; } @@ -205,7 +205,7 @@ void IOMap::parseTileArea(FileStream &stream, Map &map, const Position &pos) { throw IOMapException(fmt::format("[x:{}, y:{}, z:{}] Failed to load item {}, Node Type.", x, y, z, id)); } - if (tile->isHouse() && iType.isBed()) { + if (tile->isHouse() && (iType.isBed() || iType.isTrashHolder())) { // nothing } else if (tile->isHouse() && iType.movable) { g_logger().warn("[IOMap::loadMap] - " diff --git a/src/io/iomapserialize.cpp b/src/io/iomapserialize.cpp index ad1a59e67f6..7d40bb54b73 100644 --- a/src/io/iomapserialize.cpp +++ b/src/io/iomapserialize.cpp @@ -138,7 +138,7 @@ bool IOMapSerialize::loadItem(PropStream &propStream, std::shared_ptr } const ItemType &iType = Item::items[id]; - if (iType.isBed() || iType.movable || !tile || iType.isCarpet()) { + if (iType.isBed() || iType.movable || !tile || iType.isCarpet() || iType.isTrashHolder()) { // create a new item auto item = Item::CreateItem(id); if (item) { diff --git a/src/items/decay/decay.cpp b/src/items/decay/decay.cpp index a1b709b4479..a720e28c50b 100644 --- a/src/items/decay/decay.cpp +++ b/src/items/decay/decay.cpp @@ -155,9 +155,9 @@ void Decay::internalDecayItem(std::shared_ptr item) { if (player) { bool needUpdateSkills = false; for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { - if (it.abilities && it.abilities->skills[i] != 0) { + if (it.abilities && item->getSkill(static_cast(i)) != 0) { needUpdateSkills = true; - player->setVarSkill(static_cast(i), -it.abilities->skills[i]); + player->setVarSkill(static_cast(i), -item->getSkill(static_cast(i))); } } @@ -167,10 +167,10 @@ void Decay::internalDecayItem(std::shared_ptr item) { bool needUpdateStats = false; for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { - if (it.abilities && it.abilities->stats[s] != 0) { + if (item->getStat(static_cast(s)) != 0) { needUpdateStats = true; needUpdateSkills = true; - player->setVarStats(static_cast(s), -it.abilities->stats[s]); + player->setVarStats(static_cast(s), -item->getStat(static_cast(s))); } if (it.abilities && it.abilities->statsPercent[s] != 0) { needUpdateStats = true; diff --git a/src/items/functions/item/attribute.hpp b/src/items/functions/item/attribute.hpp index 4a68cc0ec7b..8ffbcc880f7 100644 --- a/src/items/functions/item/attribute.hpp +++ b/src/items/functions/item/attribute.hpp @@ -38,6 +38,7 @@ class ItemAttributeHelper { case ItemAttribute_t::IMBUEMENT_SLOT: case ItemAttribute_t::OPENCONTAINER: case ItemAttribute_t::QUICKLOOTCONTAINER: + case ItemAttribute_t::OBTAINCONTAINER: case ItemAttribute_t::DURATION_TIMESTAMP: case ItemAttribute_t::TIER: case ItemAttribute_t::AMOUNT: diff --git a/src/items/item.cpp b/src/items/item.cpp index 2415c2a8d27..a7ff8ec957c 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -810,6 +810,17 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream &propStream) { setAttribute(OWNER, ownerId); break; } + + case ATTR_OBTAINCONTAINER: { + uint32_t flags; + if (!propStream.read(flags)) { + return ATTR_READ_ERROR; + } + + g_logger().debug("Setting flag {} flags, to item id {}", flags, getID()); + setAttribute(ItemAttribute_t::OBTAINCONTAINER, flags); + break; + } default: return ATTR_READ_ERROR; } @@ -993,6 +1004,13 @@ void Item::serializeAttr(PropWriteStream &propWriteStream) const { customAttribute.serialize(propWriteStream); } } + + if (hasAttribute(ItemAttribute_t::OBTAINCONTAINER)) { + propWriteStream.write(ATTR_OBTAINCONTAINER); + auto flags = getAttribute(ItemAttribute_t::OBTAINCONTAINER); + g_logger().debug("Reading flag {}, to item id {}", flags, getID()); + propWriteStream.write(flags); + } } void Item::setOwner(std::shared_ptr owner) { @@ -1976,11 +1994,13 @@ std::string Item::parseClassificationDescription(std::shared_ptr item) { << "Classification: " << std::to_string(item->getClassification()) << " Tier: " << std::to_string(item->getTier()); if (item->getTier() != 0) { if (Item::items[item->getID()].weaponType != WEAPON_NONE) { - string << fmt::format(" ({}% Onslaught).", item->getFatalChance()); + string << fmt::format(" ({:.2f}% Onslaught).", item->getFatalChance()); } else if (g_game().getObjectCategory(item) == OBJECTCATEGORY_HELMETS) { - string << fmt::format(" ({}% Momentum).", item->getMomentumChance()); + string << fmt::format(" ({:.2f}% Momentum).", item->getMomentumChance()); } else if (g_game().getObjectCategory(item) == OBJECTCATEGORY_ARMORS) { string << fmt::format(" ({:.2f}% Ruse).", item->getDodgeChance()); + } else if (g_game().getObjectCategory(item) == OBJECTCATEGORY_LEGS) { + string << fmt::format(" ({:.2f}% Transcendence).", item->getTranscendenceChance()); } } } diff --git a/src/items/item.hpp b/src/items/item.hpp index 90f3c3d83ef..a15ea92c5cf 100644 --- a/src/items/item.hpp +++ b/src/items/item.hpp @@ -380,6 +380,21 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { return items[id].abilities->specializedMagicLevel[combatTypeToIndex(combat)]; } + int32_t getSpeed() const { + int32_t value = items[id].abilities->speed; + return value; + } + + int32_t getSkill(skills_t skill) const { + int32_t value = items[id].abilities ? items[id].abilities->skills[skill] : 0; + return value; + } + + int32_t getStat(stats_t stat) const { + int32_t value = items[id].abilities ? items[id].abilities->stats[stat] : 0; + return value; + } + int32_t getAttack() const { if (hasAttribute(ItemAttribute_t::ATTACK)) { return getAttribute(ItemAttribute_t::ATTACK); @@ -471,6 +486,12 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { bool isWrapable() const { return items[id].wrapable && items[id].wrapableTo; } + bool isRing() const { + return items[id].isRing(); + } + bool isAmulet() const { + return items[id].isAmulet(); + } bool isAmmo() const { return items[id].isAmmo(); } @@ -480,6 +501,12 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { bool isQuiver() const { return items[id].isQuiver(); } + bool isShield() const { + return items[id].isShield(); + } + bool isWand() const { + return items[id].isWand(); + } bool isSpellBook() const { return items[id].isSpellBook(); } @@ -654,25 +681,52 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { return false; } - double_t getDodgeChance() const { + double getDodgeChance() const { + if (getTier() == 0) { + return 0; + } + return quadraticPoly( + g_configManager().getFloat(RUSE_CHANCE_FORMULA_A, __FUNCTION__), + g_configManager().getFloat(RUSE_CHANCE_FORMULA_B, __FUNCTION__), + g_configManager().getFloat(RUSE_CHANCE_FORMULA_C, __FUNCTION__), + getTier() + ); + } + + double getFatalChance() const { if (getTier() == 0) { return 0; } - return (0.0307576 * getTier() * getTier()) + (0.440697 * getTier()) + 0.026; + return quadraticPoly( + g_configManager().getFloat(ONSLAUGHT_CHANCE_FORMULA_A, __FUNCTION__), + g_configManager().getFloat(ONSLAUGHT_CHANCE_FORMULA_B, __FUNCTION__), + g_configManager().getFloat(ONSLAUGHT_CHANCE_FORMULA_C, __FUNCTION__), + getTier() + ); } - double_t getFatalChance() const { + double getMomentumChance() const { if (getTier() == 0) { return 0; } - return 0.5 * getTier() + 0.05 * ((getTier() - 1) * (getTier() - 1)); + return quadraticPoly( + g_configManager().getFloat(MOMENTUM_CHANCE_FORMULA_A, __FUNCTION__), + g_configManager().getFloat(MOMENTUM_CHANCE_FORMULA_B, __FUNCTION__), + g_configManager().getFloat(MOMENTUM_CHANCE_FORMULA_C, __FUNCTION__), + getTier() + ); } - double_t getMomentumChance() const { + double getTranscendenceChance() const { if (getTier() == 0) { return 0; } - return 2 * getTier() + 0.05 * ((getTier() - 1) * (getTier() - 1)); + return quadraticPoly( + g_configManager().getFloat(TRANSCENDANCE_CHANCE_FORMULA_A, __FUNCTION__), + g_configManager().getFloat(TRANSCENDANCE_CHANCE_FORMULA_B, __FUNCTION__), + g_configManager().getFloat(TRANSCENDANCE_CHANCE_FORMULA_C, __FUNCTION__), + getTier() + ); } uint8_t getTier() const { diff --git a/src/items/items.hpp b/src/items/items.hpp index b04e128fd7f..3201219d47e 100644 --- a/src/items/items.hpp +++ b/src/items/items.hpp @@ -125,6 +125,9 @@ class ItemType { bool isFluidContainer() const { return group == ITEM_GROUP_FLUID; } + bool isShield() const { + return type == ITEM_TYPE_SHIELD && !isSpellBook(); + } bool isSpellBook() const { return spellbook; } @@ -174,6 +177,12 @@ class ItemType { bool isQuiver() const { return (type == ITEM_TYPE_QUIVER); } + bool isRing() const { + return (type == ITEM_TYPE_RING); + } + bool isAmulet() const { + return (type == ITEM_TYPE_AMULET); + } bool isAmmo() const { return (type == ITEM_TYPE_AMMO); } @@ -189,12 +198,18 @@ class ItemType { bool isWeapon() const { return weaponType != WEAPON_NONE && weaponType != WEAPON_SHIELD && weaponType != WEAPON_AMMO; } + bool isWand() const { + return weaponType == WEAPON_WAND; + } bool isArmor() const { return slotPosition & SLOTP_ARMOR; } bool isHelmet() const { return slotPosition & SLOTP_HEAD; } + bool isLegs() const { + return slotPosition & SLOTP_LEGS; + } bool isRanged() const { return weaponType == WEAPON_DISTANCE && weaponType != WEAPON_NONE; } diff --git a/src/items/items_classification.hpp b/src/items/items_classification.hpp index 09133fac36f..bd8b61f801d 100644 --- a/src/items/items_classification.hpp +++ b/src/items/items_classification.hpp @@ -10,8 +10,10 @@ #pragma once struct TierInfo { - uint64_t priceToUpgrade = 0; - uint8_t corePriceToFuse = 0; + uint8_t corePrice = 0; + uint64_t regularPrice = 0; + uint64_t convergenceFusionPrice = 0; + uint64_t convergenceTransferPrice = 0; }; // Classification class for forging system and market. @@ -22,10 +24,12 @@ class ItemClassification { id(id) { } virtual ~ItemClassification() = default; - void addTier(uint8_t tierId, uint64_t tierPrice, uint8_t corePrice) { + void addTier(uint8_t tierId, uint8_t corePrice, uint64_t regularPrice, uint64_t convergenceFusionPrice, uint64_t convergenceTransferPrice) { auto &table = tiers[tierId]; - table.priceToUpgrade = tierPrice; - table.corePriceToFuse = corePrice; + table.corePrice = corePrice; + table.regularPrice = regularPrice; + table.convergenceFusionPrice = convergenceFusionPrice; + table.convergenceTransferPrice = convergenceTransferPrice; } uint8_t id; diff --git a/src/items/items_definitions.hpp b/src/items/items_definitions.hpp index bd5c152d378..3c200daa3e9 100644 --- a/src/items/items_definitions.hpp +++ b/src/items/items_definitions.hpp @@ -239,6 +239,7 @@ enum AttrTypes_t { ATTR_CUSTOM = 41, ATTR_STORE_INBOX_CATEGORY = 42, ATTR_OWNER = 43, + ATTR_OBTAINCONTAINER = 44, // Always the last ATTR_NONE = 0 diff --git a/src/items/weapons/weapons.cpp b/src/items/weapons/weapons.cpp index 796aea212a6..05bec0b0a6f 100644 --- a/src/items/weapons/weapons.cpp +++ b/src/items/weapons/weapons.cpp @@ -474,9 +474,10 @@ int32_t WeaponMelee::getElementDamage(std::shared_ptr player, std::share int32_t attackValue = elementDamage; float attackFactor = player->getAttackFactor(); uint32_t level = player->getLevel(); - int32_t minValue = level / 5; int32_t maxValue = Weapons::getMaxWeaponDamage(level, attackSkill, attackValue, attackFactor, true); + int32_t minValue = level / 5; + return -normal_random(minValue, static_cast(maxValue * player->getVocation()->meleeDamageMultiplier)); } diff --git a/src/kv/kv.cpp b/src/kv/kv.cpp index f3133dec9ca..6f516c78480 100644 --- a/src/kv/kv.cpp +++ b/src/kv/kv.cpp @@ -12,6 +12,10 @@ #include "kv/kv.hpp" #include "lib/di/container.hpp" +int64_t KV::lastTimestamp_ = 0; +uint64_t KV::counter_ = 0; +std::mutex KV::mutex_ = {}; + KVStore &KVStore::getInstance() { return inject(); } diff --git a/src/kv/kv.hpp b/src/kv/kv.hpp index ea76276c80c..d30e46f2a0d 100644 --- a/src/kv/kv.hpp +++ b/src/kv/kv.hpp @@ -39,6 +39,31 @@ class KV : public std::enable_shared_from_this { virtual void flush() { saveAll(); } + + static std::string generateUUID() { + std::lock_guard lock(mutex_); + + auto now = std::chrono::system_clock::now().time_since_epoch(); + auto milliseconds = std::chrono::duration_cast(now).count(); + + if (milliseconds != lastTimestamp_) { + counter_ = 0; + lastTimestamp_ = milliseconds; + } else { + ++counter_; + } + + std::stringstream ss; + ss << std::setw(20) << std::setfill('0') << milliseconds << "-" + << std::setw(12) << std::setfill('0') << counter_; + + return ss.str(); + } + +private: + static int64_t lastTimestamp_; + static uint64_t counter_; + static std::mutex mutex_; }; class KVStore : public KV { diff --git a/src/kv/value_wrapper.hpp b/src/kv/value_wrapper.hpp index 3ff804402e0..1503575c5b1 100644 --- a/src/kv/value_wrapper.hpp +++ b/src/kv/value_wrapper.hpp @@ -49,6 +49,8 @@ class ValueWrapper { template T get() const { + static_assert(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, "Invalid type T"); + if (std::holds_alternative(data_)) { return std::get(data_); } @@ -148,6 +150,7 @@ class ValueWrapper { template T ValueWrapper::get(const std::string &key) const { + static_assert(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, "Invalid type T"); auto optValue = get(key); if (optValue.has_value()) { if (auto pval = std::get_if(&optValue->data_)) { @@ -159,6 +162,7 @@ T ValueWrapper::get(const std::string &key) const { template T ValueWrapper::get(size_t index) const { + static_assert(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, "Invalid type T"); auto optValue = get(index); if (optValue.has_value()) { if (auto pval = std::get_if(&optValue->data_)) { diff --git a/src/lib/metrics/metrics.cpp b/src/lib/metrics/metrics.cpp index 77a4bb8b3a8..ffdfda919fc 100644 --- a/src/lib/metrics/metrics.cpp +++ b/src/lib/metrics/metrics.cpp @@ -37,6 +37,11 @@ void Metrics::init(Options opts) { p->AddMetricReader(std::move(prometheusExporter)); } + metrics_api::Provider::SetMeterProvider(std::move(provider)); + initHistograms(); +} + +void Metrics ::initHistograms() { for (auto name : latencyNames) { auto instrumentSelector = metrics_sdk::InstrumentSelectorFactory::Create(metrics_sdk::InstrumentType::kHistogram, name, "us"); auto meterSelector = metrics_sdk::MeterSelectorFactory::Create("performance", otelVersion, otelSchema); @@ -68,12 +73,12 @@ void Metrics::init(Options opts) { // clang-format on auto view = metrics_sdk::ViewFactory::Create(name, "Latency", "us", metrics_sdk::AggregationType::kHistogram, std::move(aggregationConfig)); + auto provider = metrics_api::Provider::GetMeterProvider(); + auto* p = static_cast(provider.get()); p->AddView(std::move(instrumentSelector), std::move(meterSelector), std::move(view)); latencyHistograms[name] = getMeter()->CreateDoubleHistogram(name, "Latency", "us"); } - - metrics_api::Provider::SetMeterProvider(std::move(provider)); } void Metrics::shutdown() { diff --git a/src/lib/metrics/metrics.hpp b/src/lib/metrics/metrics.hpp index 116676fb3b7..4ea34ef9e9f 100644 --- a/src/lib/metrics/metrics.hpp +++ b/src/lib/metrics/metrics.hpp @@ -112,6 +112,7 @@ namespace metrics { ~Metrics() = default; void init(Options opts); + void initHistograms(); void shutdown(); static Metrics &getInstance(); diff --git a/src/lib/thread/thread_pool.cpp b/src/lib/thread/thread_pool.cpp index 93b730b0b8f..b36c137904c 100644 --- a/src/lib/thread/thread_pool.cpp +++ b/src/lib/thread/thread_pool.cpp @@ -47,11 +47,32 @@ void ThreadPool::shutdown() { ioService.stop(); + std::vector> futures; for (std::size_t i = 0; i < threads.size(); i++) { logger.debug("Joining thread {}/{}.", i + 1, threads.size()); if (threads[i].joinable()) { - threads[i].join(); + futures.emplace_back(std::async(std::launch::async, [&]() { + threads[i].join(); + })); + } + } + + std::future_status status = std::future_status::timeout; + auto timeout = std::chrono::seconds(5); + auto start = std::chrono::steady_clock::now(); + int tries = 0; + while (status == std::future_status::timeout && std::chrono::steady_clock::now() - start < timeout) { + tries++; + if (tries > 5) { + logger.error("Thread pool shutdown timed out."); + break; + } + for (auto &future : futures) { + status = future.wait_for(std::chrono::seconds(0)); + if (status != std::future_status::timeout) { + break; + } } } } diff --git a/src/lua/creature/movement.cpp b/src/lua/creature/movement.cpp index a011c32343e..b3901ea1ff0 100644 --- a/src/lua/creature/movement.cpp +++ b/src/lua/creature/movement.cpp @@ -478,10 +478,6 @@ uint32_t MoveEvent::EquipItem(const std::shared_ptr moveEvent, std::s return 0; } - if (player->isItemAbilityEnabled(slot)) { - return 1; - } - if (!player->hasFlag(PlayerFlags_t::IgnoreWeaponCheck) && moveEvent->getWieldInfo() != 0) { if (player->getLevel() < moveEvent->getReqLevel() || player->getMagicLevel() < moveEvent->getReqMagLv()) { return 0; @@ -504,10 +500,14 @@ uint32_t MoveEvent::EquipItem(const std::shared_ptr moveEvent, std::s const ItemType &it = Item::items[item->getID()]; if (it.transformEquipTo != 0) { g_game().transformItem(item, it.transformEquipTo); - } else { - player->setItemAbility(slot, true); } + if (player->isItemAbilityEnabled(slot)) { + return 1; + } + + player->setItemAbility(slot, true); + for (uint8_t slotid = 0; slotid < item->getImbuementSlot(); slotid++) { player->updateImbuementTrackerStats(); ImbuementInfo imbuementInfo; @@ -529,8 +529,8 @@ uint32_t MoveEvent::EquipItem(const std::shared_ptr moveEvent, std::s player->addCondition(condition); } - if (it.abilities->speed != 0) { - g_game().changePlayerSpeed(player, it.abilities->speed); + if (item->getSpeed() != 0) { + g_game().changePlayerSpeed(player, item->getSpeed()); } player->addConditionSuppressions(it.abilities->conditionSuppressions); @@ -560,14 +560,14 @@ uint32_t MoveEvent::EquipItem(const std::shared_ptr moveEvent, std::s // Skill and stats modifiers for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { - if (it.abilities->skills[i]) { - player->setVarSkill(static_cast(i), it.abilities->skills[i]); + if (item->getSkill(static_cast(i)) != 0) { + player->setVarSkill(static_cast(i), item->getSkill(static_cast(i))); } } for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { - if (it.abilities->stats[s]) { - player->setVarStats(static_cast(s), it.abilities->stats[s]); + if (item->getStat(static_cast(s)) != 0) { + player->setVarStats(static_cast(s), item->getStat(static_cast(s))); } if (it.abilities->statsPercent[s]) { @@ -576,6 +576,12 @@ uint32_t MoveEvent::EquipItem(const std::shared_ptr moveEvent, std::s } } + // Updates the main backpack as unasigned if there is no item equipped + if (slot == CONST_SLOT_BACKPACK) { + g_logger().debug("[{}] does not have backpack, trying to add new container as unasigned", __FUNCTION__); + player->setMainBackpackUnassigned(item->getContainer()); + } + player->sendStats(); player->sendSkills(); return 1; @@ -593,15 +599,12 @@ uint32_t MoveEvent::DeEquipItem(const std::shared_ptr MoveEvent, std: } if (!player->isItemAbilityEnabled(slot)) { + g_logger().debug("[{}] item ability is not enabled", __FUNCTION__); return 1; } - player->setItemAbility(slot, false); - const ItemType &it = Item::items[item->getID()]; - if (it.transformDeEquipTo != 0) { - g_game().transformItem(item, it.transformDeEquipTo); - } + player->setItemAbility(slot, false); for (uint8_t slotid = 0; slotid < item->getImbuementSlot(); slotid++) { player->updateImbuementTrackerStats(); @@ -622,35 +625,40 @@ uint32_t MoveEvent::DeEquipItem(const std::shared_ptr MoveEvent, std: player->removeCondition(CONDITION_MANASHIELD, static_cast(slot)); } - if (it.abilities->speed != 0) { - g_game().changePlayerSpeed(player, -it.abilities->speed); - } - - player->removeConditionSuppressions(); - player->sendIcons(); - if (it.abilities->regeneration) { player->removeCondition(CONDITION_REGENERATION, static_cast(slot)); } - // Skill and stats modifiers - for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { - if (it.abilities->skills[i] != 0) { - player->setVarSkill(static_cast(i), -it.abilities->skills[i]); - } - } - for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { - if (it.abilities->stats[s]) { - player->setVarStats(static_cast(s), -it.abilities->stats[s]); - } - if (it.abilities->statsPercent[s]) { player->setVarStats(static_cast(s), -static_cast(player->getDefaultStats(static_cast(s)) * ((it.abilities->statsPercent[s] - 100) / 100.f))); } } } + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (item->getSkill(static_cast(i)) != 0) { + player->setVarSkill(static_cast(i), -item->getSkill(static_cast(i))); + } + } + + for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { + if (item->getStat(static_cast(s))) { + player->setVarStats(static_cast(s), -item->getStat(static_cast(s))); + } + } + + if (item->getSpeed() != 0) { + g_game().changePlayerSpeed(player, -item->getSpeed()); + } + + player->removeConditionSuppressions(); + player->sendIcons(); + + if (it.transformDeEquipTo != 0) { + g_game().transformItem(item, it.transformDeEquipTo); + } + player->sendStats(); player->sendSkills(); return 1; diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp index 6391cdffb2e..0ee12e65000 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -11,6 +11,7 @@ #include "lua/functions/core/game/lua_enums.hpp" +#include "creatures/players/wheel/wheel_gems.hpp" #include "creatures/players/wheel/wheel_definitions.hpp" #include "io/io_bosstiary.hpp" #include "config/configmanager.hpp" @@ -148,7 +149,6 @@ void LuaEnums::initOthersEnums(lua_State* L) { registerEnum(L, LIGHT_STATE_SUNSET); registerEnum(L, LIGHT_STATE_SUNRISE); registerEnum(L, STORAGEVALUE_EMOTE); - registerEnum(L, STORAGEVALUE_AUTO_LOOT); registerEnum(L, IMMOVABLE_ACTION_ID); diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index b23c0090276..47b9f4b3a34 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -595,10 +595,6 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerIsPzLocked(lua_State* L); static int luaPlayerIsOffline(lua_State* L); - static int luaPlayerGetContainers(lua_State* L); - static int luaPlayerSetLootContainer(lua_State* L); - static int luaPlayerGetLootContainer(lua_State* L); - static int luaPlayerGetClient(lua_State* L); static int luaPlayerGetHouse(lua_State* L); diff --git a/src/lua/functions/items/item_classification_functions.cpp b/src/lua/functions/items/item_classification_functions.cpp index 85eef94d810..5120f8391ff 100644 --- a/src/lua/functions/items/item_classification_functions.cpp +++ b/src/lua/functions/items/item_classification_functions.cpp @@ -28,10 +28,16 @@ int ItemClassificationFunctions::luaItemClassificationCreate(lua_State* L) { } int ItemClassificationFunctions::luaItemClassificationAddTier(lua_State* L) { - // itemClassification:addTier(id, gold[, core = 0]) + // itemClassification:addTier(id, core, regularPrice, convergenceFusionPrice, convergenceTransferPrice) ItemClassification* itemClassification = getUserdata(L, 1); if (itemClassification) { - itemClassification->addTier(getNumber(L, 2), getNumber(L, 3), getNumber(L, 4, 0)); + itemClassification->addTier( + getNumber(L, 2), + getNumber(L, 3), + getNumber(L, 4), + getNumber(L, 5), + getNumber(L, 6) + ); pushBoolean(L, true); } else { lua_pushnil(L); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 313bec0d9a4..99952852222 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -131,7 +131,7 @@ namespace { } if (!imbueDmg) { msg.addByte(0); - msg.addByte(CIPBIA_ELEMENTAL_AGONY); + msg.addByte(0); } } @@ -144,7 +144,7 @@ namespace { * @param[in] player The pointer to the player whose equipped items are considered. */ void calculateAbsorbValues(std::shared_ptr player, NetworkMessage &msg, uint8_t &combats) { - alignas(16) uint16_t damageReduction[COMBAT_COUNT] = { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 }; + alignas(16) uint16_t damageModifiers[COMBAT_COUNT] = { 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000 }; for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { if (!player->isItemAbilityEnabled(static_cast(slot))) { @@ -162,7 +162,7 @@ namespace { } for (uint16_t i = 0; i < COMBAT_COUNT; ++i) { - damageReduction[i] *= (std::floor(100 - itemType.abilities->absorbPercent[i]) / 100.); + damageModifiers[i] *= (std::floor(100. - itemType.abilities->absorbPercent[i]) / 100.); } uint8_t imbuementSlots = itemType.imbuementSlot; @@ -186,22 +186,23 @@ namespace { g_logger().debug("[cyclopedia damage reduction] imbued item {}, reduced {} percent, for element {}", item->getName(), imbuementAbsorbPercent, combatTypeToName(indexToCombatType(combat))); - damageReduction[combat] *= (std::floor(100 - imbuementAbsorbPercent) / 100.); + damageModifiers[combat] *= (std::floor(100. - imbuementAbsorbPercent) / 100.); } } } } for (size_t i = 0; i < COMBAT_COUNT; ++i) { - damageReduction[i] -= player->getAbsorbPercent(indexToCombatType(i)); + damageModifiers[i] -= 100 * player->getAbsorbPercent(indexToCombatType(i)); if (g_configManager().getBoolean(TOGGLE_WHEELSYSTEM, __FUNCTION__)) { - damageReduction[i] -= static_cast(player->wheel()->getResistance(indexToCombatType(i))) / 100.f; + damageModifiers[i] -= player->wheel()->getResistance(indexToCombatType(i)); } - if (damageReduction[i] != 100) { - g_logger().debug("CombatType: {}, DamageReduction: {}", i, damageReduction[i]); + if (damageModifiers[i] != 10000) { + int16_t clientModifier = std::clamp(10000 - static_cast(damageModifiers[i]), -10000, 10000); + g_logger().debug("[{}] CombatType: {}, Damage Modifier: {}, Resulting Client Modifier: {}", __FUNCTION__, i, damageModifiers[i], clientModifier); msg.addByte(getCipbiaElement(indexToCombatType(i))); - msg.addByte(std::max(-100, std::min(100, 100 - damageReduction[i]))); + msg.add(clientModifier); ++combats; } } @@ -344,16 +345,24 @@ void ProtocolGame::AddItem(NetworkMessage &msg, std::shared_ptr item) { std::shared_ptr container = item->getContainer(); if (container && containerType == 0 && container->getHoldingPlayer() == player) { uint32_t lootFlags = 0; - for (auto itt : player->quickLootContainers) { - if (itt.second == container) { - lootFlags |= 1 << itt.first; + uint32_t obtainFlags = 0; + for (auto [category, containerMap] : player->m_managedContainers) { + if (!isValidObjectCategory(category)) { + continue; + } + if (containerMap.first == container) { + lootFlags |= 1 << category; + } + if (containerMap.second == container) { + obtainFlags |= 1 << category; } } - if (lootFlags != 0) { - containerType = 1; + if (lootFlags != 0 || obtainFlags != 0) { + containerType = 9; msg.addByte(containerType); msg.add(lootFlags); + msg.add(obtainFlags); } } @@ -1273,7 +1282,8 @@ void ProtocolGame::parsePacketFromDispatcher(NetworkMessage msg, uint8_t recvbyt case 0xE6: parseBugReport(msg); break; - case 0xE7: /* thank you */ + case 0xE7: + parseWheelGemAction(msg); break; case 0xE8: parseDebugAssert(msg); @@ -1782,17 +1792,32 @@ void ProtocolGame::parseLootContainer(NetworkMessage &msg) { Position pos = msg.getPosition(); uint16_t itemId = msg.get(); uint8_t stackpos = msg.getByte(); - addGameTask(&Game::playerSetLootContainer, player->getID(), category, pos, itemId, stackpos); + addGameTask(&Game::playerSetManagedContainer, player->getID(), category, pos, itemId, stackpos, true); } else if (action == 1) { ObjectCategory_t category = (ObjectCategory_t)msg.getByte(); - addGameTask(&Game::playerClearLootContainer, player->getID(), category); + addGameTask(&Game::playerClearManagedContainer, player->getID(), category, true); } else if (action == 2) { ObjectCategory_t category = (ObjectCategory_t)msg.getByte(); - addGameTask(&Game::playerOpenLootContainer, player->getID(), category); + addGameTask(&Game::playerOpenManagedContainer, player->getID(), category, true); } else if (action == 3) { bool useMainAsFallback = msg.getByte() == 1; addGameTask(&Game::playerSetQuickLootFallback, player->getID(), useMainAsFallback); + } else if (action == 4) { + ObjectCategory_t category = (ObjectCategory_t)msg.getByte(); + Position pos = msg.getPosition(); + uint16_t itemId = msg.get(); + uint8_t stackpos = msg.getByte(); + g_logger().debug("[{}] action {}, category {}, pos {}, itemId {}, stackPos {}", __FUNCTION__, action, static_cast(category), pos.toString(), itemId, stackpos); + addGameTask(&Game::playerSetManagedContainer, player->getID(), category, pos, itemId, stackpos, false); + } else if (action == 5) { + ObjectCategory_t category = (ObjectCategory_t)msg.getByte(); + addGameTask(&Game::playerClearManagedContainer, player->getID(), category, false); + } else if (action == 6) { + ObjectCategory_t category = (ObjectCategory_t)msg.getByte(); + addGameTask(&Game::playerOpenManagedContainer, player->getID(), category, false); } + + g_logger().debug("[{}] action type {}", __FUNCTION__, action); } void ProtocolGame::parseQuickLootBlackWhitelist(NetworkMessage &msg) { @@ -3475,7 +3500,7 @@ void ProtocolGame::sendCyclopediaCharacterCombatStats() { msg.add(it.maxHitChance); msg.addByte(getCipbiaElement(it.combatType)); msg.addByte(0); - msg.addByte(CIPBIA_ELEMENTAL_AGONY); + msg.addByte(0); } else if (it.weaponType == WEAPON_DISTANCE || it.weaponType == WEAPON_AMMO || it.weaponType == WEAPON_MISSILE) { int32_t attackValue = weapon->getAttack(); if (it.weaponType == WEAPON_AMMO) { @@ -3525,7 +3550,7 @@ void ProtocolGame::sendCyclopediaCharacterCombatStats() { msg.add(maxDamage >> 1); msg.addByte(CIPBIA_ELEMENTAL_PHYSICAL); msg.addByte(0); - msg.addByte(CIPBIA_ELEMENTAL_AGONY); + msg.addByte(0); } msg.add(player->getArmor()); @@ -4093,11 +4118,16 @@ void ProtocolGame::sendTextMessage(const TextMessage &message) { break; } case MESSAGE_HEALED: - case MESSAGE_HEALED_OTHERS: + case MESSAGE_HEALED_OTHERS: { + msg.addPosition(message.position); + msg.add(message.primary.value); + msg.addByte(message.primary.color); + break; + } case MESSAGE_EXPERIENCE: case MESSAGE_EXPERIENCE_OTHERS: { msg.addPosition(message.position); - msg.add(message.primary.value); + msg.add(message.primary.value); msg.addByte(message.primary.color); break; } @@ -4319,17 +4349,33 @@ void ProtocolGame::sendLootContainers() { NetworkMessage msg; msg.addByte(0xC0); msg.addByte(player->quickLootFallbackToMainContainer ? 1 : 0); - std::map> quickLoot; - for (auto it : player->quickLootContainers) { - if (it.second && !it.second->isRemoved()) { - quickLoot[it.first] = it.second; + + std::map, std::shared_ptr>> managedContainersMap; + for (auto [category, containersPair] : player->m_managedContainers) { + if (containersPair.first && !containersPair.first->isRemoved()) { + managedContainersMap[category].first = containersPair.first; + } + if (containersPair.second && !containersPair.second->isRemoved()) { + managedContainersMap[category].second = containersPair.second; } } - msg.addByte(quickLoot.size()); - for (auto it : quickLoot) { - msg.addByte(it.first); - msg.add(it.second->getID()); + + auto msgPosition = msg.getBufferPosition(); + msg.skipBytes(1); + uint8_t containers = 0; + for (auto [category, containersPair] : managedContainersMap) { + if (!isValidObjectCategory(category)) { + continue; + } + containers++; + msg.addByte(category); + uint16_t lootContainerId = containersPair.first ? containersPair.first->getID() : 0; + uint16_t obtainContainerId = containersPair.second ? containersPair.second->getID() : 0; + msg.add(lootContainerId); + msg.add(obtainContainerId); } + msg.setBufferPosition(msgPosition); + msg.addByte(containers); writeToOutputBuffer(msg); } @@ -4838,6 +4884,8 @@ void ProtocolGame::sendForgingData() { msg.addByte(0x86); std::map tierCorePrices; + std::map convergenceFusionPrices; + std::map convergenceTransferPrices; const auto classifications = g_game().getItemsClassifications(); msg.addByte(classifications.size()); @@ -4845,13 +4893,15 @@ void ProtocolGame::sendForgingData() { msg.addByte(classification->id); msg.addByte(classification->tiers.size()); for (const auto &[tier, tierInfo] : classification->tiers) { - msg.addByte(tier); - msg.add(tierInfo.priceToUpgrade); - tierCorePrices[tier] = tierInfo.corePriceToFuse; + msg.addByte(tier - 1); + msg.add(tierInfo.regularPrice); + tierCorePrices[tier] = tierInfo.corePrice; + convergenceFusionPrices[tier] = tierInfo.convergenceFusionPrice; + convergenceTransferPrices[tier] = tierInfo.convergenceTransferPrice; } } - // Version 13.16 + // Version 13.30 // Forge Config Bytes // Exalted core table per tier @@ -4861,6 +4911,20 @@ void ProtocolGame::sendForgingData() { msg.addByte(cores); } + // Convergence fusion prices per tier + msg.addByte(static_cast(convergenceFusionPrices.size())); + for (const auto &[tier, price] : convergenceFusionPrices) { + msg.addByte(tier - 1); + msg.add(price); + } + + // Convergence transfer prices per tier + msg.addByte(static_cast(convergenceTransferPrices.size())); + for (const auto &[tier, price] : convergenceTransferPrices) { + msg.addByte(tier); + msg.add(price); + } + // (conversion) (left column top) Cost to make 1 bottom item - 20 msg.addByte(static_cast(g_configManager().getNumber(FORGE_COST_ONE_SLIVER, __FUNCTION__))); // (conversion) (left column bottom) How many items to make - 3 @@ -4871,12 +4935,16 @@ void ProtocolGame::sendForgingData() { msg.addByte(75); // (conversion) (right column bottom) Starting stored dust limit msg.add(player->getForgeDustLevel()); - // (conversion) (right column bottom) Max stored dust limit - 225 + // (conversion) (right column bottom) Max stored dust limit - 325 msg.add(g_configManager().getNumber(FORGE_MAX_DUST, __FUNCTION__)); - // (fusion) Dust cost - 100 + // (normal fusion) dust cost - 100 msg.addByte(static_cast(g_configManager().getNumber(FORGE_FUSION_DUST_COST, __FUNCTION__))); - // (transfer) Dust cost - 100 + // (convergence fusion) dust cost - 130 + msg.addByte(static_cast(g_configManager().getNumber(FORGE_CONVERGENCE_FUSION_DUST_COST, __FUNCTION__))); + // (normal transfer) dust cost - 100 msg.addByte(static_cast(g_configManager().getNumber(FORGE_TRANSFER_DUST_COST, __FUNCTION__))); + // (convergence transfer) dust cost - 160 + msg.addByte(static_cast(g_configManager().getNumber(FORGE_CONVERGENCE_TRANSFER_DUST_COST, __FUNCTION__))); // (fusion) Base success rate - 50 msg.addByte(static_cast(g_configManager().getNumber(FORGE_BASE_SUCCESS_RATE, __FUNCTION__))); // (fusion) Bonus success rate - 15 @@ -4893,9 +4961,12 @@ void ProtocolGame::sendForgingData() { void ProtocolGame::sendOpenForge() { // We will use it when sending the bytes to send the item information to the client std::map> fusionItemsMap; + std::map>> convergenceItemsMap; std::map> donorTierItemMap; std::map> receiveTierItemMap; + auto maxConfigTier = g_configManager().getNumber(FORGE_MAX_ITEM_TIER, __FUNCTION__); + /* *Start - Parsing items informations */ @@ -4906,7 +4977,6 @@ void ProtocolGame::sendOpenForge() { auto itemClassification = item->getClassification(); auto itemTier = item->getTier(); - auto maxConfigTier = g_configManager().getNumber(FORGE_MAX_ITEM_TIER, __FUNCTION__); auto maxTier = (itemClassification == 4 ? maxConfigTier : itemClassification); // Save fusion items on map if (itemClassification != 0 && itemTier < maxTier) { @@ -4925,12 +4995,16 @@ void ProtocolGame::sendOpenForge() { if (itemTier == 0) { getForgeInfoMap(item, receiveTierItemMap); } + if (itemClassification == 4) { + getForgeInfoMap(item, convergenceItemsMap[item->getSlotPosition()]); + } } } // Checking size of map to send in the addByte (total fusion items count) uint8_t fusionTotalItemsCount = 0; for (const auto &[itemId, tierAndCountMap] : fusionItemsMap) { + auto classification = Item::items[itemId].upgradeClassification; for (const auto [itemTier, itemCount] : tierAndCountMap) { if (itemCount >= 2) { fusionTotalItemsCount++; @@ -4942,8 +5016,10 @@ void ProtocolGame::sendOpenForge() { * Start - Sending bytes */ NetworkMessage msg; + // Header byte (135) msg.addByte(0x87); + msg.add(fusionTotalItemsCount); for (const auto &[itemId, tierAndCountMap] : fusionItemsMap) { for (const auto [itemTier, itemCount] : tierAndCountMap) { @@ -4956,6 +5032,47 @@ void ProtocolGame::sendOpenForge() { } } + // msg.add(convergenceItemsMap.size()); + auto convergenceFusionCountPosition = msg.getBufferPosition(); + msg.skipBytes(2); + uint16_t convergenceFusionCount = 0; + /* + for each convergence fusion (1 per item slot, only class 4): + 1 byte: count fusable items + for each fusable item: + 2 bytes: item id + 1 byte: tier + 2 bytes: count + */ + for (const auto &[slot, itemMap] : convergenceItemsMap) { + uint8_t totalItemsCount = 0; + auto totalItemsCountPosition = msg.getBufferPosition(); + msg.skipBytes(1); // Total items count + for (const auto &[itemId, tierAndCountMap] : itemMap) { + for (const auto [tier, itemCount] : tierAndCountMap) { + if (tier >= maxConfigTier) { + continue; + } + totalItemsCount++; + msg.add(itemId); + msg.addByte(tier); + msg.add(itemCount); + } + } + auto endPosition = msg.getBufferPosition(); + msg.setBufferPosition(totalItemsCountPosition); + if (totalItemsCount > 0) { + msg.addByte(totalItemsCount); + msg.setBufferPosition(endPosition); + convergenceFusionCount++; + } + } + + auto transferTotalCountPosition = msg.getBufferPosition(); + msg.setBufferPosition(convergenceFusionCountPosition); + msg.add(convergenceFusionCount); + msg.setBufferPosition(transferTotalCountPosition); + auto transferTotalCount = getIterationIncreaseCount(donorTierItemMap); msg.addByte(static_cast(transferTotalCount)); if (transferTotalCount > 0) { @@ -4999,6 +5116,63 @@ void ProtocolGame::sendOpenForge() { } } + auto convergenceCountPosition = msg.getBufferPosition(); + msg.skipBytes(1); + uint8_t convergenceTransferCount = 0; + + /* + for each convergence transfer: + 2 bytes: count donors + for each donor: + 2 bytes: item id + 1 byte: tier + 2 bytes: count + 2 bytes: count receivers + for each receiver: + 2 bytes: item id + 2 bytes: count + */ + for (const auto &[slot, itemMap] : convergenceItemsMap) { + uint16_t donorCount = 0; + uint16_t receiverCount = 0; + auto donorCountPosition = msg.getBufferPosition(); + msg.skipBytes(2); // Donor count + for (const auto &[itemId, tierAndCountMap] : itemMap) { + for (const auto [tier, itemCount] : tierAndCountMap) { + if (tier >= 1) { + donorCount++; + msg.add(itemId); + msg.addByte(tier); + msg.add(itemCount); + } else { + receiverCount++; + } + } + } + if (donorCount == 0 && receiverCount == 0) { + msg.setBufferPosition(donorCountPosition); + continue; + } + auto receiverCountPosition = msg.getBufferPosition(); + msg.setBufferPosition(donorCountPosition); + msg.add(donorCount); + ++convergenceTransferCount; + msg.setBufferPosition(receiverCountPosition); + msg.add(receiverCount); + for (const auto &[itemId, tierAndCountMap] : itemMap) { + for (const auto [tier, itemCount] : tierAndCountMap) { + if (tier == 0) { + msg.add(itemId); + msg.add(itemCount); + } + } + } + } + auto dustLevelPosition = msg.getBufferPosition(); + msg.setBufferPosition(convergenceCountPosition); + msg.addByte(convergenceTransferCount); + msg.setBufferPosition(dustLevelPosition); + msg.add(player->getForgeDustLevel()); // Player dust limit writeToOutputBuffer(msg); // Update forging informations @@ -5011,18 +5185,19 @@ void ProtocolGame::parseForgeEnter(NetworkMessage &msg) { } // 0xBF -> 0 = fusion, 1 = transfer, 2 = dust to sliver, 3 = sliver to core, 4 = increase dust limit - uint8_t action = msg.getByte(); + auto actionType = static_cast(msg.getByte()); + bool convergence = msg.getByte(); uint16_t firstItem = msg.get(); uint8_t tier = msg.getByte(); uint16_t secondItem = msg.get(); bool usedCore = msg.getByte(); bool reduceTierLoss = msg.getByte(); - if (action == 0) { - addGameTask(&Game::playerForgeFuseItems, player->getID(), firstItem, tier, usedCore, reduceTierLoss); - } else if (action == 1) { - addGameTask(&Game::playerForgeTransferItemTier, player->getID(), firstItem, tier, secondItem); - } else if (action <= 4) { - addGameTask(&Game::playerForgeResourceConversion, player->getID(), action); + if (actionType == ForgeAction_t::FUSION) { + addGameTask(&Game::playerForgeFuseItems, player->getID(), actionType, firstItem, tier, secondItem, usedCore, reduceTierLoss, convergence); + } else if (actionType == ForgeAction_t::TRANSFER) { + addGameTask(&Game::playerForgeTransferItemTier, player->getID(), actionType, firstItem, tier, secondItem, convergence); + } else if (actionType <= ForgeAction_t::INCREASELIMIT) { + addGameTask(&Game::playerForgeResourceConversion, player->getID(), actionType); } } @@ -5034,47 +5209,41 @@ void ProtocolGame::parseForgeBrowseHistory(NetworkMessage &msg) { addGameTask(&Game::playerBrowseForgeHistory, player->getID(), msg.getByte()); } -void ProtocolGame::sendForgeFusionItem(uint16_t itemId, uint8_t tier, bool success, uint8_t bonus, uint8_t coreCount) { +void ProtocolGame::sendForgeResult(ForgeAction_t actionType, uint16_t leftItemId, uint8_t leftTier, uint16_t rightItemId, uint8_t rightTier, bool success, uint8_t bonus, uint8_t coreCount, bool convergence) { NetworkMessage msg; msg.addByte(0x8A); - msg.addByte(0x00); // Fusion = 0 - // Was succeeded bool - msg.addByte(success); - - msg.add(itemId); // Left item - msg.addByte(tier); // Left item tier - msg.add(itemId); // Right item - msg.addByte(tier + 1); // Right item tier + // 0 = fusion | 1 = transfer + msg.addByte(static_cast(actionType)); + msg.addByte(convergence); - msg.addByte(bonus); // Roll fusion bonus - // Core kept - if (bonus == 2) { - msg.addByte(coreCount); - } else if (bonus >= 4 && bonus <= 8) { - msg.add(itemId); - msg.addByte(tier); + if (convergence && actionType == ForgeAction_t::FUSION) { + success = true; + std::swap(leftItemId, rightItemId); } - writeToOutputBuffer(msg); - sendOpenForge(); -} - -void ProtocolGame::sendTransferItemTier(uint16_t firstItem, uint8_t tier, uint16_t secondItem) { - NetworkMessage msg; - msg.addByte(0x8A); - - msg.addByte(0x01); // Transfer = 1 - msg.addByte(0x01); // Always success + msg.addByte(success); - msg.add(firstItem); // Left item - msg.addByte(tier); // Left item tier - msg.add(secondItem); // Right item - msg.addByte(tier - 1); // Right item tier + msg.add(leftItemId); + msg.addByte(leftTier); + msg.add(rightItemId); + msg.addByte(rightTier); - msg.addByte(0x00); // Bonus type always none + if (actionType == ForgeAction_t::TRANSFER) { + msg.addByte(0x00); // Bonus type always none for transfer + } else { + msg.addByte(bonus); // Roll fusion bonus + // Core kept + if (bonus == 2) { + msg.addByte(coreCount); + } else if (bonus >= 4 && bonus <= 8) { + msg.add(leftItemId); + msg.addByte(leftTier); + } + } writeToOutputBuffer(msg); + g_logger().debug("Send forge fusion: type {}, left item {}, left tier {}, right item {}, rightTier {}, success {}, bonus {}, coreCount {}, convergence {}", fmt::underlying(actionType), leftItemId, leftTier, rightItemId, rightTier, success, bonus, coreCount, convergence); sendOpenForge(); } @@ -5271,7 +5440,8 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { } for (uint8_t i = SKILL_CRITICAL_HIT_CHANCE; i <= SKILL_LAST; i++) { - if (!it.abilities->skills[i]) { + auto skills = it.abilities->skills[i]; + if (!skills) { continue; } @@ -5281,19 +5451,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { separator = true; } - ss << getSkillName(i) << ' '; - if (i != SKILL_CRITICAL_HIT_CHANCE) { - ss << std::showpos; - } - if (i == SKILL_LIFE_LEECH_AMOUNT || i == SKILL_MANA_LEECH_AMOUNT) { - ss << (it.abilities->skills[i] / 100.) << '%'; - } else { - ss << it.abilities->skills[i] << '%'; - } - - if (i != SKILL_CRITICAL_HIT_CHANCE) { - ss << std::noshowpos; - } + ss << fmt::format("{} {:+.2f}%", getSkillName(i), skills / 100.0); } if (it.abilities->stats[STAT_MAGICPOINTS] != 0) { @@ -5735,6 +5893,7 @@ void ProtocolGame::sendRestingStatus(uint8_t protection) { NetworkMessage msg; msg.addByte(0xA9); msg.addByte(protection); // 1 / 0 + int32_t dailyStreak = static_cast(player->kv()->scoped("daily-reward")->get("streak")->getNumber()); msg.addByte(dailyStreak < 2 ? 0 : 1); if (dailyStreak < 2) { @@ -6191,12 +6350,7 @@ void ProtocolGame::sendAddCreature(std::shared_ptr creature, const Pos sendInventoryIds(); std::shared_ptr slotItem = player->getInventoryItem(CONST_SLOT_BACKPACK); if (slotItem) { - std::shared_ptr mainBackpack = slotItem->getContainer(); - std::shared_ptr hasQuickLootContainer = player->getLootContainer(OBJECTCATEGORY_DEFAULT); - if (mainBackpack && !hasQuickLootContainer) { - player->setLootContainer(OBJECTCATEGORY_DEFAULT, mainBackpack); - sendInventoryItem(CONST_SLOT_BACKPACK, player->getInventoryItem(CONST_SLOT_BACKPACK)); - } + player->setMainBackpackUnassigned(slotItem->getContainer()); } sendLootContainers(); @@ -8105,7 +8259,7 @@ void ProtocolGame::sendForgeSkillStats(NetworkMessage &msg) const { return; } - std::vector slots { CONST_SLOT_LEFT, CONST_SLOT_ARMOR, CONST_SLOT_HEAD }; + std::vector slots { CONST_SLOT_LEFT, CONST_SLOT_ARMOR, CONST_SLOT_HEAD, CONST_SLOT_LEGS }; for (const auto &slot : slots) { double_t skill = 0; if (std::shared_ptr item = player->getInventoryItem(slot); item) { @@ -8119,6 +8273,9 @@ void ProtocolGame::sendForgeSkillStats(NetworkMessage &msg) const { if (it.isHelmet()) { skill = item->getMomentumChance() * 100; } + if (it.isLegs()) { + skill = item->getTranscendenceChance() * 100; + } } auto skillCast = static_cast(skill); @@ -8561,6 +8718,14 @@ void ProtocolGame::parseOpenWheel(NetworkMessage &msg) { addGameTask(&Game::playerOpenWheel, player->getID(), ownerId); } +void ProtocolGame::parseWheelGemAction(NetworkMessage &msg) { + if (oldProtocol || !g_configManager().getBoolean(TOGGLE_WHEELSYSTEM, __FUNCTION__)) { + return; + } + + addGameTask(&Game::playerWheelGemAction, player->getID(), msg); +} + void ProtocolGame::sendOpenWheelWindow(uint32_t ownerId) { if (!player || oldProtocol || !g_configManager().getBoolean(TOGGLE_WHEELSYSTEM, __FUNCTION__)) { return; diff --git a/src/server/network/protocol/protocolgame.hpp b/src/server/network/protocol/protocolgame.hpp index 8838fc39e7d..0cf3cf3787c 100644 --- a/src/server/network/protocol/protocolgame.hpp +++ b/src/server/network/protocol/protocolgame.hpp @@ -12,6 +12,7 @@ #include "server/network/protocol/protocol.hpp" #include "creatures/interactions/chat.hpp" #include "creatures/creature.hpp" +#include "enums/forge_conversion.hpp" class NetworkMessage; class Player; @@ -255,9 +256,10 @@ class ProtocolGame final : public Protocol { uint8_t tier, bool success, uint8_t bonus, - uint8_t coreCount + uint8_t coreCount, + bool convergence ); - void sendTransferItemTier(uint16_t firstItem, uint8_t tier, uint16_t secondItem); + void sendForgeResult(ForgeAction_t actionType, uint16_t leftItemId, uint8_t leftTier, uint16_t rightItemId, uint8_t rightTier, bool success, uint8_t bonus, uint8_t coreCount, bool convergence); void sendForgeHistory(uint8_t page); void sendForgeSkillStats(NetworkMessage &msg) const; @@ -467,6 +469,7 @@ class ProtocolGame final : public Protocol { void parseOpenWheel(NetworkMessage &msg); void sendOpenWheelWindow(uint32_t ownerId); void parseSaveWheel(NetworkMessage &msg); + void parseWheelGemAction(NetworkMessage &msg); friend class Player; friend class PlayerWheel; diff --git a/src/server/server_definitions.hpp b/src/server/server_definitions.hpp index 9d06dffb1f8..7b3c254e189 100644 --- a/src/server/server_definitions.hpp +++ b/src/server/server_definitions.hpp @@ -61,6 +61,9 @@ enum Resource_t : uint8_t { RESOURCE_FORGE_DUST = 0x46, RESOURCE_FORGE_SLIVER = 0x47, RESOURCE_FORGE_CORES = 0x48, + RESOURCE_LESSER_GEMS = 0x51, + RESOURCE_REGULAR_GEMS = 0x52, + RESOURCE_GREATER_GEMS = 0x53, RESOURCE_WHEEL_OF_DESTINY = 0x56 }; diff --git a/src/utils/const.hpp b/src/utils/const.hpp index f86ec7aec7b..c681d110c69 100644 --- a/src/utils/const.hpp +++ b/src/utils/const.hpp @@ -27,7 +27,6 @@ static constexpr uint8_t IMBUEMENT_MAX_TIER = 3; static constexpr int32_t STORAGEVALUE_EMOTE = 30008; static constexpr int32_t STORAGEVALUE_PODIUM = 30020; -static constexpr int32_t STORAGEVALUE_AUTO_LOOT = 30063; static constexpr int32_t STORAGEVALUE_BESTIARYKILLCOUNT = 61305000; // Can get up to 2000 storages! // Hazard system storage diff --git a/src/utils/tools.cpp b/src/utils/tools.cpp index 56c76ac0418..25d2e8bc478 100644 --- a/src/utils/tools.cpp +++ b/src/utils/tools.cpp @@ -470,12 +470,12 @@ std::time_t getTimeNow() { return std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); } -std::time_t getTimeMsNow() { +int64_t getTimeMsNow() { auto duration = std::chrono::system_clock::now().time_since_epoch(); return std::chrono::duration_cast(duration).count(); } -std::time_t getTimeUsNow() { +int64_t getTimeUsNow() { auto duration = std::chrono::system_clock::now().time_since_epoch(); return std::chrono::duration_cast(duration).count(); } @@ -1604,8 +1604,6 @@ std::string getObjectCategoryName(ObjectCategory_t category) { return "Tibia Coins"; case OBJECTCATEGORY_CREATUREPRODUCTS: return "Creature Products"; - case OBJECTCATEGORY_STASHRETRIEVE: - return "Stash Retrieve"; case OBJECTCATEGORY_GOLD: return "Gold"; case OBJECTCATEGORY_DEFAULT: @@ -1809,3 +1807,19 @@ std::string toKey(const std::string &str) { key.erase(std::remove_if(key.begin(), key.end(), [](char c) { return std::isspace(c); }), key.end()); return key; } + +uint8_t convertWheelGemAffinityToDomain(uint8_t affinity) { + switch (affinity) { + case 0: + return 1; + case 1: + return 3; + case 2: + return 0; + case 3: + return 2; + default: + g_logger().error("Failed to get gem affinity {}", affinity); + return 0; + } +} diff --git a/src/utils/tools.hpp b/src/utils/tools.hpp index 580a962c9e4..add8fe59117 100644 --- a/src/utils/tools.hpp +++ b/src/utils/tools.hpp @@ -72,8 +72,8 @@ std::string formatTime(time_t time); */ std::string formatEnumName(std::string_view name); std::time_t getTimeNow(); -std::time_t getTimeMsNow(); -std::time_t getTimeUsNow(); +int64_t getTimeMsNow(); +int64_t getTimeUsNow(); std::string convertIPToString(uint32_t ip); void trimString(std::string &str); @@ -191,3 +191,9 @@ std::string getPlayerReflexivePronoun(PlayerPronoun_t pronoun, PlayerSex_t sex, std::string getVerbForPronoun(PlayerPronoun_t pronoun, bool pastTense = false); std::string toKey(const std::string &str); + +static inline double quadraticPoly(double a, double b, double c, double x) { + return a * x * x + b * x + c; +} + +uint8_t convertWheelGemAffinityToDomain(uint8_t affinity); diff --git a/src/utils/utils_definitions.hpp b/src/utils/utils_definitions.hpp index 25fce70f47a..712f3f5f587 100644 --- a/src/utils/utils_definitions.hpp +++ b/src/utils/utils_definitions.hpp @@ -236,7 +236,9 @@ enum MagicEffectClasses : uint16_t { CONST_ME_AGONY = 249, - CONST_ME_LAST = CONST_ME_AGONY + CONST_ME_LOOT_HIGHLIGHT = 252, + + CONST_ME_LAST }; enum ShootType_t : uint8_t { diff --git a/vcproj/canary.vcxproj b/vcproj/canary.vcxproj index 8e779cf57fd..52ffdb55022 100644 --- a/vcproj/canary.vcxproj +++ b/vcproj/canary.vcxproj @@ -43,6 +43,7 @@ + @@ -254,6 +255,7 @@ +