From 88414330c101c59281de769a8acbf7ef37678440 Mon Sep 17 00:00:00 2001 From: Karin Date: Wed, 17 Apr 2024 14:18:27 -0300 Subject: [PATCH 01/19] fix: remove deprecated conjure diamond/spectral (#2551) https://tibia.fandom.com/wiki/Updates/12.55.10451 --- .../conjuring/conjure_diamond_arrow.lua | 21 ------------------- .../conjuring/conjure_spectral_bolt.lua | 21 ------------------- 2 files changed, 42 deletions(-) delete mode 100644 data/scripts/spells/conjuring/conjure_diamond_arrow.lua delete mode 100644 data/scripts/spells/conjuring/conjure_spectral_bolt.lua diff --git a/data/scripts/spells/conjuring/conjure_diamond_arrow.lua b/data/scripts/spells/conjuring/conjure_diamond_arrow.lua deleted file mode 100644 index 2ab4bbe0647..00000000000 --- a/data/scripts/spells/conjuring/conjure_diamond_arrow.lua +++ /dev/null @@ -1,21 +0,0 @@ -local spell = Spell("instant") - -function spell.onCastSpell(creature, variant) - return creature:conjureItem(0, 25757, 100, CONST_ME_MAGIC_BLUE) -end - -spell:group("support") -spell:id(192) -spell:name("Conjure Diamond Arrow") -spell:words("exevo gran con hur") -spell:cooldown(2 * 1000) -spell:groupCooldown(2 * 1000) -spell:level(150) -spell:mana(1000) -spell:soul(0) -spell:isPremium(true) -spell:isSelfTarget(true) -spell:isAggressive(false) -spell:vocation("paladin;true", "royal paladin;true") -spell:needLearn(false) -spell:register() diff --git a/data/scripts/spells/conjuring/conjure_spectral_bolt.lua b/data/scripts/spells/conjuring/conjure_spectral_bolt.lua deleted file mode 100644 index 336eb423139..00000000000 --- a/data/scripts/spells/conjuring/conjure_spectral_bolt.lua +++ /dev/null @@ -1,21 +0,0 @@ -local spell = Spell("instant") - -function spell.onCastSpell(creature, variant) - return creature:conjureItem(0, 35902, 100, CONST_ME_MAGIC_BLUE) -end - -spell:group("support") -spell:id(193) -spell:name("Conjure Spectral Bolt") -spell:words("exevo gran con vis") -spell:cooldown(2 * 1000) -spell:groupCooldown(2 * 1000) -spell:level(150) -spell:mana(1000) -spell:soul(0) -spell:isPremium(true) -spell:isSelfTarget(true) -spell:isAggressive(false) -spell:vocation("paladin;true", "royal paladin;true") -spell:needLearn(false) -spell:register() From 533e9d617388e3c16f3cc6cf5074e27a43c4c9d1 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 17 Apr 2024 17:34:02 -0300 Subject: [PATCH 02/19] fix: check nullptr town (avoid crash if town not exist) (#2549) --- src/creatures/players/player.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 592db179071..855b48a782b 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -657,6 +657,11 @@ class Player final : public Creature, public Cylinder, public Bankable { return loginPosition; } const Position &getTemplePosition() const { + if (!town) { + static auto emptyPosition = Position(); + return emptyPosition; + } + return town->getTemplePosition(); } std::shared_ptr getTown() const { From d2d44cd4fa95a125a774d88771fe78b48a6a9457 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Wed, 17 Apr 2024 13:38:22 -0700 Subject: [PATCH 03/19] feat: configurable party share range (#2539) Pretty small yet useful configuration feature to allow one to easily change party share ranges. The configuration is a float, default to 1.5 which is the same as cipbia. --- config.lua.dist | 2 ++ src/config/config_enums.hpp | 1 + src/config/configmanager.cpp | 1 + src/creatures/players/grouping/party.cpp | 9 ++++++--- src/creatures/players/grouping/party.hpp | 1 + 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/config.lua.dist b/config.lua.dist index a5bb3d73ca5..97dc347c8ee 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -212,6 +212,8 @@ wheelAtelierRevealGreaterCost = 6000000 familiarTime = 30 partyAutoShareExperience = true +-- partyShareRangeMultiplier: the range of the party share experience, default 3/2 (1.5) +partyShareRangeMultiplier = 1.5 partyShareLootBoosts = false partyShareLootBoostsDimishingFactor = 0.7 diff --git a/src/config/config_enums.hpp b/src/config/config_enums.hpp index b9b857f435c..dc3271daaf9 100644 --- a/src/config/config_enums.hpp +++ b/src/config/config_enums.hpp @@ -178,6 +178,7 @@ enum ConfigKey_t : uint16_t { OWNER_NAME, PARALLELISM, PARTY_AUTO_SHARE_EXPERIENCE, + PARTY_SHARE_RANGE_MULTIPLIER, PARTY_LIST_MAX_DISTANCE, PARTY_SHARE_LOOT_BOOSTS_DIMINISHING_FACTOR, PARTY_SHARE_LOOT_BOOSTS, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index c1702672e71..ac78a2bbcfb 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -317,6 +317,7 @@ bool ConfigManager::load() { loadIntConfig(L, STAMINA_PZ_GAIN, "staminaPzGain", 1); loadIntConfig(L, STAMINA_TRAINER_DELAY, "staminaTrainerDelay", 5); loadIntConfig(L, STAMINA_TRAINER_GAIN, "staminaTrainerGain", 1); + loadFloatConfig(L, PARTY_SHARE_RANGE_MULTIPLIER, "partyShareRangeMultiplier", 1.5f); loadIntConfig(L, START_STREAK_LEVEL, "startStreakLevel", 0); loadIntConfig(L, STATUSQUERY_TIMEOUT, "statusTimeout", 5000); loadIntConfig(L, STORE_COIN_PACKET, "coinPacketSize", 25); diff --git a/src/creatures/players/grouping/party.cpp b/src/creatures/players/grouping/party.cpp index 880fc77594f..c7d6fd48363 100644 --- a/src/creatures/players/grouping/party.cpp +++ b/src/creatures/players/grouping/party.cpp @@ -491,6 +491,10 @@ SharedExpStatus_t Party::getMemberSharedExperienceStatus(std::shared_ptr return SHAREDEXP_OK; } +float Party::shareRangeMultiplier() const { + return g_configManager().getFloat(PARTY_SHARE_RANGE_MULTIPLIER, __FUNCTION__); +} + uint32_t Party::getHighestLevel() { auto leader = getLeader(); if (!leader) { @@ -507,7 +511,7 @@ uint32_t Party::getHighestLevel() { } uint32_t Party::getMinLevel() { - return static_cast(std::ceil((static_cast(getHighestLevel()) * 2) / 3)); + return static_cast(std::ceil(static_cast(getHighestLevel()) / shareRangeMultiplier())); } uint32_t Party::getLowestLevel() { @@ -525,7 +529,7 @@ uint32_t Party::getLowestLevel() { } uint32_t Party::getMaxLevel() { - return static_cast(std::floor((static_cast(getLowestLevel()) * 3) / 2)); + return static_cast(std::floor(static_cast(getLowestLevel()) * shareRangeMultiplier())); } bool Party::isPlayerActive(std::shared_ptr player) { @@ -533,7 +537,6 @@ bool Party::isPlayerActive(std::shared_ptr player) { if (it == ticksMap.end()) { return false; } - uint64_t timeDiff = OTSYS_TIME() - it->second; return timeDiff <= 2 * 60 * 1000; } diff --git a/src/creatures/players/grouping/party.hpp b/src/creatures/players/grouping/party.hpp index a356d1a032b..5da0f4e0647 100644 --- a/src/creatures/players/grouping/party.hpp +++ b/src/creatures/players/grouping/party.hpp @@ -133,6 +133,7 @@ class Party : public SharedObject { uint32_t getLowestLevel(); uint32_t getMinLevel(); uint32_t getMaxLevel(); + float shareRangeMultiplier() const; std::map ticksMap; From 582e286ffb2c57afa050a93c5ae308321009599a Mon Sep 17 00:00:00 2001 From: Luan Luciano Date: Wed, 17 Apr 2024 17:40:14 -0300 Subject: [PATCH 04/19] fix: potions add flask to player (#2538) --- data/scripts/actions/items/potions.lua | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/data/scripts/actions/items/potions.lua b/data/scripts/actions/items/potions.lua index 02e92349363..473796d79df 100644 --- a/data/scripts/actions/items/potions.lua +++ b/data/scripts/actions/items/potions.lua @@ -89,10 +89,13 @@ function flaskPotion.onUse(player, item, fromPosition, target, toPosition, isHot local deactivatedFlasks = player:kv():get("talkaction.potions.flask") or false if not deactivatedFlasks then local container = Container(item:getParent().uid) - local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX) - - if fromPosition.x == CONTAINER_POSITION and container ~= inbox and container:getEmptySlots() ~= 0 then - container:addItem(potion.flask, 1) + if container then + local storeInbox = player:getSlotItem(CONST_SLOT_STORE_INBOX) + if fromPosition.x == CONTAINER_POSITION and container ~= storeInbox and container:getEmptySlots() ~= 0 then + container:addItem(potion.flask, 1) + else + player:addItem(potion.flask, 1) + end else Game.createItem(potion.flask, 1, fromPosition) end From c43338e5ee766fd6a699d79ca32a50d12c9dcefe Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 17 Apr 2024 17:42:23 -0300 Subject: [PATCH 05/19] feat: cupcakes (#2537) This introduces a new feature to the Cupcake item behavior in the game. With this update, each Cupcake can now only be consumed once every 10 minutes. This cooldown is unique to each Cupcake, meaning that different Cupcakes do not share this cooldown period with each other. --- data-otservbr-global/lib/core/storages.lua | 3 -- .../scripts/actions/other/cup_cakes.lua | 51 ------------------- .../actions/items/blueberry_cupcake.lua | 18 +++++++ data/scripts/actions/items/lemon_cupcake.lua | 24 +++++++++ .../actions/items/strawberry_cupcake.lua | 18 +++++++ 5 files changed, 60 insertions(+), 54 deletions(-) delete mode 100644 data-otservbr-global/scripts/actions/other/cup_cakes.lua create mode 100644 data/scripts/actions/items/blueberry_cupcake.lua create mode 100644 data/scripts/actions/items/lemon_cupcake.lua create mode 100644 data/scripts/actions/items/strawberry_cupcake.lua diff --git a/data-otservbr-global/lib/core/storages.lua b/data-otservbr-global/lib/core/storages.lua index 69f1d7f7948..6ce29967451 100644 --- a/data-otservbr-global/lib/core/storages.lua +++ b/data-otservbr-global/lib/core/storages.lua @@ -106,7 +106,6 @@ Storage = { -- unused ExerciseDummyExhaust = 30029, SamsOldBackpack = 30030, SamsOldBackpackDoor = 30031, - StrawberryCupcake = 30032, ChayenneReward = 30033, SwampDiggingTimeout = 30034, HydraEggQuest = 30035, @@ -125,8 +124,6 @@ Storage = { Navigator = 30048, DwarvenLegs = 30049, PrinceDrazzakTime = 30050, - LemonCupcake = 30052, - BlueberryCupcake = 30053, -- Reserved in Global.Storage.FamiliarSummonEvent10 = 30054 -- Reserved in Global.Storage.FamiliarSummonEvent60 = 30055 ChayenneKeyTime = 30056, diff --git a/data-otservbr-global/scripts/actions/other/cup_cakes.lua b/data-otservbr-global/scripts/actions/other/cup_cakes.lua deleted file mode 100644 index a9d94dbaacd..00000000000 --- a/data-otservbr-global/scripts/actions/other/cup_cakes.lua +++ /dev/null @@ -1,51 +0,0 @@ -local data = { - [28484] = { - Type = "mana", - ExhaustStor = Storage.BlueberryCupcake, - timestamp = 10, - }, - [28485] = { - Type = "health", - ExhaustStor = Storage.StrawberryCupcake, - timestamp = 10, - }, - [28486] = { - Type = "skill", - ExhaustStor = Storage.LemonCupcake, - timestamp = 10, - }, -} - -local lemon = Condition(CONDITION_ATTRIBUTES) -lemon:setParameter(CONDITION_PARAM_TICKS, 60 * 60 * 1000) -lemon:setParameter(CONDITION_PARAM_SKILL_DISTANCE, 10) - -local cupCakes = Action() - -function cupCakes.onUse(player, item, fromPos, itemEx, toPos) - local foundItem = data[item.itemid] - if not foundItem then - return - end - if (player:getStorageValue(foundItem.ExhaustStor)) < os.time() then - if foundItem.Type == "mana" then - player:addMana(player:getMaxMana()) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your mana has been refilled.") - elseif foundItem.Type == "health" then - player:addHealth(player:getMaxHealth()) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your health has been refilled.") - elseif foundItem.Type == "skill" then - player:addCondition(lemon) - player:sendTextMessage(MESSAGE_FAILURE, "You feel more focused.") - end - player:say("Mmmm.", TALKTYPE_MONSTER_SAY) - item:remove(1) - player:setStorageValue(foundItem.ExhaustStor, os.time() + (foundItem.timestamp * 60)) - else - player:sendTextMessage(MESSAGE_FAILURE, "You need to wait before using it again.") - end - return true -end - -cupCakes:id(28484, 28485, 28486) -cupCakes:register() diff --git a/data/scripts/actions/items/blueberry_cupcake.lua b/data/scripts/actions/items/blueberry_cupcake.lua new file mode 100644 index 00000000000..f645e852101 --- /dev/null +++ b/data/scripts/actions/items/blueberry_cupcake.lua @@ -0,0 +1,18 @@ +local blueberryCupcake = Action() + +function blueberryCupcake.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if player:hasExhaustion("blueberry-cupcake-cooldown") then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to wait before using it again.") + return true + end + + player:addMana(player:getMaxMana()) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your mana has been refilled.") + player:say("Mmmm.", TALKTYPE_MONSTER_SAY) + player:setExhaustion("blueberry-cupcake-cooldown", 10 * 60) + item:remove(1) + return true +end + +blueberryCupcake:id(28484) +blueberryCupcake:register() diff --git a/data/scripts/actions/items/lemon_cupcake.lua b/data/scripts/actions/items/lemon_cupcake.lua new file mode 100644 index 00000000000..3cf085d8ad8 --- /dev/null +++ b/data/scripts/actions/items/lemon_cupcake.lua @@ -0,0 +1,24 @@ +local distanceCondition = Condition(CONDITION_ATTRIBUTES) +distanceCondition:setParameter(CONDITION_PARAM_BUFF_SPELL, 1) +distanceCondition:setParameter(CONDITION_PARAM_TICKS, 60 * 60 * 1000) +distanceCondition:setParameter(CONDITION_PARAM_SKILL_DISTANCE, 10) +distanceCondition:setParameter(CONDITION_PARAM_FORCEUPDATE, true) + +local lemonCupcake = Action() + +function lemonCupcake.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if player:hasExhaustion("lemon-cupcake-cooldown") then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to wait before using it again.") + return true + end + + player:addCondition(distanceCondition) + player:sendTextMessage(MESSAGE_FAILURE, "You feel more focused.") + player:say("Mmmm.", TALKTYPE_MONSTER_SAY) + player:setExhaustion("lemon-cupcake-cooldown", 10 * 60) + item:remove(1) + return true +end + +lemonCupcake:id(28486) +lemonCupcake:register() diff --git a/data/scripts/actions/items/strawberry_cupcake.lua b/data/scripts/actions/items/strawberry_cupcake.lua new file mode 100644 index 00000000000..7ee7ec75764 --- /dev/null +++ b/data/scripts/actions/items/strawberry_cupcake.lua @@ -0,0 +1,18 @@ +local strawberryCupcake = Action() + +function strawberryCupcake.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if player:hasExhaustion("strawberry-cupcake-cooldown") then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to wait before using it again.") + return true + end + + player:addHealth(player:getMaxHealth()) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your health has been refilled.") + player:say("Mmmm.", TALKTYPE_MONSTER_SAY) + player:setExhaustion("strawberry-cupcake-cooldown", 10 * 60) + item:remove(1) + return true +end + +strawberryCupcake:id(28485) +strawberryCupcake:register() From 6503aa4589ad4623f1b4d20e6b6269d2af961959 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 17 Apr 2024 17:44:24 -0300 Subject: [PATCH 06/19] improve: bank transfer min town id (#2547) --- config.lua.dist | 2 ++ src/config/config_enums.hpp | 3 ++- src/config/configmanager.cpp | 7 ++++--- src/game/bank/bank.cpp | 8 +++++--- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/config.lua.dist b/config.lua.dist index 97dc347c8ee..0507b652e29 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -241,6 +241,7 @@ onlyPremiumAccount = false -- NOTE: enablePlayerPutItemInAmmoSlot = true, will enable players to put any items on ammo slot, more used in custom shopping system -- NOTE: startStreakLevel will make a reward streak level for new players who never logged in -- NOTE: if showLootsInBestiary is true, will cause all loots to be shown in the bestiary even if the player has not reached the required number of kills +-- NOTE: minTownIdToBankTransfer blocks towns less than defined from receiving money transfers stashMoving = false depotChest = 4 autoLoot = false @@ -259,6 +260,7 @@ storeInboxMaxLimit = 2000 enablePlayerPutItemInAmmoSlot = false startStreakLevel = 0 showLootsInBestiary = false +minTownIdToBankTransfer = 3 -- Teleport summon -- Set to true will never remove the summon diff --git a/src/config/config_enums.hpp b/src/config/config_enums.hpp index dc3271daaf9..4abf29c04cb 100644 --- a/src/config/config_enums.hpp +++ b/src/config/config_enums.hpp @@ -36,10 +36,10 @@ enum ConfigKey_t : uint16_t { CLASSIC_ATTACK_SPEED, CLEAN_PROTECTION_ZONES, COMBAT_CHAIN_DELAY, - COMBAT_CHAIN_TARGETS, COMBAT_CHAIN_SKILL_FORMULA_AXE, COMBAT_CHAIN_SKILL_FORMULA_CLUB, COMBAT_CHAIN_SKILL_FORMULA_SWORD, + COMBAT_CHAIN_TARGETS, COMPRESSION_LEVEL, CONVERT_UNSAFE_SCRIPTS, CORE_DIRECTORY, @@ -155,6 +155,7 @@ enum ConfigKey_t : uint16_t { METRICS_PROMETHEUS_ADDRESS, MIN_DELAY_BETWEEN_CONDITIONS, MIN_ELEMENTAL_RESISTANCE, + MIN_TOWN_ID_TO_BANK_TRANSFER, MOMENTUM_CHANCE_FORMULA_A, MOMENTUM_CHANCE_FORMULA_B, MOMENTUM_CHANCE_FORMULA_C, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index ac78a2bbcfb..8d4ba7a77da 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -166,6 +166,9 @@ bool ConfigManager::load() { loadBoolConfig(L, XP_DISPLAY_MODE, "experienceDisplayRates", true); loadFloatConfig(L, BESTIARY_RATE_CHARM_SHOP_PRICE, "bestiaryRateCharmShopPrice", 1.0); + loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_AXE, "combatChainSkillFormulaAxe", 0.9); + loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_CLUB, "combatChainSkillFormulaClub", 0.7); + loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_SWORD, "combatChainSkillFormulaSword", 1.1); loadFloatConfig(L, FORGE_AMOUNT_MULTIPLIER, "forgeAmountMultiplier", 3.0); loadFloatConfig(L, HAZARD_EXP_BONUS_MULTIPLIER, "hazardExpBonusMultiplier", 2.0); loadFloatConfig(L, LOYALTY_BONUS_PERCENTAGE_MULTIPLIER, "loyaltyBonusPercentageMultiplier", 1.0); @@ -218,9 +221,6 @@ bool ConfigManager::load() { loadIntConfig(L, CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES, "checkExpiredMarketOffersEachMinutes", 60); loadIntConfig(L, COMBAT_CHAIN_DELAY, "combatChainDelay", 50); loadIntConfig(L, COMBAT_CHAIN_TARGETS, "combatChainTargets", 5); - loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_AXE, "combatChainSkillFormulaAxe", 0.9); - loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_CLUB, "combatChainSkillFormulaClub", 0.7); - loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_SWORD, "combatChainSkillFormulaSword", 1.1); loadIntConfig(L, COMPRESSION_LEVEL, "packetCompressionLevel", 6); loadIntConfig(L, CRITICALCHANCE, "criticalChance", 10); loadIntConfig(L, DAY_KILLS_TO_RED, "dayKillsToRedSkull", 3); @@ -287,6 +287,7 @@ bool ConfigManager::load() { loadIntConfig(L, METRICS_OSTREAM_INTERVAL, "metricsOstreamInterval", 1000); loadIntConfig(L, MIN_DELAY_BETWEEN_CONDITIONS, "minDelayBetweenConditions", 0); loadIntConfig(L, MIN_ELEMENTAL_RESISTANCE, "minElementalResistance", -200); + loadIntConfig(L, MIN_TOWN_ID_TO_BANK_TRANSFER, "minTownIdToBankTransfer", 3); loadIntConfig(L, MONTH_KILLS_TO_RED, "monthKillsToRedSkull", 10); loadIntConfig(L, MULTIPLIER_ATTACKONFIST, "multiplierSpeedOnFist", 5); loadIntConfig(L, ORANGE_SKULL_DURATION, "orangeSkullDuration", 7); diff --git a/src/game/bank/bank.cpp b/src/game/bank/bank.cpp index 30345e0495b..d9a056396ce 100644 --- a/src/game/bank/bank.cpp +++ b/src/game/bank/bank.cpp @@ -80,18 +80,18 @@ const std::set deniedNames = { "paladinsample" }; -const uint32_t minTownId = 3; - bool Bank::transferTo(const std::shared_ptr destination, uint64_t amount) { if (!destination) { g_logger().error("Bank::transferTo: destination is nullptr"); return false; } + auto bankable = getBankable(); if (!bankable) { g_logger().error("Bank::transferTo: bankable is nullptr"); return false; } + auto destinationBankable = destination->getBankable(); if (!destinationBankable) { g_logger().error("Bank::transferTo: destinationBankable is nullptr"); @@ -102,11 +102,13 @@ bool Bank::transferTo(const std::shared_ptr destination, uint64_t amount) if (destinationPlayer != nullptr) { auto name = asLowerCaseString(destinationPlayer->getName()); replaceString(name, " ", ""); + if (deniedNames.contains(name)) { g_logger().warn("Bank::transferTo: denied name: {}", name); return false; } - if (destinationPlayer->getTown()->getID() < minTownId) { + + if (destinationPlayer->getTown()->getID() < g_configManager().getNumber(MIN_TOWN_ID_TO_BANK_TRANSFER, __FUNCTION__)) { g_logger().warn("Bank::transferTo: denied town: {}", destinationPlayer->getTown()->getID()); return false; } From fceeac126c47d9e8628f40f0fe9ba0409a6c478b Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 18 Apr 2024 09:36:45 -0300 Subject: [PATCH 07/19] fix: offline training messages types and speed (#2548) --- data/scripts/creaturescripts/player/offline_training.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/scripts/creaturescripts/player/offline_training.lua b/data/scripts/creaturescripts/player/offline_training.lua index abfb0b94b3d..4466c6ee0e3 100644 --- a/data/scripts/creaturescripts/player/offline_training.lua +++ b/data/scripts/creaturescripts/player/offline_training.lua @@ -12,7 +12,7 @@ function offlineTraining.onLogin(player) player:setOfflineTrainingSkill(SKILL_NONE) if offlineTime < 600 then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You must be logged out for more than 10 minutes to start offline training.") + player:sendTextMessage(MESSAGE_OFFLINE_TRAINING, "You must be logged out for more than 10 minutes to start offline training.") return true end @@ -50,15 +50,15 @@ function offlineTraining.onLogin(player) end text = string.format("%s.", text) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, text) + player:sendTextMessage(MESSAGE_OFFLINE_TRAINING, text) local vocation = player:getVocation() local promotion = vocation:getPromotion() local topVocation = not promotion and vocation or promotion - local updateSkills = false + if table.contains({ SKILL_CLUB, SKILL_SWORD, SKILL_AXE, SKILL_DISTANCE }, offlineTrainingSkill) then - local modifier = topVocation:getBaseAttackSpeed() / 1000 + local modifier = topVocation:getBaseAttackSpeed() / 1000 / configManager.getFloat(configKeys.RATE_OFFLINE_TRAINING_SPEED) updateSkills = player:addOfflineTrainingTries(offlineTrainingSkill, (trainingTime / modifier) / (offlineTrainingSkill == SKILL_DISTANCE and 4 or 2)) elseif offlineTrainingSkill == SKILL_MAGLEVEL then local gainTicks = topVocation:getManaGainTicks() * 2 From 955fddea27c32c67710bdb51da1bd6c78423fba0 Mon Sep 17 00:00:00 2001 From: Karin Date: Fri, 19 Apr 2024 17:26:31 -0300 Subject: [PATCH 08/19] fix: block gold pouch using in the obtain method (#2559) --- src/game/game.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/game/game.cpp b/src/game/game.cpp index cac0b596ebf..a11fde62c2c 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -5384,6 +5384,11 @@ void Game::playerSetManagedContainer(uint32_t playerId, ObjectCategory_t categor return; } + if (container->getID() == ITEM_GOLD_POUCH && !isLootContainer) { + player->sendTextMessage(MESSAGE_FAILURE, "You can only set the gold pouch as a loot container."); + return; + } + if (container->getHoldingPlayer() != player) { player->sendCancelMessage("You must be holding the container to set it as a loot container."); return; From b73df39ce9832e5c000906e8c757272ca7ce02e7 Mon Sep 17 00:00:00 2001 From: Eduardo Augusto <38956084+duuh30@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:56:23 -0300 Subject: [PATCH 09/19] fix: two handed weapons (#2570) --- src/creatures/players/player.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 75e5b5ef2af..618ffea1081 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -3271,13 +3271,6 @@ ReturnValue Player::queryAdd(int32_t index, const std::shared_ptr &thing, case CONST_SLOT_LEFT: { if (item->isQuiver()) { ret = RETURNVALUE_CANNOTBEDRESSED; - } else if (slotPosition & SLOTP_LEFT) { - WeaponType_t type = item->getWeaponType(); - if (type == WEAPON_NONE || type == WEAPON_SHIELD || type == WEAPON_AMMO) { - ret = RETURNVALUE_CANNOTBEDRESSED; - } else { - ret = RETURNVALUE_NOERROR; - } } else if (slotPosition & SLOTP_TWO_HAND) { if (inventory[CONST_SLOT_RIGHT]) { WeaponType_t type = item->getWeaponType(); @@ -3290,6 +3283,13 @@ ReturnValue Player::queryAdd(int32_t index, const std::shared_ptr &thing, } else { ret = RETURNVALUE_NOERROR; } + } else if (slotPosition & SLOTP_LEFT) { + WeaponType_t type = item->getWeaponType(); + if (type == WEAPON_NONE || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + ret = RETURNVALUE_CANNOTBEDRESSED; + } else { + ret = RETURNVALUE_NOERROR; + } } else if (inventory[CONST_SLOT_RIGHT]) { std::shared_ptr rightItem = inventory[CONST_SLOT_RIGHT]; WeaponType_t type = item->getWeaponType(), rightType = rightItem->getWeaponType(); From 3fcf5856002a021820f1aec2341108bf5e2b04c7 Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Tue, 23 Apr 2024 09:07:35 -0300 Subject: [PATCH 10/19] fix: improves on gamestore, fix on gitignore and items (#2415) --- .gitignore | 12 +++-- data/items/items.xml | 4 +- data/modules/scripts/gamestore/gamestore.lua | 2 +- data/modules/scripts/gamestore/init.lua | 46 +++++++++++--------- src/game/game.cpp | 8 ++-- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index d233ae17409..a73a8c3022c 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,9 @@ bld/ [Ll]og/ build/ vcproj/ +!vcproj/canary.sln +!vcproj/canary.vcxproj +!vcproj/settings.props # Visual Studio 2015/2017 cache/options directory .vs/ @@ -372,9 +375,7 @@ config.lua config_canary.lua client_assertions.txt .env -otservbr.otbm -canary.otbm -otservbr-custom.otbm +data-otservbr-global/world/otservbr.otbm # Extensions *.ini @@ -382,9 +383,6 @@ otservbr-custom.otbm *.exe *.manifest *.rar -*-house.xml -*-monster.xml -*-npc.xml monster_count.txt # SFTP for Sublime @@ -398,4 +396,4 @@ canary.old vcpkg_installed # CLION -cmake-build-debug* +cmake-build-* diff --git a/data/items/items.xml b/data/items/items.xml index 765c6e9db51..0bc1c782452 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -75427,7 +75427,7 @@ Granted by TibiaGoals.com"/> - + @@ -75455,7 +75455,7 @@ Granted by TibiaGoals.com"/> - + diff --git a/data/modules/scripts/gamestore/gamestore.lua b/data/modules/scripts/gamestore/gamestore.lua index 852bd4d4405..f000c4079af 100644 --- a/data/modules/scripts/gamestore/gamestore.lua +++ b/data/modules/scripts/gamestore/gamestore.lua @@ -6770,7 +6770,7 @@ for k, category in ipairs(GameStore.Categories) do offer.type = GameStore.OfferTypes.OFFER_TYPE_NONE end if not offer.coinType then - offer.coinType = GameStore.CoinType.Coin + offer.coinType = GameStore.CoinType.Transferable end end end diff --git a/data/modules/scripts/gamestore/init.lua b/data/modules/scripts/gamestore/init.lua index 6146f562307..e53bab813f0 100644 --- a/data/modules/scripts/gamestore/init.lua +++ b/data/modules/scripts/gamestore/init.lua @@ -206,6 +206,13 @@ GameStore.DefaultDescriptions = { TEMPLE = { "Need a quick way home? Buy this transportation service to get instantly teleported to your home temple. \n\nNote, you cannot use this service while having a battle sign or a protection zone block. Further, the service will not work in no-logout zones or close to your home temple." }, } +GameStore.ItemLimit = { + PREY_WILDCARD = 50, + INSTANT_REWARD_ACCESS = 90, + EXPBOOST = 6, + HIRELING = 10, +} + --==Parsing==-- GameStore.isItsPacket = function(byte) for k, v in pairs(GameStore.RecivedPackets) do @@ -507,8 +514,8 @@ function parseBuyStoreOffer(playerId, msg) if not pcallOk then local alertMessage = pcallError.code and pcallError.message or "Something went wrong. Your purchase has been cancelled." - if not pcallError.code then -- unhandled error - -- log some debugging info + -- unhandled error + if not pcallError.code then logger.warn("[parseBuyStoreOffer] - Purchase failed due to an unhandled script error. Stacktrace: {}", pcallError) end @@ -618,7 +625,6 @@ function sendOfferDescription(player, offerId, description) end function Player.canBuyOffer(self, offer) - local playerId = self:getId() local disabled, disabledReason = 0, "" if offer.disabled or not offer.type then disabled = 1 @@ -697,12 +703,12 @@ function Player.canBuyOffer(self, offer) disabledReason = "You already have this mount." end elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_INSTANT_REWARD_ACCESS then - if self:getCollectionTokens() >= 90 then + if self:getCollectionTokens() >= GameStore.ItemLimit.INSTANT_REWARD_ACCESS then disabled = 1 disabledReason = "You already have maximum of reward tokens." end elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYBONUS then - if self:getPreyCards() >= 50 then + if self:getPreyCards() >= GameStore.ItemLimit.PREY_WILDCARD then disabled = 1 disabledReason = "You already have maximum of prey wildcards." end @@ -722,7 +728,7 @@ function Player.canBuyOffer(self, offer) disabledReason = "You already have 3 slots released." end elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_EXPBOOST then - if self:getStorageValue(GameStore.Storages.expBoostCount) == 6 then + if self:getStorageValue(GameStore.Storages.expBoostCount) == GameStore.ItemLimit.EXPBOOST then disabled = 1 disabledReason = "You can't buy XP Boost for today." end @@ -731,7 +737,7 @@ function Player.canBuyOffer(self, offer) disabledReason = "You already have an active XP boost." end elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HIRELING then - if self:getHirelingsCount() >= 10 then + if self:getHirelingsCount() >= GameStore.ItemLimit.HIRELING then disabled = 1 disabledReason = "You already have bought the maximum number of allowed hirelings." end @@ -1565,8 +1571,9 @@ function GameStore.processAllBlessingsPurchase(player, count) end function GameStore.processInstantRewardAccess(player, offerCount) - if player:getCollectionTokens() + offerCount >= 91 then - return error({ code = 1, message = "You cannot own more than 90 reward tokens." }) + local limit = GameStore.ItemLimit.INSTANT_REWARD_ACCESS + if player:getCollectionTokens() + offerCount >= limit + 1 then + return error({ code = 1, message = "You cannot own more than " .. limit .. " reward tokens." }) end player:setCollectionTokens(player:getCollectionTokens() + offerCount) end @@ -1641,7 +1648,6 @@ function GameStore.processHouseRelatedPurchase(player, offer) decoKit:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime()) end end - player:sendUpdateContainer(inbox) else for i = 1, offer.count do local decoKit = inbox:addItem(ITEM_DECORATION_KIT, 1) @@ -1653,10 +1659,10 @@ function GameStore.processHouseRelatedPurchase(player, offer) decoKit:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime()) end end - player:sendUpdateContainer(inbox) end end end + player:sendUpdateContainer(inbox) end end @@ -1677,11 +1683,7 @@ function GameStore.processOutfitPurchase(player, offerSexIdTable, addon) elseif player:hasOutfit(looktype, _addon) then return error({ code = 0, message = "You already own this outfit." }) else - if - not (player:addOutfitAddon(looktype, _addon)) -- TFS call failed - or (not player:hasOutfit(looktype, _addon)) -- Additional check; if the looktype doesn't match player sex for example, - -- then the TFS check will still pass... bug? (TODO) - then + if not player:addOutfitAddon(looktype, _addon) or not player:hasOutfit(looktype, _addon) then error({ code = 0, message = "There has been an issue with your outfit purchase. Your purchase has been cancelled." }) else player:addOutfitAddon(offerSexIdTable.male, _addon) @@ -1769,8 +1771,9 @@ function GameStore.processTaskHuntingThirdSlot(player) end function GameStore.processPreyBonusReroll(player, offerCount) - if player:getPreyCards() + offerCount >= 51 then - return error({ code = 1, message = "You cannot own more than 50 prey wildcards." }) + local limit = GameStore.ItemLimit.PREY_WILDCARD + if player:getPreyCards() + offerCount >= limit + 1 then + return error({ code = 1, message = "You cannot own more than " .. limit .. " prey wildcards." }) end player:addPreyCards(offerCount) end @@ -1812,8 +1815,8 @@ function GameStore.processHirelingPurchase(player, offer, productType, hirelingN return addPlayerEvent(sendStorePurchaseSuccessful, 650, player:getId(), message) -- If not, we ask him to do! else - if player:getHirelingsCount() >= 10 then - return error({ code = 1, message = "You cannot have more than 10 hirelings." }) + if player:getHirelingsCount() >= GameStore.ItemLimit.HIRELING then + return error({ code = 1, message = "You cannot have more than " .. GameStore.ItemLimit.HIRELING .. " hirelings." }) end -- TODO: Use the correct dialog (byte 0xDB) on client 1205+ -- for compatibility, request name using the change name dialog @@ -2219,7 +2222,8 @@ function sendHomePage(playerId) msg:sendToPlayer(player) end -function Player:openStore(serviceName) --exporting the method so other scripts can use to open store +--exporting the method so other scripts can use to open store +function Player:openStore(serviceName) local playerId = self:getId() openStore(playerId) diff --git a/src/game/game.cpp b/src/game/game.cpp index a11fde62c2c..0dfba6f6724 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -7841,9 +7841,11 @@ void Game::addBestiaryList(uint16_t raceid, std::string name) { } void Game::broadcastMessage(const std::string &text, MessageClasses type) const { - g_logger().info("Broadcasted message: {}", text); - for (const auto &it : players) { - it.second->sendTextMessage(type, text); + if (!text.empty()) { + g_logger().info("Broadcasted message: {}", text); + for (const auto &it : players) { + it.second->sendTextMessage(type, text); + } } } From bd72f6618e11221e2d8b8d58c3436e34e44fe0d9 Mon Sep 17 00:00:00 2001 From: Felipe Paluco <87909998+FelipePaluco@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:20:33 -0300 Subject: [PATCH 11/19] fix: transcendence applies correct status without unlocked stages (#2566) Fix the logic to ensure correct status application when using tiered legs and undergoing transcendence without any avatar stages unlocked. Remove the early return that blocks code execution in this scenario. Ensure proper application of damage reduction, 100% critical chance, and 15% critical damage. --- src/creatures/players/player.cpp | 2 +- src/creatures/players/wheel/player_wheel.cpp | 24 +++++++++++-------- .../players/wheel/wheel_definitions.hpp | 5 ++-- .../creatures/player/player_functions.cpp | 4 ++-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 618ffea1081..bcb31a4abfa 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -6688,7 +6688,7 @@ void Player::triggerTranscendance() { outfit.lookType = getVocation()->getAvatarLookType(); outfitCondition->setOutfit(outfit); addCondition(outfitCondition); - wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR, OTSYS_TIME() + duration); + wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR_FORGE, OTSYS_TIME() + duration); g_game().addMagicEffect(getPosition(), CONST_ME_AVATAR_APPEAR); sendTextMessage(MESSAGE_ATTENTION, "Transcendance was triggered."); sendSkills(); diff --git a/src/creatures/players/wheel/player_wheel.cpp b/src/creatures/players/wheel/player_wheel.cpp index 77a15f7cbf7..20e8f683bf9 100644 --- a/src/creatures/players/wheel/player_wheel.cpp +++ b/src/creatures/players/wheel/player_wheel.cpp @@ -2410,21 +2410,25 @@ int32_t PlayerWheel::checkBattleHealingAmount() const { } int32_t PlayerWheel::checkAvatarSkill(WheelAvatarSkill_t skill) const { - if (skill == WheelAvatarSkill_t::NONE || getOnThinkTimer(WheelOnThink_t::AVATAR) <= OTSYS_TIME()) { + if (skill == WheelAvatarSkill_t::NONE || (getOnThinkTimer(WheelOnThink_t::AVATAR_SPELL) <= OTSYS_TIME() && getOnThinkTimer(WheelOnThink_t::AVATAR_FORGE) <= OTSYS_TIME())) { return 0; } uint8_t stage = 0; - if (getInstant("Avatar of Light")) { - stage = getStage(WheelStage_t::AVATAR_OF_LIGHT); - } else if (getInstant("Avatar of Steel")) { - stage = getStage(WheelStage_t::AVATAR_OF_STEEL); - } else if (getInstant("Avatar of Nature")) { - stage = getStage(WheelStage_t::AVATAR_OF_NATURE); - } else if (getInstant("Avatar of Storm")) { - stage = getStage(WheelStage_t::AVATAR_OF_STORM); + if (getOnThinkTimer(WheelOnThink_t::AVATAR_SPELL) > OTSYS_TIME()) { + if (getInstant("Avatar of Light")) { + stage = getStage(WheelStage_t::AVATAR_OF_LIGHT); + } else if (getInstant("Avatar of Steel")) { + stage = getStage(WheelStage_t::AVATAR_OF_STEEL); + } else if (getInstant("Avatar of Nature")) { + stage = getStage(WheelStage_t::AVATAR_OF_NATURE); + } else if (getInstant("Avatar of Storm")) { + stage = getStage(WheelStage_t::AVATAR_OF_STORM); + } else { + return 0; + } } else { - return 0; + stage = 3; } if (skill == WheelAvatarSkill_t::DAMAGE_REDUCTION) { diff --git a/src/creatures/players/wheel/wheel_definitions.hpp b/src/creatures/players/wheel/wheel_definitions.hpp index 8432e9dca21..c23d2adf53f 100644 --- a/src/creatures/players/wheel/wheel_definitions.hpp +++ b/src/creatures/players/wheel/wheel_definitions.hpp @@ -105,9 +105,10 @@ enum class WheelOnThink_t : uint8_t { FOCUS_MASTERY = 4, GIFT_OF_LIFE = 5, DIVINE_EMPOWERMENT = 6, - AVATAR = 7, + AVATAR_SPELL = 7, + AVATAR_FORGE = 8, - TOTAL_COUNT = 8 + TOTAL_COUNT = 9 }; enum class WheelStat_t : uint8_t { diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 3ba2c43841d..621023e5c52 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -3997,9 +3997,9 @@ int PlayerFunctions::luaPlayerAvatarTimer(lua_State* L) { } if (lua_gettop(L) == 1) { - lua_pushnumber(L, (lua_Number)player->wheel()->getOnThinkTimer(WheelOnThink_t::AVATAR)); + lua_pushnumber(L, (lua_Number)player->wheel()->getOnThinkTimer(WheelOnThink_t::AVATAR_SPELL)); } else { - player->wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR, getNumber(L, 2)); + player->wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR_SPELL, getNumber(L, 2)); pushBoolean(L, true); } return 1; From e60612e9ad6da36c2de1824e597e5c3b6efd7615 Mon Sep 17 00:00:00 2001 From: Karin Date: Thu, 25 Apr 2024 10:12:36 -0300 Subject: [PATCH 12/19] fix: alana sio only to "aleta som" users (#2564) --- data/scripts/spells/house/kick.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/data/scripts/spells/house/kick.lua b/data/scripts/spells/house/kick.lua index b4b583c1007..265ac48f796 100644 --- a/data/scripts/spells/house/kick.lua +++ b/data/scripts/spells/house/kick.lua @@ -4,6 +4,7 @@ function spell.onCastSpell(player, variant) local targetPlayer = Player(variant:getString()) or player local guest = targetPlayer:getTile():getHouse() local owner = player:getTile():getHouse() + -- Owner kick yourself from house if targetPlayer == player then player:getPosition():sendMagicEffect(CONST_ME_POFF) @@ -11,6 +12,13 @@ function spell.onCastSpell(player, variant) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) return true end + + if not owner:canEditAccessList(GUEST_LIST, player) then + player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + player:getPosition():sendMagicEffect(CONST_ME_POFF) + return false + end + if not owner or not guest or not guest:kickPlayer(player, targetPlayer) then player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) player:getPosition():sendMagicEffect(CONST_ME_POFF) From 72794c40f1d13ffe64613cf67bae6c7bb1208281 Mon Sep 17 00:00:00 2001 From: Karin Date: Thu, 25 Apr 2024 10:12:52 -0300 Subject: [PATCH 13/19] fix: destroy field is working inside pz (#2558) --- data/scripts/runes/destroy_field_rune.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data/scripts/runes/destroy_field_rune.lua b/data/scripts/runes/destroy_field_rune.lua index 024dbc1bcd7..8752be7eb51 100644 --- a/data/scripts/runes/destroy_field_rune.lua +++ b/data/scripts/runes/destroy_field_rune.lua @@ -4,6 +4,13 @@ local fields = { 105, 2118, 2119, 2120, 2121, 2122, 2123, 2124, 2125, 2126, 2132 local rune = Spell("rune") function rune.onCastSpell(creature, variant, isHotkey) + local inPz = creature:getTile():hasFlag(TILESTATE_PROTECTIONZONE) + if inPz then + creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + creature:getPosition():sendMagicEffect(CONST_ME_POFF) + return false + end + local position = Variant.getPosition(variant) local tile = Tile(position) local field = tile and tile:getItemByType(ITEM_TYPE_MAGICFIELD) From 69b25aa431c869e8758a2624fd9dd18de8508b87 Mon Sep 17 00:00:00 2001 From: Karin Date: Thu, 25 Apr 2024 10:13:12 -0300 Subject: [PATCH 14/19] fix: stamina not recovering when dead (#2557) --- src/creatures/players/player.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index bcb31a4abfa..39a8841fdf2 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -2918,6 +2918,7 @@ bool Player::spawn() { getParent()->postAddNotification(static_self_cast(), nullptr, 0); g_game().addCreatureCheck(static_self_cast()); g_game().addPlayer(static_self_cast()); + static_self_cast()->onChangeZone(static_self_cast()->getZoneType()); return true; } @@ -4673,6 +4674,8 @@ void Player::onPlacedCreature() { removePlayer(true); } + this->onChangeZone(this->getZoneType()); + sendUnjustifiedPoints(); } From 38651ca103ee8674bdd6bcb7a994e2cea2220afd Mon Sep 17 00:00:00 2001 From: Aluisio Penna Date: Thu, 25 Apr 2024 10:15:52 -0300 Subject: [PATCH 15/19] fix: kill count of each type of minotaur in 'Turmoil of War' quest log (#2569) --- .../quests/killing_in_the_name_of/monster_kill.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua b/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua index b2ba681d251..f8f7ddc5c6b 100644 --- a/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua +++ b/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua @@ -57,7 +57,7 @@ function deathEvent.onDeath(creature, _corpse, _lastHitKiller, mostDamageKiller) end end -- Minotaurs - killCheck(player, targetName, Storage.KillingInTheNameOf.BudrikMinos, 0, tasks.Budrik[1].creatures, nil, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.MinotaurCount) + killCheck(player, targetName, Storage.KillingInTheNameOf.BudrikMinos, 0, tasks.Budrik[1].creatures, Storage.Quest.U8_5.KillingInTheNameOf.AltKillCount.MinotaurCount, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.MinotaurCount) -- Necromancers and Priestesses killCheck(player, targetName, Storage.KillingInTheNameOf.LugriNecromancers, 0, tasks.Lugri[1].creatures, Storage.Quest.U8_5.KillingInTheNameOf.AltKillCount.NecromancerCount, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.NecromancerCount) killCheck(player, targetName, Storage.KillingInTheNameOf.LugriNecromancers, 3, tasks.Lugri[1].creatures, Storage.Quest.U8_5.KillingInTheNameOf.AltKillCount.NecromancerCount, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.NecromancerCount) From dd8cdde213f4258f8f577462d12622bb88872e3d Mon Sep 17 00:00:00 2001 From: svetrey <51045033+svetrey@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:17:02 +0100 Subject: [PATCH 16/19] fix: diamond arrow static attack points (#2560) --- data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua b/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua index 61984209287..36be8dc38b4 100644 --- a/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua +++ b/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua @@ -16,7 +16,7 @@ combat:setParameter(COMBAT_PARAM_BLOCKARMOR, true) function onGetFormulaValues(player, skill, attack, factor) local distanceSkill = player:getEffectiveSkillLevel(SKILL_DISTANCE) local min = (player:getLevel() / 5) - local max = (0.09 * factor) * distanceSkill * 37 + (player:getLevel() / 5) + local max = (0.09 * factor) * distanceSkill * attack + (player:getLevel() / 5) return -min, -max end From 0f91db0e379714e20652fda85714a4259e7a2bba Mon Sep 17 00:00:00 2001 From: Jeswill David Bolivar Mendoza <76903590+jeswilldbm@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:17:53 +0200 Subject: [PATCH 17/19] fix: missing attribute in bone fiddle (#2531) --- data/items/items.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/items/items.xml b/data/items/items.xml index 0bc1c782452..421e981ad02 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -53105,6 +53105,9 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + From 13798e59702e8de713ae8f78fded17e2e5de35e2 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 25 Apr 2024 10:18:20 -0300 Subject: [PATCH 18/19] feat: include day count in getTimeInWords function (#2525) --- data/libs/functions/functions.lua | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/data/libs/functions/functions.lua b/data/libs/functions/functions.lua index 9eb1b0b57b5..c2349ce7fd6 100644 --- a/data/libs/functions/functions.lua +++ b/data/libs/functions/functions.lua @@ -67,17 +67,28 @@ end function getTimeInWords(secsParam) local secs = tonumber(secsParam) + local days = math.floor(secs / (24 * 3600)) + secs = secs - (days * 24 * 3600) local hours, minutes, seconds = getHours(secs), getMinutes(secs), getSeconds(secs) local timeStr = "" + if days > 0 then + timeStr = days .. (days > 1 and " days" or " day") + end + if hours > 0 then - timeStr = hours .. (hours > 1 and " hours" or " hour") + if timeStr ~= "" then + timeStr = timeStr .. ", " + end + + timeStr = timeStr .. hours .. (hours > 1 and " hours" or " hour") end if minutes > 0 then if timeStr ~= "" then timeStr = timeStr .. ", " end + timeStr = timeStr .. minutes .. (minutes > 1 and " minutes" or " minute") end @@ -85,9 +96,9 @@ function getTimeInWords(secsParam) if timeStr ~= "" then timeStr = timeStr .. " and " end + timeStr = timeStr .. seconds .. (seconds > 1 and " seconds" or " second") end - return timeStr end From 09984296e7172f550d3889670205bc7f0bda5bca Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Thu, 25 Apr 2024 10:18:39 -0300 Subject: [PATCH 19/19] feat: playerOnWalk event callback and afk talkaction (#2518) --- data/scripts/eventcallbacks/README.md | 1 + data/scripts/talkactions/gm/afk.lua | 94 +++++++++++++++++++++ src/creatures/players/player.cpp | 2 + src/lua/callbacks/callbacks_definitions.hpp | 1 + src/lua/callbacks/event_callback.cpp | 23 +++++ src/lua/callbacks/event_callback.hpp | 1 + 6 files changed, 122 insertions(+) create mode 100644 data/scripts/talkactions/gm/afk.lua diff --git a/data/scripts/eventcallbacks/README.md b/data/scripts/eventcallbacks/README.md index bdafcb41b33..601653574bd 100644 --- a/data/scripts/eventcallbacks/README.md +++ b/data/scripts/eventcallbacks/README.md @@ -47,6 +47,7 @@ Event callbacks are available for several categories of game entities, such as ` - `(void)` `playerOnCombat` - `(void)` `playerOnInventoryUpdate` - `(bool)` `playerOnRotateItem` +- `(void)` `playerOnWalk` - `(void)` `monsterOnDropLoot` - `(void)` `monsterPostDropLoot` - `(void)` `monsterOnSpawn` diff --git a/data/scripts/talkactions/gm/afk.lua b/data/scripts/talkactions/gm/afk.lua new file mode 100644 index 00000000000..6167a2b6068 --- /dev/null +++ b/data/scripts/talkactions/gm/afk.lua @@ -0,0 +1,94 @@ +local afk = TalkAction("/afk") + +playersAFKs = {} + +local function checkIsAFK(id) + for index, item in pairs(playersAFKs) do + if id == item.id then + return { afk = true, index = index } + end + end + return { afk = false } +end + +local function showAfkMessage(playerPosition) + local spectators = Game.getSpectators(playerPosition, false, true, 8, 8, 8, 8) + if #spectators > 0 then + for _, spectator in ipairs(spectators) do + spectator:say("AFK !", TALKTYPE_MONSTER_SAY, false, spectator, playerPosition) + end + end +end + +function afk.onSay(player, words, param) + if param == "" then + player:sendCancelMessage("You need to specify on/off param.") + return true + end + + local id, playerPosition = player:getId(), player:getPosition() + local isAfk = checkIsAFK(id) + if param == "on" then + if isAfk.afk then + player:sendCancelMessage("You are already AFK!") + return true + end + + table.insert(playersAFKs, { id = id, position = playerPosition }) + if player:isInGhostMode() then + player:setGhostMode(false) + end + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are now AFK!") + playerPosition:sendMagicEffect(CONST_ME_REDSMOKE) + showAfkMessage(playerPosition) + elseif param == "off" then + if isAfk.afk then + table.remove(playersAFKs, isAfk.index) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are no longer AFK!") + playerPosition:sendMagicEffect(CONST_ME_REDSMOKE) + end + end + + return true +end + +afk:separator(" ") +afk:groupType("gamemaster") +afk:register() + +------------------ AFK Effect Message ------------------ +local afkEffect = GlobalEvent("GodAfkEffect") +function afkEffect.onThink(interval) + for _, player in ipairs(playersAFKs) do + showAfkMessage(player.position) + end + return true +end + +afkEffect:interval(5000) +afkEffect:register() + +------------------ Stop AFK Message when moves ------------------ +local callback = EventCallback() +function callback.playerOnWalk(player, creature, creaturePos, toPos) + local isAfk = checkIsAFK(player:getId()) + if isAfk.afk then + table.remove(playersAFKs, isAfk.index) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are no longer AFK!") + end + return true +end + +callback:register() + +------------------ Player Logout ------------------ +local godAfkLogout = CreatureEvent("GodAfkLogout") +function godAfkLogout.onLogout(player) + local isAfk = checkIsAFK(player:getId()) + if isAfk.afk then + table.remove(playersAFKs, isAfk.index) + end + return true +end + +godAfkLogout:register() diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 39a8841fdf2..7e53f476683 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -1926,6 +1926,8 @@ void Player::onWalk(Direction &dir) { Creature::onWalk(dir); setNextActionTask(nullptr); setNextAction(OTSYS_TIME() + getStepDuration(dir)); + + g_callbacks().executeCallback(EventCallback_t::playerOnWalk, &EventCallback::playerOnWalk, getPlayer(), dir); } void Player::onCreatureMove(const std::shared_ptr &creature, const std::shared_ptr &newTile, const Position &newPos, const std::shared_ptr &oldTile, const Position &oldPos, bool teleport) { diff --git a/src/lua/callbacks/callbacks_definitions.hpp b/src/lua/callbacks/callbacks_definitions.hpp index 521a2c4cda7..3b8016f5f5b 100644 --- a/src/lua/callbacks/callbacks_definitions.hpp +++ b/src/lua/callbacks/callbacks_definitions.hpp @@ -56,6 +56,7 @@ enum class EventCallback_t : uint16_t { playerOnCombat, playerOnInventoryUpdate, playerOnRotateItem, + playerOnWalk, // Monster monsterOnDropLoot, monsterPostDropLoot, diff --git a/src/lua/callbacks/event_callback.cpp b/src/lua/callbacks/event_callback.cpp index 51c03131fa9..d2b88c30f3a 100644 --- a/src/lua/callbacks/event_callback.cpp +++ b/src/lua/callbacks/event_callback.cpp @@ -985,6 +985,29 @@ bool EventCallback::playerOnRotateItem(std::shared_ptr player, std::shar return getScriptInterface()->callFunction(3); } +void EventCallback::playerOnWalk(std::shared_ptr player, Direction &dir) const { + if (!getScriptInterface()->reserveScriptEnv()) { + g_logger().error("[EventCallback::eventOnWalk - " + "Player {}] " + "Call stack overflow. Too many lua script calls being nested.", + player->getName()); + return; + } + + ScriptEnvironment* scriptEnvironment = getScriptInterface()->getScriptEnv(); + scriptEnvironment->setScriptId(getScriptId(), getScriptInterface()); + + lua_State* L = getScriptInterface()->getLuaState(); + getScriptInterface()->pushFunction(getScriptId()); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + lua_pushnumber(L, dir); + + getScriptInterface()->callVoidFunction(2); +} + void EventCallback::playerOnStorageUpdate(std::shared_ptr player, const uint32_t key, const int32_t value, int32_t oldValue, uint64_t currentTime) const { if (!getScriptInterface()->reserveScriptEnv()) { g_logger().error("[EventCallback::eventOnStorageUpdate - " diff --git a/src/lua/callbacks/event_callback.hpp b/src/lua/callbacks/event_callback.hpp index d9dc4a9b110..9141235a028 100644 --- a/src/lua/callbacks/event_callback.hpp +++ b/src/lua/callbacks/event_callback.hpp @@ -116,6 +116,7 @@ class EventCallback : public Script { void playerOnCombat(std::shared_ptr player, std::shared_ptr target, std::shared_ptr item, CombatDamage &damage) const; void playerOnInventoryUpdate(std::shared_ptr player, std::shared_ptr item, Slots_t slot, bool equip) const; bool playerOnRotateItem(std::shared_ptr player, std::shared_ptr item, const Position &position) const; + void playerOnWalk(std::shared_ptr player, Direction &dir) const; // Monster void monsterOnDropLoot(std::shared_ptr monster, std::shared_ptr corpse) const;