diff --git a/data-canary/scripts/actions/objects/imbuement_shrine.lua b/data-canary/scripts/actions/objects/imbuement_shrine.lua index 71e3776f2f0..11d75150cf8 100644 --- a/data-canary/scripts/actions/objects/imbuement_shrine.lua +++ b/data-canary/scripts/actions/objects/imbuement_shrine.lua @@ -13,5 +13,7 @@ function imbuement.onUse(player, item, fromPosition, target, toPosition, isHotke return true end +imbuement:position({ x = 1943, y = 1340, z = 7 }, 25061) + imbuement:id(25060, 25061, 25103, 25104, 25202, 25174, 25175, 25182, 25183) imbuement:register() diff --git a/data-otservbr-global/npc/hireling.lua b/data-otservbr-global/npc/hireling.lua index ce5c6737e02..bfc9000ca0f 100644 --- a/data-otservbr-global/npc/hireling.lua +++ b/data-otservbr-global/npc/hireling.lua @@ -420,11 +420,13 @@ function createHirelingType(HirelingName) local TOPIC_FOOD = { SKILL_CHOOSE = 1301, + SKILL_SURPRISE = 1302, } local GREETINGS = { BANK = "Alright! What can I do for you and your bank business, |PLAYERNAME|?", - FOOD = "Hmm, yes! A variety of fine food awaits! However, a small expense of 15000 gold is expected to make these delicious masterpieces happen. Shall I?", + FOOD = [[Hmm, yes! A variety of fine food awaits! However, a small expense of 15000 gold is expected to make these delicious masterpieces happen. + For 90000 gold I will also serve you a specific dish. Just tell me what it shall be: a {specific} meal or a little {surprise}.]], STASH = "Of course, here is your stash! Well-maintained and neatly sorted for your convenience!", } @@ -513,7 +515,7 @@ function createHirelingType(HirelingName) return message end - local function deliverFood(npc, creature, food_id) + local function deliverFood(npc, creature, food_id, cost) local playerId = creature:getId() local player = Player(creature) local itType = ItemType(food_id) @@ -523,8 +525,8 @@ function createHirelingType(HirelingName) npcHandler:say("Sorry, but you don't have enough capacity.", npc, creature) elseif not inbox or #inboxItems >= inbox:getMaxCapacity() then player:getPosition():sendMagicEffect(CONST_ME_POFF) - npcHandler:say("Sorry, you don't have enough room on your inbox", npc, creature) - elseif not player:removeMoneyBank(15000) then + npcHandler:say("Sorry, you don't have enough room on your inbox.", npc, creature) + elseif not player:removeMoneyBank(cost) then npcHandler:say("Sorry, you don't have enough money.", npc, creature) else local message = getDeliveredMessageByFoodId(food_id) @@ -534,38 +536,66 @@ function createHirelingType(HirelingName) npcHandler:setTopic(playerId, TOPIC.SERVICES) end - local function cookFood(npc, creature) + local function cookFood(npc, creature, specificRequest) local playerId = creature:getId() - local random = math.random(6) - if random == 6 then - -- ask for preferred skill + if specificRequest then + npcHandler:say("Very well. You may choose one of the following: {chilli con carniphila}, {svargrond salmon filet}, {carrion casserole}, {consecrated beef}, {roasted wyvern wings}, {carrot pie}, {tropical marinated tiger}, or {delicatessen salad}.", npc, creature) npcHandler:setTopic(playerId, TOPIC_FOOD.SKILL_CHOOSE) - npcHandler:say("Yay! I have the ingredients to make a skill boost dish. Would you rather like to boost your {magic}, {melee}, {shielding} or {distance} skill?", npc, creature) - else -- deliver the random generated index - deliverFood(npc, creature, HIRELING_FOODS_IDS[random]) + else + npcHandler:say("Alright, let me astonish you. Shall I?", npc, creature) + deliverFood(npc, creature, HIRELING_FOODS_IDS[math.random(#HIRELING_FOODS_IDS)], 15000) end end local function handleFoodActions(npc, creature, message) local playerId = creature:getId() + if npcHandler:getTopic(playerId) == TOPIC.FOOD then - if MsgContains(message, "yes") then - cookFood(npc, creature) + if MsgContains(message, "specific") then + npcHandler:setTopic(playerId, TOPIC_FOOD.SPECIFIC) + npcHandler:say("Which specific meal would you like? Choices are: {chilli con carniphila}, {svargrond salmon filet}, {carrion casserole}, {consecrated beef}, {roasted wyvern wings}, {carrot pie}, {tropical marinated tiger}, or {delicatessen salad}.", npc, creature) + elseif MsgContains(message, "surprise") then + local random = math.random(6) + if random == 6 then + npcHandler:setTopic(playerId, TOPIC_FOOD.SKILL_CHOOSE) + npcHandler:say("Yay! I have the ingredients to make a skill boost dish. Would you rather like to boost your {magic}, {melee}, {shielding}, or {distance} skill?", npc, creature) + else + deliverFood(npc, creature, HIRELING_FOODS_IDS[random], 15000) + end + elseif MsgContains(message, "yes") then + deliverFood(npc, creature, HIRELING_FOODS_IDS[math.random(#HIRELING_FOODS_IDS)], 15000) elseif MsgContains(message, "no") then npcHandler:setTopic(playerId, TOPIC.SERVICES) npcHandler:say("Alright then, ask me for other {services}, if you want.", npc, creature) end elseif npcHandler:getTopic(playerId) == TOPIC_FOOD.SKILL_CHOOSE then if MsgContains(message, "magic") then - deliverFood(npc, creature, HIRELING_FOODS_BOOST.MAGIC) + deliverFood(npc, creature, HIRELING_FOODS_BOOST.MAGIC, 15000) elseif MsgContains(message, "melee") then - deliverFood(npc, creature, HIRELING_FOODS_BOOST.MELEE) + deliverFood(npc, creature, HIRELING_FOODS_BOOST.MELEE, 15000) elseif MsgContains(message, "shielding") then - deliverFood(npc, creature, HIRELING_FOODS_BOOST.SHIELDING) + deliverFood(npc, creature, HIRELING_FOODS_BOOST.SHIELDING, 15000) elseif MsgContains(message, "distance") then - deliverFood(npc, creature, HIRELING_FOODS_BOOST.DISTANCE) + deliverFood(npc, creature, HIRELING_FOODS_BOOST.DISTANCE, 15000) + else + npcHandler:say("Sorry, but you must choose a valid skill class. Would you like to boost your {magic}, {melee}, {shielding}, or {distance} skill?", npc, creature) + end + elseif npcHandler:getTopic(playerId) == TOPIC_FOOD.SPECIFIC then + local specificFoodOptions = { + ["chilli con carniphila"] = 29412, + ["svargrond salmon filet"] = 29413, + ["carrion casserole"] = 29414, + ["consecrated beef"] = 29415, + ["roasted wyvern wings"] = 29408, + ["carrot pie"] = 29409, + ["tropical marinated tiger"] = 29410, + ["delicatessen salad"] = 29411, + } + + if specificFoodOptions[message:lower()] then + deliverFood(npc, creature, specificFoodOptions[message:lower()], 90000) else - npcHandler:say("Sorry, but you must choose a valid skill class. Would you like to boost your {magic}, {melee}, {shielding} or {distance} skill?", npc, creature) + npcHandler:say("I'm sorry, but that's not a valid food option. Please choose from: {chilli con carniphila}, {svargrond salmon filet}, {carrion casserole}, {consecrated beef}, {roasted wyvern wings}, {carrot pie}, {tropical marinated tiger}, or {delicatessen salad}.", npc, creature) end end end @@ -656,7 +686,7 @@ function createHirelingType(HirelingName) end elseif npcHandler:getTopic(playerId) == TOPIC.BANK then enableBankSystem[playerId] = true - elseif npcHandler:getTopic(playerId) == TOPIC.FOOD or npcHandler:getTopic(playerId) == TOPIC_FOOD.SKILL_CHOOSE then + elseif npcHandler:getTopic(playerId) == TOPIC.FOOD or npcHandler:getTopic(playerId) == TOPIC_FOOD.SKILL_CHOOSE or npcHandler:getTopic(playerId) == TOPIC_FOOD.SPECIFIC then handleFoodActions(npc, creature, message) elseif npcHandler:getTopic(playerId) == TOPIC.GOODS then -- Ensures players cannot access other shop categories diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua index 78653733b58..c526553132d 100644 --- a/data/events/scripts/player.lua +++ b/data/events/scripts/player.lua @@ -315,7 +315,8 @@ function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, end -- Reward System - if toPosition.x == CONTAINER_POSITION then + local containerThing = tile and tile:getItemByType(ITEM_TYPE_CONTAINER) + if containerThing and toPosition.x == CONTAINER_POSITION then local containerId = toPosition.y - 64 local container = self:getContainerById(containerId) if not container then diff --git a/data/libs/systems/blessing.lua b/data/libs/systems/blessing.lua index fee0e6fe8e8..f5403fbb07b 100644 --- a/data/libs/systems/blessing.lua +++ b/data/libs/systems/blessing.lua @@ -258,8 +258,9 @@ Blessings.BuyAllBlesses = function(player) local hasToF = Blessings.Config.HasToF and player:hasBlessing(1) or true local missingBless = player:getBlessings(nil, donthavefilter) local missingBlessAmt = #missingBless + (hasToF and 0 or 1) - local totalCost - for i, bless in ipairs(missingBless) do + local totalCost = 0 + + for _, bless in ipairs(missingBless) do totalCost = totalCost + Blessings.getBlessingCost(player:getLevel(), true, bless.id >= 7) end @@ -274,19 +275,19 @@ Blessings.BuyAllBlesses = function(player) end if player:removeMoneyBank(totalCost) then - metrics.addCounter("balance_decrease", remainsPrice, { + metrics.addCounter("balance_decrease", totalCost, { player = player:getName(), context = "blessings", }) - for i, v in ipairs(missingBless) do - player:addBlessing(v.id, 1) + for _, bless in ipairs(missingBless) do + player:addBlessing(bless.id, 1) end - player:sendCancelMessage("You received the remaining " .. missingBlessAmt .. " blesses for a total of " .. totalCost .. " gold.") + player:sendCancelMessage(string.format("You received the remaining %d blesses for a total of %d gold.", missingBlessAmt, totalCost)) player:getPosition():sendMagicEffect(CONST_ME_HOLYAREA) else - player:sendCancelMessage("You don't have enough money. You need " .. totalCost .. " to buy all blesses.", cid) + player:sendCancelMessage(string.format("You don't have enough money. You need %d to buy all blesses.", totalCost)) player:getPosition():sendMagicEffect(CONST_ME_POFF) end diff --git a/src/game/game.cpp b/src/game/game.cpp index cde008d4f65..068ecad4a50 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -3409,18 +3409,22 @@ void Game::playerEquipItem(uint32_t playerId, uint16_t itemId, bool hasTier /* = const auto &slotItem = player->getInventoryItem(slot); const auto &equipItem = searchForItem(backpack, it.id, hasTier, tier); ReturnValue ret = RETURNVALUE_NOERROR; + if (slotItem && slotItem->getID() == it.id && (!it.stackable || slotItem->getItemCount() == slotItem->getStackSize() || !equipItem)) { ret = internalMoveItem(slotItem->getParent(), player, CONST_SLOT_WHEREEVER, slotItem, slotItem->getItemCount(), nullptr); g_logger().debug("Item {} was unequipped", slotItem->getName()); } else if (equipItem) { // Shield slot item const auto &rightItem = player->getInventoryItem(CONST_SLOT_RIGHT); + // Check Ammo item if (it.weaponType == WEAPON_AMMO) { if (rightItem && rightItem->isQuiver()) { ret = internalMoveItem(equipItem->getParent(), rightItem->getContainer(), 0, equipItem, equipItem->getItemCount(), nullptr); } } else { + const auto &leftItem = player->getInventoryItem(CONST_SLOT_LEFT); + const int32_t &slotPosition = equipItem->getSlotPosition(); // Checks if a two-handed item is being equipped in the left slot when the right slot is already occupied and move to backpack if ( @@ -3429,10 +3433,35 @@ void Game::playerEquipItem(uint32_t playerId, uint16_t itemId, bool hasTier /* = && rightItem && !(it.weaponType == WEAPON_DISTANCE) && !rightItem->isQuiver() + && (!leftItem || leftItem->getWeaponType() != WEAPON_DISTANCE) ) { ret = internalCollectManagedItems(player, rightItem, getObjectCategory(rightItem), false); } + // Check if trying to equip a quiver while another quiver is already equipped in the right slot + if (slot == CONST_SLOT_RIGHT && rightItem && rightItem->isQuiver() && it.isQuiver()) { + // Replace the existing quiver with the new one + ret = internalMoveItem(rightItem->getParent(), player, INDEX_WHEREEVER, rightItem, rightItem->getItemCount(), nullptr); + if (ret == RETURNVALUE_NOERROR) { + g_logger().debug("Quiver {} was unequipped to equip new quiver", rightItem->getName()); + } else { + player->sendCancelMessage(ret); + return; + } + } else { + // Check if trying to equip a shield while a two-handed weapon is equipped in the left slot + if (slot == CONST_SLOT_RIGHT && leftItem && leftItem->getSlotPosition() & SLOTP_TWO_HAND) { + // Unequip the two-handed weapon from the left slot + ret = internalMoveItem(leftItem->getParent(), player, INDEX_WHEREEVER, leftItem, leftItem->getItemCount(), nullptr); + if (ret == RETURNVALUE_NOERROR) { + g_logger().debug("Two-handed weapon {} was unequipped to equip shield", leftItem->getName()); + } else { + player->sendCancelMessage(ret); + return; + } + } + } + if (slotItem) { ret = internalMoveItem(slotItem->getParent(), player, INDEX_WHEREEVER, slotItem, slotItem->getItemCount(), nullptr); g_logger().debug("Item {} was moved back to player", slotItem->getName()); @@ -7428,10 +7457,6 @@ bool Game::combatChangeHealth(const std::shared_ptr &attacker, const s target->drainHealth(attacker, realDamage); if (realDamage > 0 && targetMonster) { - if (attackerPlayer && attackerPlayer->getPlayer()) { - attackerPlayer->updateImpactTracker(damage.secondary.type, damage.secondary.value); - } - if (targetMonster->israndomStepping()) { targetMonster->setIgnoreFieldDamage(true); } diff --git a/src/lua/functions/lua_functions_loader.hpp b/src/lua/functions/lua_functions_loader.hpp index fffc7e5b355..68cfeb45c51 100644 --- a/src/lua/functions/lua_functions_loader.hpp +++ b/src/lua/functions/lua_functions_loader.hpp @@ -63,7 +63,7 @@ class Lua { static void setCreatureMetatable(lua_State* L, int32_t index, const std::shared_ptr &creature); template - static T getNumber(lua_State* L, int32_t arg) { + static T getNumber(lua_State* L, int32_t arg, std::source_location location = std::source_location::current()) { auto number = lua_tonumber(L, arg); if constexpr (std::is_enum_v) { @@ -73,7 +73,7 @@ class Lua { if constexpr (std::is_integral_v) { if constexpr (std::is_unsigned_v) { if (number < 0) { - g_logger().debug("[{}] overflow, setting to default unsigned value (0)", __FUNCTION__); + g_logger().debug("[{}] overflow, setting to default unsigned value (0), called line: {}:{}, in {}", __FUNCTION__, location.line(), location.column(), location.function_name()); return T(0); } }