From 282e73e886d64e07787ce36619803d40040d7916 Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Sun, 17 Dec 2023 14:15:14 -0300 Subject: [PATCH 01/28] fix: players healing when attacked (#2026) Removed event 'LeidenHeal' from the player login. --- .../scripts/creaturescripts/others/login_events.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/data-otservbr-global/scripts/creaturescripts/others/login_events.lua b/data-otservbr-global/scripts/creaturescripts/others/login_events.lua index 2f570a72e3e..de3b9cc4f0a 100644 --- a/data-otservbr-global/scripts/creaturescripts/others/login_events.lua +++ b/data-otservbr-global/scripts/creaturescripts/others/login_events.lua @@ -10,7 +10,6 @@ function loginEvents.onLogin(player) "FamiliarAdvance", --Quests --Cults Of Tibia Quest - "LeidenHeal", "HealthPillar", "YalahariHealth", } From 7647e1fd9b5539acc7c83f55e7995dd3a9168e69 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Sun, 17 Dec 2023 10:24:01 -0800 Subject: [PATCH 02/28] fix: add missing 'alchemist container' monster (#2036) --- .../monster/bosses/alchemist_container.lua | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 data-otservbr-global/monster/bosses/alchemist_container.lua diff --git a/data-otservbr-global/monster/bosses/alchemist_container.lua b/data-otservbr-global/monster/bosses/alchemist_container.lua new file mode 100644 index 00000000000..62905d5975e --- /dev/null +++ b/data-otservbr-global/monster/bosses/alchemist_container.lua @@ -0,0 +1,96 @@ +local mType = Game.createMonsterType("Alchemist Container") +local monster = {} + +monster.description = "Alchemist Container" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 39952, +} + +monster.health = 1200 +monster.maxHealth = 1200 +monster.race = "undead" +monster.corpse = 39949 +monster.speed = 0 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 15, +} + +monster.strategiesTarget = { + nearest = 60, + health = 30, + damage = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + critChance = 10, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = {} + +monster.defenses = { + defense = 54, + armor = 59, + mitigation = 3.7, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onThink = function(monster, interval) end + +mType.onAppear = function(monster, creature) + if monster:getType():isRewardBoss() then + monster:setReward(true) + end +end + +mType.onDisappear = function(monster, creature) end + +mType.onMove = function(monster, creature, fromPosition, toPosition) end + +mType.onSay = function(monster, creature, type, message) end + +mType:register(monster) From 2f54ccf207d0a7d14490bb49ccaafb12228d907c Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Mon, 18 Dec 2023 09:07:42 -0300 Subject: [PATCH 03/28] fix: max limit of character name (#2035) It was increased to the max limit of character name. (can use exiva and other spells and remover to update in config.php of AAC). --- data/modules/scripts/gamestore/init.lua | 8 ++++---- src/utils/tools.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/modules/scripts/gamestore/init.lua b/data/modules/scripts/gamestore/init.lua index 981db5cec6b..c1da5dee30b 100644 --- a/data/modules/scripts/gamestore/init.lua +++ b/data/modules/scripts/gamestore/init.lua @@ -1366,8 +1366,8 @@ GameStore.canUseHirelingName = function(name) local result = { ability = false, } - if name:len() < 3 or name:len() > 14 then - result.reason = "The length of the hireling name must be between 3 and 14 characters." + if name:len() < 3 or name:len() > 18 then + result.reason = "The length of the hireling name must be between 3 and 18 characters." return result end @@ -1422,8 +1422,8 @@ GameStore.canChangeToName = function(name) local result = { ability = false, } - if name:len() < 3 or name:len() > 14 then - result.reason = "The length of your new name must be between 3 and 14 characters." + if name:len() < 3 or name:len() > 18 then + result.reason = "The length of your new name must be between 3 and 18 characters." return result end diff --git a/src/utils/tools.cpp b/src/utils/tools.cpp index ce42c04ad50..39ca687f06a 100644 --- a/src/utils/tools.cpp +++ b/src/utils/tools.cpp @@ -1525,7 +1525,7 @@ NameEval_t validateName(const std::string &name) { std::istream_iterator end; std::copy(begin, end, std::back_inserter(toks)); - if (name.length() < 3 || name.length() > 14) { + if (name.length() < 3 || name.length() > 18) { return INVALID_LENGTH; } From d31ee3097798b748cf8a4519df187a8de2cbba94 Mon Sep 17 00:00:00 2001 From: Luan Colombo <94877887+luancolombo@users.noreply.github.com> Date: Tue, 19 Dec 2023 19:16:16 +0000 Subject: [PATCH 04/28] fix: adventurer stone (#2048) --- .../scripts/actions/adventurers_guild/adventurers_stone.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/scripts/actions/adventurers_guild/adventurers_stone.lua b/data-otservbr-global/scripts/actions/adventurers_guild/adventurers_stone.lua index a73caac912e..ad737b276d3 100644 --- a/data-otservbr-global/scripts/actions/adventurers_guild/adventurers_stone.lua +++ b/data-otservbr-global/scripts/actions/adventurers_guild/adventurers_stone.lua @@ -60,7 +60,7 @@ end function adventurersStone.onUse(player, item, fromPosition, target, toPosition, isHotkey) local tile = Tile(player:getPosition()) - if not tile:hasFlag(TILESTATE_PROTECTIONZONE) or tile:hasFlag(TILESTATE_HOUSE) or player:isPzLocked() or player:getCondition(CONDITION_INFIGHT, CONDITIONID_DEFAULT) then + if not tile:hasFlag(TILESTATE_PROTECTIONZONE) or tile:hasFlag(TILESTATE_HOUSE) or player:isPzLocked() then doNotTeleport(player) return false end From f68972daefecc77d409b8447069293a3dd773b68 Mon Sep 17 00:00:00 2001 From: Beats Date: Thu, 21 Dec 2023 17:25:59 -0300 Subject: [PATCH 05/28] improve: connection management (#2040) --- src/server/network/connection/connection.cpp | 102 ++++++++++++------- src/server/network/connection/connection.hpp | 2 +- vcproj/canary.vcxproj | 3 +- 3 files changed, 67 insertions(+), 40 deletions(-) diff --git a/src/server/network/connection/connection.cpp b/src/server/network/connection/connection.cpp index f0046f0aa3a..a6678565fba 100644 --- a/src/server/network/connection/connection.cpp +++ b/src/server/network/connection/connection.cpp @@ -26,15 +26,17 @@ void ConnectionManager::releaseConnection(const Connection_ptr &connection) { } void ConnectionManager::closeAll() { - connections.for_each([](const Connection_ptr &connection) { - try { - std::error_code error; - connection->socket.shutdown(asio::ip::tcp::socket::shutdown_both, error); - if (error) { - g_logger().error("[ConnectionManager::closeAll] - Failed to close connection, system error code {}", error.message()); + connections.for_each([&](const Connection_ptr &connection) { + if (connection->socket.is_open()) { + try { + std::error_code error; + connection->socket.shutdown(asio::ip::tcp::socket::shutdown_both, error); + if (error) { + g_logger().error("[ConnectionManager::closeAll] - Failed to close connection, system error code {}", error.message()); + } + } catch (const std::system_error &systemError) { + g_logger().error("[ConnectionManager::closeAll] - Exception caught: {}", systemError.what()); } - } catch (const std::system_error &systemError) { - g_logger().error("[ConnectionManager::closeAll] - Exception caught: {}", systemError.what()); } }); @@ -45,8 +47,7 @@ Connection::Connection(asio::io_service &initIoService, ConstServicePort_ptr ini readTimer(initIoService), writeTimer(initIoService), service_port(std::move(initservicePort)), - socket(initIoService), - timeConnected(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())) { + socket(initIoService) { } void Connection::close(bool force) { @@ -74,19 +75,23 @@ void Connection::closeSocket() { return; } - readTimer.cancel(); - writeTimer.cancel(); - socket.cancel(); + try { + readTimer.cancel(); + writeTimer.cancel(); + socket.cancel(); - std::error_code error; - socket.shutdown(asio::ip::tcp::socket::shutdown_both, error); - if (error) { - g_logger().error("[Connection::closeSocket] - Failed to shutdown socket: {}", error.message()); - } + std::error_code error; + socket.shutdown(asio::ip::tcp::socket::shutdown_both, error); + if (error && error != asio::error::not_connected) { + g_logger().error("[Connection::closeSocket] - Failed to shutdown socket: {}", error.message()); + } - socket.close(error); - if (error) { - g_logger().error("[Connection::closeSocket] - Failed to close socket: {}", error.message()); + socket.close(error); + if (error && error != asio::error::not_connected) { + g_logger().error("[Connection::closeSocket] - Failed to close socket: {}", error.message()); + } + } catch (const std::system_error &e) { + g_logger().error("[Connection::closeSocket] - error closeSocket: {}", e.what()); } } @@ -102,17 +107,21 @@ void Connection::acceptInternal(bool toggleParseHeader) { readTimer.expires_from_now(std::chrono::seconds(CONNECTION_READ_TIMEOUT)); readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); - auto readCallback = toggleParseHeader ? std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1) - : std::bind(&Connection::parseProxyIdentification, shared_from_this(), std::placeholders::_1); - asio::async_read(socket, asio::buffer(msg.getBuffer(), HEADER_LENGTH), readCallback); + try { + auto readCallback = toggleParseHeader ? std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1) + : std::bind(&Connection::parseProxyIdentification, shared_from_this(), std::placeholders::_1); + asio::async_read(socket, asio::buffer(msg.getBuffer(), HEADER_LENGTH), readCallback); + } catch (const std::system_error &e) { + g_logger().error("[Connection::acceptInternal] - Exception in async_read: {}", e.what()); + close(FORCE_CLOSE); + } } - void Connection::parseProxyIdentification(const std::error_code &error) { std::scoped_lock lock(connectionLock); readTimer.cancel(); if (error || connectionState == CONNECTION_STATE_CLOSED) { - if (error) { + if (error != asio::error::operation_aborted && error != asio::error::eof && error != asio::error::connection_reset) { g_logger().error("[Connection::parseProxyIdentification] - Read error: {}", error.message()); } close(FORCE_CLOSE); @@ -166,7 +175,7 @@ void Connection::parseHeader(const std::error_code &error) { readTimer.cancel(); if (error || connectionState == CONNECTION_STATE_CLOSED) { - if (error != asio::error::operation_aborted) { + if (error != asio::error::operation_aborted && error != asio::error::eof && error != asio::error::connection_reset) { g_logger().error("[Connection::parseHeader] - Read error: {}", error.message()); } close(FORCE_CLOSE); @@ -275,7 +284,12 @@ void Connection::resumeWork() { readTimer.expires_from_now(std::chrono::seconds(CONNECTION_READ_TIMEOUT)); readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); - asio::async_read(socket, asio::buffer(msg.getBuffer(), HEADER_LENGTH), std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + try { + asio::async_read(socket, asio::buffer(msg.getBuffer(), HEADER_LENGTH), std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + } catch (const std::system_error &e) { + g_logger().error("[Connection::resumeWork] - Exception in async_read: {}", e.what()); + close(FORCE_CLOSE); + } } void Connection::send(const OutputMessage_ptr &outputMessage) { @@ -286,9 +300,15 @@ void Connection::send(const OutputMessage_ptr &outputMessage) { bool noPendingWrite = messageQueue.empty(); messageQueue.emplace_back(outputMessage); + if (noPendingWrite) { if (socket.is_open()) { - asio::post(socket.get_executor(), std::bind(&Connection::internalWorker, shared_from_this())); + try { + asio::post(socket.get_executor(), std::bind(&Connection::internalWorker, shared_from_this())); + } catch (const std::system_error &e) { + g_logger().error("[Connection::send] - Exception in posting write operation: {}", e.what()); + close(FORCE_CLOSE); + } } else { g_logger().error("[Connection::send] - Socket is not open for writing."); close(FORCE_CLOSE); @@ -326,7 +346,6 @@ uint32_t Connection::getIP() { ip = htonl(endpoint.address().to_v4().to_uint()); } } - return ip; } @@ -334,7 +353,12 @@ void Connection::internalSend(const OutputMessage_ptr &outputMessage) { writeTimer.expires_from_now(std::chrono::seconds(CONNECTION_WRITE_TIMEOUT)); writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); - asio::async_write(socket, asio::buffer(outputMessage->getOutputBuffer(), outputMessage->getLength()), std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1)); + try { + asio::async_write(socket, asio::buffer(outputMessage->getOutputBuffer(), outputMessage->getLength()), std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1)); + } catch (const std::system_error &e) { + g_logger().error("[Connection::internalSend] - Exception in async_write: {}", e.what()); + close(FORCE_CLOSE); + } } void Connection::onWriteOperation(const std::error_code &error) { @@ -362,12 +386,16 @@ void Connection::onWriteOperation(const std::error_code &error) { } void Connection::handleTimeout(ConnectionWeak_ptr connectionWeak, const std::error_code &error) { - if (error) { - if (error != asio::error::operation_aborted) { - g_logger().warn("[Connection::handleTimeout] - Timeout or error: {}", error.message()); - if (auto connection = connectionWeak.lock()) { - connection->close(FORCE_CLOSE); - } + if (error == asio::error::operation_aborted) { + return; + } + + if (auto connection = connectionWeak.lock()) { + if (!error) { + g_logger().warn("Connection Timeout, IP: {}", convertIPToString(connection->getIP())); + } else { + g_logger().warn("Connection Timeout or error: {}, IP: {}", error.message(), convertIPToString(connection->getIP())); } + connection->close(FORCE_CLOSE); } } diff --git a/src/server/network/connection/connection.hpp b/src/server/network/connection/connection.hpp index d7d0f4b005f..2054cf69f9e 100644 --- a/src/server/network/connection/connection.hpp +++ b/src/server/network/connection/connection.hpp @@ -100,7 +100,7 @@ class Connection : public std::enable_shared_from_this { asio::ip::tcp::socket socket; - time_t timeConnected; + std::time_t timeConnected = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); uint32_t packetsSent = 0; uint32_t ip = 1; diff --git a/vcproj/canary.vcxproj b/vcproj/canary.vcxproj index e387dbddbea..8e779cf57fd 100644 --- a/vcproj/canary.vcxproj +++ b/vcproj/canary.vcxproj @@ -129,7 +129,6 @@ - @@ -559,4 +558,4 @@ - \ No newline at end of file + From cb60cda5fad225153f9aef31fc0330c288eae8af Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 28 Dec 2023 10:11:05 -0800 Subject: [PATCH 06/28] feat: namelocks (#2005) Adds a `/namelock` command to help you ax those offensive names from your server. It works by providing the player with a "free" mandatory name change upon login. --------- Co-authored-by: Elson Costa --- .../creaturescripts/players/namelock.lua | 21 ++++++++ data/modules/scripts/gamestore/gamestore.lua | 1 + data/modules/scripts/gamestore/init.lua | 50 ++++++++++--------- data/scripts/talkactions/gm/namelock.lua | 48 ++++++++++++++++++ src/game/game.cpp | 8 ++- src/game/game.hpp | 2 +- .../functions/core/game/game_functions.cpp | 5 +- .../creatures/player/player_functions.cpp | 18 +++++++ .../creatures/player/player_functions.hpp | 2 + 9 files changed, 127 insertions(+), 28 deletions(-) create mode 100644 data-otservbr-global/scripts/creaturescripts/players/namelock.lua create mode 100644 data/scripts/talkactions/gm/namelock.lua diff --git a/data-otservbr-global/scripts/creaturescripts/players/namelock.lua b/data-otservbr-global/scripts/creaturescripts/players/namelock.lua new file mode 100644 index 00000000000..c34eab6f181 --- /dev/null +++ b/data-otservbr-global/scripts/creaturescripts/players/namelock.lua @@ -0,0 +1,21 @@ +function CheckNamelock(player) + local namelockReason = player:kv():get("namelock") + if not namelockReason then + return true + end + player:setMoveLocked(true) + player:teleportTo(player:getTown():getTemplePosition()) + player:sendTextMessage(MESSAGE_ADMINISTRADOR, "Your name has been locked for the following reason: " .. namelockReason .. ".") + player:openStore("extras") + addPlayerEvent(sendRequestPurchaseData, 50, player, 65002, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE) + addPlayerEvent(CheckNamelock, 30000, player) +end + +local playerLogin = CreatureEvent("NamelockLogin") + +function playerLogin.onLogin(player) + addPlayerEvent(CheckNamelock, 1000, player) + return true +end + +playerLogin:register() diff --git a/data/modules/scripts/gamestore/gamestore.lua b/data/modules/scripts/gamestore/gamestore.lua index afaffcb0b6a..f524932a4bd 100644 --- a/data/modules/scripts/gamestore/gamestore.lua +++ b/data/modules/scripts/gamestore/gamestore.lua @@ -6342,6 +6342,7 @@ GameStore.Categories = { { icons = { "Name_Change.png" }, name = "Character Name Change", + home = true, price = 250, id = 65002, description = "Tired of your current character name? Purchase a new one!\n\n{character}\n{info} relog required after purchase to finalise the name change", diff --git a/data/modules/scripts/gamestore/init.lua b/data/modules/scripts/gamestore/init.lua index c1da5dee30b..64151e2ff20 100644 --- a/data/modules/scripts/gamestore/init.lua +++ b/data/modules/scripts/gamestore/init.lua @@ -428,8 +428,11 @@ function parseBuyStoreOffer(playerId, msg) -- At this point the purchase is assumed to be formatted correctly local offerPrice = offer.type == GameStore.OfferTypes.OFFER_TYPE_EXPBOOST and GameStore.ExpBoostValues[player:getStorageValue(GameStore.Storages.expBoostCount)] or offer.price local offerCoinType = offer.coinType + if offer.type == GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and player:kv():get("namelock") then + offerPrice = 0 + end -- Check if offer can be honored - if not player:canPayForOffer(offerPrice, offerCoinType) then + if offerPrice > 0 and not player:canPayForOffer(offerPrice, offerCoinType) then return queueSendStoreAlertToUser("You don't have enough coins. Your purchase has been cancelled.", 250, playerId) end @@ -864,9 +867,14 @@ function sendShowStoreOffers(playerId, category, redirectId) xpBoostPrice = GameStore.ExpBoostValues[player:getStorageValue(GameStore.Storages.expBoostCount)] end + nameLockPrice = nil + if offer.type == GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and player:kv():get("namelock") then + nameLockPrice = 0 + end + msg:addU32(off.id) msg:addU16(off.count) - msg:addU32(xpBoostPrice or off.price) + msg:addU32(xpBoostPrice or nameLockPrice or off.price) msg:addByte(off.coinType or 0x00) msg:addByte((off.disabledReadonIndex ~= nil) and 1 or 0) @@ -1703,8 +1711,12 @@ function GameStore.processNameChangePurchase(player, offer, productType, newName end end - local resultId = db.storeQuery("SELECT * FROM `players` WHERE `name` = " .. db.escapeString(newName) .. "") - if resultId ~= false then + newName = newName:lower():trim():gsub("(%l)(%w*)", function(a, b) + return string.upper(a) .. b + end) + + local normalizedName = Game.getNormalizedPlayerName(newName, true) + if normalizedName then return error({ code = 1, message = "This name is already used, please try again!" }) end @@ -1713,25 +1725,16 @@ function GameStore.processNameChangePurchase(player, offer, productType, newName return error({ code = 1, message = result.reason }) end - player:makeCoinTransaction(offer) - - local message = string.format("You have purchased %s for %d coins.", offer.name, offer.price) + local message, namelockReason = "", player:kv():get("namelock") + if not namelockReason then + player:makeCoinTransaction(offer) + message = string.format("You have purchased %s for %d coins.", offer.name, offer.price) + else + message = "Your character has been renamed successfully." + end addPlayerEvent(sendStorePurchaseSuccessful, 500, playerId, message) - newName = newName:lower():gsub("(%l)(%w*)", function(a, b) - return string.upper(a) .. b - end) - db.query("UPDATE `players` SET `name` = " .. db.escapeString(newName) .. " WHERE `id` = " .. player:getGuid()) - message = "You have successfully changed you name, relogin!" - addEvent(function() - local player = Player(playerId) - if not player then - return false - end - - player:remove() - end, 1000) - -- If not, we ask him to do! + player:changeName(newName) else return addPlayerEvent(sendRequestPurchaseData, 250, playerId, offer.id, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE) end @@ -2176,13 +2179,14 @@ function sendHomePage(playerId) end function Player:openStore(serviceName) --exporting the method so other scripts can use to open store - openStore(self:getId()) + local playerId = self:getId() + openStore(playerId) --local serviceType = msg:getByte() local category = GameStore.Categories and GameStore.Categories[1] or nil if serviceName and serviceName:lower() == "home" then - return sendHomePage(self:getId()) + return sendHomePage(playerId) end if serviceName and GameStore.getCategoryByName(serviceName) then diff --git a/data/scripts/talkactions/gm/namelock.lua b/data/scripts/talkactions/gm/namelock.lua new file mode 100644 index 00000000000..1b204759e02 --- /dev/null +++ b/data/scripts/talkactions/gm/namelock.lua @@ -0,0 +1,48 @@ +local namelock = TalkAction("/namelock") + +function namelock.onSay(player, words, param) + -- create log + logCommand(player, words, param) + + if param == "" then + player:sendCancelMessage("Command param required.") + return true + end + + local name = param + local reason = "" + + local separatorPos = param:find(",") + if separatorPos then + name = param:sub(0, separatorPos - 1) + reason = string.trim(param:sub(separatorPos + 1)) + end + + if reason == "" then + player:sendCancelMessage("You must specify a reason.") + return true + end + + local target = Player(name) + local online = true + if not target then + target = Game.getOfflinePlayer(name) + online = false + end + if target and target:isPlayer() then + target:kv():set("namelock", reason) + local text = target:getName() .. " has been namelocked" + logger.info(text .. ", reason: " .. reason) + player:sendTextMessage(MESSAGE_ADMINISTRADOR, text) + Webhook.sendMessage("Player Namelocked", text .. " reason: " .. reason .. ".", WEBHOOK_COLOR_YELLOW, announcementChannels["serverAnnouncements"]) + if online then + CheckNamelock(target) + end + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, name .. " was not found.") + end +end + +namelock:separator(" ") +namelock:groupType("gamemaster") +namelock:register() diff --git a/src/game/game.cpp b/src/game/game.cpp index f70af5cf864..5da0ca3f6ce 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -748,7 +748,7 @@ std::shared_ptr Game::getNpcByName(const std::string &s) { return nullptr; } -std::shared_ptr Game::getPlayerByName(const std::string &s, bool allowOffline /* = false */) { +std::shared_ptr Game::getPlayerByName(const std::string &s, bool allowOffline /* = false */, bool isNewName /* = false */) { if (s.empty()) { return nullptr; } @@ -760,7 +760,11 @@ std::shared_ptr Game::getPlayerByName(const std::string &s, bool allowOf } std::shared_ptr tmpPlayer = std::make_shared(nullptr); if (!IOLoginData::loadPlayerByName(tmpPlayer, s)) { - g_logger().error("Failed to load player {} from database", s); + if (!isNewName) { + g_logger().error("Failed to load player {} from database", s); + } else { + g_logger().info("New name {} is available", s); + } return nullptr; } tmpPlayer->setOnline(false); diff --git a/src/game/game.hpp b/src/game/game.hpp index 263119d3518..9fb56f8b88c 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -153,7 +153,7 @@ class Game { std::shared_ptr getPlayerByID(uint32_t id, bool allowOffline = false); - std::shared_ptr getPlayerByName(const std::string &s, bool allowOffline = false); + std::shared_ptr getPlayerByName(const std::string &s, bool allowOffline = false, bool isNewName = false); std::shared_ptr getPlayerByGUID(const uint32_t &guid, bool allowOffline = false); diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index 633f39489de..5221d2c9e4f 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -635,9 +635,10 @@ int GameFunctions::luaGameGetOfflinePlayer(lua_State* L) { } int GameFunctions::luaGameGetNormalizedPlayerName(lua_State* L) { - // Game.getNormalizedPlayerName(name) + // Game.getNormalizedPlayerName(name[, isNewName = false]) auto name = getString(L, 1); - std::shared_ptr player = g_game().getPlayerByName(name, true); + auto isNewName = getBoolean(L, 2, false); + std::shared_ptr player = g_game().getPlayerByName(name, true, isNewName); if (player) { pushString(L, player->getName()); } else { diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index df80a42b13c..b50d62c7d2a 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -3700,6 +3700,24 @@ int PlayerFunctions::luaPlayerGetName(lua_State* L) { return 1; } +int PlayerFunctions::luaPlayerChangeName(lua_State* L) { + // player:changeName(newName) + const auto player = getUserdataShared(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 0; + } + if (player->isOnline()) { + player->removePlayer(true, true); + } + player->kv()->remove("namelock"); + auto newName = getString(L, 2); + player->setName(newName); + g_saveManager().savePlayer(player); + return 1; +} + int PlayerFunctions::luaPlayerHasGroupFlag(lua_State* L) { // player:hasGroupFlag(flag) std::shared_ptr player = getUserdataShared(L, 1); diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index 89bf59b2740..60458816487 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -330,6 +330,7 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "sendDoubleSoundEffect", PlayerFunctions::luaPlayerSendDoubleSoundEffect); registerMethod(L, "Player", "getName", PlayerFunctions::luaPlayerGetName); + registerMethod(L, "Player", "changeName", PlayerFunctions::luaPlayerChangeName); registerMethod(L, "Player", "hasGroupFlag", PlayerFunctions::luaPlayerHasGroupFlag); registerMethod(L, "Player", "setGroupFlag", PlayerFunctions::luaPlayerSetGroupFlag); @@ -674,6 +675,7 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerSendDoubleSoundEffect(lua_State* L); static int luaPlayerGetName(lua_State* L); + static int luaPlayerChangeName(lua_State* L); static int luaPlayerHasGroupFlag(lua_State* L); static int luaPlayerSetGroupFlag(lua_State* L); From 14e360626eb4538c50635bbe86c241f82ec96919 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 28 Dec 2023 15:12:00 -0300 Subject: [PATCH 07/28] fix: check player nullptr and connection timeout (#2060) --- src/server/network/connection/connection.cpp | 4 ++-- src/server/network/protocol/protocolgame.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/network/connection/connection.cpp b/src/server/network/connection/connection.cpp index a6678565fba..5f007df6b6b 100644 --- a/src/server/network/connection/connection.cpp +++ b/src/server/network/connection/connection.cpp @@ -62,7 +62,7 @@ void Connection::close(bool force) { connectionState = CONNECTION_STATE_CLOSED; if (protocol) { - g_dispatcher().addEvent(std::bind_front(&Protocol::release, protocol), "Protocol::release", std::chrono::milliseconds(1000).count()); + g_dispatcher().addEvent(std::bind_front(&Protocol::release, protocol), "Protocol::release", std::chrono::milliseconds(CONNECTION_WRITE_TIMEOUT * 1000).count()); } if (messageQueue.empty() || force) { @@ -98,7 +98,7 @@ void Connection::closeSocket() { void Connection::accept(Protocol_ptr protocolPtr) { connectionState = CONNECTION_STATE_IDENTIFYING; protocol = std::move(protocolPtr); - g_dispatcher().addEvent(std::bind_front(&Protocol::onConnect, protocol), "Protocol::onConnect", std::chrono::milliseconds(1000).count()); + g_dispatcher().addEvent(std::bind_front(&Protocol::onConnect, protocol), "Protocol::onConnect", std::chrono::milliseconds(CONNECTION_WRITE_TIMEOUT * 1000).count()); acceptInternal(false); } diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 357c85521fb..a7e42e60d40 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -8473,7 +8473,7 @@ void ProtocolGame::parseSetMonsterPodium(NetworkMessage &msg) const { } void ProtocolGame::sendBosstiaryCooldownTimer() { - if (oldProtocol) { + if (!player || oldProtocol) { return; } From e5f5fede90693d239ad632591c125b9288892db4 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 28 Dec 2023 10:36:28 -0800 Subject: [PATCH 08/28] ci: disable sonar in draft prs and main branch (#2062) We don't look at sonar on main/drafts. And it's a _very_ heavy build that takes a really long time. Disabling it here is sensible to allow merges to be worked on quicker. --- .github/workflows/analysis-sonarcloud.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/analysis-sonarcloud.yml b/.github/workflows/analysis-sonarcloud.yml index a929886f291..71b910fa995 100644 --- a/.github/workflows/analysis-sonarcloud.yml +++ b/.github/workflows/analysis-sonarcloud.yml @@ -3,19 +3,14 @@ name: Analysis - SonarCloud on: pull_request_target: - types: [opened, synchronize, reopened] + types: [opened, synchronize, reopened, ready_for_review] paths: - - 'src/**' - push: - paths: - - 'src/**' - branches: - - main + - "src/**" env: VCPKG_BUILD_TYPE: debug CMAKE_BUILD_PARALLEL_LEVEL: 2 - MAKEFLAGS: '-j 2' + MAKEFLAGS: "-j 2" VCPKG_BINARY_SOURCES: clear;default,readwrite jobs: From 836b524bbfb3e419f3ec978ed9b69a7f74931bb8 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 28 Dec 2023 10:49:06 -0800 Subject: [PATCH 09/28] ci: enable merge_group in key builds for merge-queue (#2063) We want to try to use github's merge-queue: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue#triggering-merge-group-checks-with-github-actions Which requires builds to be configured this way first. --- .../workflows/analysis-reviewdog-cppcheck.yml | 7 +++---- .github/workflows/analysis-reviewdog.yml | 17 +++-------------- .github/workflows/build-docker.yml | 19 ++++++++++--------- .github/workflows/build-ubuntu.yml | 15 ++++++++------- .github/workflows/build-windows-cmake.yml | 15 ++++++++------- .github/workflows/build-windows-solution.yml | 15 ++++++++------- .github/workflows/clang-lint.yml | 13 +++++++------ .github/workflows/cron-stale.yml | 16 ++++++++-------- .github/workflows/lua-format.yml | 5 +++-- .github/workflows/pr-labeler.yml | 4 ++-- .github/workflows/tests-lua.yml | 1 + 11 files changed, 61 insertions(+), 66 deletions(-) diff --git a/.github/workflows/analysis-reviewdog-cppcheck.yml b/.github/workflows/analysis-reviewdog-cppcheck.yml index 4098ccbdcda..bca1d0578bb 100644 --- a/.github/workflows/analysis-reviewdog-cppcheck.yml +++ b/.github/workflows/analysis-reviewdog-cppcheck.yml @@ -4,13 +4,12 @@ name: Analysis - Review Dog on: pull_request: paths: - - 'src/**' + - "src/**" push: paths: - - 'src/**' + - "src/**" jobs: - cppcheck: runs-on: ubuntu-latest steps: @@ -18,7 +17,7 @@ jobs: if: github.ref != 'refs/heads/main' uses: fkirc/skip-duplicate-actions@master with: - concurrent_skipping: 'same_content' + concurrent_skipping: "same_content" cancel_others: true - name: Check out code. diff --git a/.github/workflows/analysis-reviewdog.yml b/.github/workflows/analysis-reviewdog.yml index b3bf0b562e6..a7d6eddac7b 100644 --- a/.github/workflows/analysis-reviewdog.yml +++ b/.github/workflows/analysis-reviewdog.yml @@ -12,7 +12,7 @@ jobs: if: github.ref != 'refs/heads/main' uses: fkirc/skip-duplicate-actions@master with: - concurrent_skipping: 'same_content' + concurrent_skipping: "same_content" cancel_others: true - name: Check out code. @@ -32,11 +32,9 @@ jobs: luac -v reviewdog -reporter=github-pr-check -runners=luac - luacheck: runs-on: ubuntu-latest steps: - - name: Check out code. uses: actions/checkout@main @@ -54,11 +52,9 @@ jobs: cd "$GITHUB_WORKSPACE" reviewdog -reporter=github-pr-check -runners=luacheck - shellcheck: runs-on: ubuntu-latest steps: - - name: Check out code. uses: actions/checkout@main @@ -67,14 +63,12 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} reporter: github-pr-check - pattern: '*.sh' - exclude: './.git/*' - + pattern: "*.sh" + exclude: "./.git/*" xmllint: runs-on: ubuntu-latest steps: - - name: Check out code. uses: actions/checkout@main @@ -92,11 +86,9 @@ jobs: xmllint --version reviewdog -reporter=github-pr-check -runners=xmllint - yamllint: runs-on: ubuntu-latest steps: - - name: Check out code. uses: actions/checkout@main @@ -106,11 +98,9 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} reporter: github-pr-check - hadolint: runs-on: ubuntu-latest steps: - - name: Check out code uses: actions/checkout@main @@ -122,7 +112,6 @@ jobs: actionlint: runs-on: ubuntu-latest steps: - - name: Check out code uses: actions/checkout@main diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index f4366e8fe65..c1a4a77af7b 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -6,10 +6,11 @@ on: pull_request: types: [opened, synchronize, reopened, ready_for_review] paths: - - 'src/**' + - "src/**" + merge_group: push: paths: - - 'src/**' + - "src/**" branches: - main @@ -18,10 +19,10 @@ jobs: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} build_docker_x86: if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} @@ -35,7 +36,7 @@ jobs: - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0.9.15 with: - versionSpec: '5.x' + versionSpec: "5.x" - name: Determine Version id: gitversion @@ -93,7 +94,7 @@ jobs: if: github.ref != 'refs/heads/main' uses: fkirc/skip-duplicate-actions@master with: - concurrent_skipping: 'same_content' + concurrent_skipping: "same_content" cancel_others: true - name: Checkout @@ -104,7 +105,7 @@ jobs: - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0.9.15 with: - versionSpec: '5.x' + versionSpec: "5.x" - name: Determine Version id: gitversion diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 7d2543a59bf..e0ea11220fb 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -6,26 +6,27 @@ on: pull_request: types: [opened, synchronize, reopened, ready_for_review] paths: - - 'src/**' + - "src/**" + merge_group: push: paths: - - 'src/**' + - "src/**" branches: - main env: CMAKE_BUILD_PARALLEL_LEVEL: 2 - MAKEFLAGS: '-j 2' + MAKEFLAGS: "-j 2" jobs: cancel-runs: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} job: if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} diff --git a/.github/workflows/build-windows-cmake.yml b/.github/workflows/build-windows-cmake.yml index a59041dffd2..caba8a0aa90 100644 --- a/.github/workflows/build-windows-cmake.yml +++ b/.github/workflows/build-windows-cmake.yml @@ -5,24 +5,25 @@ on: pull_request: types: [opened, synchronize, reopened, ready_for_review] paths: - - 'src/**' + - "src/**" + merge_group: push: paths: - - 'src/**' + - "src/**" branches: - main env: CMAKE_BUILD_PARALLEL_LEVEL: 2 - MAKEFLAGS: '-j 2' + MAKEFLAGS: "-j 2" jobs: cancel-runs: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} job: if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} diff --git a/.github/workflows/build-windows-solution.yml b/.github/workflows/build-windows-solution.yml index 1189acec818..9e3addb6e74 100644 --- a/.github/workflows/build-windows-solution.yml +++ b/.github/workflows/build-windows-solution.yml @@ -6,26 +6,27 @@ on: pull_request: types: [opened, synchronize, reopened, ready_for_review] paths: - - 'src/**' + - "src/**" + merge_group: push: paths: - - 'src/**' + - "src/**" branches: - main env: CMAKE_BUILD_PARALLEL_LEVEL: 2 - MAKEFLAGS: '-j 2' + MAKEFLAGS: "-j 2" jobs: cancel-runs: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} job: if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} diff --git a/.github/workflows/clang-lint.yml b/.github/workflows/clang-lint.yml index 2fe9166c4f5..85699e408c9 100644 --- a/.github/workflows/clang-lint.yml +++ b/.github/workflows/clang-lint.yml @@ -3,19 +3,20 @@ name: Clang-format on: pull_request: paths: - - 'src/**' + - "src/**" + merge_group: push: paths: - - 'src/**' + - "src/**" jobs: cancel-runs: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} build: runs-on: ubuntu-latest diff --git a/.github/workflows/cron-stale.yml b/.github/workflows/cron-stale.yml index 03d1868c449..b4231144943 100644 --- a/.github/workflows/cron-stale.yml +++ b/.github/workflows/cron-stale.yml @@ -1,18 +1,18 @@ --- -name: 'Cron - Stale issues and PRs' +name: "Cron - Stale issues and PRs" on: schedule: - - cron: '30 1 * * *' + - cron: "30 1 * * *" jobs: cancel-runs: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} stale: runs-on: ubuntu-latest @@ -20,8 +20,8 @@ jobs: - uses: actions/stale@v6 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue is stale because it has been open 120 days with no activity.' - stale-pr-message: 'This PR is stale because it has been open 45 days with no activity.' + stale-issue-message: "This issue is stale because it has been open 120 days with no activity." + stale-pr-message: "This PR is stale because it has been open 45 days with no activity." days-before-issue-stale: 90 days-before-pr-stale: 30 days-before-issue-close: -1 diff --git a/.github/workflows/lua-format.yml b/.github/workflows/lua-format.yml index 57caa336668..5a9a644e7aa 100644 --- a/.github/workflows/lua-format.yml +++ b/.github/workflows/lua-format.yml @@ -3,10 +3,11 @@ name: Lua-format on: pull_request: paths: - - 'data*/**' + - "data*/**" + merge_group: push: paths: - - 'data*/**' + - "data*/**" jobs: lua-formatter: runs-on: ubuntu-latest diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 066aaa23595..345a0e6f089 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -1,5 +1,5 @@ --- -name: 'PR - Labeler' +name: "PR - Labeler" on: - pull_request_target @@ -9,4 +9,4 @@ jobs: steps: - uses: actions/labeler@main with: - repo-token: '${{ secrets.GITHUB_TOKEN }}' + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/tests-lua.yml b/.github/workflows/tests-lua.yml index ebc52323ad2..6455c5705de 100644 --- a/.github/workflows/tests-lua.yml +++ b/.github/workflows/tests-lua.yml @@ -3,6 +3,7 @@ name: Tests - Lua on: pull_request: + merge_group: push: branches: - main From e3e633851b9e74e0cfa8628300a29be93efe7e21 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 28 Dec 2023 10:49:45 -0800 Subject: [PATCH 10/28] feat: flexible monster mitigation settings (#1998) New configs: ``` disableMonsterArmor = false combatChainDelay = 50 -- minimum: 50 miliseconds minElementalResistance = -200 maxElementalResistance = 200 maxDamageReflection = 200 ``` ## `disableMonsterArmor` Will disable the classic "armor" based defense for monsters and switch all damage to "mitigation". This brings `physical` damage back into play versus elemental (of course limted by regular elemental resistantces. ## `combatChainDelay` Delay between each target jumping when using chaining abilities ## `minElementalResistance` How low an elemental resistance can go for monsters. Default is -200%. ## `maxElementalResistance` How high an elemental resistance can go for monsters. Default is 200%. ## `maxDamageReflection` How high damage reflection can go for monsters. Default is 200%. `minElementalResistance`, `maxElementalResistance` and `maxDamageReflection` will "clamp" the values set on the monster type. --------- Co-authored-by: Elson Costa --- config.lua.dist | 5 ++++ .../scripts/lib/register_monster_type.lua | 24 +++++++++++++++-- src/config/config_definitions.hpp | 5 ++++ src/config/configmanager.cpp | 6 +++++ src/creatures/combat/combat.cpp | 18 ++++++++++--- src/creatures/combat/combat.hpp | 2 +- src/creatures/creature.cpp | 5 ++++ src/creatures/creature.hpp | 9 +++++++ src/creatures/monsters/monster.cpp | 27 ++++++++++++++----- src/creatures/monsters/monster.hpp | 4 +-- src/creatures/monsters/monsters.hpp | 3 ++- .../monsters/spawns/spawn_monster.cpp | 18 ++++++++++--- .../monsters/spawns/spawn_monster.hpp | 5 ++++ src/creatures/npcs/npcs.hpp | 3 ++- src/game/game.cpp | 5 +++- .../creatures/creature_functions.cpp | 23 ++++++++++++++++ .../creatures/creature_functions.hpp | 5 ++++ .../monster/monster_type_functions.cpp | 2 +- .../creatures/npc/npc_type_functions.cpp | 2 +- 19 files changed, 147 insertions(+), 24 deletions(-) diff --git a/config.lua.dist b/config.lua.dist index fec7fb25e72..a304784d4a3 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -380,6 +380,11 @@ experienceDisplayRates = true toggleAttackSpeedOnFist = false multiplierSpeedOnFist = 5 maxSpeedOnFist = 500 +disableMonsterArmor = false +combatChainDelay = 50 -- minimum: 50 miliseconds +minElementalResistance = -200 +maxElementalResistance = 200 +maxDamageReflection = 200 -- Global server Save -- NOTE: globalServerSaveNotifyDuration in minutes diff --git a/data-otservbr-global/scripts/lib/register_monster_type.lua b/data-otservbr-global/scripts/lib/register_monster_type.lua index 49537ac9c4c..c62ca0281d7 100644 --- a/data-otservbr-global/scripts/lib/register_monster_type.lua +++ b/data-otservbr-global/scripts/lib/register_monster_type.lua @@ -472,20 +472,40 @@ registerMonsterType.loot = function(mtype, mask) end end end +local playerElements = { COMBAT_PHYSICALDAMAGE, COMBAT_ENERGYDAMAGE, COMBAT_EARTHDAMAGE, COMBAT_FIREDAMAGE, COMBAT_ICEDAMAGE, COMBAT_HOLYDAMAGE, COMBAT_DEATHDAMAGE } registerMonsterType.elements = function(mtype, mask) + local min = configManager.getNumber(configKeys.MIN_ELEMENTAL_RESISTANCE) + local max = configManager.getNumber(configKeys.MAX_ELEMENTAL_RESISTANCE) + local canClip = false if type(mask.elements) == "table" then + for _, playerElement in pairs(playerElements) do + local found = false + for _, element in pairs(mask.elements) do + if element.type == playerElement then + found = true + canClip = canClip or element.percent ~= 100 + break + end + end + canClip = canClip or not found + end for _, element in pairs(mask.elements) do if element.type and element.percent then - mtype:addElement(element.type, element.percent) + local value = element.percent + if canClip then + value = math.min(math.max(element.percent, min), max) + end + mtype:addElement(element.type, value) end end end end registerMonsterType.reflects = function(mtype, mask) + local max = configManager.getNumber(configKeys.MAX_DAMAGE_REFLECTION) if type(mask.reflects) == "table" then for _, reflect in pairs(mask.reflects) do if reflect.type and reflect.percent then - mtype:addReflect(reflect.type, reflect.percent) + mtype:addReflect(reflect.type, math.min(reflect.percent, max)) end end end diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp index 541da679351..fc5f608ee57 100644 --- a/src/config/config_definitions.hpp +++ b/src/config/config_definitions.hpp @@ -36,6 +36,7 @@ enum ConfigKey_t : uint16_t { CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES, CLASSIC_ATTACK_SPEED, CLEAN_PROTECTION_ZONES, + COMBAT_CHAIN_DELAY, COMPRESSION_LEVEL, CONVERT_UNSAFE_SCRIPTS, CORE_DIRECTORY, @@ -48,6 +49,7 @@ enum ConfigKey_t : uint16_t { DEFAULT_PRIORITY, DEPOTCHEST, DEPOT_BOXES, + DISABLE_MONSTER_ARMOR, DISCORD_WEBHOOK_DELAY_MS, DISCORD_WEBHOOK_URL, EMOTE_SPELLS, @@ -127,6 +129,8 @@ enum ConfigKey_t : uint16_t { MAX_ALLOWED_ON_A_DUMMY, MAX_CONTAINER, MAX_CONTAINER_ITEM, + MAX_DAMAGE_REFLECTION, + MAX_ELEMENTAL_RESISTANCE, MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER, MAX_MESSAGEBUFFER, MAX_PACKETS_PER_SECOND, @@ -138,6 +142,7 @@ enum ConfigKey_t : uint16_t { METRICS_ENABLE_PROMETHEUS, METRICS_OSTREAM_INTERVAL, METRICS_PROMETHEUS_ADDRESS, + MIN_ELEMENTAL_RESISTANCE, MONTH_KILLS_TO_RED, MULTIPLIER_ATTACKONFIST, MYSQL_DB, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index 97d1052ad03..c24a2a60884 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -97,6 +97,7 @@ bool ConfigManager::load() { loadBoolConfig(L, CONVERT_UNSAFE_SCRIPTS, "convertUnsafeScripts", true); loadBoolConfig(L, CLASSIC_ATTACK_SPEED, "classicAttackSpeed", false); loadBoolConfig(L, TOGGLE_ATTACK_SPEED_ONFIST, "toggleAttackSpeedOnFist", false); + loadBoolConfig(L, DISABLE_MONSTER_ARMOR, "disableMonsterArmor", false); loadIntConfig(L, MULTIPLIER_ATTACKONFIST, "multiplierSpeedOnFist", 5); loadIntConfig(L, MAX_SPEED_ATTACKONFIST, "maxSpeedOnFist", 500); loadBoolConfig(L, SCRIPTS_CONSOLE_LOGS, "showScriptsLogInConsole", true); @@ -246,6 +247,7 @@ bool ConfigManager::load() { loadFloatConfig(L, RATE_ATTACK_SPEED, "rateAttackSpeed", 1.0); loadFloatConfig(L, RATE_OFFLINE_TRAINING_SPEED, "rateOfflineTrainingSpeed", 1.0); loadFloatConfig(L, RATE_EXERCISE_TRAINING_SPEED, "rateExerciseTrainingSpeed", 1.0); + loadIntConfig(L, COMBAT_CHAIN_DELAY, "combatChainDelay", 50); loadFloatConfig(L, RATE_MONSTER_HEALTH, "rateMonsterHealth", 1.0); loadFloatConfig(L, RATE_MONSTER_ATTACK, "rateMonsterAttack", 1.0); @@ -256,6 +258,10 @@ bool ConfigManager::load() { loadIntConfig(L, BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN, "bossDefaultTimeToFightAgain", 20 * 60 * 60); loadIntConfig(L, BOSS_DEFAULT_TIME_TO_DEFEAT, "bossDefaultTimeToDefeat", 20 * 60); + loadIntConfig(L, MIN_ELEMENTAL_RESISTANCE, "minElementalResistance", -200); + loadIntConfig(L, MAX_ELEMENTAL_RESISTANCE, "maxElementalResistance", 200); + loadIntConfig(L, MAX_DAMAGE_REFLECTION, "maxDamageReflection", 200); + loadFloatConfig(L, RATE_NPC_HEALTH, "rateNpcHealth", 1.0); loadFloatConfig(L, RATE_NPC_ATTACK, "rateNpcAttack", 1.0); loadFloatConfig(L, RATE_NPC_DEFENSE, "rateNpcDefense", 1.0); diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index a348f364078..f790706c5df 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -937,15 +937,26 @@ bool Combat::doCombatChain(std::shared_ptr caster, std::shared_ptr(50, g_configManager().getNumber(COMBAT_CHAIN_DELAY, __FUNCTION__)); + ++i; for (auto to : toVector) { auto nextTarget = g_game().getCreatureByID(to); if (!nextTarget) { continue; } - combat->doChainEffect(from, nextTarget->getPosition(), combat->params.chainEffect); - combat->doCombat(caster, nextTarget, from); + g_dispatcher().scheduleEvent( + delay, [combat, caster, nextTarget, from, affected]() { + if (combat && caster && nextTarget) { + combat->doChainEffect(from, nextTarget->getPosition(), combat->params.chainEffect); + combat->doCombat(caster, nextTarget, from, affected); + } + }, + "Combat::doCombatChain" + ); } } @@ -960,10 +971,11 @@ bool Combat::doCombat(std::shared_ptr caster, std::shared_ptrgetPosition() : Position()); } -bool Combat::doCombat(std::shared_ptr caster, std::shared_ptr target, const Position &origin) const { +bool Combat::doCombat(std::shared_ptr caster, std::shared_ptr target, const Position &origin, int affected /* = 1 */) const { // target combat callback function if (params.combatType != COMBAT_NONE) { CombatDamage damage = getCombatDamage(caster, target); + damage.affected = affected; if (damage.primary.type != COMBAT_MANADRAIN) { doCombatHealth(caster, target, origin, damage, params); } else { diff --git a/src/creatures/combat/combat.hpp b/src/creatures/combat/combat.hpp index 2bb2d0a3589..71554b3761d 100644 --- a/src/creatures/combat/combat.hpp +++ b/src/creatures/combat/combat.hpp @@ -289,7 +289,7 @@ class Combat { static void addDistanceEffect(std::shared_ptr caster, const Position &fromPos, const Position &toPos, uint16_t effect); bool doCombat(std::shared_ptr caster, std::shared_ptr target) const; - bool doCombat(std::shared_ptr caster, std::shared_ptr target, const Position &origin) const; + bool doCombat(std::shared_ptr caster, std::shared_ptr target, const Position &origin, int affected = 1) const; bool doCombat(std::shared_ptr caster, const Position &pos) const; bool setCallback(CallBackParam_t key); diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index bc77050268d..dcd94d950c7 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -937,6 +937,11 @@ BlockType_t Creature::blockHit(std::shared_ptr attacker, CombatType_t // Apply skills 12.72 absorbs damage applyAbsorbDamageModifications(attacker, damage, combatType); + if (getMonster() && g_configManager().getBoolean(DISABLE_MONSTER_ARMOR, __FUNCTION__)) { + checkDefense = false; + checkArmor = false; + } + if (isImmune(combatType)) { damage = 0; blockType = BLOCK_IMMUNITY; diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp index 2052e7a2866..027b3a6af4d 100644 --- a/src/creatures/creature.hpp +++ b/src/creatures/creature.hpp @@ -150,6 +150,14 @@ class Creature : virtual public Thing, public SharedObject { moveLocked = locked; } + bool isDirectionLocked() const { + return directionLocked; + } + + void setDirectionLocked(bool locked) { + directionLocked = locked; + } + int32_t getThrowRange() const override final { return 1; } @@ -773,6 +781,7 @@ class Creature : virtual public Thing, public SharedObject { bool floorChange = false; bool canUseDefense = true; bool moveLocked = false; + bool directionLocked = false; bool hasFollowPath = false; int8_t charmChanceModifier = 0; diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index f4acbc1f39c..fe7657e3f1f 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -486,10 +486,11 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL if (++it != resultList.end()) { const Position &targetPosition = getTarget->getPosition(); int32_t minRange = std::max(Position::getDistanceX(myPos, targetPosition), Position::getDistanceY(myPos, targetPosition)); + int32_t factionOffset = static_cast(getTarget->getFaction()) * 100; do { const Position &pos = (*it)->getPosition(); - int32_t distance = std::max(Position::getDistanceX(myPos, pos), Position::getDistanceY(myPos, pos)); + int32_t distance = std::max(Position::getDistanceX(myPos, pos), Position::getDistanceY(myPos, pos)) + factionOffset; if (distance < minRange) { getTarget = *it; minRange = distance; @@ -504,7 +505,8 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL } const Position &pos = creature->getPosition(); - int32_t distance = std::max(Position::getDistanceX(myPos, pos), Position::getDistanceY(myPos, pos)); + int32_t factionOffset = static_cast(getTarget->getFaction()) * 100; + int32_t distance = std::max(Position::getDistanceX(myPos, pos), Position::getDistanceY(myPos, pos)) + factionOffset; if (distance < minRange) { getTarget = creature; minRange = distance; @@ -523,12 +525,14 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL auto it = resultList.begin(); getTarget = *it; if (++it != resultList.end()) { - int32_t minHp = getTarget->getHealth(); + int32_t factionOffset = static_cast(getTarget->getFaction()) * 100000; + int32_t minHp = getTarget->getHealth() + factionOffset; do { - if ((*it)->getHealth() < minHp) { + auto hp = (*it)->getHealth() + factionOffset; + factionOffset = static_cast((*it)->getFaction()) * 100000; + if (hp < minHp) { getTarget = *it; - - minHp = getTarget->getHealth(); + minHp = hp; } } while (++it != resultList.end()); } @@ -546,9 +550,10 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL if (++it != resultList.end()) { int32_t mostDamage = 0; do { + int32_t factionOffset = static_cast((*it)->getFaction()) * 100000; const auto dmg = damageMap.find((*it)->getID()); if (dmg != damageMap.end()) { - if (dmg->second.total > mostDamage) { + if (dmg->second.total + factionOffset > mostDamage) { mostDamage = dmg->second.total; getTarget = *it; } @@ -584,6 +589,14 @@ void Monster::onFollowCreatureComplete(const std::shared_ptr &creature } } +float Monster::getMitigation() const { + float mitigation = mType->info.mitigation * getDefenseMultiplier(); + if (g_configManager().getBoolean(DISABLE_MONSTER_ARMOR, __FUNCTION__)) { + mitigation += std::ceil(static_cast(getDefense() + getArmor()) / 100.f) * getDefenseMultiplier() * 2.f; + } + return std::min(mitigation, 30.f); +} + BlockType_t Monster::blockHit(std::shared_ptr attacker, CombatType_t combatType, int32_t &damage, bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool /* field = false */) { BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor); diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index b6194543099..c299434a80d 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -73,9 +73,7 @@ class Monster final : public Creature { RaceType_t getRace() const override { return mType->info.race; } - float getMitigation() const override { - return mType->info.mitigation * getDefenseMultiplier(); - } + float getMitigation() const override; int32_t getArmor() const override { return mType->info.armor * getDefenseMultiplier(); } diff --git a/src/creatures/monsters/monsters.hpp b/src/creatures/monsters/monsters.hpp index bb911f2cce6..ab18ca6c9d6 100644 --- a/src/creatures/monsters/monsters.hpp +++ b/src/creatures/monsters/monsters.hpp @@ -66,7 +66,8 @@ class MonsterType { std::vector voiceVector; std::vector lootItems; - std::vector scripts; + // We need to keep the order of scripts, so we use a set isntead of an unordered_set + std::set scripts; std::vector attackSpells; std::vector defenseSpells; std::vector summons; diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp index 05435d6b4a8..c0f2fb382e8 100644 --- a/src/creatures/monsters/spawns/spawn_monster.cpp +++ b/src/creatures/monsters/spawns/spawn_monster.cpp @@ -149,6 +149,8 @@ SpawnMonster::~SpawnMonster() { for (const auto &[_, monster] : spawnedMonsterMap) { monster->setSpawnMonster(nullptr); } + stopEvent(); + spawnMonsterMap.clear(); } bool SpawnMonster::findPlayer(const Position &pos) { @@ -365,13 +367,23 @@ void SpawnMonster::removeMonster(std::shared_ptr monster) { spawnedMonsterMap.erase(spawnMonsterId); } +void SpawnMonster::removeMonsters() { + spawnMonsterMap.clear(); + spawnedMonsterMap.clear(); +} + void SpawnMonster::setMonsterVariant(const std::string &variant) { for (auto &it : spawnMonsterMap) { std::unordered_map, uint32_t> monsterTypes; for (const auto &[monsterType, weight] : it.second.monsterTypes) { - auto variantName = variant + monsterType->typeName; - auto variantType = g_monsters().getMonsterType(variantName, false); - monsterTypes.emplace(variantType, weight); + if (!monsterType || monsterType->typeName.empty()) { + continue; + } + auto variantName = variant + "|" + monsterType->typeName; + auto variantType = g_monsters().getMonsterType(variantName, true); + if (variantType) { + monsterTypes.emplace(variantType, weight); + } } it.second.monsterTypes = monsterTypes; } diff --git a/src/creatures/monsters/spawns/spawn_monster.hpp b/src/creatures/monsters/spawns/spawn_monster.hpp index 9ea29e4f317..7e7f7cf3f49 100644 --- a/src/creatures/monsters/spawns/spawn_monster.hpp +++ b/src/creatures/monsters/spawns/spawn_monster.hpp @@ -38,6 +38,7 @@ class SpawnMonster { bool addMonster(const std::string &name, const Position &pos, Direction dir, uint32_t interval, uint32_t weight = 1); void removeMonster(std::shared_ptr monster); + void removeMonsters(); uint32_t getInterval() const { return interval; @@ -82,6 +83,10 @@ class SpawnsMonster { bool loadFromXML(const std::string &filemonstername); void startup(); void clear(); + SpawnMonster &addSpawnMonster(const Position &pos, int32_t radius) { + spawnMonsterList.emplace_front(pos, radius); + return spawnMonsterList.front(); + } bool isStarted() const { return started; diff --git a/src/creatures/npcs/npcs.hpp b/src/creatures/npcs/npcs.hpp index d836e6e9252..48170a94fd4 100644 --- a/src/creatures/npcs/npcs.hpp +++ b/src/creatures/npcs/npcs.hpp @@ -66,7 +66,8 @@ class NpcType : public SharedObject { std::vector soundVector; std::vector voiceVector; - std::vector scripts; + // We need to keep the order of scripts, so we use a set isntead of an unordered_set + std::set scripts; std::vector shopItemVector; NpcsEvent_t eventType = NPCS_EVENT_NONE; diff --git a/src/game/game.cpp b/src/game/game.cpp index 5da0ca3f6ce..546ac979fb6 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -5732,7 +5732,10 @@ bool Game::internalCreatureTurn(std::shared_ptr creature, Direction di if (const auto &player = creature->getPlayer()) { player->cancelPush(); } - creature->setDirection(dir); + + if (!creature->isDirectionLocked()) { + creature->setDirection(dir); + } for (const auto &spectator : Spectators().find(creature->getPosition(), true)) { spectator->getPlayer()->sendCreatureTurn(creature); diff --git a/src/lua/functions/creatures/creature_functions.cpp b/src/lua/functions/creatures/creature_functions.cpp index 5965adb7f8f..5ef10be08bf 100644 --- a/src/lua/functions/creatures/creature_functions.cpp +++ b/src/lua/functions/creatures/creature_functions.cpp @@ -581,6 +581,29 @@ int CreatureFunctions::luaCreatureSetMoveLocked(lua_State* L) { return 1; } +int CreatureFunctions::luaCreatureIsDirectionLocked(lua_State* L) { + // creature:isDirectionLocked() + std::shared_ptr creature = getUserdataShared(L, 1); + if (creature) { + pushBoolean(L, creature->isDirectionLocked()); + } else { + lua_pushnil(L); + } + return 1; +} + +int CreatureFunctions::luaCreatureSetDirectionLocked(lua_State* L) { + // creature:setDirectionLocked(directionLocked) + std::shared_ptr creature = getUserdataShared(L, 1); + if (creature) { + creature->setDirectionLocked(getBoolean(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int CreatureFunctions::luaCreatureGetSkull(lua_State* L) { // creature:getSkull() std::shared_ptr creature = getUserdataShared(L, 1); diff --git a/src/lua/functions/creatures/creature_functions.hpp b/src/lua/functions/creatures/creature_functions.hpp index af86c5c7097..48c374936c0 100644 --- a/src/lua/functions/creatures/creature_functions.hpp +++ b/src/lua/functions/creatures/creature_functions.hpp @@ -60,7 +60,9 @@ class CreatureFunctions final : LuaScriptInterface { registerMethod(L, "Creature", "setMaxHealth", CreatureFunctions::luaCreatureSetMaxHealth); registerMethod(L, "Creature", "setHiddenHealth", CreatureFunctions::luaCreatureSetHiddenHealth); registerMethod(L, "Creature", "isMoveLocked", CreatureFunctions::luaCreatureIsMoveLocked); + registerMethod(L, "Creature", "isDirectionLocked", CreatureFunctions::luaCreatureIsDirectionLocked); registerMethod(L, "Creature", "setMoveLocked", CreatureFunctions::luaCreatureSetMoveLocked); + registerMethod(L, "Creature", "setDirectionLocked", CreatureFunctions::luaCreatureSetDirectionLocked); registerMethod(L, "Creature", "getSkull", CreatureFunctions::luaCreatureGetSkull); registerMethod(L, "Creature", "setSkull", CreatureFunctions::luaCreatureSetSkull); registerMethod(L, "Creature", "getOutfit", CreatureFunctions::luaCreatureGetOutfit); @@ -151,6 +153,9 @@ class CreatureFunctions final : LuaScriptInterface { static int luaCreatureIsMoveLocked(lua_State* L); static int luaCreatureSetMoveLocked(lua_State* L); + static int luaCreatureIsDirectionLocked(lua_State* L); + static int luaCreatureSetDirectionLocked(lua_State* L); + static int luaCreatureGetSkull(lua_State* L); static int luaCreatureSetSkull(lua_State* L); diff --git a/src/lua/functions/creatures/monster/monster_type_functions.cpp b/src/lua/functions/creatures/monster/monster_type_functions.cpp index edaa77b2870..269ff6aa3c3 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.cpp @@ -1004,7 +1004,7 @@ int MonsterTypeFunctions::luaMonsterTypeRegisterEvent(lua_State* L) { const auto monsterType = getUserdataShared(L, 1); if (monsterType) { auto eventName = getString(L, 2); - monsterType->info.scripts.push_back(eventName); + monsterType->info.scripts.insert(eventName); for (const auto &[_, monster] : g_game().getMonsters()) { if (monster->getMonsterType() == monsterType) { monster->registerCreatureEvent(eventName); diff --git a/src/lua/functions/creatures/npc/npc_type_functions.cpp b/src/lua/functions/creatures/npc/npc_type_functions.cpp index 8c2fb604fe4..35bbebbe848 100644 --- a/src/lua/functions/creatures/npc/npc_type_functions.cpp +++ b/src/lua/functions/creatures/npc/npc_type_functions.cpp @@ -258,7 +258,7 @@ int NpcTypeFunctions::luaNpcTypeRegisterEvent(lua_State* L) { // npcType:registerEvent(name) const auto &npcType = getUserdataShared(L, 1); if (npcType) { - npcType->info.scripts.push_back(getString(L, 2)); + npcType->info.scripts.insert(getString(L, 2)); pushBoolean(L, true); } else { lua_pushnil(L); From 853ef5f77c5764a90537cf5a99fee1e2b313d0a1 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 28 Dec 2023 10:50:03 -0800 Subject: [PATCH 11/28] feat: defense boost for hazard system (#1999) --- config.lua.dist | 1 + .../scripts/hazard/primal.lua | 1 + data/libs/hazard_lib.lua | 50 +++++++++++++++---- src/config/config_definitions.hpp | 1 + src/config/configmanager.cpp | 1 + src/creatures/creature.cpp | 3 ++ src/creatures/creature.hpp | 1 + src/creatures/monsters/monster.hpp | 7 +++ src/creatures/players/player.cpp | 14 ++++++ src/creatures/players/player.hpp | 1 + .../creatures/monster/monster_functions.cpp | 19 ++++++- .../creatures/monster/monster_functions.hpp | 2 + 12 files changed, 90 insertions(+), 11 deletions(-) diff --git a/config.lua.dist b/config.lua.dist index a304784d4a3..4d2436cfd32 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -147,6 +147,7 @@ hazardCriticalInterval = 2000 hazardCriticalChance = 750 hazardCriticalMultiplier = 25 hazardDamageMultiplier = 200 +hazardDefenseMultiplier = 0 hazardDodgeMultiplier = 85 hazardPodsDropMultiplier = 87 hazardPodsTimeToDamage = 2000 diff --git a/data-otservbr-global/scripts/hazard/primal.lua b/data-otservbr-global/scripts/hazard/primal.lua index 2b8fd67a25d..ef9a9c31ca5 100644 --- a/data-otservbr-global/scripts/hazard/primal.lua +++ b/data-otservbr-global/scripts/hazard/primal.lua @@ -7,6 +7,7 @@ local hazard = Hazard.new({ crit = true, dodge = true, damageBoost = true, + defenseBoost = true, }) hazard:register() diff --git a/data/libs/hazard_lib.lua b/data/libs/hazard_lib.lua index c13f120f740..f9338299e79 100644 --- a/data/libs/hazard_lib.lua +++ b/data/libs/hazard_lib.lua @@ -8,12 +8,14 @@ function Hazard.new(prototype) instance.name = prototype.name instance.from = prototype.from instance.to = prototype.to + instance.minLevel = prototype.minLevel or 1 instance.maxLevel = prototype.maxLevel instance.storageMax = prototype.storageMax ---@deprecated instance.storageCurrent = prototype.storageCurrent ---@deprecated instance.crit = prototype.crit instance.dodge = prototype.dodge instance.damageBoost = prototype.damageBoost + instance.defenseBoost = prototype.defenseBoost instance.zone = Zone(instance.name) if instance.from and instance.to then @@ -41,19 +43,43 @@ function Hazard:getHazardPlayerAndPoints(damageMap) end if hazardPoints == -1 then - hazardPoints = 1 + hazardPoints = self.minLevel end return hazardPlayer, hazardPoints end +function Hazard:getCurrentLevel(players) + local hazardPlayer = nil + local hazardPoints = -1 + for _, player in ipairs(players) do + local playerHazardPoints = self:getPlayerCurrentLevel(player) + + if playerHazardPoints < hazardPoints or hazardPoints == -1 then + hazardPlayer = player + hazardPoints = playerHazardPoints + end + end + + if hazardPoints == -1 then + hazardPoints = self.minLevel + end + + return hazardPoints +end + function Hazard:getPlayerCurrentLevel(player) if self.storageCurrent then local fromStorage = player:getStorageValue(self.storageCurrent) - return fromStorage <= 0 and 1 or fromStorage + return fromStorage <= 0 and self.minLevel or fromStorage end - local fromKV = player:kv():scoped(self.name):get("currentLevel") or 1 - return fromKV <= 0 and 1 or fromKV + local fromKV = player:kv():scoped(self.name):get("current-level") or self.minLevel + return fromKV <= 0 and self.minLevel or fromKV +end + +function Hazard:getPlayerMaxLevelEver(player) + local fromKV = player:kv():scoped(self.name):get("max-level-set") or self.minLevel + return fromKV <= 0 and self.minLevel or fromKV end function Hazard:setPlayerCurrentLevel(player, level) @@ -64,7 +90,11 @@ function Hazard:setPlayerCurrentLevel(player, level) if self.storageCurrent then player:setStorageValue(self.storageCurrent, level) else - player:kv():scoped(self.name):set("currentLevel", level) + player:kv():scoped(self.name):set("current-level", level) + local maxEver = player:kv():scoped(self.name):get("max-level-set") or self.minLevel + if level > maxEver then + player:kv():scoped(self.name):set("max-level-set", level) + end end local zones = player:getZones() if not zones then @@ -86,11 +116,10 @@ end function Hazard:getPlayerMaxLevel(player) if self.storageMax then local fromStorage = player:getStorageValue(self.storageMax) - return fromStorage <= 0 and 1 or fromStorage + return fromStorage <= 0 and self.minLevel or fromStorage end - local fromKV = player:kv():scoped(self.name):get("maxLevel") or 1 - - return fromKV <= 0 and 1 or fromKV + local fromKV = player:kv():scoped(self.name):get("max-level") or self.minLevel + return fromKV <= 0 and self.minLevel or fromKV end function Hazard:levelUp(player) @@ -110,7 +139,7 @@ function Hazard:setPlayerMaxLevel(player, level) player:setStorageValue(self.storageMax, level) return end - player:kv():scoped(self.name):set("maxLevel", level) + player:kv():scoped(self.name):set("max-level", level) end function Hazard:isInZone(position) @@ -181,6 +210,7 @@ function HazardMonster.onSpawn(monster, position) monster:hazardCrit(hazard.crit) monster:hazardDodge(hazard.dodge) monster:hazardDamageBoost(hazard.damageBoost) + monster:hazardDefenseBoost(hazard.defenseBoost) end end end diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp index fc5f608ee57..361e9c52c94 100644 --- a/src/config/config_definitions.hpp +++ b/src/config/config_definitions.hpp @@ -90,6 +90,7 @@ enum ConfigKey_t : uint16_t { HAZARD_CRITICAL_INTERVAL, HAZARD_CRITICAL_MULTIPLIER, HAZARD_DAMAGE_MULTIPLIER, + HAZARD_DEFENSE_MULTIPLIER, HAZARD_DODGE_MULTIPLIER, HAZARD_EXP_BONUS_MULTIPLIER, HAZARD_LOOT_BONUS_MULTIPLIER, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index c24a2a60884..f82f3a66425 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -303,6 +303,7 @@ bool ConfigManager::load() { loadIntConfig(L, HAZARD_CRITICAL_CHANCE, "hazardCriticalChance", 750); loadIntConfig(L, HAZARD_CRITICAL_MULTIPLIER, "hazardCriticalMultiplier", 25); loadIntConfig(L, HAZARD_DAMAGE_MULTIPLIER, "hazardDamageMultiplier", 200); + loadIntConfig(L, HAZARD_DEFENSE_MULTIPLIER, "hazardDefenseMultiplier", 0); loadIntConfig(L, HAZARD_DODGE_MULTIPLIER, "hazardDodgeMultiplier", 85); loadIntConfig(L, HAZARD_PODS_DROP_MULTIPLIER, "hazardPodsDropMultiplier", 87); loadIntConfig(L, HAZARD_PODS_TIME_TO_DAMAGE, "hazardPodsTimeToDamage", 2000); diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index dcd94d950c7..8e2e748c004 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -989,6 +989,9 @@ BlockType_t Creature::blockHit(std::shared_ptr attacker, CombatType_t mitigateDamage(combatType, blockType, damage); + if (damage != 0) { + onTakeDamage(attacker, damage); + } onAttacked(); return blockType; } diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp index 027b3a6af4d..cbbdc94e3ac 100644 --- a/src/creatures/creature.hpp +++ b/src/creatures/creature.hpp @@ -457,6 +457,7 @@ class Creature : virtual public Thing, public SharedObject { virtual void onGainExperience(uint64_t gainExp, std::shared_ptr target); virtual void onAttackedCreatureBlockHit(BlockType_t) { } virtual void onBlockHit() { } + virtual void onTakeDamage(std::shared_ptr, int32_t) { } virtual void onChangeZone(ZoneType_t zone); virtual void onAttackedCreatureChangeZone(ZoneType_t zone); virtual void onIdleStatus(); diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index c299434a80d..b4b3337bbed 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -270,6 +270,12 @@ class Monster final : public Creature { void setHazardSystemDamageBoost(bool value) { hazardDamageBoost = value; } + bool getHazardSystemDefenseBoost() const { + return hazardDefenseBoost; + } + void setHazardSystemDefenseBoost(bool value) { + hazardDefenseBoost = value; + } // Hazard end void updateTargetList(); @@ -389,6 +395,7 @@ class Monster final : public Creature { bool hazardCrit = false; bool hazardDodge = false; bool hazardDamageBoost = false; + bool hazardDefenseBoost = false; void onCreatureEnter(std::shared_ptr creature); void onCreatureLeave(std::shared_ptr creature); diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 47789f5ba18..64807ec0f89 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -2482,6 +2482,9 @@ void Player::onBlockHit() { } } +void Player::onTakeDamage(std::shared_ptr attacker, int32_t damage) { +} + void Player::onAttackedCreatureBlockHit(BlockType_t blockType) { lastAttackBlockType = blockType; @@ -7725,6 +7728,17 @@ void Player::parseAttackDealtHazardSystem(CombatDamage &damage, std::shared_ptr< return; } } + if (monster->getHazardSystemDefenseBoost()) { + stage = points * static_cast(g_configManager().getNumber(HAZARD_DEFENSE_MULTIPLIER, __FUNCTION__)); + if (stage != 0) { + damage.exString = "(hazard -" + std::to_string(stage / 100) + "%)"; + damage.primary.value -= static_cast(std::ceil((static_cast(damage.primary.value) * stage) / 10000)); + if (damage.secondary.value != 0) { + damage.secondary.value -= static_cast(std::ceil((static_cast(damage.secondary.value) * stage) / 10000)); + } + return; + } + } } /******************************************************************************* diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index a33c538c814..c717f84c6f4 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -936,6 +936,7 @@ class Player final : public Creature, public Cylinder, public Bankable { void onGainSharedExperience(uint64_t gainExp, std::shared_ptr target); void onAttackedCreatureBlockHit(BlockType_t blockType) override; void onBlockHit() override; + void onTakeDamage(std::shared_ptr attacker, int32_t damage) override; void onChangeZone(ZoneType_t zone) override; void onAttackedCreatureChangeZone(ZoneType_t zone) override; void onIdleStatus() override; diff --git a/src/lua/functions/creatures/monster/monster_functions.cpp b/src/lua/functions/creatures/monster/monster_functions.cpp index 5e32115a21c..0c6bcf8c5bf 100644 --- a/src/lua/functions/creatures/monster/monster_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_functions.cpp @@ -589,7 +589,24 @@ int MonsterFunctions::luaMonsterHazardDamageBoost(lua_State* L) { pushBoolean(L, monster->getHazardSystemDamageBoost()); } else { monster->setHazardSystemDamageBoost(hazardDamageBoost); - pushBoolean(L, monster->getHazardSystemCrit()); + pushBoolean(L, monster->getHazardSystemDamageBoost()); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int MonsterFunctions::luaMonsterHazardDefenseBoost(lua_State* L) { + // get: monster:hazardDefenseBoost() ; set: monster:hazardDefenseBoost(hazardDefenseBoost) + std::shared_ptr monster = getUserdataShared(L, 1); + bool hazardDefenseBoost = getBoolean(L, 2, false); + if (monster) { + if (lua_gettop(L) == 1) { + pushBoolean(L, monster->getHazardSystemDefenseBoost()); + } else { + monster->setHazardSystemDefenseBoost(hazardDefenseBoost); + pushBoolean(L, monster->getHazardSystemDefenseBoost()); } } else { lua_pushnil(L); diff --git a/src/lua/functions/creatures/monster/monster_functions.hpp b/src/lua/functions/creatures/monster/monster_functions.hpp index 9dbb3f81cc9..a74253a1905 100644 --- a/src/lua/functions/creatures/monster/monster_functions.hpp +++ b/src/lua/functions/creatures/monster/monster_functions.hpp @@ -61,6 +61,7 @@ class MonsterFunctions final : LuaScriptInterface { registerMethod(L, "Monster", "hazardCrit", MonsterFunctions::luaMonsterHazardCrit); registerMethod(L, "Monster", "hazardDodge", MonsterFunctions::luaMonsterHazardDodge); registerMethod(L, "Monster", "hazardDamageBoost", MonsterFunctions::luaMonsterHazardDamageBoost); + registerMethod(L, "Monster", "hazardDefenseBoost", MonsterFunctions::luaMonsterHazardDefenseBoost); CharmFunctions::init(L); LootFunctions::init(L); @@ -120,6 +121,7 @@ class MonsterFunctions final : LuaScriptInterface { static int luaMonsterHazardCrit(lua_State* L); static int luaMonsterHazardDodge(lua_State* L); static int luaMonsterHazardDamageBoost(lua_State* L); + static int luaMonsterHazardDefenseBoost(lua_State* L); friend class CreatureFunctions; }; From c4284e242ae294ccb70bbaec0d3f569da8830d71 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 28 Dec 2023 10:50:22 -0800 Subject: [PATCH 12/28] feat: missing creatures and bosses (#2004) Adds a chunk of missing content such as "The Brain Head", "The Monster" (no lever yet, that's in another PR) and Oskayaat creatures along with a few other fixes to creature scripts/spells. --------- Co-authored-by: Elson Costa --- .../monster/amphibics/makara.lua | 15 +- .../monster/aquatics/deathling_scout.lua | 2 +- .../aquatics/deathling_spellsinger.lua | 2 +- .../monster/aquatics/deepling_brawler.lua | 2 +- .../monster/aquatics/deepling_elite.lua | 2 +- .../monster/aquatics/deepling_guard.lua | 2 +- .../monster/aquatics/deepling_scout.lua | 2 +- .../monster/aquatics/deepling_spellsinger.lua | 2 +- .../monster/aquatics/deepling_tyrant.lua | 2 +- .../monster/aquatics/deepling_warrior.lua | 2 +- .../monster/aquatics/deepling_worker.lua | 2 +- .../monster/birds/dire_penguin.lua | 8 +- .../monster/bosses/antenna.lua | 96 ++++++++++++ .../monster/bosses/cerebellum.lua | 94 ++++++++++++ .../monster/bosses/doctor_marrow.lua | 7 +- .../monster/bosses/the_monster.lua | 133 ++++++++++++++++ data-otservbr-global/monster/demons/demon.lua | 5 +- .../monster/giants/ogre_brute.lua | 2 +- .../monster/giants/ogre_rowdy.lua | 2 +- .../monster/giants/ogre_ruffian.lua | 2 +- .../monster/giants/ogre_sage.lua | 2 +- .../humanoids/crazed_summer_rearguard.lua | 8 +- .../monster/humanoids/troll_guard.lua | 10 +- .../monster/humans/black_sphinx_acolyte.lua | 2 +- .../monster/humans/burning_gladiator.lua | 2 +- .../humans/hardened_usurper_archer.lua | 2 +- .../humans/hardened_usurper_knight.lua | 2 +- .../humans/hardened_usurper_warlock.lua | 2 +- .../humans/priestess_of_the_wild_sun.lua | 2 +- .../monster/humans/usurper_archer.lua | 2 +- .../monster/humans/usurper_knight.lua | 2 +- .../monster/humans/usurper_warlock.lua | 2 +- .../lycanthropes/cunning_werepanther.lua | 131 ++++++++++++++++ .../lycanthropes/feral_werecrocodile.lua | 127 +++++++++++++++ .../monster/lycanthropes/werecrocodile.lua | 126 +++++++++++++++ .../monster/lycanthropes/werepanther.lua | 131 ++++++++++++++++ .../monster/lycanthropes/weretiger.lua | 126 +++++++++++++++ .../monster/lycanthropes/white_weretiger.lua | 124 +++++++++++++++ .../monster/magicals/blue_djinn.lua | 2 +- .../monster/magicals/crypt_warden.lua | 2 +- .../monster/magicals/efreet.lua | 2 +- .../monster/magicals/feral_sphinx.lua | 29 ++-- .../monster/magicals/green_djinn.lua | 2 +- .../monster/magicals/guzzlemaw.lua | 3 +- .../monster/magicals/lamassu.lua | 9 +- .../monster/magicals/lumbering_carnivor.lua | 30 ++-- .../monster/magicals/marid.lua | 2 +- .../monster/magicals/menacing_carnivor.lua | 44 +++--- .../monster/magicals/phantasm_summon.lua | 6 +- .../monster/magicals/sphinx.lua | 2 +- .../monster/magicals/spiky_carnivor.lua | 36 ++--- .../monster/mammals/white_lion.lua | 2 +- .../monster/mammals/white_tiger.lua | 112 ++++++++++++++ .../cults_of_tibia/animated_ogre_brute.lua | 1 - .../cults_of_tibia/bosses/ravenous_hunger.lua | 1 - .../ferumbras_ascendant/ferumbras_essence.lua | 4 +- .../grave_danger/bosses/sir_nictros.lua | 16 +- .../quests/grave_danger/frozen_soul.lua | 4 +- .../quests/grave_danger/soul_scourge.lua | 4 +- .../in_service_of_yalahar/rift_lord.lua | 3 - .../in_service_of_yalahar/rift_phantom.lua | 3 - .../primal_ordeal_quest/the_primal_menace.lua | 16 +- .../the_explorer_society/blue_butterfly.lua | 15 ++ .../the_explorer_society/pink_butterfly.lua | 15 ++ .../the_explorer_society/purple_butterfly.lua | 15 ++ .../bosses/ancient_lion_knight.lua | 2 +- .../the_secret_library/bosses/ghulosh.lua | 4 +- .../the_secret_library/bosses/gorzindel.lua | 3 +- .../quests/the_secret_library/lokathmor.lua | 2 +- .../monster/reptiles/adult_goanna.lua | 56 ++++--- .../monster/reptiles/boar_man.lua | 8 +- .../monster/reptiles/carnivostrich.lua | 2 +- .../monster/reptiles/crape_man.lua | 20 +-- .../monster/reptiles/fungosaurus.lua | 4 +- .../monster/reptiles/harpy.lua | 8 +- .../monster/reptiles/liodile.lua | 6 +- .../monster/reptiles/naga_archer.lua | 23 +-- .../monster/reptiles/naga_warrior.lua | 18 +-- .../monster/reptiles/rhindeer.lua | 10 +- .../monster/reptiles/two-headed_turtle.lua | 16 +- .../monster/reptiles/young_goanna.lua | 46 +++--- .../monster/trainers/training_machine.lua | 13 +- .../monster/vermins/diremaw.lua | 2 +- .../adventures_of_galthen/ahau_lever.lua | 24 +++ .../adventures_of_galthen/idol_of_tukh.lua | 31 ++++ .../feaster_of_souls/portal_brain_head.lua | 106 +++++++++++++ .../creaturescripts/monster/invulnerable.lua | 18 +++ .../scripts/spells/monster/death_barrage.lua | 29 ++++ .../spells/monster/destroy_magic_walls.lua | 33 ++++ .../spells/monster/diabolic_imp_fireball.lua | 26 ++++ .../monster/doctor_marrow_explosion.lua | 90 +++++++++++ .../scripts/spells/monster/earth_barrage.lua | 29 ++++ .../scripts/spells/monster/energy_barrage.lua | 29 ++++ .../scripts/spells/monster/exploding_cask.lua | 106 +++++++++++++ .../scripts/spells/monster/fire_barrage.lua | 29 ++++ .../spells/monster/fire_wave_delayed.lua | 144 ++++++++++++++++++ .../spells/monster/half_circle_wave_earth.lua | 30 ++++ .../spells/monster/heal_brain_head.lua | 27 ++++ .../scripts/spells/monster/holy_barrage.lua | 29 ++++ .../scripts/spells/monster/ice_barrage.lua | 29 ++++ .../scripts/spells/monster/mort_ring.lua | 17 +++ .../spells/monster/physical_barrage.lua | 29 ++++ .../spells/monster/teleport_strike.lua | 64 ++++++++ .../monster/werecrocodile_fire_ring.lua | 33 ++++ .../monster/white_weretiger_ice_ring.lua | 78 ++++++++++ data-otservbr-global/world/otservbr-zones.xml | 4 +- 106 files changed, 2570 insertions(+), 274 deletions(-) create mode 100644 data-otservbr-global/monster/bosses/antenna.lua create mode 100644 data-otservbr-global/monster/bosses/cerebellum.lua create mode 100644 data-otservbr-global/monster/bosses/the_monster.lua create mode 100644 data-otservbr-global/monster/lycanthropes/cunning_werepanther.lua create mode 100644 data-otservbr-global/monster/lycanthropes/feral_werecrocodile.lua create mode 100644 data-otservbr-global/monster/lycanthropes/werecrocodile.lua create mode 100644 data-otservbr-global/monster/lycanthropes/werepanther.lua create mode 100644 data-otservbr-global/monster/lycanthropes/weretiger.lua create mode 100644 data-otservbr-global/monster/lycanthropes/white_weretiger.lua create mode 100644 data-otservbr-global/monster/mammals/white_tiger.lua create mode 100644 data-otservbr-global/scripts/actions/quests/adventures_of_galthen/ahau_lever.lua create mode 100644 data-otservbr-global/scripts/actions/quests/adventures_of_galthen/idol_of_tukh.lua create mode 100644 data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua create mode 100644 data-otservbr-global/scripts/creaturescripts/monster/invulnerable.lua create mode 100644 data-otservbr-global/scripts/spells/monster/death_barrage.lua create mode 100644 data-otservbr-global/scripts/spells/monster/destroy_magic_walls.lua create mode 100644 data-otservbr-global/scripts/spells/monster/diabolic_imp_fireball.lua create mode 100644 data-otservbr-global/scripts/spells/monster/doctor_marrow_explosion.lua create mode 100644 data-otservbr-global/scripts/spells/monster/earth_barrage.lua create mode 100644 data-otservbr-global/scripts/spells/monster/energy_barrage.lua create mode 100644 data-otservbr-global/scripts/spells/monster/exploding_cask.lua create mode 100644 data-otservbr-global/scripts/spells/monster/fire_barrage.lua create mode 100644 data-otservbr-global/scripts/spells/monster/fire_wave_delayed.lua create mode 100644 data-otservbr-global/scripts/spells/monster/half_circle_wave_earth.lua create mode 100644 data-otservbr-global/scripts/spells/monster/heal_brain_head.lua create mode 100644 data-otservbr-global/scripts/spells/monster/holy_barrage.lua create mode 100644 data-otservbr-global/scripts/spells/monster/ice_barrage.lua create mode 100644 data-otservbr-global/scripts/spells/monster/mort_ring.lua create mode 100644 data-otservbr-global/scripts/spells/monster/physical_barrage.lua create mode 100644 data-otservbr-global/scripts/spells/monster/teleport_strike.lua create mode 100644 data-otservbr-global/scripts/spells/monster/werecrocodile_fire_ring.lua create mode 100644 data-otservbr-global/scripts/spells/monster/white_weretiger_ice_ring.lua diff --git a/data-otservbr-global/monster/amphibics/makara.lua b/data-otservbr-global/monster/amphibics/makara.lua index 66bd91a17dd..a0da9573236 100644 --- a/data-otservbr-global/monster/amphibics/makara.lua +++ b/data-otservbr-global/monster/amphibics/makara.lua @@ -54,7 +54,7 @@ monster.flags = { canPushCreatures = true, staticAttackChance = 90, targetDistance = 1, - runHealth = 10, + runHealth = 0, healthHidden = false, isBlockable = false, canWalkOnEnergy = false, @@ -71,12 +71,13 @@ monster.voices = { interval = 5000, chance = 10, { text = "waddle waddle", yell = false }, + { text = "Nihahaha!", yell = false }, } monster.loot = { - { name = "platinum coin", chance = 100000, maxCount = 13 }, + { name = "platinum coin", chance = 100000, maxCount = 18 }, { name = "makara tongue", chance = 10160 }, - { name = "makara fin", chance = 7420 }, + { name = "makara fin", chance = 7420, maxCount = 2 }, { name = "meat", chance = 7030, maxCount = 2 }, { name = "cyan crystal fragment", chance = 4300 }, { name = "yellow gem", chance = 4100 }, @@ -90,10 +91,10 @@ monster.loot = { monster.attacks = { { name = "combat", interval = 2000, chance = 100, type = COMBAT_PHYSICALDAMAGE, minDamage = -145, maxDamage = -390, target = true }, -- basic_attack - { name = "combat", interval = 2000, chance = 25, type = COMBAT_EARTHDAMAGE, minDamage = -305, maxDamage = -390, radius = 3, effect = CONST_ME_STONES, shootEffect = CONST_ANI_EARTH, target = true }, -- stone_shower_ball - { name = "combat", interval = 2000, chance = 25, type = COMBAT_EARTHDAMAGE, minDamage = -305, maxDamage = -390, radius = 5, effect = CONST_ME_STONES, shootEffect = CONST_ANI_EARTH, target = true }, -- great_stone_shower_ball - { name = "combat", interval = 2000, chance = 25, type = COMBAT_ICEDAMAGE, minDamage = -360, maxDamage = -390, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = true }, -- ice_strike - { name = "makarawatersplash", interval = 2000, chance = 25, minDamage = -380, maxDamage = -455, target = false }, -- short_water_cone-wave + { name = "combat", interval = 2500, chance = 25, type = COMBAT_EARTHDAMAGE, minDamage = -305, maxDamage = -390, radius = 3, effect = CONST_ME_STONES, shootEffect = CONST_ANI_EARTH, target = true }, -- stone_shower_ball + { name = "combat", interval = 3000, chance = 20, type = COMBAT_EARTHDAMAGE, minDamage = -305, maxDamage = -390, radius = 5, effect = CONST_ME_STONES, shootEffect = CONST_ANI_EARTH, target = true }, -- great_stone_shower_ball + { name = "combat", interval = 3500, chance = 20, type = COMBAT_ICEDAMAGE, minDamage = -360, maxDamage = -390, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = true }, -- ice_strike + { name = "makarawatersplash", interval = 4000, chance = 25, minDamage = -380, maxDamage = -455, target = false }, -- short_water_cone-wave } monster.defenses = { diff --git a/data-otservbr-global/monster/aquatics/deathling_scout.lua b/data-otservbr-global/monster/aquatics/deathling_scout.lua index d0a54d6b17a..23b959b41fe 100644 --- a/data-otservbr-global/monster/aquatics/deathling_scout.lua +++ b/data-otservbr-global/monster/aquatics/deathling_scout.lua @@ -34,7 +34,7 @@ monster.speed = 155 monster.manaCost = 0 monster.faction = FACTION_DEATHLING -monster.enemyFactions = { FACTION_DEEPLING, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_DEEPLING } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/aquatics/deathling_spellsinger.lua b/data-otservbr-global/monster/aquatics/deathling_spellsinger.lua index 68d5df39ea4..a7fdf805bc8 100644 --- a/data-otservbr-global/monster/aquatics/deathling_spellsinger.lua +++ b/data-otservbr-global/monster/aquatics/deathling_spellsinger.lua @@ -34,7 +34,7 @@ monster.speed = 155 monster.manaCost = 0 monster.faction = FACTION_DEATHLING -monster.enemyFactions = { FACTION_DEEPLING, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_DEEPLING } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/aquatics/deepling_brawler.lua b/data-otservbr-global/monster/aquatics/deepling_brawler.lua index ed81778f449..bc44da075f9 100644 --- a/data-otservbr-global/monster/aquatics/deepling_brawler.lua +++ b/data-otservbr-global/monster/aquatics/deepling_brawler.lua @@ -34,7 +34,7 @@ monster.speed = 85 monster.manaCost = 0 monster.faction = FACTION_DEEPLING -monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/aquatics/deepling_elite.lua b/data-otservbr-global/monster/aquatics/deepling_elite.lua index a80ee5575cb..c990f4b0447 100644 --- a/data-otservbr-global/monster/aquatics/deepling_elite.lua +++ b/data-otservbr-global/monster/aquatics/deepling_elite.lua @@ -34,7 +34,7 @@ monster.speed = 165 monster.manaCost = 0 monster.faction = FACTION_DEEPLING -monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/aquatics/deepling_guard.lua b/data-otservbr-global/monster/aquatics/deepling_guard.lua index c77609e5964..23f87f55459 100644 --- a/data-otservbr-global/monster/aquatics/deepling_guard.lua +++ b/data-otservbr-global/monster/aquatics/deepling_guard.lua @@ -35,7 +35,7 @@ monster.speed = 135 monster.manaCost = 0 monster.faction = FACTION_DEEPLING -monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/aquatics/deepling_scout.lua b/data-otservbr-global/monster/aquatics/deepling_scout.lua index d7ce0c2db19..dadb1d8a19a 100644 --- a/data-otservbr-global/monster/aquatics/deepling_scout.lua +++ b/data-otservbr-global/monster/aquatics/deepling_scout.lua @@ -34,7 +34,7 @@ monster.speed = 65 monster.manaCost = 0 monster.faction = FACTION_DEEPLING -monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/aquatics/deepling_spellsinger.lua b/data-otservbr-global/monster/aquatics/deepling_spellsinger.lua index 56557793784..15efdb2cf87 100644 --- a/data-otservbr-global/monster/aquatics/deepling_spellsinger.lua +++ b/data-otservbr-global/monster/aquatics/deepling_spellsinger.lua @@ -34,7 +34,7 @@ monster.speed = 95 monster.manaCost = 0 monster.faction = FACTION_DEEPLING -monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/aquatics/deepling_tyrant.lua b/data-otservbr-global/monster/aquatics/deepling_tyrant.lua index 290aeee78d2..59a2a2ca79d 100644 --- a/data-otservbr-global/monster/aquatics/deepling_tyrant.lua +++ b/data-otservbr-global/monster/aquatics/deepling_tyrant.lua @@ -34,7 +34,7 @@ monster.speed = 155 monster.manaCost = 0 monster.faction = FACTION_DEEPLING -monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/aquatics/deepling_warrior.lua b/data-otservbr-global/monster/aquatics/deepling_warrior.lua index 7f31c9ae504..1da00f6c2a7 100644 --- a/data-otservbr-global/monster/aquatics/deepling_warrior.lua +++ b/data-otservbr-global/monster/aquatics/deepling_warrior.lua @@ -34,7 +34,7 @@ monster.speed = 145 monster.manaCost = 0 monster.faction = FACTION_DEEPLING -monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/aquatics/deepling_worker.lua b/data-otservbr-global/monster/aquatics/deepling_worker.lua index cb958c11553..395f4ed57be 100644 --- a/data-otservbr-global/monster/aquatics/deepling_worker.lua +++ b/data-otservbr-global/monster/aquatics/deepling_worker.lua @@ -34,7 +34,7 @@ monster.speed = 65 monster.manaCost = 0 monster.faction = FACTION_DEEPLING -monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/birds/dire_penguin.lua b/data-otservbr-global/monster/birds/dire_penguin.lua index 6eb73eff344..d0b70ec954a 100644 --- a/data-otservbr-global/monster/birds/dire_penguin.lua +++ b/data-otservbr-global/monster/birds/dire_penguin.lua @@ -17,10 +17,10 @@ monster.raceId = 335 monster.Bestiary = { class = "Bird", race = BESTY_RACE_BIRD, - toKill = 500, - FirstUnlock = 25, - SecondUnlock = 250, - CharmsPoints = 15, + toKill = 5, + FirstUnlock = 1, + SecondUnlock = 3, + CharmsPoints = 30, Stars = 2, Occurrence = 3, Locations = "Any place with penguins like, Formorgar Glacier, Helheim, Tyrsung or Svargrond. \z diff --git a/data-otservbr-global/monster/bosses/antenna.lua b/data-otservbr-global/monster/bosses/antenna.lua new file mode 100644 index 00000000000..f1a165177cc --- /dev/null +++ b/data-otservbr-global/monster/bosses/antenna.lua @@ -0,0 +1,96 @@ +local mType = Game.createMonsterType("Antenna") +local monster = {} + +monster.description = "Antenna" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 850, +} + +monster.health = 5000 +monster.maxHealth = 5000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 0 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 15, +} + +monster.strategiesTarget = { + nearest = 60, + health = 30, + damage = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + critChance = 10, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = {} + +monster.defenses = { + defense = 54, + armor = 59, + mitigation = 3.7, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onThink = function(monster, interval) end + +mType.onAppear = function(monster, creature) + if monster:getType():isRewardBoss() then + monster:setReward(true) + end +end + +mType.onDisappear = function(monster, creature) end + +mType.onMove = function(monster, creature, fromPosition, toPosition) end + +mType.onSay = function(monster, creature, type, message) end + +mType:register(monster) diff --git a/data-otservbr-global/monster/bosses/cerebellum.lua b/data-otservbr-global/monster/bosses/cerebellum.lua new file mode 100644 index 00000000000..b83fcec8615 --- /dev/null +++ b/data-otservbr-global/monster/bosses/cerebellum.lua @@ -0,0 +1,94 @@ +local mType = Game.createMonsterType("Cerebellum") +local monster = {} + +monster.description = "Cerebellum" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 32572, +} + +monster.health = 20000 +monster.maxHealth = monster.health +monster.race = "undead" +monster.corpse = 32576 +monster.speed = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.loot = {} + +monster.attacks = { + { name = "combat", interval = 2000, chance = 50, type = COMBAT_ENERGYDAMAGE, minDamage = -90, maxDamage = -180, range = 7, shootEffect = CONST_ANI_ENERGY, target = true }, + { name = "heal brain head", interval = 2000, chance = 10, target = false }, +} + +monster.defenses = { + defense = 78, + armor = 78, + mitigation = 3.27, + { name = "combat", type = COMBAT_HEALING, chance = 15, interval = 2000, minDamage = 50, maxDamage = 200, effect = CONST_ME_MAGIC_BLUE }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = -30 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "invisible", condition = true }, +} + +monster.voices = { + interval = 5000, + chance = 10, + { text = "Feel the power of death unleashed!", yell = false }, + { text = "I will rule again and my realm of death will span the world!", yell = false }, + { text = "My lich-knights will conquer this world for me!", yell = false }, +} + +mType.onThink = function(monster, interval) end + +mType.onAppear = function(monster, creature) + if monster:getType():isRewardBoss() then + monster:setReward(true) + end +end + +mType.onDisappear = function(monster, creature) end + +mType.onMove = function(monster, creature, fromPosition, toPosition) end + +mType.onSay = function(monster, creature, type, message) end + +mType:register(monster) diff --git a/data-otservbr-global/monster/bosses/doctor_marrow.lua b/data-otservbr-global/monster/bosses/doctor_marrow.lua index 308ab14b27b..cccdd2e4d57 100644 --- a/data-otservbr-global/monster/bosses/doctor_marrow.lua +++ b/data-otservbr-global/monster/bosses/doctor_marrow.lua @@ -22,7 +22,7 @@ monster.manaCost = 0 monster.changeTarget = { interval = 4000, - chance = 15, + chance = 10, } monster.strategiesTarget = { @@ -65,12 +65,17 @@ monster.voices = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -2800 }, + { name = "combat", interval = 3000, chance = 20, type = COMBAT_LIFEDRAIN, minDamage = -50, maxDamage = -2800, effect = CONST_ME_MAGIC_RED, target = false, radius = 3 }, + { name = "doctor marrow explosion", interval = 10000, chance = 25, target = true, range = 1 }, + { name = "root", interval = 4000, chance = 10, target = true }, + { name = "fear", interval = 3500, chance = 10, target = true }, } monster.defenses = { defense = 54, armor = 59, mitigation = 3.7, + { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 900, maxDamage = 2400, effect = CONST_ME_MAGIC_BLUE, target = false }, } monster.elements = { diff --git a/data-otservbr-global/monster/bosses/the_monster.lua b/data-otservbr-global/monster/bosses/the_monster.lua new file mode 100644 index 00000000000..c7f84935b20 --- /dev/null +++ b/data-otservbr-global/monster/bosses/the_monster.lua @@ -0,0 +1,133 @@ +local mType = Game.createMonsterType("The Monster") +local monster = {} + +monster.description = "The Monster" +monster.experience = 30000 +monster.outfit = { + lookType = 1600, +} + +monster.bosstiary = { + bossRaceId = 2299, + bossRace = RARITY_ARCHFOE, +} + +monster.health = 450000 +monster.maxHealth = 450000 +monster.race = "blood" +monster.corpse = 42247 +monster.speed = 180 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 15, +} + +monster.strategiesTarget = { + nearest = 60, + health = 30, + damage = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = true, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + critChance = 10, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.loot = { + { name = "platinum coin", chance = 100000, maxcount = 30 }, + { id = 3039, chance = 35542, maxCount = 2 }, -- red gem + { name = "ultimate health potion", chance = 27000, maxcount = 7 }, + { name = "ultimate mana potion", chance = 24300, maxcount = 5 }, + { name = "ultimate spirit potion", chance = 25750, maxcount = 4 }, + { name = "mastermind potion", chance = 23200, maxcount = 3 }, + { name = "berserk potion", chance = 24800, maxcount = 3 }, + { name = "bullseye potion", chance = 23500, maxcount = 3 }, + { name = "yellow gem", chance = 26200, maxcount = 5 }, + { name = "blue gem", chance = 25100 }, + { name = "green gem", chance = 24600 }, + { name = "violet gem", chance = 25350 }, + { name = "giant amethyst", chance = 4300 }, + { name = "giant topaz", chance = 4600 }, + { name = "giant emerald", chance = 4500 }, + { id = 33778, chance = 900 }, -- raw watermelon turmaline + { name = "alchemist's notepad", chance = 420 }, + { name = "antler-horn helmet", chance = 390 }, + { name = "mutant bone kilt", chance = 450 }, + { name = "mutated skin armor", chance = 430 }, + { name = "mutated skin legs", chance = 410 }, + { name = "stitched mutant hide legs", chance = 440 }, + { name = "alchemist's boots", chance = 460 }, + { name = "mutant bone boots", chance = 400 }, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -2800 }, + { name = "combat", interval = 2000, chance = 35, type = COMBAT_ENERGYDAMAGE, minDamage = -600, maxDamage = -1200, effect = CONST_ME_ENERGYAREA, target = true, radius = 5, range = 3 }, + { name = "destroy magic walls", interval = 1000, chance = 50 }, +} + +monster.defenses = { + defense = 54, + armor = 59, + mitigation = 3.7, + { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 900, maxDamage = 2400, effect = CONST_ME_MAGIC_BLUE, target = false }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onThink = function(monster, interval) end + +mType.onAppear = function(monster, creature) + if monster:getType():isRewardBoss() then + monster:setReward(true) + end +end + +mType.onDisappear = function(monster, creature) end + +mType.onMove = function(monster, creature, fromPosition, toPosition) end + +mType.onSay = function(monster, creature, type, message) end + +mType:register(monster) diff --git a/data-otservbr-global/monster/demons/demon.lua b/data-otservbr-global/monster/demons/demon.lua index 3a1208c210b..0839ae00b41 100644 --- a/data-otservbr-global/monster/demons/demon.lua +++ b/data-otservbr-global/monster/demons/demon.lua @@ -23,10 +23,7 @@ monster.Bestiary = { CharmsPoints = 50, Stars = 4, Occurrence = 0, - Locations = "Hero Cave, Ferumbras' Citadel, Goroma, Ghostlands Warlock area unreachable, \z - Liberty Bay hidden underground passage unreachable, Razachai, deep in Pits of Inferno (found in every throneroom except Verminor's), \z - deep Formorgar Mines, Demon Forge, Alchemist Quarter, Magician Quarter, Chyllfroest, Oramond Dungeon, \z - Abandoned Sewers, Hell Hub and Halls of Ascension.", + Locations = "Hero Cave, Ferumbras' Citadel, Goroma, Ghostlands Warlock area unreachable, Liberty Bay hidden underground passage unreachable, Razachai, deep in Pits of Inferno (found in every throneroom except Verminor's), deep Formorgar Mines, Demon Forge, Alchemist Quarter, Magician Quarter, Chyllfroest, Oramond Dungeon, Abandoned Sewers, Hell Hub and Halls of Ascension.", } monster.health = 8200 diff --git a/data-otservbr-global/monster/giants/ogre_brute.lua b/data-otservbr-global/monster/giants/ogre_brute.lua index 26272cc0716..10f479ce379 100644 --- a/data-otservbr-global/monster/giants/ogre_brute.lua +++ b/data-otservbr-global/monster/giants/ogre_brute.lua @@ -95,7 +95,7 @@ monster.loot = { { id = 7428, chance = 500 }, -- bonebreaker { id = 22171, chance = 800 }, -- ogre klubba { id = 3465, chance = 500 }, -- pot - { id = 8906, chance = 200 }, -- heavily rusted helmet + { name = "rusted helmet", chance = 220 }, { id = 22192, chance = 300 }, -- shamanic mask } diff --git a/data-otservbr-global/monster/giants/ogre_rowdy.lua b/data-otservbr-global/monster/giants/ogre_rowdy.lua index c479a8f9c7b..70b45bedd2e 100644 --- a/data-otservbr-global/monster/giants/ogre_rowdy.lua +++ b/data-otservbr-global/monster/giants/ogre_rowdy.lua @@ -34,7 +34,7 @@ monster.speed = 210 monster.manaCost = 0 monster.faction = FACTION_ANUMA -monster.enemyFactions = { FACTION_FAFNAR, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_FAFNAR } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/giants/ogre_ruffian.lua b/data-otservbr-global/monster/giants/ogre_ruffian.lua index fd9ab8232f1..aa561883463 100644 --- a/data-otservbr-global/monster/giants/ogre_ruffian.lua +++ b/data-otservbr-global/monster/giants/ogre_ruffian.lua @@ -34,7 +34,7 @@ monster.speed = 215 monster.manaCost = 0 monster.faction = FACTION_ANUMA -monster.enemyFactions = { FACTION_FAFNAR, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_FAFNAR } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/giants/ogre_sage.lua b/data-otservbr-global/monster/giants/ogre_sage.lua index 83bb6ce0b71..5f619a75ce7 100644 --- a/data-otservbr-global/monster/giants/ogre_sage.lua +++ b/data-otservbr-global/monster/giants/ogre_sage.lua @@ -34,7 +34,7 @@ monster.speed = 230 monster.manaCost = 0 monster.faction = FACTION_ANUMA -monster.enemyFactions = { FACTION_FAFNAR, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_FAFNAR } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/humanoids/crazed_summer_rearguard.lua b/data-otservbr-global/monster/humanoids/crazed_summer_rearguard.lua index 03c8a6000c3..8e269d760c4 100644 --- a/data-otservbr-global/monster/humanoids/crazed_summer_rearguard.lua +++ b/data-otservbr-global/monster/humanoids/crazed_summer_rearguard.lua @@ -98,11 +98,9 @@ monster.loot = { } monster.attacks = { - { name = "melee", interval = 2000, chance = 100, minDamage = -210, maxDamage = -530 }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_FIREDAMAGE, minDamage = -270, maxDamage = -710, length = 3, spread = 0, effect = CONST_ME_FIREAREA, target = false }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_FIREDAMAGE, minDamage = -250, maxDamage = -300, range = 7, shootEffect = CONST_ANI_FIRE, target = false }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_FIREDAMAGE, minDamage = -350, maxDamage = -380, radius = 5, effect = CONST_ME_EXPLOSIONHIT, target = true }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_FIREDAMAGE, minDamage = -200, maxDamage = -350, radius = 5, effect = CONST_ME_EXPLOSIONAREA, target = true }, + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -450 }, + { name = "combat", interval = 2500, chance = 30, type = COMBAT_FIREDAMAGE, minDamage = -150, maxDamage = -300, range = 6, effect = CONST_ME_FIREATTACK, target = true }, + { name = "combat", interval = 3000, chance = 30, type = COMBAT_FIREDAMAGE, minDamage = -200, maxDamage = -300, range = 6, radius = 2, effect = CONST_ME_FIREAREA, target = true }, } monster.defenses = { diff --git a/data-otservbr-global/monster/humanoids/troll_guard.lua b/data-otservbr-global/monster/humanoids/troll_guard.lua index a796c9226dc..67887f566c4 100644 --- a/data-otservbr-global/monster/humanoids/troll_guard.lua +++ b/data-otservbr-global/monster/humanoids/troll_guard.lua @@ -17,13 +17,13 @@ monster.raceId = 745 monster.Bestiary = { class = "Humanoid", race = BESTY_RACE_HUMANOID, - toKill = 500, - FirstUnlock = 25, - SecondUnlock = 250, - CharmsPoints = 15, + toKill = 5, + FirstUnlock = 1, + SecondUnlock = 3, + CharmsPoints = 30, Stars = 2, Occurrence = 3, - Locations = "Rookgaards central cave in the Mapper Coords125.64125.136104textnew western Troll tunnel, north-west of Carlin during raids and Thais Knights Guild arena during raids on Kingsday Mini World ChangeKingsday.", + Locations = "Rookgaard and in Thais during raids", } monster.health = 60 diff --git a/data-otservbr-global/monster/humans/black_sphinx_acolyte.lua b/data-otservbr-global/monster/humans/black_sphinx_acolyte.lua index fc98bcab36d..924b18fd4f0 100644 --- a/data-otservbr-global/monster/humans/black_sphinx_acolyte.lua +++ b/data-otservbr-global/monster/humans/black_sphinx_acolyte.lua @@ -34,7 +34,7 @@ monster.speed = 155 monster.manaCost = 0 monster.faction = FACTION_FAFNAR -monster.enemyFactions = { FACTION_ANUMA, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_ANUMA } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/humans/burning_gladiator.lua b/data-otservbr-global/monster/humans/burning_gladiator.lua index 8923d0928cb..b2ba5ba4d9f 100644 --- a/data-otservbr-global/monster/humans/burning_gladiator.lua +++ b/data-otservbr-global/monster/humans/burning_gladiator.lua @@ -38,7 +38,7 @@ monster.speed = 145 monster.manaCost = 0 monster.faction = FACTION_FAFNAR -monster.enemyFactions = { FACTION_ANUMA, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_ANUMA } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/humans/hardened_usurper_archer.lua b/data-otservbr-global/monster/humans/hardened_usurper_archer.lua index cbfe82cd9eb..3da9570b2a1 100644 --- a/data-otservbr-global/monster/humans/hardened_usurper_archer.lua +++ b/data-otservbr-global/monster/humans/hardened_usurper_archer.lua @@ -21,7 +21,7 @@ monster.speed = 125 monster.manaCost = 0 monster.faction = FACTION_LIONUSURPERS -monster.enemyFactions = { FACTION_LION, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_LION } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/humans/hardened_usurper_knight.lua b/data-otservbr-global/monster/humans/hardened_usurper_knight.lua index 341b85f2de2..4ae4ae081e1 100644 --- a/data-otservbr-global/monster/humans/hardened_usurper_knight.lua +++ b/data-otservbr-global/monster/humans/hardened_usurper_knight.lua @@ -21,7 +21,7 @@ monster.speed = 130 monster.manaCost = 0 monster.faction = FACTION_LIONUSURPERS -monster.enemyFactions = { FACTION_LION, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_LION } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/humans/hardened_usurper_warlock.lua b/data-otservbr-global/monster/humans/hardened_usurper_warlock.lua index 04282653df3..6b25ce5ad76 100644 --- a/data-otservbr-global/monster/humans/hardened_usurper_warlock.lua +++ b/data-otservbr-global/monster/humans/hardened_usurper_warlock.lua @@ -21,7 +21,7 @@ monster.speed = 165 monster.manaCost = 0 monster.faction = FACTION_LIONUSURPERS -monster.enemyFactions = { FACTION_LION, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_LION } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/humans/priestess_of_the_wild_sun.lua b/data-otservbr-global/monster/humans/priestess_of_the_wild_sun.lua index a97c9a3c56d..b185ccbf11d 100644 --- a/data-otservbr-global/monster/humans/priestess_of_the_wild_sun.lua +++ b/data-otservbr-global/monster/humans/priestess_of_the_wild_sun.lua @@ -38,7 +38,7 @@ monster.speed = 160 monster.manaCost = 0 monster.faction = FACTION_FAFNAR -monster.enemyFactions = { FACTION_ANUMA, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_ANUMA } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/humans/usurper_archer.lua b/data-otservbr-global/monster/humans/usurper_archer.lua index 4f01920b395..88a4b3782f7 100644 --- a/data-otservbr-global/monster/humans/usurper_archer.lua +++ b/data-otservbr-global/monster/humans/usurper_archer.lua @@ -34,7 +34,7 @@ monster.speed = 125 monster.manaCost = 0 monster.faction = FACTION_LIONUSURPERS -monster.enemyFactions = { FACTION_LION, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_LION } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/humans/usurper_knight.lua b/data-otservbr-global/monster/humans/usurper_knight.lua index 7116b67fbfa..aada0828995 100644 --- a/data-otservbr-global/monster/humans/usurper_knight.lua +++ b/data-otservbr-global/monster/humans/usurper_knight.lua @@ -34,7 +34,7 @@ monster.speed = 130 monster.manaCost = 0 monster.faction = FACTION_LIONUSURPERS -monster.enemyFactions = { FACTION_LION, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_LION } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/humans/usurper_warlock.lua b/data-otservbr-global/monster/humans/usurper_warlock.lua index 86f582d8e2c..8a3672a9d90 100644 --- a/data-otservbr-global/monster/humans/usurper_warlock.lua +++ b/data-otservbr-global/monster/humans/usurper_warlock.lua @@ -34,7 +34,7 @@ monster.speed = 165 monster.manaCost = 0 monster.faction = FACTION_LIONUSURPERS -monster.enemyFactions = { FACTION_LION, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_LION } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/lycanthropes/cunning_werepanther.lua b/data-otservbr-global/monster/lycanthropes/cunning_werepanther.lua new file mode 100644 index 00000000000..b0e6c74131f --- /dev/null +++ b/data-otservbr-global/monster/lycanthropes/cunning_werepanther.lua @@ -0,0 +1,131 @@ +local mType = Game.createMonsterType("Cunning Werepanther") +local monster = {} + +monster.description = "a cunning werepanther" +monster.experience = 3620 +monster.outfit = { + lookType = 1648, + lookHead = 18, + lookBody = 124, + lookLegs = 74, + lookFeet = 81, + lookAddons = 1, + lookMount = 0, +} + +monster.raceId = 2403 +monster.Bestiary = { + class = "Lycanthrope", + race = BESTY_RACE_LYCANTHROPE, + toKill = 2500, + FirstUnlock = 100, + SecondUnlock = 1000, + CharmsPoints = 50, + Stars = 4, + Occurrence = 0, + Locations = "Murky Caverns, Oskayaat", +} + +monster.health = 4300 +monster.maxHealth = 4300 +monster.race = "blood" +monster.corpse = 43959 +monster.speed = 125 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 5000, + chance = 20, +} + +monster.strategiesTarget = { + nearest = 70, + health = 10, + damage = 10, + random = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 80, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, + { text = "Grrr", yell = false }, +} + +monster.loot = { + { name = "gold coin", chance = 100000, maxCount = 80 }, + { name = "platinum coin", chance = 100000, maxCount = 11 }, + { name = "werepanther claw", chance = 12780 }, + { name = "golden sickle", chance = 5120 }, + { name = "meat", chance = 5500, maxCount = 2 }, + { name = "small topaz", chance = 7120, maxCount = 4 }, + { name = "moonlight crystals", chance = 2550 }, + { id = 3037, chance = 5130 }, -- yellow gem + { name = "lightning headband", chance = 7200 }, + { name = "ripper lance", chance = 850 }, + { name = "gemmed figurine", chance = 1770 }, + { id = 816, chance = 4710 }, -- lightning pendant + { name = "fur armor", chance = 2620 }, + { id = 43917, chance = 600 }, -- werepanther trophy +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -75, maxDamage = -540 }, + { name = "combat", interval = 3300, chance = 20, type = COMBAT_ENERGYDAMAGE, minDamage = -175, maxDamage = -350, radius = 2, effect = CONST_ME_ENERGYAREA, shootEffect = CONST_ANI_ENERGY, range = 4, target = true }, + { name = "combat", interval = 2300, chance = 15, type = COMBAT_ENERGYDAMAGE, minDamage = -250, maxDamage = -425, radius = 3, effect = CONST_ME_ENERGYAREA, target = false }, + { name = "combat", interval = 2700, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -225, maxDamage = -350, radius = 2, effect = CONST_ME_YELLOWSMOKE, shootEffect = CONST_ANI_LARGEROCK, range = 4, target = true }, + { name = "combat", interval = 3700, chance = 35, type = COMBAT_PHYSICALDAMAGE, minDamage = -200, maxDamage = -375, radius = 3, effect = CONST_ME_STONE_STORM, target = false }, +} + +monster.defenses = { + defense = 30, + armor = 72, + mitigation = 2.05, + { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 50, maxDamage = 100, effect = CONST_ME_MAGIC_BLUE, target = false }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 20 }, + { type = COMBAT_EARTHDAMAGE, percent = -25 }, + { type = COMBAT_FIREDAMAGE, percent = -15 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 20 }, + { type = COMBAT_HOLYDAMAGE, percent = 10 }, + { type = COMBAT_DEATHDAMAGE, percent = -10 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/lycanthropes/feral_werecrocodile.lua b/data-otservbr-global/monster/lycanthropes/feral_werecrocodile.lua new file mode 100644 index 00000000000..724e5bcced7 --- /dev/null +++ b/data-otservbr-global/monster/lycanthropes/feral_werecrocodile.lua @@ -0,0 +1,127 @@ +local mType = Game.createMonsterType("Feral Werecrocodile") +local monster = {} + +monster.description = "a feral werecrocodile" +monster.experience = 5430 +monster.outfit = { + lookType = 1647, + lookHead = 116, + lookBody = 95, + lookLegs = 19, + lookFeet = 21, + lookAddons = 0, + lookMount = 0, +} + +monster.raceId = 2389 +monster.Bestiary = { + class = "Lycanthrope", + race = BESTY_RACE_LYCANTHROPE, + toKill = 2500, + FirstUnlock = 100, + SecondUnlock = 1000, + CharmsPoints = 50, + Stars = 4, + Occurrence = 0, + Locations = "Murky Caverns", +} + +monster.health = 6400 +monster.maxHealth = 6400 +monster.race = "blood" +monster.corpse = 43767 +monster.speed = 110 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 5000, + chance = 20, +} + +monster.strategiesTarget = { + nearest = 70, + health = 10, + damage = 10, + random = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 80, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = {} + +monster.loot = { + { name = "gold coin", chance = 100000, maxCount = 100 }, + { name = "platinum coin", chance = 100000, maxCount = 21 }, + { name = "werecrocodile tongue", chance = 10800 }, + { name = "war hammer", chance = 5000 }, + { name = "ham", chance = 5500, maxCount = 2 }, + { name = "moonlight crystals", chance = 3000 }, + { name = "violet gem", chance = 1370 }, + { name = "green crystal shard", chance = 2800 }, + { name = "ornate crossbow", chance = 680 }, + { name = "terra mantle", chance = 2190 }, + { name = "golden sun coin", chance = 1820 }, + { name = "sun brooch", chance = 680 }, + { name = "swamplair armor", chance = 230 }, + { id = 43916, chance = 200 }, -- werecrocodile trophy +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -50, maxDamage = -485 }, + { name = "combat", interval = 3400, chance = 33, type = COMBAT_DEATHDAMAGE, minDamage = -300, maxDamage = -450, length = 7, spread = 3, effect = CONST_ME_MORTAREA, target = false }, + { name = "combat", interval = 2700, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -300, maxDamage = -450, radius = 3, effect = CONST_ME_MORTAREA, shootEffect = CONST_ANI_DEATH, range = 4, target = true }, + { name = "combat", interval = 3300, chance = 30, type = COMBAT_LIFEDRAIN, minDamage = -175, maxDamage = -350, radius = 1, effect = CONST_ME_MAGIC_RED, range = 1, target = true }, + { name = "werecrocodile fire ring", interval = 4100, chance = 25, minDamage = -275, maxDamage = -350, target = false }, +} + +monster.defenses = { + defense = 30, + armor = 82, + mitigation = 2.28, + { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 50, maxDamage = 100, effect = CONST_ME_MAGIC_BLUE, target = false }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 25 }, + { type = COMBAT_ENERGYDAMAGE, percent = -5 }, + { type = COMBAT_EARTHDAMAGE, percent = 20 }, + { type = COMBAT_FIREDAMAGE, percent = 35 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = -15 }, + { type = COMBAT_HOLYDAMAGE, percent = -20 }, + { type = COMBAT_DEATHDAMAGE, percent = 60 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/lycanthropes/werecrocodile.lua b/data-otservbr-global/monster/lycanthropes/werecrocodile.lua new file mode 100644 index 00000000000..125c25799c0 --- /dev/null +++ b/data-otservbr-global/monster/lycanthropes/werecrocodile.lua @@ -0,0 +1,126 @@ +local mType = Game.createMonsterType("Werecrocodile") +local monster = {} + +monster.description = "a werecrocodile" +monster.experience = 4140 +monster.outfit = { + lookType = 1647, + lookHead = 95, + lookBody = 117, + lookLegs = 4, + lookFeet = 116, + lookAddons = 0, + lookMount = 0, +} + +monster.raceId = 2403 +monster.Bestiary = { + class = "Lycanthrope", + race = BESTY_RACE_LYCANTHROPE, + toKill = 2500, + FirstUnlock = 100, + SecondUnlock = 1000, + CharmsPoints = 50, + Stars = 4, + Occurrence = 0, + Locations = "Murky Caverns", +} + +monster.health = 5280 +monster.maxHealth = 5280 +monster.race = "blood" +monster.corpse = 43754 +monster.speed = 115 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 5000, + chance = 20, +} + +monster.strategiesTarget = { + nearest = 70, + health = 10, + damage = 10, + random = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 80, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = {} + +monster.loot = { + { name = "gold coin", chance = 100000, maxCount = 100 }, + { name = "platinum coin", chance = 100000, maxCount = 13 }, + { name = "werecrocodile tongue", chance = 10800 }, + { name = "serpent sword", chance = 5910 }, + { name = "crocodile boots", chance = 8530 }, + { name = "meat", chance = 5500, maxCount = 4 }, + { name = "small topaz", chance = 9120, maxCount = 4 }, + { name = "moonlight crystals", chance = 3000 }, + { id = 3039, chance = 5120 }, -- red gem + { name = "green crystal shard", chance = 2800 }, + { name = "bonebreaker", chance = 500 }, + { name = "glorious axe", chance = 2190 }, + { name = "golden sun coin", chance = 1770 }, + { id = 43916, chance = 200 }, -- werecrocodile trophy +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -575 }, + { name = "combat", interval = 2700, chance = 37, type = COMBAT_PHYSICALDAMAGE, minDamage = -175, maxDamage = -325, radius = 1, effect = CONST_ME_BIG_SCRATCH, range = 1, target = true }, + { name = "combat", interval = 3300, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -200, maxDamage = -375, length = 6, spread = 3, effect = CONST_ME_BLACKSMOKE, target = false }, + { name = "combat", interval = 3700, chance = 25, type = COMBAT_DEATHDAMAGE, minDamage = -250, maxDamage = -400, radius = 2, range = 4, effect = CONST_ME_MORTAREA, target = true }, +} + +monster.defenses = { + defense = 30, + armor = 82, + mitigation = 2.28, + { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 50, maxDamage = 100, effect = CONST_ME_MAGIC_BLUE, target = false }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 10 }, + { type = COMBAT_ENERGYDAMAGE, percent = -5 }, + { type = COMBAT_EARTHDAMAGE, percent = 5 }, + { type = COMBAT_FIREDAMAGE, percent = 25 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = -25 }, + { type = COMBAT_HOLYDAMAGE, percent = -15 }, + { type = COMBAT_DEATHDAMAGE, percent = 25 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/lycanthropes/werepanther.lua b/data-otservbr-global/monster/lycanthropes/werepanther.lua new file mode 100644 index 00000000000..fb351221cea --- /dev/null +++ b/data-otservbr-global/monster/lycanthropes/werepanther.lua @@ -0,0 +1,131 @@ +local mType = Game.createMonsterType("Werepanther") +local monster = {} + +monster.description = "a werepanther" +monster.experience = 3550 +monster.outfit = { + lookType = 1648, + lookHead = 114, + lookBody = 114, + lookLegs = 67, + lookFeet = 122, + lookAddons = 1, + lookMount = 0, +} + +monster.raceId = 2390 +monster.Bestiary = { + class = "Lycanthrope", + race = BESTY_RACE_LYCANTHROPE, + toKill = 2500, + FirstUnlock = 100, + SecondUnlock = 1000, + CharmsPoints = 50, + Stars = 4, + Occurrence = 0, + Locations = "Murky Caverns, Oskayaat", +} + +monster.health = 4200 +monster.maxHealth = 4200 +monster.race = "blood" +monster.corpse = 43758 +monster.speed = 125 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 5000, + chance = 20, +} + +monster.strategiesTarget = { + nearest = 70, + health = 10, + damage = 10, + random = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 80, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, + { text = "Grrr", yell = false }, +} + +monster.loot = { + { name = "gold coin", chance = 100000, maxCount = 80 }, + { name = "platinum coin", chance = 100000, maxCount = 11 }, + { name = "werepanther claw", chance = 13820, maxCount = 2 }, + { name = "golden sickle", chance = 6720 }, + { name = "meat", chance = 5500, maxCount = 2 }, + { name = "small ruby", chance = 8470, maxCount = 3 }, + { name = "moonlight crystals", chance = 2550 }, + { id = 3039, chance = 1240 }, -- red gem + { name = "magma monocle", chance = 3080 }, + { name = "ripper lance", chance = 850 }, + { name = "gemmed figurine", chance = 1770 }, + { id = 817, chance = 2770 }, -- magma amulet + { name = "fur armor", chance = 2620 }, + { id = 43917, chance = 650 }, -- werepanther trophy +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -75, maxDamage = -525 }, + { name = "combat", interval = 3700, chance = 40, type = COMBAT_FIREDAMAGE, minDamage = -265, maxDamage = -400, radius = 3, effect = CONST_ME_FIREATTACK, target = false }, + { name = "combat", interval = 2700, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -275, maxDamage = -375, radius = 3, effect = CONST_ME_GROUNDSHAKER, range = 4, target = true }, + { name = "combat", interval = 2300, chance = 15, type = COMBAT_PHYSICALDAMAGE, minDamage = -200, maxDamage = -375, radius = 2, effect = CONST_ME_YELLOWSMOKE, target = false }, + { name = "combat", interval = 3300, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -175, maxDamage = -300, radius = 2, effect = CONST_ME_MORTAREA, range = 4, target = true }, +} + +monster.defenses = { + defense = 30, + armor = 72, + mitigation = 2.05, + { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 50, maxDamage = 100, effect = CONST_ME_MAGIC_BLUE, target = false }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = -10 }, + { type = COMBAT_EARTHDAMAGE, percent = 10 }, + { type = COMBAT_FIREDAMAGE, percent = 20 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = -15 }, + { type = COMBAT_HOLYDAMAGE, percent = -25 }, + { type = COMBAT_DEATHDAMAGE, percent = 20 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/lycanthropes/weretiger.lua b/data-otservbr-global/monster/lycanthropes/weretiger.lua new file mode 100644 index 00000000000..6584860abb0 --- /dev/null +++ b/data-otservbr-global/monster/lycanthropes/weretiger.lua @@ -0,0 +1,126 @@ +local mType = Game.createMonsterType("Weretiger") +local monster = {} + +monster.description = "a weretiger" +monster.experience = 3920 +monster.outfit = { + lookType = 1646, + lookHead = 97, + lookBody = 114, + lookLegs = 113, + lookFeet = 94, + lookAddons = 1, + lookMount = 0, +} + +monster.raceId = 2386 +monster.Bestiary = { + class = "Lycanthrope", + race = BESTY_RACE_LYCANTHROPE, + toKill = 2500, + FirstUnlock = 100, + SecondUnlock = 1000, + CharmsPoints = 50, + Stars = 4, + Occurrence = 0, + Locations = "Murky Caverns", +} + +monster.health = 5000 +monster.maxHealth = 5000 +monster.race = "blood" +monster.corpse = 43669 +monster.speed = 125 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 5000, + chance = 20, +} + +monster.strategiesTarget = { + nearest = 70, + health = 10, + damage = 10, + random = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 80, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = {} + +monster.loot = { + { name = "gold coin", chance = 100000, maxCount = 100 }, + { name = "platinum coin", chance = 100000, maxCount = 13 }, + { name = "weretiger tooth", chance = 10800 }, + { name = "furry club", chance = 6230 }, + { name = "meat", chance = 5500, maxCount = 4 }, + { name = "violet crystal shard", chance = 3370 }, + { name = "moonlight crystals", chance = 2550 }, + { id = 3041, chance = 1200 }, -- blue gem + { name = "knight armor", chance = 3000 }, + { name = "angelic axe", chance = 1430 }, + { name = "gemmed figurine", chance = 1770 }, + { id = 817, chance = 1770 }, -- magma amulet + { name = "silver moon coin", chance = 510 }, + { id = 43915, chance = 610 }, -- weretiger trophy +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -50, maxDamage = -625 }, + { name = "energy chain", interval = 3300, chance = 20, minDamage = -175, maxDamage = -375, range = 3, target = true }, + { name = "combat", interval = 3300, chance = 20, type = COMBAT_ENERGYDAMAGE, minDamage = -200, maxDamage = -375, length = 5, spread = 2, effect = CONST_ME_BLUE_ENERGY_SPARK, target = false }, + { name = "combat", interval = 2700, chance = 37, type = COMBAT_PHYSICALDAMAGE, minDamage = -175, maxDamage = -325, radius = 1, effect = CONST_ME_BIG_SCRATCH, range = 1, target = true }, +} + +monster.defenses = { + defense = 30, + armor = 76, + mitigation = 2.16, + { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 50, maxDamage = 100, effect = CONST_ME_MAGIC_BLUE, target = false }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = -5 }, + { type = COMBAT_ENERGYDAMAGE, percent = 25 }, + { type = COMBAT_EARTHDAMAGE, percent = -15 }, + { type = COMBAT_FIREDAMAGE, percent = -25 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 30 }, + { type = COMBAT_HOLYDAMAGE, percent = 10 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/lycanthropes/white_weretiger.lua b/data-otservbr-global/monster/lycanthropes/white_weretiger.lua new file mode 100644 index 00000000000..f892a60b117 --- /dev/null +++ b/data-otservbr-global/monster/lycanthropes/white_weretiger.lua @@ -0,0 +1,124 @@ +local mType = Game.createMonsterType("White Weretiger") +local monster = {} + +monster.description = "a white weretiger" +monster.experience = 5200 +monster.outfit = { + lookType = 1646, + lookHead = 19, + lookBody = 59, + lookLegs = 113, + lookFeet = 94, + lookAddons = 2, + lookMount = 0, +} + +monster.raceId = 2387 +monster.Bestiary = { + class = "Lycanthrope", + race = BESTY_RACE_LYCANTHROPE, + toKill = 2500, + FirstUnlock = 100, + SecondUnlock = 1000, + CharmsPoints = 50, + Stars = 4, + Occurrence = 0, + Locations = "Murky Caverns", +} + +monster.health = 6100 +monster.maxHealth = 6100 +monster.race = "blood" +monster.corpse = 43762 +monster.speed = 120 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 5000, + chance = 20, +} + +monster.strategiesTarget = { + nearest = 70, + health = 10, + damage = 10, + random = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 80, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = {} + +monster.loot = { + { name = "gold coin", chance = 100000, maxCount = 100 }, + { name = "platinum coin", chance = 100000, maxCount = 20 }, + { name = "weretiger tooth", chance = 13400 }, + { name = "beastslayer axe", chance = 3970 }, + { name = "ham", chance = 5500, maxCount = 2 }, + { name = "moonlight crystals", chance = 7000 }, + { name = "white gem", chance = 1650 }, + { name = "silver moon coin", chance = 2000 }, + { name = "blue robe", chance = 1160 }, + { name = "moon pin", chance = 660 }, + { name = "crystal mace", chance = 500 }, + { id = 43915, chance = 610 }, -- weretiger trophy +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -585 }, + { name = "white weretiger ice ring", interval = 3700, chance = 20, minDamage = -300, maxDamage = -425 }, + { name = "energy ring", interval = 4300, chance = 40, minDamage = -300, maxDamage = -425 }, + { name = "combat", interval = 2300, chance = 15, type = COMBAT_ICEDAMAGE, minDamage = -200, maxDamage = -375, radius = 2, effect = CONST_ME_ICEAREA, target = false }, +} + +monster.defenses = { + defense = 30, + armor = 83, + mitigation = 2.25, + { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 50, maxDamage = 100, effect = CONST_ME_MAGIC_BLUE, target = false }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = -5 }, + { type = COMBAT_ENERGYDAMAGE, percent = 25 }, + { type = COMBAT_EARTHDAMAGE, percent = -20 }, + { type = COMBAT_FIREDAMAGE, percent = -15 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 40 }, + { type = COMBAT_HOLYDAMAGE, percent = 25 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/magicals/blue_djinn.lua b/data-otservbr-global/monster/magicals/blue_djinn.lua index 627002e5469..c0a2985933f 100644 --- a/data-otservbr-global/monster/magicals/blue_djinn.lua +++ b/data-otservbr-global/monster/magicals/blue_djinn.lua @@ -34,7 +34,7 @@ monster.speed = 110 monster.manaCost = 0 monster.faction = FACTION_MARID -monster.enemyFactions = { FACTION_EFREET, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_EFREET } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/magicals/crypt_warden.lua b/data-otservbr-global/monster/magicals/crypt_warden.lua index e5a2db7ec77..7eedb1ff920 100644 --- a/data-otservbr-global/monster/magicals/crypt_warden.lua +++ b/data-otservbr-global/monster/magicals/crypt_warden.lua @@ -34,7 +34,7 @@ monster.speed = 145 monster.manaCost = 0 monster.faction = FACTION_ANUMA -monster.enemyFactions = { FACTION_FAFNAR, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_FAFNAR } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/magicals/efreet.lua b/data-otservbr-global/monster/magicals/efreet.lua index b0955162290..d20dd609a9f 100644 --- a/data-otservbr-global/monster/magicals/efreet.lua +++ b/data-otservbr-global/monster/magicals/efreet.lua @@ -34,7 +34,7 @@ monster.speed = 117 monster.manaCost = 0 monster.faction = FACTION_EFREET -monster.enemyFactions = { FACTION_MARID, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_MARID } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/magicals/feral_sphinx.lua b/data-otservbr-global/monster/magicals/feral_sphinx.lua index 481057d5654..376daa84443 100644 --- a/data-otservbr-global/monster/magicals/feral_sphinx.lua +++ b/data-otservbr-global/monster/magicals/feral_sphinx.lua @@ -75,21 +75,28 @@ monster.voices = { monster.loot = { { name = "platinum coin", chance = 100000, maxCount = 3 }, - { name = "wand of draconia", chance = 4770 }, - { name = "sphinx feather", chance = 3450 }, - { name = "fire axe", chance = 2650 }, - { id = 31438, chance = 3450 }, -- sphinx tiara - { name = "magma legs", chance = 1860 }, - { name = "magma monocle", chance = 1590 }, - { name = "magma boots", chance = 2120 }, - { name = "magma amulet", chance = 7160 }, - { name = "wand of inferno", chance = 7690 }, - { name = "dragon necklace", chance = 800 }, + { name = "green crystal shard", chance = 8740 }, + { name = "cyan crystal fragment", chance = 8620 }, + { id = 3039, chance = 8390 }, -- red gem + { name = "magma amulet", chance = 6060 }, + { name = "wand of inferno", chance = 5710 }, + { name = "small sapphire", chance = 5590, maxCount = 2 }, + { name = "dragon necklace", chance = 5590 }, + { name = "blue gem", chance = 5480 }, + { name = "sphinx feather", chance = 5480 }, + { name = "sphinx tiara", chance = 5240 }, + { name = "fire axe", chance = 4200 }, + { name = "wand of draconia", chance = 2910 }, + { name = "green gem", chance = 2680 }, + { name = "magma monocle", chance = 1400 }, + { name = "magma boots", chance = 1280 }, + { name = "small enchanted emerald", chance = 1050, maxCount = 2 }, + { name = "magma legs", chance = 930 }, } monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -450 }, - { name = "fire wave", interval = 2000, chance = 15, minDamage = -350, maxDamage = -500, length = 1, spread = 1, effect = CONST_ME_FIREAREA, target = true }, + { name = "fire wave", interval = 2000, chance = 15, minDamage = -350, maxDamage = -500, length = 1, spread = 0, effect = CONST_ME_FIREAREA, target = true }, { name = "combat", interval = 2000, chance = 25, type = COMBAT_ENERGYDAMAGE, minDamage = -300, maxDamage = -500, radius = 4, effect = CONST_ME_ENERGYAREA, target = false }, { name = "combat", interval = 2000, chance = 20, type = COMBAT_FIREDAMAGE, minDamage = -350, maxDamage = -550, range = 1, shootEffect = CONST_ANI_FIRE, target = false }, { name = "combat", interval = 2000, chance = 18, type = COMBAT_HOLYDAMAGE, minDamage = -400, maxDamage = -580, length = 6, spread = 3, effect = CONST_ME_HOLYAREA, target = false }, diff --git a/data-otservbr-global/monster/magicals/green_djinn.lua b/data-otservbr-global/monster/magicals/green_djinn.lua index 3f7f6092068..31b13556cff 100644 --- a/data-otservbr-global/monster/magicals/green_djinn.lua +++ b/data-otservbr-global/monster/magicals/green_djinn.lua @@ -35,7 +35,7 @@ monster.speed = 110 monster.manaCost = 0 monster.faction = FACTION_EFREET -monster.enemyFactions = { FACTION_MARID, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_MARID } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/magicals/guzzlemaw.lua b/data-otservbr-global/monster/magicals/guzzlemaw.lua index 9ea1f726827..79a58ca0c50 100644 --- a/data-otservbr-global/monster/magicals/guzzlemaw.lua +++ b/data-otservbr-global/monster/magicals/guzzlemaw.lua @@ -23,8 +23,7 @@ monster.Bestiary = { CharmsPoints = 50, Stars = 4, Occurrence = 0, - Locations = "Guzzlemaw Valley, and a single spawn in a tower in Upper Roshamuul \z - (south of the Depot and west of the entrance to Roshamuul Prison).", + Locations = "Guzzlemaw Valley, and a single spawn in a tower in Upper Roshamuul (south of the Depot and west of the entrance to Roshamuul Prison).", } monster.health = 6400 diff --git a/data-otservbr-global/monster/magicals/lamassu.lua b/data-otservbr-global/monster/magicals/lamassu.lua index b59aa14c364..2d22d43c092 100644 --- a/data-otservbr-global/monster/magicals/lamassu.lua +++ b/data-otservbr-global/monster/magicals/lamassu.lua @@ -34,7 +34,7 @@ monster.speed = 160 monster.manaCost = 0 monster.faction = FACTION_ANUMA -monster.enemyFactions = { FACTION_FAFNAR, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_FAFNAR } monster.changeTarget = { interval = 4000, @@ -94,9 +94,10 @@ monster.loot = { } monster.attacks = { - { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -600 }, - { name = "combat", interval = 2000, chance = 20, type = COMBAT_HOLYDAMAGE, minDamage = -200, maxDamage = -485, radius = 3, effect = CONST_ME_HOLYAREA, target = false }, - { name = "combat", interval = 2000, chance = 20, type = COMBAT_EARTHDAMAGE, minDamage = -100, maxDamage = -405, range = 5, radius = 3, shootEffect = CONST_ANI_SMALLEARTH, effect = CONST_ME_SMALLPLANTS, target = true }, + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -500 }, + { name = "combat", interval = 2000, chance = 20, type = COMBAT_HOLYDAMAGE, minDamage = -400, maxDamage = -500, radius = 1, effect = CONST_ME_HOLYAREA, target = false }, + { name = "combat", interval = 2000, chance = 20, type = COMBAT_HOLYDAMAGE, minDamage = -300, maxDamage = -500, radius = 3, effect = CONST_ME_HOLYAREA, target = false }, + { name = "combat", interval = 2000, chance = 20, type = COMBAT_EARTHDAMAGE, minDamage = -300, maxDamage = -405, range = 5, radius = 2, shootEffect = CONST_ANI_SMALLEARTH, effect = CONST_ME_SMALLPLANTS, target = true }, } monster.defenses = { diff --git a/data-otservbr-global/monster/magicals/lumbering_carnivor.lua b/data-otservbr-global/monster/magicals/lumbering_carnivor.lua index b6878c11e06..7eee6229eaa 100644 --- a/data-otservbr-global/monster/magicals/lumbering_carnivor.lua +++ b/data-otservbr-global/monster/magicals/lumbering_carnivor.lua @@ -76,21 +76,21 @@ monster.voices = { } monster.loot = { - { name = "platinum coin", chance = 100000, maxCount = 5 }, - { name = "blue glass plate", chance = 100000, maxCount = 3 }, - { id = 3264, chance = 15000 }, -- sword - { name = "axe", chance = 14000 }, - { name = "ice rapier", chance = 12000 }, - { name = "glorious axe", chance = 6100 }, - { name = "blue robe", chance = 4600 }, - { name = "two handed sword", chance = 13000 }, - { name = "fur armor", chance = 5400 }, - { id = 281, chance = 3200 }, -- giant shimmering pearl (green) - { name = "green crystal shard", chance = 3100 }, - { name = "violet gem", chance = 4000 }, - { name = "green gem", chance = 4800 }, - { name = "blue gem", chance = 4000 }, - { name = "focus cape", chance = 3000 }, + { name = "platinum coin", chance = 64770, maxCount = 3 }, + { name = "blue glass plate", chance = 20840, maxCount = 3 }, + { name = "axe", chance = 14620 }, + { name = "ice rapier", chance = 7600 }, + { id = 3264, chance = 5500 }, -- sword + { id = 281, chance = 1830 }, -- giant shimmering pearl (green) + { name = "green gem", chance = 1680 }, + { name = "violet gem", chance = 1560 }, + { name = "glorious axe", chance = 1530 }, + { name = "two handed sword", chance = 1490 }, + { name = "blue robe", chance = 760 }, + { name = "blue gem", chance = 990 }, + { name = "fur armor", chance = 950 }, + { name = "green crystal shard", chance = 920 }, + { name = "focus cape", chance = 80 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/magicals/marid.lua b/data-otservbr-global/monster/magicals/marid.lua index fa14fce413e..36340b8ccb9 100644 --- a/data-otservbr-global/monster/magicals/marid.lua +++ b/data-otservbr-global/monster/magicals/marid.lua @@ -34,7 +34,7 @@ monster.speed = 117 monster.manaCost = 0 monster.faction = FACTION_MARID -monster.enemyFactions = { FACTION_EFREET, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_EFREET } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/magicals/menacing_carnivor.lua b/data-otservbr-global/monster/magicals/menacing_carnivor.lua index fa314084b33..fed50464588 100644 --- a/data-otservbr-global/monster/magicals/menacing_carnivor.lua +++ b/data-otservbr-global/monster/magicals/menacing_carnivor.lua @@ -76,28 +76,28 @@ monster.voices = { } monster.loot = { - { name = "platinum coin", chance = 100000, maxCount = 6 }, - { name = "morning star", chance = 100000 }, - { name = "terra rod", chance = 15550 }, - { name = "small ruby", chance = 15000 }, - { name = "crystal sword", chance = 25000 }, - { name = "ultimate mana potion", chance = 50000 }, - { name = "wand of dragonbreath", chance = 15000 }, - { name = "machete", chance = 30000 }, - { name = "iron helmet", chance = 20000 }, - { name = "serpent sword", chance = 18000 }, - { name = "heavy machete", chance = 17000 }, - { name = "terra legs", chance = 6000 }, - { name = "knight legs", chance = 4500 }, - { name = "wand of starstorm", chance = 8000 }, - { name = "wand of voodoo", chance = 7100 }, - { name = "violet glass plate", chance = 6200 }, - { name = "small enchanted ruby", chance = 1400 }, - { name = "green crystal fragment", chance = 1600 }, - { name = "onyx chip", chance = 9800 }, - { name = "opal", chance = 2000 }, - { name = "tiger eye", chance = 3000 }, - { name = "wand of decay", chance = 8700 }, + { name = "platinum coin", chance = 65410, maxCount = 8 }, + { name = "morning star", chance = 16730 }, + { name = "ultimate mana potion", chance = 9820 }, + { name = "violet glass plate", chance = 691 }, + { name = "crystal sword", chance = 4750 }, + { name = "terra rod", chance = 4480 }, + { name = "small ruby", chance = 4000 }, + { name = "onyx chip", chance = 3350 }, + { name = "green crystal fragment", chance = 3180 }, + { name = "small enchanted ruby", chance = 2050 }, + { name = "terra legs", chance = 2000 }, + { name = "knight legs", chance = 1780 }, + { name = "machete", chance = 1730 }, + { name = "wand of voodoo", chance = 1570 }, + { name = "heavy machete", chance = 1240 }, + { name = "wand of starstorm", chance = 1240 }, + { name = "wand of dragonbreath", chance = 970 }, + { name = "tiger eye", chance = 920 }, + { name = "opal", chance = 810 }, + { name = "iron helmet", chance = 760 }, + { name = "serpent sword", chance = 700 }, + { name = "wand of decay", chance = 490 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/magicals/phantasm_summon.lua b/data-otservbr-global/monster/magicals/phantasm_summon.lua index 152680243a9..4926c01aada 100644 --- a/data-otservbr-global/monster/magicals/phantasm_summon.lua +++ b/data-otservbr-global/monster/magicals/phantasm_summon.lua @@ -3,7 +3,7 @@ local monster = {} monster.name = "Phantasm" monster.description = "a phantasm" -monster.experience = 4400 +monster.experience = 1 monster.outfit = { lookType = 241, lookHead = 0, @@ -14,8 +14,8 @@ monster.outfit = { lookMount = 0, } -monster.health = 3950 -monster.maxHealth = 3950 +monster.health = 65 +monster.maxHealth = 65 monster.race = "undead" monster.corpse = 6343 monster.speed = 170 diff --git a/data-otservbr-global/monster/magicals/sphinx.lua b/data-otservbr-global/monster/magicals/sphinx.lua index 2ca6c8f4dba..b7b305db787 100644 --- a/data-otservbr-global/monster/magicals/sphinx.lua +++ b/data-otservbr-global/monster/magicals/sphinx.lua @@ -34,7 +34,7 @@ monster.speed = 145 monster.manaCost = 0 monster.faction = FACTION_ANUMA -monster.enemyFactions = { FACTION_FAFNAR, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_FAFNAR } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/magicals/spiky_carnivor.lua b/data-otservbr-global/monster/magicals/spiky_carnivor.lua index e39750e556a..f8a29ef234f 100644 --- a/data-otservbr-global/monster/magicals/spiky_carnivor.lua +++ b/data-otservbr-global/monster/magicals/spiky_carnivor.lua @@ -76,24 +76,24 @@ monster.voices = { } monster.loot = { - { name = "platinum coin", chance = 100000, maxCount = 6 }, - { name = "green glass plate", chance = 12000, maxCount = 17 }, - { name = "blue crystal splinter", chance = 11500 }, - { name = "brown crystal splinter", chance = 11000 }, - { name = "dark armor", chance = 10000 }, - { name = "guardian shield", chance = 9000 }, - { name = "rainbow quartz", chance = 8500 }, - { name = "blue robe", chance = 8000 }, - { name = "glacier amulet", chance = 7500 }, - { name = "lightning pendant", chance = 2200 }, - { name = "prismatic quartz", chance = 6500 }, - { name = "talon", chance = 6000 }, - { name = "terra amulet", chance = 5500 }, - { name = "warrior helmet", chance = 4000 }, - { name = "shockwave amulet", chance = 2550 }, - { name = "terra mantle", chance = 4050 }, - { name = "buckle", chance = 250 }, - { name = "doublet", chance = 250 }, + { name = "platinum coin", chance = 66230, maxCount = 6 }, + { name = "dark armor", chance = 13870 }, + { name = "green glass plate", chance = 10490, maxCount = 2 }, + { name = "blue crystal splinter", chance = 7590 }, + { name = "brown crystal splinter", chance = 7330 }, + { name = "guardian shield", chance = 5010 }, + { name = "warrior helmet", chance = 2980 }, + { name = "rainbow quartz", chance = 2540, maxCount = 2 }, + { name = "talon", chance = 2000 }, + { name = "glacier amulet", chance = 1920 }, + { name = "terra amulet", chance = 1920 }, + { name = "blue robe", chance = 1670 }, + { name = "prismatic quartz", chance = 1380 }, + { name = "lightning pendant", chance = 1270 }, + { name = "doublet", chance = 360 }, + { name = "terra mantle", chance = 330 }, + { name = "buckle", chance = 180 }, + { name = "shockwave amulet", chance = 150 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/mammals/white_lion.lua b/data-otservbr-global/monster/mammals/white_lion.lua index 9f47df6e6b9..0330aca13c0 100644 --- a/data-otservbr-global/monster/mammals/white_lion.lua +++ b/data-otservbr-global/monster/mammals/white_lion.lua @@ -16,7 +16,7 @@ monster.outfit = { monster.raceId = 1967 monster.Bestiary = { class = "Mammal", - + race = BESTY_RACE_MAMMAL, toKill = 2500, FirstUnlock = 100, SecondUnlock = 1000, diff --git a/data-otservbr-global/monster/mammals/white_tiger.lua b/data-otservbr-global/monster/mammals/white_tiger.lua new file mode 100644 index 00000000000..3d34267d2fb --- /dev/null +++ b/data-otservbr-global/monster/mammals/white_tiger.lua @@ -0,0 +1,112 @@ +local mType = Game.createMonsterType("White Tiger") +local monster = {} + +monster.description = "a white tiger" +monster.experience = 40 +monster.outfit = { + lookType = 1649, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.raceId = 2391 +monster.Bestiary = { + class = "Mammal", + race = BESTY_RACE_MAMMAL, + toKill = 500, + FirstUnlock = 25, + SecondUnlock = 250, + CharmsPoints = 15, + Stars = 2, + Occurrence = 0, + Locations = "Oskayaat", +} + +monster.health = 75 +monster.maxHealth = 75 +monster.race = "blood" +monster.corpse = 43771 +monster.speed = 110 +monster.manaCost = 420 + +monster.changeTarget = { + interval = 4000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 70, + damage = 30, +} + +monster.flags = { + summonable = true, + attackable = true, + hostile = true, + convinceable = true, + pushable = false, + rewardBoss = false, + illusionable = true, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 70, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = false, + canWalkOnPoison = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.loot = { + { name = "meat", chance = 35190, maxCount = 4 }, + { name = "striped fur", chance = 10830 }, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -40 }, +} + +monster.defenses = { + defense = 15, + armor = 5, + mitigation = 0.38, + { name = "speed", interval = 2000, chance = 15, speedChange = 200, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = -10 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = -10 }, +} + +monster.immunities = { + { type = "paralyze", condition = false }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/cults_of_tibia/animated_ogre_brute.lua b/data-otservbr-global/monster/quests/cults_of_tibia/animated_ogre_brute.lua index f91e51a5793..56b2ed30216 100644 --- a/data-otservbr-global/monster/quests/cults_of_tibia/animated_ogre_brute.lua +++ b/data-otservbr-global/monster/quests/cults_of_tibia/animated_ogre_brute.lua @@ -80,7 +80,6 @@ monster.loot = { { id = 22172, chance = 600 }, -- ogre choppa { id = 22171, chance = 800 }, -- ogre klubba { id = 3465, chance = 500 }, -- pot - { id = 8906, chance = 200 }, -- heavily rusted helmet { id = 22192, chance = 300 }, -- shamanic mask } diff --git a/data-otservbr-global/monster/quests/cults_of_tibia/bosses/ravenous_hunger.lua b/data-otservbr-global/monster/quests/cults_of_tibia/bosses/ravenous_hunger.lua index ee9b5d37a66..e3eef7671c6 100644 --- a/data-otservbr-global/monster/quests/cults_of_tibia/bosses/ravenous_hunger.lua +++ b/data-otservbr-global/monster/quests/cults_of_tibia/bosses/ravenous_hunger.lua @@ -14,7 +14,6 @@ monster.outfit = { } monster.events = { - "LeidenHeal", "CultsOfTibiaBossDeath", } diff --git a/data-otservbr-global/monster/quests/ferumbras_ascendant/ferumbras_essence.lua b/data-otservbr-global/monster/quests/ferumbras_ascendant/ferumbras_essence.lua index 973fa02afea..e3c0cd883c5 100644 --- a/data-otservbr-global/monster/quests/ferumbras_ascendant/ferumbras_essence.lua +++ b/data-otservbr-global/monster/quests/ferumbras_ascendant/ferumbras_essence.lua @@ -38,10 +38,10 @@ monster.strategiesTarget = { monster.flags = { summonable = false, - attackable = true, + attackable = false, hostile = true, convinceable = false, - pushable = false, + pushable = true, rewardBoss = false, illusionable = false, canPushItems = true, diff --git a/data-otservbr-global/monster/quests/grave_danger/bosses/sir_nictros.lua b/data-otservbr-global/monster/quests/grave_danger/bosses/sir_nictros.lua index 8637363a949..921c63ad286 100644 --- a/data-otservbr-global/monster/quests/grave_danger/bosses/sir_nictros.lua +++ b/data-otservbr-global/monster/quests/grave_danger/bosses/sir_nictros.lua @@ -40,7 +40,7 @@ monster.flags = { hostile = true, convinceable = false, pushable = false, - rewardBoss = true, + rewardBoss = false, illusionable = false, canPushItems = true, canPushCreatures = true, @@ -122,18 +122,4 @@ monster.immunities = { { type = "bleed", condition = false }, } -mType.onThink = function(monster, interval) end - -mType.onAppear = function(monster, creature) - if monster:getType():isRewardBoss() then - monster:setReward(true) - end -end - -mType.onDisappear = function(monster, creature) end - -mType.onMove = function(monster, creature, fromPosition, toPosition) end - -mType.onSay = function(monster, creature, type, message) end - mType:register(monster) diff --git a/data-otservbr-global/monster/quests/grave_danger/frozen_soul.lua b/data-otservbr-global/monster/quests/grave_danger/frozen_soul.lua index 7c8c548d9e0..ba37eb8168e 100644 --- a/data-otservbr-global/monster/quests/grave_danger/frozen_soul.lua +++ b/data-otservbr-global/monster/quests/grave_danger/frozen_soul.lua @@ -62,8 +62,8 @@ monster.voices = { monster.loot = {} monster.attacks = { - { name = "combat", interval = 2000, chance = 40, type = COMBAT_ICEDAMAGE, minDamage = -325, maxDamage = -450, range = 5, shootEffect = CONST_ANI_ICE, effect = CONST_ME_ICEAREA, target = true }, - { name = "combat", interval = 3500, chance = 30, type = COMBAT_LIFEDRAIN, minDamage = -275, maxDamage = -400, range = 5, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_MAGIC_RED, target = true }, + { name = "combat", interval = 2000, chance = 35, type = COMBAT_ICEDAMAGE, minDamage = -225, maxDamage = -400, range = 5, shootEffect = CONST_ANI_ICE, effect = CONST_ME_ICEAREA, target = true }, + { name = "combat", interval = 3500, chance = 25, type = COMBAT_LIFEDRAIN, minDamage = -175, maxDamage = -350, range = 5, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_MAGIC_RED, target = true }, } monster.defenses = { diff --git a/data-otservbr-global/monster/quests/grave_danger/soul_scourge.lua b/data-otservbr-global/monster/quests/grave_danger/soul_scourge.lua index e2cff3e976d..34295fba4f9 100644 --- a/data-otservbr-global/monster/quests/grave_danger/soul_scourge.lua +++ b/data-otservbr-global/monster/quests/grave_danger/soul_scourge.lua @@ -62,8 +62,8 @@ monster.voices = { monster.loot = {} monster.attacks = { - { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -700 }, - { name = "combat", interval = 2000, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -500, maxDamage = -750, range = 5, shootEffect = CONST_ANI_SUDDENDEATH, target = true }, + { name = "melee", interval = 2000, chance = 80, minDamage = 0, maxDamage = -500 }, + { name = "combat", interval = 3500, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -300, maxDamage = -500, range = 5, shootEffect = CONST_ANI_SUDDENDEATH, target = true }, } monster.defenses = { diff --git a/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_lord.lua b/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_lord.lua index f49fa38af71..939c159e83e 100644 --- a/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_lord.lua +++ b/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_lord.lua @@ -64,9 +64,6 @@ monster.voices = { monster.loot = {} ---monster.attacks = { ---} - monster.defenses = { defense = 5, armor = 10, diff --git a/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_phantom.lua b/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_phantom.lua index 3fd5039cc06..e0fc91bedb3 100644 --- a/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_phantom.lua +++ b/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_phantom.lua @@ -61,9 +61,6 @@ monster.voices = { monster.loot = {} ---monster.attacks = { ---} - monster.defenses = { defense = 5, armor = 10, diff --git a/data-otservbr-global/monster/quests/primal_ordeal_quest/the_primal_menace.lua b/data-otservbr-global/monster/quests/primal_ordeal_quest/the_primal_menace.lua index f233c41680b..b0bc59364d6 100644 --- a/data-otservbr-global/monster/quests/primal_ordeal_quest/the_primal_menace.lua +++ b/data-otservbr-global/monster/quests/primal_ordeal_quest/the_primal_menace.lua @@ -111,17 +111,15 @@ monster.voices = { chance = 10, } -monster.loot = { - { name = "primal bag", chance = 50 }, -} +monster.loot = {} monster.attacks = { - { name = "melee", interval = 2000, chance = 85, minDamage = -0, maxDamage = -763 }, - { name = "combat", interval = 4000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -1500, maxDamage = -2200, length = 10, spread = 3, effect = CONST_ME_CARNIPHILA, target = false }, - { name = "combat", interval = 2500, chance = 25, type = COMBAT_FIREDAMAGE, minDamage = -700, maxDamage = -1000, length = 10, spread = 3, effect = CONST_ME_HITBYFIRE, target = false }, - { name = "big death wave", interval = 3500, chance = 20, minDamage = -250, maxDamage = -300, target = false }, - { name = "combat", interval = 5000, chance = 15, type = COMBAT_ENERGYDAMAGE, effect = CONST_ME_ENERGYHIT, minDamage = -1200, maxDamage = -1300, range = 4, target = false }, - { name = "combat", interval = 2700, chance = 30, type = COMBAT_EARTHDAMAGE, shootEffect = CONST_ANI_POISON, effect = CONST_ANI_EARTH, minDamage = -600, maxDamage = -1800, range = 4, target = true }, + { name = "melee", interval = 2000, chance = 100, minDamage = -0, maxDamage = -763 }, + { name = "combat", interval = 4000, chance = 25, type = COMBAT_EARTHDAMAGE, minDamage = -1500, maxDamage = -2200, length = 10, spread = 3, effect = CONST_ME_CARNIPHILA, target = false }, + { name = "combat", interval = 2500, chance = 35, type = COMBAT_FIREDAMAGE, minDamage = -700, maxDamage = -1000, length = 10, spread = 3, effect = CONST_ME_HITBYFIRE, target = false }, + { name = "big death wave", interval = 3500, chance = 25, minDamage = -250, maxDamage = -300, target = false }, + { name = "combat", interval = 5000, chance = 25, type = COMBAT_ENERGYDAMAGE, effect = CONST_ME_ENERGYHIT, minDamage = -1200, maxDamage = -1300, range = 4, target = false }, + { name = "combat", interval = 2700, chance = 35, type = COMBAT_EARTHDAMAGE, shootEffect = CONST_ANI_POISON, effect = CONST_ANI_EARTH, minDamage = -600, maxDamage = -1800, range = 4, target = true }, } monster.defenses = { diff --git a/data-otservbr-global/monster/quests/the_explorer_society/blue_butterfly.lua b/data-otservbr-global/monster/quests/the_explorer_society/blue_butterfly.lua index 87ef3250d85..3893adff8c4 100644 --- a/data-otservbr-global/monster/quests/the_explorer_society/blue_butterfly.lua +++ b/data-otservbr-global/monster/quests/the_explorer_society/blue_butterfly.lua @@ -14,6 +14,21 @@ monster.outfit = { lookMount = 0, } +monster.raceId = 213 +monster.Bestiary = { + class = "Vermin", + race = BESTY_RACE_VERMIN, + toKill = 25, + FirstUnlock = 5, + SecondUnlock = 10, + CharmsPoints = 1, + Stars = 0, + Occurrence = 0, + Locations = "Ab'Dendriel, Ab'Dendriel Surroundings, Carlin, Cormaya, Edron Surroundings, \z + Feyrist Meadows, Fibula, Fields of Glory, Green Claw Swamp, Issavi, Kazordoon Surroundings, Meriana, \z + Outlaw Camp, Port Hope Surroundings, Stonehome, Thais Surroundings, Venore Southern Swamp, Venore Surroundings.", +} + monster.health = 2 monster.maxHealth = 2 monster.race = "venom" diff --git a/data-otservbr-global/monster/quests/the_explorer_society/pink_butterfly.lua b/data-otservbr-global/monster/quests/the_explorer_society/pink_butterfly.lua index 7003fdd3878..1a8f0d8c2a0 100644 --- a/data-otservbr-global/monster/quests/the_explorer_society/pink_butterfly.lua +++ b/data-otservbr-global/monster/quests/the_explorer_society/pink_butterfly.lua @@ -14,6 +14,21 @@ monster.outfit = { lookMount = 0, } +monster.raceId = 213 +monster.Bestiary = { + class = "Vermin", + race = BESTY_RACE_VERMIN, + toKill = 25, + FirstUnlock = 5, + SecondUnlock = 10, + CharmsPoints = 1, + Stars = 0, + Occurrence = 0, + Locations = "Ab'Dendriel, Ab'Dendriel Surroundings, Carlin, Cormaya, Edron Surroundings, \z + Feyrist Meadows, Fibula, Fields of Glory, Green Claw Swamp, Issavi, Kazordoon Surroundings, Meriana, \z + Outlaw Camp, Port Hope Surroundings, Stonehome, Thais Surroundings, Venore Southern Swamp, Venore Surroundings.", +} + monster.health = 2 monster.maxHealth = 2 monster.race = "venom" diff --git a/data-otservbr-global/monster/quests/the_explorer_society/purple_butterfly.lua b/data-otservbr-global/monster/quests/the_explorer_society/purple_butterfly.lua index d2c42510683..4b71858189d 100644 --- a/data-otservbr-global/monster/quests/the_explorer_society/purple_butterfly.lua +++ b/data-otservbr-global/monster/quests/the_explorer_society/purple_butterfly.lua @@ -14,6 +14,21 @@ monster.outfit = { lookMount = 0, } +monster.raceId = 213 +monster.Bestiary = { + class = "Vermin", + race = BESTY_RACE_VERMIN, + toKill = 25, + FirstUnlock = 5, + SecondUnlock = 10, + CharmsPoints = 1, + Stars = 0, + Occurrence = 0, + Locations = "Ab'Dendriel, Ab'Dendriel Surroundings, Carlin, Cormaya, Edron Surroundings, \z + Feyrist Meadows, Fibula, Fields of Glory, Green Claw Swamp, Issavi, Kazordoon Surroundings, Meriana, \z + Outlaw Camp, Port Hope Surroundings, Stonehome, Thais Surroundings, Venore Southern Swamp, Venore Surroundings.", +} + monster.health = 2 monster.maxHealth = 2 monster.race = "venom" diff --git a/data-otservbr-global/monster/quests/the_order_of_lion/bosses/ancient_lion_knight.lua b/data-otservbr-global/monster/quests/the_order_of_lion/bosses/ancient_lion_knight.lua index 820931f81c0..aba0cc866b4 100644 --- a/data-otservbr-global/monster/quests/the_order_of_lion/bosses/ancient_lion_knight.lua +++ b/data-otservbr-global/monster/quests/the_order_of_lion/bosses/ancient_lion_knight.lua @@ -21,7 +21,7 @@ monster.speed = 130 monster.manaCost = 0 monster.faction = FACTION_LIONUSURPERS -monster.enemyFactions = { FACTION_LION, FACTION_PLAYER } +monster.enemyFactions = { FACTION_PLAYER, FACTION_LION } monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/quests/the_secret_library/bosses/ghulosh.lua b/data-otservbr-global/monster/quests/the_secret_library/bosses/ghulosh.lua index bd3a786b365..09d4abb0935 100644 --- a/data-otservbr-global/monster/quests/the_secret_library/bosses/ghulosh.lua +++ b/data-otservbr-global/monster/quests/the_secret_library/bosses/ghulosh.lua @@ -96,8 +96,8 @@ monster.loot = { { name = "dreaded cleaver", chance = 1000 }, { name = "mercenary sword", chance = 1000 }, { id = 28341, chance = 1000 }, -- tessellated wall - { id = 8900, chance = 1000 }, -- heavily rusted shield - { id = 8906, chance = 1000 }, -- heavily rusted helmet + { name = "slightly rusted shield", chance = 5880 }, + { name = "slightly rusted helmet", chance = 35290 }, { name = "epaulette", chance = 500 }, { name = "giant emerald", chance = 500 }, { name = "unliving demonbone", chance = 500 }, diff --git a/data-otservbr-global/monster/quests/the_secret_library/bosses/gorzindel.lua b/data-otservbr-global/monster/quests/the_secret_library/bosses/gorzindel.lua index b00bffd6d60..e5a1e3d6424 100644 --- a/data-otservbr-global/monster/quests/the_secret_library/bosses/gorzindel.lua +++ b/data-otservbr-global/monster/quests/the_secret_library/bosses/gorzindel.lua @@ -98,8 +98,7 @@ monster.loot = { { name = "magic sulphur", chance = 1000, maxCount = 2 }, { name = "muck rod", chance = 1000 }, { id = 3039, chance = 1000 }, -- red gem - { id = 8906, chance = 1000 }, -- heavily rusted helmet - { id = 8900, chance = 1000 }, -- heavily rusted shield + { name = "slightly rusted shield", chance = 11760 }, { name = "silver Token", chance = 1000, maxCount = 6 }, { name = "sinister book", chance = 1000 }, { name = "spellbook of warding", chance = 1000 }, diff --git a/data-otservbr-global/monster/quests/the_secret_library/lokathmor.lua b/data-otservbr-global/monster/quests/the_secret_library/lokathmor.lua index be0e44216a9..621717cd277 100644 --- a/data-otservbr-global/monster/quests/the_secret_library/lokathmor.lua +++ b/data-otservbr-global/monster/quests/the_secret_library/lokathmor.lua @@ -98,7 +98,7 @@ monster.loot = { { name = "silver token", chance = 30000, maxCount = 4 }, { name = "blue robe", chance = 30000 }, { name = "dreaded cleaver", chance = 30000 }, - { id = 8900, chance = 30000 }, -- heavily rusted shield + { name = "slightly rusted shield", chance = 26670 }, { name = "wand of inferno", chance = 30000 }, { id = 28341, chance = 1000 }, -- tessellated wall { name = "sturdy book", chance = 1000 }, diff --git a/data-otservbr-global/monster/reptiles/adult_goanna.lua b/data-otservbr-global/monster/reptiles/adult_goanna.lua index da07033e656..956af1034a1 100644 --- a/data-otservbr-global/monster/reptiles/adult_goanna.lua +++ b/data-otservbr-global/monster/reptiles/adult_goanna.lua @@ -74,35 +74,49 @@ monster.voices = { monster.loot = { { name = "platinum coin", chance = 100000, maxCount = 3 }, - { name = "envenomed arrow", chance = 55360, maxCount = 8 }, - { name = "earth arrow", chance = 16800, maxCount = 29 }, - { name = "terra rod", chance = 11000 }, - { name = "goanna meat", chance = 12140 }, - { name = "goanna claw", chance = 4290 }, - { name = "lizard heart", chance = 1400 }, - { name = "red goanna scale", chance = 10000 }, - { name = "fur armor", chance = 3200 }, - { name = "serpent sword", chance = 3600 }, - { name = "terra amulet", chance = 4650 }, - { name = "terra hood", chance = 7100 }, - { name = "wood cape", chance = 1800 }, - { name = "scared frog", chance = 2100 }, - { name = "sacred tree amulet", chance = 2500 }, - { name = "small tortoise", chance = 1800 }, + { name = "envenomed arrow", chance = 60120, maxCount = 8 }, + { name = "earth arrow", chance = 13180, maxCount = 30 }, + { name = "emerald bangle", chance = 12240 }, + { name = "goanna meat", chance = 11650 }, + { name = "small enchanted emerald", chance = 10030 }, + { name = "green crystal splinter", chance = 9100 }, + { name = "terra rod", chance = 8250 }, + { name = "red goanna scale", chance = 7910 }, + { name = "blue crystal shard", chance = 7820 }, + { name = "small sapphire", chance = 6890, maxCount = 2 }, + { name = "terra hood", chance = 6630 }, + { name = "goanna claw", chance = 6210 }, + { name = "terra amulet", chance = 6040 }, + { name = "yellow gem", chance = 4250 }, + { name = "silver brooch", chance = 4000 }, + { name = "green gem", chance = 3150 }, + { name = "serpent sword", chance = 2810 }, + { name = "scared frog", chance = 2720 }, + { name = "opal", chance = 2640, maxCount = 2 }, + { name = "onyx chip", chance = 2640 }, + { name = "gemmed figurine", chance = 1530 }, + { name = "small amethyst", chance = 1360 }, + { name = "fur armor", chance = 1360 }, + { name = "wood cape", chance = 1280 }, + { name = "white pearl", chance = 1280 }, + { name = "small tortoise", chance = 1190 }, + { name = "sacred tree amulet", chance = 1020 }, + { name = "coral brooch", chance = 770 }, + { name = "lizard heart", chance = 770 }, } monster.attacks = { - { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -350, condition = { type = CONDITION_POISON, totalDamage = 19, interval = 4000 } }, - { name = "wave t", interval = 2000, chance = 10, minDamage = -250, maxDamage = -380, target = false }, - { name = "combat", interval = 2000, chance = 12, type = COMBAT_EARTHDAMAGE, minDamage = -450, maxDamage = -550, range = 3, radius = 1, shootEffect = CONST_ANI_EARTH, effect = CONST_ME_EXPLOSIONHIT, target = true }, - { name = "combat", interval = 2000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -210, maxDamage = -300, radius = 5, effect = CONST_ME_GROUNDSHAKER, target = false }, + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -400, condition = { type = CONDITION_POISON, totalDamage = 200, interval = 4000 } }, + { name = "combat", interval = 2500, chance = 30, type = COMBAT_EARTHDAMAGE, minDamage = -300, maxDamage = -600, range = 3, shootEffect = CONST_ANI_EARTH, effect = CONST_ME_HITBYPOISON, target = true }, + { name = "combat", interval = 3000, chance = 30, type = COMBAT_EARTHDAMAGE, minDamage = -300, maxDamage = -380, radius = 2, effect = CONST_ME_GROUNDSHAKER, target = false }, + { name = "combat", interval = 3600, chance = 40, type = COMBAT_EARTHDAMAGE, minDamage = -300, maxDamage = -390, length = 8, spread = 3, effect = CONST_ME_GREEN_RINGS, target = false }, } monster.defenses = { defense = 84, armor = 84, - mitigation = 2.60, - { name = "speed", interval = 2000, chance = 5, speedChange = 500, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 }, + mitigation = 2.6, + { name = "speed", interval = 2000, chance = 15, speedChange = 420, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 }, } monster.elements = { diff --git a/data-otservbr-global/monster/reptiles/boar_man.lua b/data-otservbr-global/monster/reptiles/boar_man.lua index 53ec742ca22..0d142e332d4 100644 --- a/data-otservbr-global/monster/reptiles/boar_man.lua +++ b/data-otservbr-global/monster/reptiles/boar_man.lua @@ -90,10 +90,10 @@ monster.loot = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -498 }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_PHYSICALDAMAGE, minDamage = -375, maxDamage = -392, range = 7, shootEffect = CONST_ANI_THROWINGKNIFE, target = true }, - { name = "combat", interval = 2000, chance = 25, type = COMBAT_DEATHDAMAGE, minDamage = -386, maxDamage = -480, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, target = true }, - { name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -60, maxDamage = -140, range = 1, radius = 4, effect = CONST_ME_EXPLOSIONAREA, target = false }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_ENERGYDAMAGE, minDamage = -311, maxDamage = -400, length = 8, spread = 3, effect = CONST_ME_ENERGYHIT, target = false }, + { name = "combat", interval = 2300, chance = 30, type = COMBAT_PHYSICALDAMAGE, minDamage = -375, maxDamage = -392, range = 7, shootEffect = CONST_ANI_THROWINGKNIFE, target = true }, + { name = "combat", interval = 2600, chance = 40, type = COMBAT_DEATHDAMAGE, minDamage = -386, maxDamage = -480, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, target = true }, + { name = "combat", interval = 2900, chance = 30, type = COMBAT_PHYSICALDAMAGE, minDamage = -60, maxDamage = -140, range = 1, radius = 4, effect = CONST_ME_EXPLOSIONAREA, target = false }, + { name = "combat", interval = 3200, chance = 35, type = COMBAT_ENERGYDAMAGE, minDamage = -311, maxDamage = -400, length = 8, spread = 3, effect = CONST_ME_ENERGYHIT, target = false }, } monster.defenses = { diff --git a/data-otservbr-global/monster/reptiles/carnivostrich.lua b/data-otservbr-global/monster/reptiles/carnivostrich.lua index e0e0e362573..e0ebbb811ed 100644 --- a/data-otservbr-global/monster/reptiles/carnivostrich.lua +++ b/data-otservbr-global/monster/reptiles/carnivostrich.lua @@ -77,7 +77,7 @@ monster.voices = { } monster.loot = { - { name = "platinum coin", chance = 80450, maxCount = 22 }, + { name = "platinum coin", chance = 80450, maxCount = 28 }, { name = "small ruby", chance = 16390, maxCount = 8 }, { name = "small emerald", chance = 8330, maxCount = 8 }, { name = "strong mana potion", chance = 4910, maxCount = 4 }, diff --git a/data-otservbr-global/monster/reptiles/crape_man.lua b/data-otservbr-global/monster/reptiles/crape_man.lua index 99d2db01f34..83097403b9c 100644 --- a/data-otservbr-global/monster/reptiles/crape_man.lua +++ b/data-otservbr-global/monster/reptiles/crape_man.lua @@ -76,25 +76,25 @@ monster.voices = { } monster.loot = { - { name = "platinum coin", chance = 71540, maxCount = 26 }, - { name = "guardian halberd", chance = 5310 }, + { name = "platinum coin", chance = 71540, maxCount = 28 }, { name = "crab man claws", chance = 5210, maxCount = 2 }, { name = "green gem", chance = 3010 }, { name = "great health potion", chance = 2000, maxCount = 5 }, { id = 281, chance = 1700 }, -- giant shimmering pearl (green) - { name = "lightning legs", chance = 1200 }, - { name = "warrior's shield", chance = 1200 }, - { name = "glacier kilt", chance = 1000 }, - { name = "noble axe", chance = 900 }, - { name = "hammer of wrath", chance = 600 }, + { name = "guardian halberd", chance = 2400 }, + { name = "lightning legs", chance = 900 }, + { name = "warrior's shield", chance = 900 }, + { name = "glacier kilt", chance = 750 }, + { name = "noble axe", chance = 700 }, + { name = "hammer of wrath", chance = 400 }, { name = "ring of the sky", chance = 300 }, } monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -498 }, - { name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -120, maxDamage = -320, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, target = true }, - { name = "combat", interval = 2000, chance = 30, type = COMBAT_ENERGYDAMAGE, minDamage = -330, maxDamage = -380, range = 7, radius = 4, shootEffect = CONST_ANI_ENERGYBALL, effect = CONST_ME_PURPLEENERGY, target = true }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_ENERGYDAMAGE, minDamage = -311, maxDamage = -370, length = 3, spread = 3, effect = CONST_ME_ENERGYHIT, target = false }, + { name = "combat", interval = 3500, chance = 40, type = COMBAT_PHYSICALDAMAGE, minDamage = -120, maxDamage = -320, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, target = true }, + { name = "combat", interval = 2500, chance = 50, type = COMBAT_ENERGYDAMAGE, minDamage = -330, maxDamage = -380, range = 7, radius = 4, shootEffect = CONST_ANI_ENERGYBALL, effect = CONST_ME_PURPLEENERGY, target = true }, + { name = "combat", interval = 3000, chance = 65, type = COMBAT_ENERGYDAMAGE, minDamage = -311, maxDamage = -370, length = 3, spread = 3, effect = CONST_ME_ENERGYHIT, target = false }, } monster.defenses = { diff --git a/data-otservbr-global/monster/reptiles/fungosaurus.lua b/data-otservbr-global/monster/reptiles/fungosaurus.lua index d97e91d200d..f7e65e7eb9d 100644 --- a/data-otservbr-global/monster/reptiles/fungosaurus.lua +++ b/data-otservbr-global/monster/reptiles/fungosaurus.lua @@ -65,8 +65,8 @@ monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 300, maxDamage = -801 }, { name = "combat", interval = 3000, chance = 47, type = COMBAT_PHYSICALDAMAGE, minDamage = -800, maxDamage = -1500, effect = CONST_ME_YELLOWSMOKE, target = true }, { name = "combat", interval = 4000, chance = 31, type = COMBAT_LIFEDRAIN, minDamage = -800, maxDamage = -1500, radius = 4, effect = CONST_ME_DRAWBLOOD, target = false }, - { name = "root", interval = 2000, chance = 1, target = true }, - { name = "fear", interval = 2000, chance = 1, target = true }, + { name = "root", interval = 2000, chance = 3, target = true }, + { name = "fear", interval = 2000, chance = 3, target = true }, } monster.defenses = { diff --git a/data-otservbr-global/monster/reptiles/harpy.lua b/data-otservbr-global/monster/reptiles/harpy.lua index 01108f33336..cf146658994 100644 --- a/data-otservbr-global/monster/reptiles/harpy.lua +++ b/data-otservbr-global/monster/reptiles/harpy.lua @@ -76,18 +76,18 @@ monster.voices = { } monster.loot = { - { name = "platinum coin", chance = 73130, maxCount = 30 }, + { name = "platinum coin", chance = 73130, maxCount = 25 }, { name = "harpy feathers", chance = 6720 }, { name = "violet crystal shard", chance = 4690 }, { name = "blue crystal shard", chance = 4530 }, { name = "great spirit potion", chance = 2970, maxCount = 3 }, - { name = "violet gem", chance = 2500 }, { name = "gold ring", chance = 1720 }, { name = "wand of defiance", chance = 1720 }, { name = "focus cape", chance = 1560 }, - { name = "ornate crossbow", chance = 1410 }, - { name = "magic plate armor", chance = 940 }, + { name = "violet gem", chance = 1200 }, + { name = "ornate crossbow", chance = 500 }, { name = "shockwave amulet", chance = 470 }, + { name = "magic plate armor", chance = 440 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/reptiles/liodile.lua b/data-otservbr-global/monster/reptiles/liodile.lua index d7f79621679..b8021eea69b 100644 --- a/data-otservbr-global/monster/reptiles/liodile.lua +++ b/data-otservbr-global/monster/reptiles/liodile.lua @@ -75,7 +75,7 @@ monster.voices = { } monster.loot = { - { name = "platinum coin", chance = 80540, maxCount = 18 }, + { name = "platinum coin", chance = 80540, maxCount = 23 }, { name = "small sapphire", chance = 9790, maxCount = 4 }, { name = "green crystal shard", chance = 5360 }, { name = "liodile fang", chance = 4030, maxCount = 3 }, @@ -89,8 +89,8 @@ monster.loot = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -500 }, - { name = "combat", interval = 2000, chance = 30, type = COMBAT_EARTHDAMAGE, minDamage = -325, maxDamage = -400, range = 7, shootEffect = CONST_ANI_POISONARROW, target = true }, - { name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -300, maxDamage = -400, range = 2, effect = CONST_ME_GROUNDSHAKER, target = true }, + { name = "combat", interval = 2000, chance = 50, type = COMBAT_EARTHDAMAGE, minDamage = -325, maxDamage = -400, range = 7, shootEffect = CONST_ANI_POISONARROW, target = true }, + { name = "combat", interval = 2000, chance = 34, type = COMBAT_PHYSICALDAMAGE, minDamage = -300, maxDamage = -400, range = 2, effect = CONST_ME_GROUNDSHAKER, target = true }, } monster.defenses = { diff --git a/data-otservbr-global/monster/reptiles/naga_archer.lua b/data-otservbr-global/monster/reptiles/naga_archer.lua index d105ddbb0fc..e8665ed210a 100644 --- a/data-otservbr-global/monster/reptiles/naga_archer.lua +++ b/data-otservbr-global/monster/reptiles/naga_archer.lua @@ -53,7 +53,7 @@ monster.flags = { canPushItems = true, canPushCreatures = true, staticAttackChance = 90, - targetDistance = 4, + targetDistance = 3, runHealth = 0, healthHidden = false, isBlockable = false, @@ -74,28 +74,29 @@ monster.voices = { } monster.loot = { - { name = "platinum coin", chance = 100000, maxCount = 13 }, + { name = "platinum coin", chance = 100000, maxCount = 17 }, { name = "naga archer scales", chance = 15050, maxCount = 3 }, - { name = "naga earring", chance = 12850 }, - { name = "naga armring", chance = 5960 }, + { name = "naga earring", chance = 12850, maxCount = 3 }, + { name = "naga armring", chance = 5960, maxCount = 3 }, { id = 3007, chance = 5330 }, -- crystal ring { name = "hunting spear", chance = 3760 }, { name = "crossbow", chance = 3130 }, { name = "blue crystal shard", chance = 1880 }, { name = "bow", chance = 1570 }, - { name = "elvish bow", chance = 1250 }, + { name = "elvish bow", chance = 750 }, { name = "ornate crossbow", chance = 630 }, + { name = "crystal crossbow", chance = 420 }, { id = 7441, chance = 630 }, -- ice cube - { name = "emerald bangle", chance = 630 }, + { name = "emerald bangle", chance = 930 }, { name = "silver brooch", chance = 310 }, } monster.attacks = { - { name = "combat", interval = 2000, chance = 100, type = COMBAT_PHYSICALDAMAGE, minDamage = -95, maxDamage = -390, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_PURPLEENERGY, range = 6, target = true }, -- basic_attack - { name = "nagadeathattack", interval = 2000, chance = 25, minDamage = -430, maxDamage = -505, range = 6, target = true }, -- death_strike - { name = "nagadeath", interval = 2000, chance = 25, minDamage = -380, maxDamage = -470, target = false }, -- short_death_wave - { name = "death chain", interval = 2000, chance = 25, minDamage = -460, maxDamage = -520, range = 6, target = true }, -- death_chain - { name = "combat", interval = 2000, chance = 25, type = COMBAT_PHYSICALDAMAGE, minDamage = -85, maxDamage = -190, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_PURPLEENERGY, range = 6, target = true }, -- explosion_strike + { 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/reptiles/naga_warrior.lua b/data-otservbr-global/monster/reptiles/naga_warrior.lua index 32e24e4bb53..485e345c74d 100644 --- a/data-otservbr-global/monster/reptiles/naga_warrior.lua +++ b/data-otservbr-global/monster/reptiles/naga_warrior.lua @@ -74,28 +74,28 @@ monster.voices = { } monster.loot = { - { name = "platinum coin", chance = 100000, maxCount = 12 }, + { name = "platinum coin", chance = 100000, maxCount = 20 }, { name = "dagger", chance = 38810 }, { name = "strong health potion", chance = 14930, maxCount = 2 }, - { name = "naga warrior scales", chance = 10600, maxCount = 4 }, - { name = "naga earring", chance = 6420, maxCount = 2 }, + { name = "naga warrior scales", chance = 10600, maxCount = 3 }, + { name = "naga earring", chance = 6420, maxCount = 3 }, { id = 3307, chance = 5520 }, -- scimitar { name = "naga armring", chance = 3730 }, { name = "plate armor", chance = 2990 }, { name = "spiky club", chance = 2090 }, { name = "serpent sword", chance = 1940 }, - { name = "violet crystal shard", chance = 1640 }, + { name = "violet crystal shard", chance = 2640 }, { name = "katana", chance = 1490 }, - { name = "relic sword", chance = 1190 }, - { name = "knight armor", chance = 450 }, + { name = "relic sword", chance = 600 }, + { name = "knight armor", chance = 1100 }, { id = 7441, chance = 300 }, -- ice cube } monster.attacks = { { name = "combat", interval = 2000, chance = 100, type = COMBAT_PHYSICALDAMAGE, minDamage = -120, maxDamage = -340, target = true }, -- basic_attack - { name = "combat", interval = 2000, chance = 25, type = COMBAT_PHYSICALDAMAGE, minDamage = -320, maxDamage = -430, effect = CONST_ME_YELLOWSMOKE, range = 3, target = true }, -- eruption_strike - { name = "nagadeathattack", interval = 2000, chance = 25, minDamage = -360, maxDamage = -415, target = true }, -- death_strike - { name = "combat", interval = 4000, chance = 31, type = COMBAT_LIFEDRAIN, minDamage = -360, maxDamage = -386, radius = 4, effect = CONST_ME_DRAWBLOOD, target = false }, -- great_blood_ball + { 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/rhindeer.lua b/data-otservbr-global/monster/reptiles/rhindeer.lua index 743d9fb492a..4cd428e94f1 100644 --- a/data-otservbr-global/monster/reptiles/rhindeer.lua +++ b/data-otservbr-global/monster/reptiles/rhindeer.lua @@ -76,18 +76,18 @@ monster.voices = { } monster.loot = { - { name = "platinum coin", chance = 72260, maxCount = 31 }, - { name = "brown crystal splinter", chance = 11550, maxCount = 7 }, + { name = "platinum coin", chance = 72260, maxCount = 30 }, + { name = "brown crystal splinter", chance = 11550, maxCount = 4 }, { name = "rhindeer antlers", chance = 6020 }, { name = "rainbow quartz", chance = 4940, maxCount = 2 }, - { name = "violet gem", chance = 4050 }, { name = "great mana potion", chance = 2670, maxCount = 4 }, { name = "titan axe", chance = 2470 }, { name = "yellow gem", chance = 1880 }, { name = "knight armor", chance = 1380 }, + { name = "violet gem", chance = 1200 }, { id = 23543, chance = 890 }, -- collar of green plasma - { name = "heavy mace", chance = 890 }, - { name = "mastermind shield", chance = 690 }, + { name = "heavy mace", chance = 300 }, + { name = "mastermind shield", chance = 400 }, { id = 3053, chance = 690 }, -- time ring } diff --git a/data-otservbr-global/monster/reptiles/two-headed_turtle.lua b/data-otservbr-global/monster/reptiles/two-headed_turtle.lua index 89d8b24c975..2d1c2090b37 100644 --- a/data-otservbr-global/monster/reptiles/two-headed_turtle.lua +++ b/data-otservbr-global/monster/reptiles/two-headed_turtle.lua @@ -65,24 +65,24 @@ monster.voices = { } monster.loot = { - { name = "platinum coin", chance = 100000, maxCount = 80 }, + { name = "platinum coin", chance = 100000, maxCount = 8 }, { name = "great health potion", chance = 15701 }, - { name = "two-headed turtle heads", chance = 15000 }, + { name = "two-headed turtle heads", chance = 8700 }, { name = "strong mana potion", chance = 13373 }, - { name = "hydrophytes", chance = 9552 }, + { name = "hydrophytes", chance = 11000 }, { id = 1047, chance = 6388 }, -- bone - { name = "glacier shoes", chance = 6239 }, + { name = "glacier shoes", chance = 4650 }, { id = 281, chance = 3582 }, -- giant shimmering pearl (green) { name = "small tropical fish", chance = 3582 }, - { name = "coral brooch", chance = 3343 }, + { name = "coral brooch", chance = 2600 }, { name = "silver brooch", chance = 2507 }, - { name = "lightning headband", chance = 6448 }, - { name = "knight legs", chance = 7269 }, + { name = "lightning headband", chance = 2110 }, + { name = "knight legs", chance = 2000 }, { name = "gemmed figurine", chance = 2090 }, { name = "emerald bangle", chance = 1373 }, { name = "terra amulet", chance = 1373 }, { id = 3040, chance = 1313 }, -- "gold nugget" - { name = "spellbook of enlightenment", chance = 6134 }, + { name = "spellbook of enlightenment", chance = 1300 }, { id = 3565, chance = 1015 }, -- "cape" { id = 10422, chance = 657 }, -- "clay lump" { name = "white gem", chance = 418 }, diff --git a/data-otservbr-global/monster/reptiles/young_goanna.lua b/data-otservbr-global/monster/reptiles/young_goanna.lua index 5f68752fb46..ad4b0c01128 100644 --- a/data-otservbr-global/monster/reptiles/young_goanna.lua +++ b/data-otservbr-global/monster/reptiles/young_goanna.lua @@ -74,23 +74,33 @@ monster.voices = { monster.loot = { { name = "platinum coin", chance = 100000, maxCount = 3 }, - { name = "envenomed arrow", chance = 68000, maxCount = 35 }, - { name = "terra rod", chance = 10900 }, - { name = "goanna meat", chance = 9800 }, - { name = "snakebite rod", chance = 9000 }, - { name = "blue goanna scale", chance = 7900 }, - { name = "goanna claw", chance = 4300 }, - { name = "serpent sword", chance = 4000 }, - { name = "leaf star", chance = 3800, maxCount = 3 }, - { name = "silver amulet", chance = 3800 }, - { name = "springsprout rod", chance = 2700 }, - { name = "scared frog", chance = 2100 }, - { name = "terra amulet", chance = 1100 }, - { name = "lizard heart", chance = 800 }, - { name = "sacred tree amulet", chance = 800 }, - { name = "small tortoise", chance = 550 }, - { name = "fur armor", chance = 270 }, - { name = "terra hood", chance = 250 }, + { name = "envenomed arrow", chance = 70400, maxCount = 35 }, + { name = "snakebite rod", chance = 10620 }, + { name = "goanna meat", chance = 10030 }, + { name = "blue crystal shard", chance = 9110 }, + { name = "terra rod", chance = 8940 }, + { name = "blue goanna scale", chance = 8260 }, + { name = "small enchanted emerald", chance = 4890 }, + { name = "leaf star", chance = 4550, maxCount = 3 }, + { name = "rainbow quartz", chance = 4050, maxCount = 3 }, + { name = "onyx chip", chance = 4050 }, + { name = "goanna claw", chance = 3880 }, + { name = "violet gem", chance = 3540 }, + { name = "serpent sword", chance = 3370 }, + { name = "springsprout rod", chance = 3370 }, + { name = "green crystal shard", chance = 2950 }, + { name = "scared frog", chance = 2610 }, + { name = "yellow gem", chance = 2530 }, + { name = "silver amulet", chance = 2280 }, + { name = "terra amulet", chance = 1430 }, + { name = "blue gem", chance = 1180 }, + { name = "terra hood", chance = 1100 }, + { name = "blue crystal splinter", chance = 1010 }, + { name = "sacred tree amulet", chance = 840 }, + { name = "small tortoise", chance = 670 }, + { name = "lizard heart", chance = 590 }, + { name = "wooden spellbook", chance = 170 }, + { name = "fur armor", chance = 80 }, } monster.attacks = { @@ -104,7 +114,7 @@ monster.defenses = { defense = 78, armor = 78, mitigation = 2.16, - { name = "speed", interval = 2000, chance = 5, speedChange = 350, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 }, + { name = "speed", interval = 2000, chance = 15, speedChange = 420, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 }, } monster.elements = { diff --git a/data-otservbr-global/monster/trainers/training_machine.lua b/data-otservbr-global/monster/trainers/training_machine.lua index cb50c0a4532..93d042efa5e 100644 --- a/data-otservbr-global/monster/trainers/training_machine.lua +++ b/data-otservbr-global/monster/trainers/training_machine.lua @@ -58,7 +58,18 @@ monster.defenses = { { name = "combat", type = COMBAT_HEALING, chance = 15, interval = 2000, minDamage = 10000, maxDamage = 50000, effect = CONST_ME_MAGIC_BLUE }, } -monster.elements = {} +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} monster.immunities = {} diff --git a/data-otservbr-global/monster/vermins/diremaw.lua b/data-otservbr-global/monster/vermins/diremaw.lua index 1e3accb0fd3..b89c47c99e7 100644 --- a/data-otservbr-global/monster/vermins/diremaw.lua +++ b/data-otservbr-global/monster/vermins/diremaw.lua @@ -93,7 +93,7 @@ monster.loot = { { name = "gold ingot", chance = 2970 }, { id = 281, chance = 3100 }, -- giant shimmering pearl (green) { name = "suspicious device", chance = 600 }, - { name = "mycological bow", chance = 1200 }, + { name = "mycological bow", chance = 200 }, { name = "mushroom backpack", chance = 1500 }, } diff --git a/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/ahau_lever.lua b/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/ahau_lever.lua new file mode 100644 index 00000000000..a5300abb8f3 --- /dev/null +++ b/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/ahau_lever.lua @@ -0,0 +1,24 @@ +local config = { + boss = { + name = "Ahau", + position = Position(34008, 31696, 10), + }, + timeAfterKill = 60, + playerPositions = { + { pos = Position(34037, 31714, 10), teleport = Position(34008, 31703, 10) }, + { pos = Position(34036, 31714, 10), teleport = Position(34008, 31703, 10) }, + { pos = Position(34035, 31714, 10), teleport = Position(34008, 31703, 10) }, + { pos = Position(34034, 31714, 10), teleport = Position(34008, 31703, 10) }, + { pos = Position(34033, 31714, 10), teleport = Position(34008, 31703, 10) }, + }, + specPos = { + from = Position(33999, 31692, 10), + to = Position(34018, 31705, 10), + }, + exit = Position(34036, 31717, 10), + exitTeleporter = Position(34002, 31706, 10), +} + +local lever = BossLever(config) +lever:position(Position(34038, 31714, 10)) +lever:register() diff --git a/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/idol_of_tukh.lua b/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/idol_of_tukh.lua new file mode 100644 index 00000000000..e48a5a477d4 --- /dev/null +++ b/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/idol_of_tukh.lua @@ -0,0 +1,31 @@ +local config = { + [40578] = { + female = 1598, + male = 1597, + msg = "ancient aucar", + }, +} + +local idol = Action() +function idol.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local choice = config[item.itemid] + if not choice then + return true + end + + if not player:hasOutfit(player:getSex() == PLAYERSEX_FEMALE and choice.female or choice.male) then + player:addOutfit(choice.female) + player:addOutfit(choice.male) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have received the " .. choice.msg .. " outfit!") + player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + item:remove(1) + else + player:sendCancelMessage("You have already obtained this outfit!") + end + return true +end + +for k, v in pairs(config) do + idol:id(k) +end +idol:register() diff --git a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua new file mode 100644 index 00000000000..d76538b4236 --- /dev/null +++ b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua @@ -0,0 +1,106 @@ +local config = { + bossName = "Brain Head", + requiredLevel = 250, + timeToFightAgain = 10, -- In hour + destination = Position(31963, 32324, 10), + exitPosition = Position(31971, 32325, 10), +} + +local zone = Zone("boss." .. toKey(config.bossName)) +local encounter = Encounter("Brain Head", { + zone = zone, + timeToSpawnMonsters = "50ms", +}) + +zone:blockFamiliars() +zone:setRemoveDestination(config.exitPosition) + +local locked = false + +function encounter:onReset() + locked = false + encounter:removeMonsters() +end + +encounter:addRemoveMonsters():autoAdvance() +encounter:addBroadcast("You've entered the Brain Head's lair."):autoAdvance() +encounter + :addSpawnMonsters({ + { + name = "Brain Head", + positions = { + Position(31954, 32325, 10), + }, + }, + { + name = "Cerebellum", + positions = { + Position(31953, 32324, 10), + Position(31955, 32324, 10), + Position(31953, 32326, 10), + Position(31955, 32326, 10), + Position(31960, 32320, 10), + Position(31960, 32330, 10), + Position(31947, 32320, 10), + Position(31947, 32330, 10), + }, + }, + }) + :autoAdvance("30s") + +encounter + :addStage({ + start = function() + locked = true + end, + }) + :autoAdvance("270s") + +encounter:addRemovePlayers():autoAdvance() + +encounter:startOnEnter() +encounter:register() + +local teleportBoss = MoveEvent() +function teleportBoss.onStepIn(creature, item, position, fromPosition) + if not creature or not creature:isPlayer() then + return false + end + local player = creature + if player:getLevel() < config.requiredLevel then + player:teleportTo(fromPosition, true) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to be level " .. config.requiredLevel .. " or higher.") + return true + end + if locked then + player:teleportTo(fromPosition, true) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "There's already someone fighting with " .. config.bossName .. ".") + return false + end + if zone:countPlayers(IgnoredByMonsters) >= 5 then + player:teleportTo(fromPosition, true) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The boss room is full.") + return false + end + local timeLeft = player:getBossCooldown(config.bossName) - os.time() + if timeLeft > 0 then + player:teleportTo(fromPosition, true) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait " .. getTimeInWords(timeLeft) .. " to face " .. config.bossName .. " again!") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + return false + end + player:teleportTo(config.destination) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + player:setBossCooldown(config.bossName, os.time() + config.timeToFightAgain * 3600) + player:sendBosstiaryCooldownTimer() +end + +teleportBoss:aid(30407) +teleportBoss:type("stepin") +teleportBoss:register() + +SimpleTeleport(Position(31946, 32334, 10), config.exitPosition) diff --git a/data-otservbr-global/scripts/creaturescripts/monster/invulnerable.lua b/data-otservbr-global/scripts/creaturescripts/monster/invulnerable.lua new file mode 100644 index 00000000000..070b46054a6 --- /dev/null +++ b/data-otservbr-global/scripts/creaturescripts/monster/invulnerable.lua @@ -0,0 +1,18 @@ +local invulnerable = CreatureEvent("monster.invulnerable") +function invulnerable.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType) + if not creature then + return primaryDamage, primaryType, secondaryDamage, secondaryType + end + return false +end +invulnerable:register() + +function Monster:setInvulnerable() + self:registerEvent("monster.invulnerable") + return true +end + +function Monster:removeInvulnerable() + self:unregisterEvent("monster.invulnerable") + return true +end diff --git a/data-otservbr-global/scripts/spells/monster/death_barrage.lua b/data-otservbr-global/scripts/spells/monster/death_barrage.lua new file mode 100644 index 00000000000..2657bcc12e3 --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/death_barrage.lua @@ -0,0 +1,29 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_DEATHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MORTAREA) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_SUDDENDEATH) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + combat:execute(creature, var) + addEvent(castDeathMissile, 150, creature:getId(), var) + addEvent(castDeathMissile, 300, creature:getId(), var) + addEvent(castDeathMissile, 450, creature:getId(), var) + return +end + +function castDeathMissile(cid, var) + local creature = Creature(cid) + if creature and var then + combat:execute(creature, var) + end +end + +spell:name("death barrage") +spell:words("###6042") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(false) +spell:needTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/destroy_magic_walls.lua b/data-otservbr-global/scripts/spells/monster/destroy_magic_walls.lua new file mode 100644 index 00000000000..662f2fd108c --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/destroy_magic_walls.lua @@ -0,0 +1,33 @@ +local magicWallIds = { + ITEM_MAGICWALL_SAFE, + ITEM_MAGICWALL, + ITEM_WILDGROWTH_SAFE, + ITEM_WILDGROWTH, +} + +local spell = Spell("instant") +function spell.onCastSpell(creature, var) + -- check tiles around the caster + local position = creature:getPosition() + for x = -2, 2 do + for y = -2, 2 do + local tile = Tile(position.x + x, position.y + y, position.z) + if tile then + local item = tile:getTopVisibleThing() + if item and table.contains(magicWallIds, item:getId()) then + item:remove() + position:sendMagicEffect(CONST_ME_POFF) + return true -- only one magic wall per cast + end + end + end + end + return true +end + +spell:name("destroy magic walls") +spell:words("###6045") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/diabolic_imp_fireball.lua b/data-otservbr-global/scripts/spells/monster/diabolic_imp_fireball.lua new file mode 100644 index 00000000000..59948c3f0c2 --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/diabolic_imp_fireball.lua @@ -0,0 +1,26 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREATTACK) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +arr_small = { + { 0, 1, 0 }, + { 1, 3, 1 }, + { 0, 1, 0 }, +} + +local area = createCombatArea(arr_small) +combat:setArea(area) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("diabolic imp fireball") +spell:words("###6035") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(false) +spell:needTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/doctor_marrow_explosion.lua b/data-otservbr-global/scripts/spells/monster/doctor_marrow_explosion.lua new file mode 100644 index 00000000000..f616e8b8cee --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/doctor_marrow_explosion.lua @@ -0,0 +1,90 @@ +local spellCombat = Combat() +spellCombat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +spellCombat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ORANGETELEPORT) + +spellCombat:setArea(createCombatArea(AREA_CIRCLE6X6)) + +local damage = 0 +local crit = false + +local function paralyze(player) + if not player then + return true + end + + local condition = Condition(CONDITION_PARALYZE) + condition:setParameter(CONDITION_PARAM_TICKS, 500) + condition:setFormula(-0.94, 0, -0.97, 0) + player:addCondition(condition) + return true +end + +local spell = Spell("instant") +function onTargetCreature(creature, target) + if not targetPos then + return true + end + local master = target:getMaster() + if not target:isPlayer() and not (master or master:isPlayer()) then + return true + end + + local distance = math.floor(targetPos:getDistance(target:getPosition())) + local actualDamage = damage / (2 ^ distance) + doTargetCombatHealth(0, target, COMBAT_EARTHDAMAGE, actualDamage, actualDamage, CONST_ME_NONE) + if crit then + target:getPosition():sendMagicEffect(CONST_ME_CRITICAL_DAMAGE) + end + return true +end + +spellCombat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function spell.onCastSpell(creature, var) + local target = Creature(var:getNumber()) + if not target then + return false + end + local targetPos = target:getPosition() + target:say("You are being targeted by Doctor Marrow's explosion!", TALKTYPE_MONSTER_SAY, false, target) + creature:getPosition():sendMagicEffect(CONST_ME_ORANGE_ENERGY_SPARK) + targetPos:sendMagicEffect(CONST_ME_ORANGETELEPORT) + + local totalDelay = 4000 + + for i = 0, totalDelay / 100 do + addEvent(function(pos) + local spectators = Game.getSpectators(pos, false, true, 6, 6, 6, 6) + for _, spectator in ipairs(spectators) do + if spectator:isPlayer() then + paralyze(spectator) + end + end + end, i * 100, targetPos) + end + + addEvent(function(cid) + local creature = Creature(cid) + creature:getPosition():sendMagicEffect(CONST_ME_ORANGE_ENERGY_SPARK) + targetPos:sendMagicEffect(CONST_ME_ORANGETELEPORT) + end, 2000, creature:getId()) + + addEvent(function(cid, pos) + damage = -math.random(3500, 7000) + if math.random(1, 100) <= 10 then + crit = true + damage = damage * 1.5 + else + crit = false + end + spellCombat:execute(creature, Variant(pos)) + end, totalDelay, creature:getId(), targetPos) + return true +end + +spell:name("doctor marrow explosion") +spell:words("###6044") +spell:needLearn(true) +spell:needTarget(true) +spell:cooldown(10000) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/earth_barrage.lua b/data-otservbr-global/scripts/spells/monster/earth_barrage.lua new file mode 100644 index 00000000000..727db77f8ca --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/earth_barrage.lua @@ -0,0 +1,29 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITBYPOISON) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_SMALLEARTH) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + combat:execute(creature, var) + addEvent(castEarthMissile, 150, creature:getId(), var) + addEvent(castEarthMissile, 300, creature:getId(), var) + addEvent(castEarthMissile, 450, creature:getId(), var) + return +end + +function castEarthMissile(cid, var) + local creature = Creature(cid) + if creature and var then + combat:execute(creature, var) + end +end + +spell:name("earth barrage") +spell:words("###6039") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(false) +spell:needTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/energy_barrage.lua b/data-otservbr-global/scripts/spells/monster/energy_barrage.lua new file mode 100644 index 00000000000..1b36296fa35 --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/energy_barrage.lua @@ -0,0 +1,29 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_PURPLEENERGY) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + combat:execute(creature, var) + addEvent(castEnergyMissile, 150, creature:getId(), var) + addEvent(castEnergyMissile, 300, creature:getId(), var) + addEvent(castEnergyMissile, 450, creature:getId(), var) + return +end + +function castEnergyMissile(cid, var) + local creature = Creature(cid) + if creature and var then + combat:execute(creature, var) + end +end + +spell:name("energy barrage") +spell:words("###6038") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(false) +spell:needTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/exploding_cask.lua b/data-otservbr-global/scripts/spells/monster/exploding_cask.lua new file mode 100644 index 00000000000..5448d0733ff --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/exploding_cask.lua @@ -0,0 +1,106 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setArea(createCombatArea(AREA_CIRCLE2X2)) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) + +local combatCast = Combat() + +local barrelId = 23485 +local bombArea = { + { 0, 1, 1, 1, 0 }, + { 1, 1, 1, 1, 1 }, + { 1, 1, 3, 1, 1 }, + { 1, 1, 1, 1, 1 }, + { 0, 1, 1, 1, 0 }, +} + +function onTargetCreature(creature, target) + local min = -800 + local max = -1100 + + if target:isPlayer() or (target:getMaster() and target:getMaster():isPlayer()) then + doTargetCombatHealth(0, target, COMBAT_FIREDAMAGE, min, max, CONST_ME_NONE) + end + + return true +end + +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +local function createBarrel() + local template = Game.createItem(barrelId, 1) + + template:setDuration(3) + template:stopDecay() + + return template +end + +createBarrel() + +local function explodeBomb(position, creatureId) + local var = {} + + var.instantName = "Cask Explode" + var.runeName = "" + var.type = 2 -- VARIANT_POSITION + var.pos = position + + combat:execute(Creature(creatureId), var) +end + +local function bombTimer(seconds, pos) + local spectators = Game.getSpectators(pos, false, true, 11, 11, 9, 9) + + if #spectators > 0 then + for i = 1, #spectators do + spectators[i]:say(seconds, TALKTYPE_MONSTER_SAY, false, spectators[i], pos) + end + end +end + +function onTargetCreature(creature, target) + if not creature or not target then + return false + end + + local position = target:getPosition() + local template = createBarrel() + template:setOwner(creature:getId()) + + local tile = Tile(position) + local item = template:clone() + tile:addItemEx(item) + item:setDuration(3) + item:decay() + item:setActionId(IMMOVABLE_ACTION_ID) + + addEvent(explodeBomb, 3000, position, creature:getId()) + bombTimer(3, position) + addEvent(bombTimer, 1000, 2, position, creature:getId()) + addEvent(bombTimer, 2000, 1, position, creature:getId()) + + return true +end + +combatCast:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + if not creature or not var then + return false + end + + var.instantName = "Exploding Cask Cast" + return combatCast:execute(creature, var) +end + +spell:name("exploding cask") +spell:words("###6049") +spell:range(6) +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(false) +spell:needTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/fire_barrage.lua b/data-otservbr-global/scripts/spells/monster/fire_barrage.lua new file mode 100644 index 00000000000..5fefadee483 --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/fire_barrage.lua @@ -0,0 +1,29 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREATTACK) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + combat:execute(creature, var) + addEvent(castFireMissile, 150, creature:getId(), var) + addEvent(castFireMissile, 300, creature:getId(), var) + addEvent(castFireMissile, 450, creature:getId(), var) + return +end + +function castFireMissile(cid, var) + local creature = Creature(cid) + if creature and var then + combat:execute(creature, var) + end +end + +spell:name("fire barrage") +spell:words("###6037") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(false) +spell:needTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/fire_wave_delayed.lua b/data-otservbr-global/scripts/spells/monster/fire_wave_delayed.lua new file mode 100644 index 00000000000..98e14ec7985 --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/fire_wave_delayed.lua @@ -0,0 +1,144 @@ +local combatWarn = Combat() +combatWarn:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combatWarn:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_RED) + +local combat1, combat2, combat3, combat4, combat5, combat6 = Combat(), Combat(), Combat(), Combat(), Combat(), Combat() + +local combats = { combat1, combat2, combat3, combat4, combat5, combat6 } + +for _, combat in pairs(combats) do + combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) + combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) + combat:setFormula(COMBAT_FORMULA_DAMAGE, 0, 700, 1500, 0) +end + +arr = { + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 1, 3, 1, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +arr1 = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 1, 3, 1, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +arr2 = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +arr3 = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +arr4 = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +arr5 = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +arr6 = { + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +local area = createCombatArea(arr) +combatWarn:setArea(area) +combat1:setArea(createCombatArea(arr1)) +combat2:setArea(createCombatArea(arr2)) +combat3:setArea(createCombatArea(arr3)) +combat4:setArea(createCombatArea(arr4)) +combat5:setArea(createCombatArea(arr5)) +combat6:setArea(createCombatArea(arr6)) + +local spell = Spell("instant") + +local function eventRemoveFreeze(creatureid) + local creature = Creature(creatureid) + if not creature then + return + end + + if creature:isMoveLocked() then + creature:setMoveLocked(false) + end + + if creature:isDirectionLocked() then + creature:setDirectionLocked(false) + end +end + +local function doCombat(combat, creatureId, var) + local creature = Creature(creatureId) + if not creature then + return false + end + + combat:execute(creature, var) +end + +function spell.onCastSpell(creature, var) + creature:setMoveLocked(true) + creature:setDirectionLocked(true) + + combatWarn:execute(creature, var) + addEvent(doCombat, 1000, combatWarn, creature:getId(), var) + addEvent(doCombat, 2000, combatWarn, creature:getId(), var) + addEvent(doCombat, 3000, combat1, creature:getId(), var) + addEvent(doCombat, 3100, combat2, creature:getId(), var) + addEvent(doCombat, 3200, combat3, creature:getId(), var) + addEvent(doCombat, 3300, combat4, creature:getId(), var) + addEvent(doCombat, 3400, combat5, creature:getId(), var) + addEvent(doCombat, 3500, combat6, creature:getId(), var) + + addEvent(eventRemoveFreeze, 3800, creature:getId()) + return true +end + +spell:name("fire wave delayed") +spell:words("###6050") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:needDirection(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/half_circle_wave_earth.lua b/data-otservbr-global/scripts/spells/monster/half_circle_wave_earth.lua new file mode 100644 index 00000000000..24db5e641cb --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/half_circle_wave_earth.lua @@ -0,0 +1,30 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITBYPOISON) + +arr = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 3, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +local area = createCombatArea(arr) +combat:setArea(area) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("half circle wave earth") +spell:words("###6047") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:needDirection(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/heal_brain_head.lua b/data-otservbr-global/scripts/spells/monster/heal_brain_head.lua new file mode 100644 index 00000000000..1e9547177d8 --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/heal_brain_head.lua @@ -0,0 +1,27 @@ +local spell = Spell("instant") +local brainHeadPosition = Position(31954, 32325, 10) + +function spell.onCastSpell(creature, var) + local tile = Tile(brainHeadPosition) + local origin = creature:getPosition() + if not tile then + return false + end + + origin:sendDistanceEffect(brainHeadPosition, CONST_ANI_HOLY) + brainHeadPosition:sendMagicEffect(CONST_ME_MAGIC_BLUE) + if tile:getTopCreature() and tile:getTopCreature():isMonster() then + if tile:getTopCreature():getName():lower() == "brain head" then + tile:getTopCreature():addHealth(math.random(300, 500)) + end + end + return true +end + +spell:name("heal brain head") +spell:words("###6051") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needTarget(false) +spell:needLearn(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/holy_barrage.lua b/data-otservbr-global/scripts/spells/monster/holy_barrage.lua new file mode 100644 index 00000000000..2ca5761dd8d --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/holy_barrage.lua @@ -0,0 +1,29 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HOLYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HOLYDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_SMALLHOLY) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + combat:execute(creature, var) + addEvent(castHolyMissile, 150, creature:getId(), var) + addEvent(castHolyMissile, 300, creature:getId(), var) + addEvent(castHolyMissile, 450, creature:getId(), var) + return +end + +function castHolyMissile(cid, var) + local creature = Creature(cid) + if creature and var then + combat:execute(creature, var) + end +end + +spell:name("holy barrage") +spell:words("###6041") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(false) +spell:needTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/ice_barrage.lua b/data-otservbr-global/scripts/spells/monster/ice_barrage.lua new file mode 100644 index 00000000000..dc4b9500c14 --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/ice_barrage.lua @@ -0,0 +1,29 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEAREA) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_SMALLICE) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + combat:execute(creature, var) + addEvent(castIceMissile, 150, creature:getId(), var) + addEvent(castIceMissile, 300, creature:getId(), var) + addEvent(castIceMissile, 450, creature:getId(), var) + return +end + +function castIceMissile(cid, var) + local creature = Creature(cid) + if creature and var then + combat:execute(creature, var) + end +end + +spell:name("ice barrage") +spell:words("###6040") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(false) +spell:needTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/mort_ring.lua b/data-otservbr-global/scripts/spells/monster/mort_ring.lua new file mode 100644 index 00000000000..c65c2b63c5d --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/mort_ring.lua @@ -0,0 +1,17 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_DEATHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MORTAREA) +combat:setArea(createCombatArea(AREA_RING1_BURST3)) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("mort ring") +spell:words("###6036") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/physical_barrage.lua b/data-otservbr-global/scripts/spells/monster/physical_barrage.lua new file mode 100644 index 00000000000..68b9c629887 --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/physical_barrage.lua @@ -0,0 +1,29 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_DRAWBLOOD) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_THROWINGSTAR) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + combat:execute(creature, var) + addEvent(castPhysicalMissile, 150, creature:getId(), var) + addEvent(castPhysicalMissile, 300, creature:getId(), var) + addEvent(castPhysicalMissile, 450, creature:getId(), var) + return +end + +function castPhysicalMissile(cid, var) + local creature = Creature(cid) + if creature and var then + combat:execute(creature, var) + end +end + +spell:name("physical barrage") +spell:words("###6043") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(false) +spell:needTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/teleport_strike.lua b/data-otservbr-global/scripts/spells/monster/teleport_strike.lua new file mode 100644 index 00000000000..5e073675e61 --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/teleport_strike.lua @@ -0,0 +1,64 @@ +local spell = Spell("instant") + +local smokeArray = { + { 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 0, 0, 0 }, + { 0, 0, 1, 3, 1, 0, 0 }, + { 0, 0, 0, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0 }, +} + +local smokeCombat = Combat() +smokeCombat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREENSMOKE) +smokeCombat:setArea(createCombatArea(smokeArray)) + +function spell.onCastSpell(creature, var) + local pos = creature:getPosition() + + local target = Creature(var:getNumber()) + local tarPos = target:getPosition() + local direction = target:getDirection() + local pos1 + local pos2 + local pos3 + if direction == 0 then + pos1 = Position(tarPos.x, tarPos.y + 1, tarPos.z) + pos2 = Position(tarPos.x + 1, tarPos.y + 1, tarPos.z) + pos3 = Position(tarPos.x - 1, tarPos.y + 1, tarPos.z) + elseif direction == 1 then + pos1 = Position(tarPos.x - 1, tarPos.y, tarPos.z) + pos2 = Position(tarPos.x - 1, tarPos.y + 1, tarPos.z) + pos3 = Position(tarPos.x - 1, tarPos.y - 1, tarPos.z) + elseif direction == 2 then + pos1 = Position(tarPos.x, tarPos.y - 1, tarPos.z) + pos2 = Position(tarPos.x - 1, tarPos.y - 1, tarPos.z) + pos3 = Position(tarPos.x + 1, tarPos.y - 1, tarPos.z) + elseif direction == 3 then + pos1 = Position(tarPos.x + 1, tarPos.y, tarPos.z) + pos2 = Position(tarPos.x + 1, tarPos.y - 1, tarPos.z) + pos3 = Position(tarPos.x + 1, tarPos.y + 1, tarPos.z) + end + + if Tile(pos1) and Tile(pos1):isWalkable(true, true, true, true) then + smokeCombat:execute(creature, Variant(pos)) + creature:teleportTo(pos1) + elseif Tile(pos2) and Tile(pos2):isWalkable(true, true, true, true) then + smokeCombat:execute(creature, Variant(pos)) + creature:teleportTo(pos2) + elseif Tile(pos3) and Tile(pos3):isWalkable(true, true, true, true) then + smokeCombat:execute(creature, Variant(pos)) + creature:teleportTo(pos3) + end + + return +end + +spell:name("teleport strike") +spell:words("###6048") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(false) +spell:needTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/werecrocodile_fire_ring.lua b/data-otservbr-global/scripts/spells/monster/werecrocodile_fire_ring.lua new file mode 100644 index 00000000000..6bcf412cad2 --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/werecrocodile_fire_ring.lua @@ -0,0 +1,33 @@ +local arrLarge = { + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0 }, + { 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0 }, + { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 }, + { 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0 }, + { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, +} + +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE) +combat:setArea(createCombatArea(arrLarge)) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("werecrocodile fire ring") +spell:words("###6052") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(true) +spell:register() diff --git a/data-otservbr-global/scripts/spells/monster/white_weretiger_ice_ring.lua b/data-otservbr-global/scripts/spells/monster/white_weretiger_ice_ring.lua new file mode 100644 index 00000000000..d027c32c11f --- /dev/null +++ b/data-otservbr-global/scripts/spells/monster/white_weretiger_ice_ring.lua @@ -0,0 +1,78 @@ +local arrLarge = { + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0 }, + { 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0 }, + { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 }, + { 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0 }, + { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, +} + +local arrMedium = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 0, 0, 3, 0, 0, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +local arrSmall = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 0, 3, 0, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +local combatSmallRing = Combat() +combatSmallRing:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE) +combatSmallRing:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEAREA) +combatSmallRing:setArea(createCombatArea(arrSmall)) + +local combatMediumRing = Combat() +combatMediumRing:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE) +combatMediumRing:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEAREA) +combatMediumRing:setArea(createCombatArea(arrMedium)) + +local combatLargeRing = Combat() +combatLargeRing:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE) +combatLargeRing:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEAREA) +combatLargeRing:setArea(createCombatArea(arrLarge)) + +local spell = Spell("instant") + +local combats = { combatSmallRing, combatMediumRing, combatLargeRing } + +function spell.onCastSpell(creature, var) + local randomCombat = combats[math.random(#combats)] + return randomCombat:execute(creature, var) +end + +spell:name("white weretiger ice ring") +spell:words("###6053") +spell:needLearn(true) +spell:cooldown("2000") +spell:isSelfTarget(true) +spell:register() diff --git a/data-otservbr-global/world/otservbr-zones.xml b/data-otservbr-global/world/otservbr-zones.xml index a9224bd3c2d..4740d50385c 100644 --- a/data-otservbr-global/world/otservbr-zones.xml +++ b/data-otservbr-global/world/otservbr-zones.xml @@ -1,2 +1,4 @@ - + + + From 8e9d9bf746b68d4356ddb25d4c8ffcbfa7ec2f3e Mon Sep 17 00:00:00 2001 From: Beats Date: Thu, 28 Dec 2023 15:51:31 -0300 Subject: [PATCH 13/28] improve: enhanced IPO for MSVC/GCC/Clang compilers (#2015) ## Changes Overview This Pull Request introduces an enhanced and more specific implementation for enabling Interprocedural Optimization (IPO/LTO) in our project. The modifications are divided into two main parts: ### 1. Specific Configuration for MSVC For the MSVC compiler, the `/GL` (compilation) and `/LTCG` (linking) flags are explicitly defined. This approach ensures that we are directly enabling IPO for builds with MSVC, fully leveraging the optimization capabilities of this compiler. ### 2. Configuration for Other Compilers (GCC/Clang) For compilers other than MSVC, such as GCC or Clang, we first check for IPO compatibility. If supported, the `-flto=auto` flag is applied to enable interprocedural optimization. This allows the compiler to automatically decide the number of threads to be used, optimizing the process based on system resources. ### Changes Details - For MSVC, we explicitly define `/GL` and `/LTCG` flags to activate IPO. - For other compilers, we use `check_ipo_supported` to ensure compatibility before activating IPO with `-flto=auto`. - Status and warning messages are included to provide clear feedback on the activation of IPO during the compilation process. ### Expected Benefits - Improved performance of compiled code, capitalizing on interprocedural optimization. - Automatic and efficient adaptation to the compilation environment, whether using MSVC or other compilers. - Greater clarity and control over the compilation process, particularly in environments with different compilers. ### Review Request I request the team's review of the changes, with special attention to: - Correct application of flags in different compilation environments. - Overall compatibility with different compiler versions. - The potential impact on compilation time and the performance of compiled code. ### Conclusion This implementation aims to optimize our build process and enhance the performance of the code, aligning with current best practices in C++ development. --- I appreciate your review and feedback on these changes in advance! Co-authored-by: Elson Costa --- CMakeLists.txt | 33 +++++++++++++++++++++++---------- cmake/modules/CanaryLib.cmake | 19 +++++++++++++++---- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e3cbb97c28..8730b5057fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,17 +81,30 @@ endif() # === IPO === option(OPTIONS_ENABLE_IPO "Check and Enable interprocedural optimization (IPO/LTO)" ON) if(OPTIONS_ENABLE_IPO) - log_option_enabled("ipo") - - include(CheckIPOSupported) - check_ipo_supported(RESULT result OUTPUT output) - if(result) - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) - else() - log_war("IPO is not supported: ${output}") - endif() + log_option_enabled("IPO/LTO") + if(MSVC) + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /GL") + set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /LTCG") + set(CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO} /LTCG") + set(CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO} /LTCG") + set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /LTCG") + else() + if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo" OR CMAKE_BUILD_TYPE STREQUAL "Release") + include(CheckIPOSupported) + check_ipo_supported(RESULT result OUTPUT output) + if(result) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto=auto") + message(STATUS "IPO/LTO enabled with -flto=auto for non-MSVC compiler.") + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + log_war("IPO/LTO not supported: ${output}") + endif() + else() + log_option_disabled("IPO/LTO") + endif () + endif() else() - log_option_disabled("ipo") + log_option_disabled("IPO/LTO") endif() option(BUILD_TESTS "Build tests" OFF) # By default, tests will not be built diff --git a/cmake/modules/CanaryLib.cmake b/cmake/modules/CanaryLib.cmake index d5fdd782b1f..3185bfcd2c9 100644 --- a/cmake/modules/CanaryLib.cmake +++ b/cmake/modules/CanaryLib.cmake @@ -44,11 +44,22 @@ if (CMAKE_COMPILER_IS_GNUCXX) endif() # === IPO === -check_ipo_supported(RESULT result OUTPUT output) -if(result) - set_property(TARGET ${PROJECT_NAME}_lib PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) +if(MSVC) + target_compile_options(${PROJECT_NAME}_lib PRIVATE "/GL") + set_target_properties(${PROJECT_NAME}_lib PROPERTIES + STATIC_LINKER_FLAGS "/LTCG" + SHARED_LINKER_FLAGS "/LTCG" + MODULE_LINKER_FLAGS "/LTCG" + EXE_LINKER_FLAGS "/LTCG") else() - message(WARNING "IPO is not supported: ${output}") + check_ipo_supported(RESULT result OUTPUT output) + if(result) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto=auto") + message(STATUS "IPO/LTO enabled with -flto=auto for non-MSVC compiler.") + set_property(TARGET ${PROJECT_NAME}_lib PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + message(WARNING "IPO/LTO is not supported: ${output}") + endif() endif() # === UNITY BUILD (compile time reducer) === From 9e4c8224e74992855f23ae54334d56aab9aa823d Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Thu, 28 Dec 2023 15:52:09 -0300 Subject: [PATCH 14/28] fix: adventurer's blessing (#2043) Players will get the adventurer's blessing until reach the 'adventurersBlessingLevel' level config from your server. Players in Rookgaard (without vocation) ignore this validation, and be like a player without blessing. - Fixed and improved adventurer's blessing. - Improvement of PR #1743 --- data/modules/scripts/blessings/blessings.lua | 6 +-- src/creatures/players/player.cpp | 57 +++++++++++++++----- src/creatures/players/player.hpp | 2 + src/server/network/protocol/protocolgame.cpp | 14 +---- 4 files changed, 50 insertions(+), 29 deletions(-) diff --git a/data/modules/scripts/blessings/blessings.lua b/data/modules/scripts/blessings/blessings.lua index c7c1ce42a72..94eaede5fbd 100644 --- a/data/modules/scripts/blessings/blessings.lua +++ b/data/modules/scripts/blessings/blessings.lua @@ -20,11 +20,11 @@ Blessings.Credits = { } Blessings.Config = { - AdventurerBlessingLevel = 0, -- Free full bless until level + AdventurerBlessingLevel = configManager.getNumber(configKeys.ADVENTURERSBLESSING_LEVEL), -- Free full bless until level HasToF = false, -- Enables/disables twist of fate InquisitonBlessPriceMultiplier = 1.1, -- Bless price multiplied by henricus SkulledDeathLoseStoreItem = true, -- Destroy all items on store when dying with red/blackskull - InventoryGlowOnFiveBless = true, -- Glow in yellow inventory items when the player has 5 or more bless, + InventoryGlowOnFiveBless = configManager.getBoolean(configKeys.INVENTORY_GLOW), -- Glow in yellow inventory items when the player has 5 or more bless, Debug = false, -- Prin debug messages in console if enabled } @@ -260,7 +260,7 @@ Blessings.doAdventurerBlessing = function(player) end player:addMissingBless(true, true) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You received adventurers blessings for you being level lower than " .. Blessings.Config.AdventurerBlessingLevel .. "!") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have adventurer's blessings for being level lower than " .. Blessings.Config.AdventurerBlessingLevel .. "!") player:getPosition():sendMagicEffect(CONST_ME_HOLYDAMAGE) return true end diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 64807ec0f89..c478e15cec0 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -2745,24 +2745,28 @@ void Player::death(std::shared_ptr lastHitCreature) { } sendTextMessage(MESSAGE_EVENT_ADVANCE, deathType.str()); + auto adventurerBlessingLevel = g_configManager().getNumber(ADVENTURERSBLESSING_LEVEL, __FUNCTION__); + auto willNotLoseBless = getLevel() < adventurerBlessingLevel && getVocationId() > VOCATION_NONE; + std::string bless = getBlessingsName(); - std::ostringstream blesses; - if (bless.length() == 0) { - blesses << "You weren't protected with any blessings."; + std::ostringstream blessOutput; + if (willNotLoseBless) { + blessOutput << fmt::format("You still have adventurer's blessings for being level lower than {}!", adventurerBlessingLevel); } else { - blesses << "You were blessed with " << bless; - } - sendTextMessage(MESSAGE_EVENT_ADVANCE, blesses.str()); + bless.empty() ? blessOutput << "You weren't protected with any blessings." + : blessOutput << "You were blessed with " << bless; - // Make player lose bless - uint8_t maxBlessing = 8; - if (pvpDeath && hasBlessing(1)) { - removeBlessing(1, 1); // Remove TOF only - } else { - for (int i = 2; i <= maxBlessing; i++) { - removeBlessing(i, 1); + // Make player lose bless + uint8_t maxBlessing = 8; + if (pvpDeath && hasBlessing(1)) { + removeBlessing(1, 1); // Remove TOF only + } else { + for (int i = 2; i <= maxBlessing; i++) { + removeBlessing(i, 1); + } } } + sendTextMessage(MESSAGE_EVENT_ADVANCE, blessOutput.str()); sendStats(); sendSkills(); @@ -7813,3 +7817,30 @@ bool Player::hasPermittedConditionInPZ() const { return hasPermittedCondition; } + +void Player::checkAndShowBlessingMessage() { + auto adventurerBlessingLevel = g_configManager().getNumber(ADVENTURERSBLESSING_LEVEL, __FUNCTION__); + auto willNotLoseBless = getLevel() < adventurerBlessingLevel && getVocationId() > VOCATION_NONE; + std::string bless = getBlessingsName(); + std::ostringstream blessOutput; + + if (willNotLoseBless) { + auto addedBless = false; + for (uint8_t i = 2; i <= 6; i++) { + if (!hasBlessing(i)) { + addBlessing(i, 1); + addedBless = true; + } + } + sendBlessStatus(); + if (addedBless) { + blessOutput << fmt::format("You have received adventurer's blessings for being level lower than {}!\nYou are still blessed with {}", adventurerBlessingLevel, bless); + } + } else { + bless.empty() ? blessOutput << "You lost all your blessings." : blessOutput << "You are still blessed with " << bless; + } + + if (!blessOutput.str().empty()) { + sendTextMessage(MESSAGE_EVENT_ADVANCE, blessOutput.str()); + } +} diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index c717f84c6f4..517715dccd9 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -2953,4 +2953,6 @@ class Player final : public Creature, public Cylinder, public Bankable { void removeEmptyRewards(); bool hasOtherRewardContainerOpen(const std::shared_ptr container) const; + + void checkAndShowBlessingMessage(); }; diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index a7e42e60d40..1b55e3e94fa 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -946,19 +946,7 @@ void ProtocolGame::addBless() { if (!player) { return; } - - std::string bless = player->getBlessingsName(); - std::ostringstream lostBlesses; - (bless.length() == 0) ? lostBlesses << "You lost all your blessings." : lostBlesses << "You are still blessed with " << bless; - player->sendTextMessage(MESSAGE_EVENT_ADVANCE, lostBlesses.str()); - if (player->getLevel() < g_configManager().getNumber(ADVENTURERSBLESSING_LEVEL, __FUNCTION__) && player->getVocationId() > VOCATION_NONE) { - for (uint8_t i = 2; i <= 6; i++) { - if (!player->hasBlessing(i)) { - player->addBlessing(i, 1); - } - } - sendBlessStatus(); - } + player->checkAndShowBlessingMessage(); } void ProtocolGame::parsePacketFromDispatcher(NetworkMessage msg, uint8_t recvbyte) { From aeec2a283c15c502aa9be8f1e48ecc8bad122826 Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Thu, 28 Dec 2023 15:59:49 -0300 Subject: [PATCH 15/28] fix: daily reward (#2059) Free players will win all rewards, and vip/premium players will win bonus rewards correctly. Fixes #2051 --------- Co-authored-by: GitHub Actions --- data/libs/daily_reward/daily_reward.lua | 10 ++--- .../scripts/daily_reward/daily_reward.lua | 43 +++---------------- 2 files changed, 11 insertions(+), 42 deletions(-) diff --git a/data/libs/daily_reward/daily_reward.lua b/data/libs/daily_reward/daily_reward.lua index e3ca90caf27..232d4826021 100644 --- a/data/libs/daily_reward/daily_reward.lua +++ b/data/libs/daily_reward/daily_reward.lua @@ -38,19 +38,17 @@ end function RegenSoul(id, delay) local soulEvent = DailyRewardBonus.Soul[id] - local maxsoul = 0 local player = Player(id) if not player then stopEvent(soulEvent) DailyRewardBonus.Soul[id] = nil return false end + local maxsoul = 100 + if (configManager.getBoolean(configKeys.VIP_SYSTEM_ENABLED) and player:isVip()) or player:isPremium() then + maxsoul = 200 + end if player:getTile():hasFlag(TILESTATE_PROTECTIONZONE) then - if player:isPremium() then - maxsoul = 200 - else - maxsoul = 100 - end if player:getSoul() < maxsoul then player:addSoul(1) player:sendTextMessage(MESSAGE_FAILURE, "One soul point has been restored.") diff --git a/data/modules/scripts/daily_reward/daily_reward.lua b/data/modules/scripts/daily_reward/daily_reward.lua index de4116f7ed3..270aa3189da 100644 --- a/data/modules/scripts/daily_reward/daily_reward.lua +++ b/data/modules/scripts/daily_reward/daily_reward.lua @@ -207,14 +207,7 @@ end -- Core functions DailyReward.insertHistory = function(playerId, dayStreak, description) - return db.query(string.format( - "INSERT INTO `daily_reward_history`(`player_id`, `daystreak`, `timestamp`, \z - `description`) VALUES (%s, %s, %s, %s)", - playerId, - dayStreak, - os.time(), - db.escapeString(description) - )) + return db.query(string.format("INSERT INTO `daily_reward_history`(`player_id`, `daystreak`, `timestamp`, `description`) VALUES (%s, %s, %s, %s)", playerId, dayStreak, os.time(), db.escapeString(description))) end DailyReward.retrieveHistoryEntries = function(playerId) @@ -224,8 +217,7 @@ DailyReward.retrieveHistoryEntries = function(playerId) end local entries = {} - local resultId = db.storeQuery("SELECT * FROM `daily_reward_history` WHERE `player_id` = \z - " .. player:getGuid() .. " ORDER BY `timestamp` DESC LIMIT 15;") + local resultId = db.storeQuery("SELECT * FROM `daily_reward_history` WHERE `player_id` = " .. player:getGuid() .. " ORDER BY `timestamp` DESC LIMIT 15;") if resultId then repeat local entry = { @@ -421,7 +413,7 @@ function Player.selectDailyReward(self, msg) end local rewardCount = dailyTable.freeAccount - if self:isPremium() then + if (configManager.getBoolean(configKeys.VIP_SYSTEM_ENABLED) and self:isVip()) or self:isPremium() then rewardCount = dailyTable.premiumAccount end @@ -453,8 +445,7 @@ function Player.selectDailyReward(self, msg) end if totalCounter > rewardCount then - self:sendError("Something went wrong here, please restart this dialog.") - return false + logger.info("Player with name {} is trying to get totalCounter: {} more than rewardCount: {}!", self:getName(), totalCounter, rewardCount) end if totalCounter ~= orderedCounter then logger.error("Player with name {} is trying to get wrong daily reward", self:getName()) @@ -471,41 +462,21 @@ function Player.selectDailyReward(self, msg) local description = "" for k, v in ipairs(items) do if dailyTable.itemCharges then - for i = 1, v.count do + for i = 1, rewardCount do local inboxItem = inbox:addItem(v.itemId, dailyTable.itemCharges) -- adding charges for each item if inboxItem then inboxItem:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime()) end end else - local inboxItem = inbox:addItem(v.itemId, v.count) -- adding single item w/o charges + local inboxItem = inbox:addItem(v.itemId, rewardCount) -- adding single item w/o charges if inboxItem then inboxItem:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime()) end end - if k ~= columnsPicked then - description = description .. "" .. v.count .. "x " .. ItemType(v.itemId):getName() .. ", " - else - description = description .. "" .. v.count .. "x " .. ItemType(v.itemId):getName() .. "." - end + description = description .. "" .. rewardCount .. "x " .. ItemType(v.itemId):getName() .. (k ~= columnsPicked and ", " or ".") end - dailyRewardMessage = "Picked items: " .. description - - -- elseif dailyTable.type == DAILY_REWARD_TYPE_STORAGE then - -- local description = "" - -- for i = 1, #reward.things do - -- for j = 1, #reward.things[i].storages do - -- self:setStorageValue(reward.things[i].storages[j].storageId, reward.things[i].storages[j].value) - -- end - -- if i ~= #reward.things then - -- description = description .. reward.things[i].name .. ", " - -- else - -- description = description .. reward.things[i].name .. "." - -- end - -- end - -- dailyRewardMessage = "Picked reward: " .. description) - -- end elseif dailyTable.type == DAILY_REWARD_TYPE_XP_BOOST then self:setExpBoostStamina(self:getExpBoostStamina() + (rewardCount * 60)) self:setStoreXpBoost(50) From 748702c7e412114c63c60e939c1badcf3d27f29c Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Thu, 28 Dec 2023 16:00:16 -0300 Subject: [PATCH 16/28] fix: time machine (#2061) Fixed time_machine and added others' usages. --- .../forgotten_knowledge/time_machine.lua | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/data-otservbr-global/scripts/actions/quests/forgotten_knowledge/time_machine.lua b/data-otservbr-global/scripts/actions/quests/forgotten_knowledge/time_machine.lua index b37d681bce9..bc92b403adf 100644 --- a/data-otservbr-global/scripts/actions/quests/forgotten_knowledge/time_machine.lua +++ b/data-otservbr-global/scripts/actions/quests/forgotten_knowledge/time_machine.lua @@ -1,13 +1,11 @@ -local forgottenKnowledgeMachine = Action() -function forgottenKnowledgeMachine.onUse(player, item, fromPosition, target, toPosition, isHotkey) +local timeMachine = Action() +function timeMachine.onUse(player, item, fromPosition, target, toPosition, isHotkey) if player:getPosition() == Position(32870, 32723, 15) then player:teleportTo(Position(32870, 32724, 14)) player:getPosition():sendMagicEffect(CONST_ME_ENERGYHIT) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The mechanism takes you back in time.") return true - end - - if player:getPosition() == Position(32870, 32723, 14) then + elseif player:getPosition() == Position(32870, 32723, 14) then if player:canFightBoss("The Time Guardian") then player:teleportTo(Position(32870, 32724, 15)) player:getPosition():sendMagicEffect(CONST_ME_ENERGYHIT) @@ -17,10 +15,22 @@ function forgottenKnowledgeMachine.onUse(player, item, fromPosition, target, toP player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait a while before travel in time!") return true end - else - return false end + + if player:getPosition() == Position(33453, 31029, 8) then + player:teleportTo(Position(32430, 32167, 8)) + player:getPosition():sendMagicEffect(CONST_ME_ENERGYHIT) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The mechanism takes you back in time.") + return true + elseif player:getPosition() == Position(32430, 32166, 8) then + player:teleportTo(Position(33453, 31030, 8)) + player:getPosition():sendMagicEffect(CONST_ME_ENERGYHIT) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The mechanism takes you back in time.") + return true + end + + return false end -forgottenKnowledgeMachine:id(25096) -forgottenKnowledgeMachine:register() +timeMachine:id(25096) +timeMachine:register() From e2def5fe155fc474fd3387aee0abbf452e199f29 Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Thu, 28 Dec 2023 16:03:05 -0300 Subject: [PATCH 17/28] improve: count_monsters script cache (#2047) --- .gitignore | 1 + data/scripts/talkactions/gm/count_monsters.lua | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 9a9fc68fab6..aae56bd35a9 100644 --- a/.gitignore +++ b/.gitignore @@ -382,6 +382,7 @@ client_assertions.txt *-house.xml *-monster.xml *-npc.xml +monster_count.txt # SFTP for Sublime sftp-config.json diff --git a/data/scripts/talkactions/gm/count_monsters.lua b/data/scripts/talkactions/gm/count_monsters.lua index 1cd9744b908..25acfdea1ee 100644 --- a/data/scripts/talkactions/gm/count_monsters.lua +++ b/data/scripts/talkactions/gm/count_monsters.lua @@ -24,14 +24,19 @@ function count_monsters.onSay(player, words, param) end end - writing_file:write("--- Total of monsters on server ---\n") - + writing_file:write("--- Monsters on Server ---\n") + local total = 0 for monster, count in pairsByKeys(monsters) do writing_file:write(monster .. " - " .. count .. "\n") + total = total + count end + local outputMsg = "Total of monsters on server: " .. total + writing_file:write("\n" .. outputMsg .. "\n------------------------") writing_file:close() + logger.info(outputMsg) + return true end From fb4338c3990b25cf3efaa84dc8b0b8d5b5d3bca0 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 28 Dec 2023 11:27:17 -0800 Subject: [PATCH 18/28] feat: item ownership (#2003) This add the ability to set ownership on items. An owned item cannot be picked up or used by anyone except the character that owns it. If an owned item is inside a container (backpack, etc) that container is now also treated as owned. "looking" at an owned item shows you the owner. New function: - `item:setOwner(ownerPlayer or id)` - `item:getOwnerId()` - `item:getOwnerName()` - `item:hasOwner()` - `item:canBeMoved()` I also sneaked in some other unrelated improvements that didn't fit any individual PR in here, but the are pretty small. --------- Co-authored-by: Eduardo Dantas --- data/items/items.xml | 18 ++ data/libs/functions/player.lua | 36 ++-- data/modules/scripts/gamestore/init.lua | 189 +++++++----------- .../scripts/eventcallbacks/player/on_look.lua | 4 + .../scripts/talkactions/god/inbox_command.lua | 2 +- src/creatures/combat/combat.cpp | 18 +- src/creatures/combat/spells.cpp | 2 +- src/creatures/monsters/monster.cpp | 2 +- src/creatures/players/player.cpp | 25 ++- src/creatures/players/player.hpp | 2 +- src/creatures/players/wheel/player_wheel.cpp | 6 +- src/game/game.cpp | 127 ++++++++++-- src/game/movement/teleport.hpp | 4 + src/io/iomapserialize.cpp | 12 +- src/items/containers/container.cpp | 38 +++- src/items/containers/container.hpp | 6 + src/items/containers/depot/depotchest.cpp | 3 + src/items/containers/mailbox/mailbox.cpp | 2 +- src/items/containers/mailbox/mailbox.hpp | 4 + src/items/functions/item/item_parse.cpp | 10 + src/items/functions/item/item_parse.hpp | 2 + src/items/item.cpp | 102 +++++++++- src/items/item.hpp | 25 +++ src/items/items.hpp | 1 + src/items/items_definitions.hpp | 4 + src/items/thing.hpp | 3 + src/items/tile.cpp | 14 +- src/items/tile.hpp | 5 + src/items/trashholder.cpp | 9 +- src/items/trashholder.hpp | 4 + src/kv/kv.hpp | 2 +- src/lua/functions/core/game/lua_enums.cpp | 2 + .../network/network_message_functions.cpp | 4 +- .../creatures/combat/combat_functions.cpp | 2 +- .../creatures/creature_functions.cpp | 6 +- .../creatures/player/player_functions.cpp | 35 +++- .../creatures/player/player_functions.hpp | 4 + src/lua/functions/items/item_functions.cpp | 115 +++++++++++ src/lua/functions/items/item_functions.hpp | 14 ++ .../functions/items/item_type_functions.cpp | 19 +- .../functions/items/item_type_functions.hpp | 2 + src/lua/functions/map/tile_functions.cpp | 37 ++++ src/lua/functions/map/tile_functions.hpp | 2 + src/map/house/house.cpp | 94 ++++++--- src/map/house/house.hpp | 2 + src/map/utils/qtreenode.hpp | 6 +- src/utils/tools.cpp | 6 + 47 files changed, 793 insertions(+), 238 deletions(-) diff --git a/data/items/items.xml b/data/items/items.xml index ade8054ec07..bce4281b538 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -9213,15 +9213,18 @@ + + + @@ -27955,6 +27958,7 @@ + @@ -31349,6 +31353,7 @@ + @@ -41612,6 +41617,7 @@ + @@ -43700,11 +43706,13 @@ + + @@ -43750,21 +43758,25 @@ + + + + @@ -44352,24 +44364,28 @@ + + + + @@ -44579,6 +44595,7 @@ + @@ -49175,6 +49192,7 @@ + diff --git a/data/libs/functions/player.lua b/data/libs/functions/player.lua index 90e739792cc..ce9c8177d9d 100644 --- a/data/libs/functions/player.lua +++ b/data/libs/functions/player.lua @@ -554,26 +554,34 @@ function Player.updateHazard(self) return true end -function Player:addItemStoreInbox(itemId, amount, moveable) +function Player:addItemStoreInboxEx(item, moveable, setOwner) local inbox = self:getSlotItem(CONST_SLOT_STORE_INBOX) if not moveable then - for _, item in pairs(inbox:getItems()) do - if item:getId() == itemId then - item:removeAttribute(ITEM_ATTRIBUTE_STORE) - end - end + item:setOwner(self) + item:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime()) + elseif setOwner then + item:setOwner(self) end + inbox:addItemEx(item, INDEX_WHEREEVER, FLAG_NOLIMIT) + return item +end - local newItem = inbox:addItem(itemId, amount, INDEX_WHEREEVER, FLAG_NOLIMIT) - - if not moveable then - for _, item in pairs(inbox:getItems()) do - if item:getId() == itemId then - item:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime()) - end +function Player:addItemStoreInbox(itemId, amount, moveable, setOwner) + local iType = ItemType(itemId) + if not iType then + return nil + end + if iType:isStackable() then + while amount > iType:getStackSize() do + self:addItemStoreInboxEx(Game.createItem(itemId, iType:getStackSize()), moveable, setOwner) + amount = amount - iType:getStackSize() end end - return newItem + local item = Game.createItem(itemId, amount) + if not item then + return nil + end + return self:addItemStoreInboxEx(item, moveable, setOwner) end ---@param monster Monster diff --git a/data/modules/scripts/gamestore/init.lua b/data/modules/scripts/gamestore/init.lua index 64151e2ff20..d33485c7091 100644 --- a/data/modules/scripts/gamestore/init.lua +++ b/data/modules/scripts/gamestore/init.lua @@ -34,6 +34,7 @@ GameStore.OfferTypes = { OFFER_TYPE_HIRELING_OUTFIT = 24, OFFER_TYPE_HUNTINGSLOT = 25, OFFER_TYPE_ITEM_BED = 26, + OFFER_TYPE_ITEM_UNIQUE = 27, } GameStore.SubActions = { @@ -97,6 +98,7 @@ function convertType(type) [GameStore.OfferTypes.OFFER_TYPE_CHARGES] = GameStore.ConverType.SHOW_ITEM, [GameStore.OfferTypes.OFFER_TYPE_HIRELING] = GameStore.ConverType.SHOW_HIRELING, [GameStore.OfferTypes.OFFER_TYPE_ITEM_BED] = GameStore.ConverType.SHOW_NONE, + [GameStore.OfferTypes.OFFER_TYPE_ITEM_UNIQUE] = GameStore.ConverType.SHOW_ITEM, } if not types[type] then @@ -294,8 +296,8 @@ function parseTransferableCoins(playerId, msg) addPlayerEvent(sendStorePurchaseSuccessful, 550, playerId, "You have transfered " .. amount .. " coins to " .. reciver .. " successfully") -- Adding history for both receiver/sender - GameStore.insertHistory(accountId, GameStore.HistoryTypes.HISTORY_TYPE_NONE, player:getName() .. " transferred you this amount.", amount, GameStore.CoinType.Coin) - GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, "You transferred this amount to " .. reciver, -1 * amount, GameStore.CoinType.Coin) + GameStore.insertHistory(accountId, GameStore.HistoryTypes.HISTORY_TYPE_NONE, player:getName() .. " transferred you this amount.", amount, GameStore.CoinType.Transferable) + GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, "You transferred this amount to " .. reciver, -1 * amount, GameStore.CoinType.Transferable) openStore(playerId) end @@ -318,14 +320,14 @@ function parseRequestStoreOffers(playerId, msg) local oldProtocol = player:getClient().version < 1200 if oldProtocol then - local categoryName = msg:getString() - local category = GameStore.getCategoryByName(categoryName) + local stringParam = msg:getString() + local category = GameStore.getCategoryByName(stringParam) if category then addPlayerEvent(sendShowStoreOffersOnOldProtocol, 350, playerId, category) end elseif actionType == GameStore.ActionType.OPEN_CATEGORY then - local categoryName = msg:getString() - local category = GameStore.getCategoryByName(categoryName) + local stringParam = msg:getString() + local category = GameStore.getCategoryByName(stringParam) if category then addPlayerEvent(sendShowStoreOffers, 50, playerId, category) end @@ -441,7 +443,9 @@ function parseBuyStoreOffer(playerId, msg) -- Handled errors have a code index and unhandled errors do not local pcallOk, pcallError = pcall(function() if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM then - GameStore.processItemPurchase(player, offer.itemtype, offer.count, offer.movable) + GameStore.processItemPurchase(player, offer.itemtype, offer.count, offer.moveable, offer.setOwner) + elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM_UNIQUE then + GameStore.processItemPurchase(player, offer.itemtype, offer.count, offer.moveable, offer.setOwner) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_POUCH then GameStore.processItemPurchase(player, offer.itemtype, offer.count) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_INSTANT_REWARD_ACCESS then @@ -455,7 +459,7 @@ function parseBuyStoreOffer(playerId, msg) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREMIUM then GameStore.processPremiumPurchase(player, offer.id) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_STACKABLE then - GameStore.processStackablePurchase(player, offer.itemtype, offer.count, offer.name, offer.movable) + GameStore.processStackablePurchase(player, offer.itemtype, offer.count, offer.name, offer.moveable, offer.setOwner) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HOUSE then GameStore.processHouseRelatedPurchase(player, offer) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT then @@ -473,14 +477,12 @@ function parseBuyStoreOffer(playerId, msg) GameStore.processExpBoostPuchase(player) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYSLOT then GameStore.processPreyThirdSlot(player) - elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HUNTINGSLOT then - GameStore.processTaskHuntingThirdSlot(player) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYBONUS then GameStore.processPreyBonusReroll(player, offer.count) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_TEMPLE then GameStore.processTempleTeleportPurchase(player) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_CHARGES then - GameStore.processChargesPurchase(player, offer.itemtype, offer.name, offer.charges, offer.movable) + GameStore.processChargesPurchase(player, offer.itemtype, offer.name, offer.charges, offer.moveable, offer.setOwner) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HIRELING then local hirelingName = msg:getString() local sex = msg:getByte() @@ -644,10 +646,16 @@ function Player.canBuyOffer(self, offer) if disabled ~= 1 then if offer.type == GameStore.OfferTypes.OFFER_TYPE_POUCH then - local pounch = self:getItemById(23721, true) - if pounch then + local pouch = self:getItemById(23721, true) + if pouch then + disabled = 1 + disabledReason = "You already have a Loot Pouch." + end + elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM_UNIQUE then + local item = self:getItemById(offer.itemtype, true) + if item then disabled = 1 - disabledReason = "You already have Loot Pouch." + disabledReason = "You already have a " .. ItemType(item:getId()):getName() .. "." end elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_BLESSINGS then if self:getBlessingCount(offer.blessid) >= 5 then @@ -1095,7 +1103,7 @@ function sendStoreTransactionHistory(playerId, page, entriesPerPage) msg:addByte(entry.mode) -- 0 = normal, 1 = gift, 2 = refund msg:add32(entry.amount) if not oldProtocol then - msg:addByte(0x0) -- 0 = transferable tibia coin, 1 = normal tibia coin + msg:addByte(entry.type or 0x00) -- 0 = transferable tibia coin, 1 = normal tibia coin end msg:addString(entry.description, "sendStoreTransactionHistory - entry.description") if not oldProtocol then @@ -1118,8 +1126,7 @@ function sendStorePurchaseSuccessful(playerId, message) msg:addString(message, "sendStorePurchaseSuccessful - message") if oldProtocol then -- Send all coins can be used for buy store offers - local totalCoins = player:getTibiaCoins() + player:getTransferableCoins() - msg:addU32(totalCoins) + msg:addU32(player:getTibiaCoins()) -- Send transferable coins can be used on transfer msg:addU32(player:getTransferableCoins()) end @@ -1173,8 +1180,7 @@ function sendUpdatedStoreBalances(playerId) msg:addByte(0x01) -- Send total of coins (transferable and normal coin) - local totalCoins = player:getTibiaCoins() + player:getTransferableCoins() - msg:addU32(totalCoins) + msg:addU32(player:getTibiaCoins()) msg:addU32(player:getTransferableCoins()) -- How many are Transferable if not oldProtocol then -- How many are reserved for a Character Auction @@ -1501,39 +1507,21 @@ end -- take a table {code = ..., message = ...} if the error is handled. When no code -- index is present the error is assumed to be unhandled. -function GameStore.processItemPurchase(player, offerId, offerCount, movable) +function GameStore.processItemPurchase(player, offerId, offerCount, moveable, setOwner) if player:getFreeCapacity() < ItemType(offerId):getWeight(offerCount) then return error({ code = 0, message = "Please make sure you have free capacity to hold this item." }) end - local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX) - if inbox then - for t = 1, offerCount do - local inboxItem = inbox:addItem(offerId, offerCount or 1) - if movable ~= true and inboxItem then - inboxItem:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime()) - end - end - else - return error({ code = 0, message = "Please make sure you have free slots in your store inbox." }) + for t = 1, offerCount do + player:addItemStoreInbox(offerId, offerCount or 1, moveable, setOwner) end end -function GameStore.processChargesPurchase(player, itemtype, name, charges, movable) +function GameStore.processChargesPurchase(player, itemtype, name, charges, moveable, setOwner) if player:getFreeCapacity() < ItemType(itemtype):getWeight(1) then return error({ code = 0, message = "Please make sure you have free capacity to hold this item." }) end - - local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX) - if inbox then - local inboxItem = inbox:addItem(itemtype, charges) - - if movable ~= true and inboxItem then - inboxItem:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime()) - end - else - return error({ code = 0, message = "Please make sure you have free slots in your store inbox." }) - end + player:addItemStoreInbox(itemtype, charges, moveable, setOwner) end function GameStore.processSingleBlessingPurchase(player, blessId, count) @@ -1569,7 +1557,7 @@ function GameStore.processPremiumPurchase(player, offerId) end end -function GameStore.processStackablePurchase(player, offerId, offerCount, offerName, movable) +function GameStore.processStackablePurchase(player, offerId, offerCount, offerName, moveable, setOwner) local function isKegItem(itemId) return itemId >= ITEM_KEG_START and itemId <= ITEM_KEG_END end @@ -1780,7 +1768,9 @@ function GameStore.processPreyBonusReroll(player, offerCount) end function GameStore.processTempleTeleportPurchase(player) - if player:getCondition(CONDITION_INFIGHT, CONDITIONID_DEFAULT) or player:isPzLocked() then + local inPz = player:getTile():hasFlag(TILESTATE_PROTECTIONZONE) + local inFight = player:isPzLocked() or player:getCondition(CONDITION_INFIGHT, CONDITIONID_DEFAULT) + if not inPz and inFight then return error({ code = 0, message = "You can't use temple teleport in fight!" }) end @@ -1831,7 +1821,10 @@ function GameStore.processHirelingChangeNamePurchase(player, offer, productType, local offerId = offer.id if player:getClient().version < 1200 then - return error({ code = 1, message = "You cannot buy hireling change name on client 10, please relog on client 12 and try again." }) + return error({ + code = 1, + message = "You cannot buy hireling change name on client 10, please relog on client 12 and try again.", + }) end if productType == GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE then @@ -1857,7 +1850,10 @@ function GameStore.processHirelingChangeSexPurchase(player, offer) local playerId = player:getId() if player:getClient().version < 1200 then - return error({ code = 1, message = "You cannot buy hireling change sex on client 10, please relog on client 12 and try again." }) + return error({ + code = 1, + message = "You cannot buy hireling change sex on client 10, please relog on client 12 and try again.", + }) end local message = "Close the store window to select which hireling should have the sex changed." @@ -1868,7 +1864,10 @@ end function GameStore.processHirelingSkillPurchase(player, offer) if player:getClient().version < 1200 then - return error({ code = 1, message = "You cannot buy hireling skill on client 10, please relog on client 12 and try again." }) + return error({ + code = 1, + message = "You cannot buy hireling skill on client 10, please relog on client 12 and try again.", + }) end player:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) @@ -1878,7 +1877,10 @@ end function GameStore.processHirelingOutfitPurchase(player, offer) if player:getClient().version < 1200 then - return error({ code = 1, message = "You cannot buy hireling outfit on client 10, please relog on client 12 and try again." }) + return error({ + code = 1, + message = "You cannot buy hireling outfit on client 10, please relog on client 12 and try again.", + }) end local outfitName = GetHirelingOutfitNameById(offer.id) @@ -1891,16 +1893,14 @@ end --==Player==-- -- Character auction coins function Player.canRemoveCoins(self, coins) - if self:getTibiaCoins() < coins then - return false - end - return true + return self:getTibiaCoins() >= coins end function Player.removeCoinsBalance(self, coins) if self:canRemoveCoins(coins) then sendStoreBalanceUpdating(self:getId(), true) - return self:removeTibiaCoins(coins) + self:removeTibiaCoins(coins) + return true end return false @@ -1914,51 +1914,16 @@ function Player.addCoinsBalance(self, coins, update) return true end --- Transferable + normal coin -function Player.canRemoveAllCoins(self, coins) - if self:getTibiaCoins() + self:getTransferableCoins() < coins then - return false - end - return true -end - ---[[ - Removes a specified amount of coins from the player's inventory. - @param coins (number) - The amount of coins to be removed. - @return (boolean) - Returns true if the coins were successfully removed, false otherwise. ---]] -function Player.removeAllCoins(self, coins) - -- Check if it is possible to remove all the coins. - if self:canRemoveAllCoins(coins) then - local tibiaCoins = self:getTibiaCoins() - -- Check if there are enough Tibia coins to remove. - if tibiaCoins >= coins then - self:removeTibiaCoins(coins) - else - -- Remove the available Tibia coins and calculate the remaining amount to remove from transferable coins. - self:removeTibiaCoins(tibiaCoins) - self:removeTransferableCoins(coins - tibiaCoins) - end - - sendStoreBalanceUpdating(self:getId(), true) - return true - end - - return false -end - -- Transferable coins function Player.canRemoveTransferableCoins(self, coins) - if self:getTransferableCoins() < coins then - return false - end - return true + return self:getTransferableCoins() >= coins end function Player.removeTransferableCoinsBalance(self, coins) if self:canRemoveTransferableCoins(coins) then sendStoreBalanceUpdating(self:getId(), true) - return self:removeTransferableCoins(coins) + self:removeTransferableCoins(coins) + return true end return false @@ -1974,7 +1939,7 @@ end --- Support Functions function Player.makeCoinTransaction(self, offer, desc) - local op = true + local op = false if desc then desc = offer.name .. " (" .. desc .. ")" @@ -1982,14 +1947,9 @@ function Player.makeCoinTransaction(self, offer, desc) desc = offer.name end - -- First try remove normal coins, later the transferable coins - if self:canRemoveAllCoins(offer.price) then - op = self:removeAllCoins(offer.price) - elseif self:canRemoveCoins(offer.price) then - -- Remove normal coins + if offer.coinType == GameStore.CoinType.Coin and self:canRemoveCoins(offer.price) then op = self:removeCoinsBalance(offer.price) - else - -- Remove transferable coins + elseif offer.coinType == GameStore.CoinType.Transferable and self:canRemoveTransferableCoins(offer.price) then op = self:removeTransferableCoinsBalance(offer.price) end @@ -2006,23 +1966,17 @@ end -- @param coinType (string) - The type of the offer. -- @return (boolean) - Returns true if the player can pay for the offer, false otherwise. function Player.canPayForOffer(self, coinsToRemove, coinType) - local can_remove_coins = self:canRemoveCoins(coinsToRemove) - local can_remove_transferable_coins = self:canRemoveTransferableCoins(coinsToRemove) - -- Check if the player has the required amount of regular coins and the offer type is regular. - if self:getTibiaCoins() >= coinsToRemove and coinType == GameStore.CoinType.Coin then - return can_remove_coins + if coinType == GameStore.CoinType.Coin then + return self:canRemoveCoins(coinsToRemove) end -- Check if the player has the required amount of transferable coins and the offer type is transferable. - if self:getTransferableCoins() >= coinsToRemove and coinType == GameStore.CoinType.Transferable then - return can_remove_transferable_coins + if coinType == GameStore.CoinType.Transferable then + return self:canRemoveTransferableCoins(coinsToRemove) end - -- Check if the player has either the required amount of regular coins or transferable coins, - -- or both amounts combined. - local remove_all_coins = self:canRemoveAllCoins(coinsToRemove) - return remove_all_coins or (can_remove_coins or can_remove_transferable_coins) + return false end --- Other players functions @@ -2113,13 +2067,17 @@ function sendHomePage(playerId) end msg:addU16(#homeOffers) -- offers - for p, offer in pairs(homeOffers) do + local offerPrice = offer.type == GameStore.OfferTypes.OFFER_TYPE_EXPBOOST and GameStore.ExpBoostValues[player:getStorageValue(GameStore.Storages.expBoostCount)] or offer.price + if offer.type == GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and player:kv():get("namelock") then + offerPrice = 0 + end + msg:addString(offer.name, "sendHomePage - offer.name") msg:addByte(0x1) -- ? msg:addU32(offer.id or 0) -- id msg:addU16(0x1) - msg:addU32(offer.price) + msg:addU32(offerPrice) msg:addByte(offer.coinType or 0x00) msg:addByte((offer.disabledReadonIndex ~= nil) and 1 or 0) @@ -2138,11 +2096,10 @@ function sendHomePage(playerId) msg:addString(offer.icons[1], "sendHomePage - offer.icons[1]") elseif type == GameStore.ConverType.SHOW_MOUNT then local mount = Mount(offer.id) - if mount then - msg:addU16(mount:getClientId()) - else - logger.debug("[sendHomePage] mount with id {} not exist, ignoring to avoid a debug on the client", offer.id) + if not mount then msg:addU16(0) + else + msg:addU16(mount:getClientId()) end elseif type == GameStore.ConverType.SHOW_ITEM then msg:addU16(offer.itemtype) diff --git a/data/scripts/eventcallbacks/player/on_look.lua b/data/scripts/eventcallbacks/player/on_look.lua index 9424ebf5785..985bbfcd3a3 100644 --- a/data/scripts/eventcallbacks/player/on_look.lua +++ b/data/scripts/eventcallbacks/player/on_look.lua @@ -16,6 +16,10 @@ function callback.playerOnLook(player, thing, position, distance) else description = description .. thing:getDescription(distance) end + local ownerName = thing:getOwnerName() + if ownerName then + description = string.format("%s\nIt belongs to %s.", description, ownerName) + end else description = description .. thing:getDescription(distance) if thing:isMonster() then diff --git a/data/scripts/talkactions/god/inbox_command.lua b/data/scripts/talkactions/god/inbox_command.lua index b9834aa2384..bfc902b1315 100644 --- a/data/scripts/talkactions/god/inbox_command.lua +++ b/data/scripts/talkactions/god/inbox_command.lua @@ -20,7 +20,7 @@ function inboxCommand.onSay(player, words, param) end end elseif param[2] == "add" then - inbox:addItem(tonumber(param[3]), 1, INDEX_WHEREEVER, FLAG_NOLIMIT) + target:addItemStoreInbox(tonumber(param[3]), 1, true, false) player:say(tonumber(param[3]) .. " added") end end diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index f790706c5df..e3211a1f52e 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -826,7 +826,7 @@ void Combat::combatTileEffects(const CreatureVector &spectators, std::shared_ptr std::shared_ptr item = Item::CreateItem(itemId); if (caster) { - item->setAttribute(ItemAttribute_t::OWNER, caster->getID()); + item->setOwner(caster); } ReturnValue ret = g_game().internalAddItem(tile, item); @@ -1999,14 +1999,18 @@ void MagicField::onStepInField(const std::shared_ptr &creature) { const ItemType &it = items[getID()]; if (it.conditionDamage) { auto conditionCopy = it.conditionDamage->clone(); - auto ownerId = getAttribute(ItemAttribute_t::OWNER); + auto ownerId = getOwnerId(); if (ownerId) { bool harmfulField = true; auto itemTile = getTile(); if (g_game().getWorldType() == WORLD_TYPE_NO_PVP || itemTile && itemTile->hasFlag(TILESTATE_NOPVPZONE)) { - std::shared_ptr owner = g_game().getCreatureByID(ownerId); - if (owner) { - if (owner->getPlayer() || (owner->isSummon() && owner->getMaster()->getPlayer())) { + auto ownerPlayer = g_game().getPlayerByGUID(ownerId); + if (ownerPlayer) { + harmfulField = false; + } + auto ownerCreature = g_game().getCreatureByID(ownerId); + if (ownerCreature) { + if (ownerCreature->getPlayer() || (ownerCreature->isSummon() && ownerCreature->getMaster()->getPlayer())) { harmfulField = false; } } @@ -2061,14 +2065,14 @@ void Combat::applyExtensions(std::shared_ptr caster, std::shared_ptrcritChance(); + chance = monster->critChance() * 100; } bonus += damage.criticalDamage; double multiplier = 1.0 + static_cast(bonus) / 100; chance += (uint16_t)damage.criticalChance; - if (chance != 0 && uniform_random(1, 100) <= chance) { + if (chance != 0 && uniform_random(1, 10000) <= chance) { damage.critical = true; damage.primary.value *= multiplier; damage.secondary.value *= multiplier; diff --git a/src/creatures/combat/spells.cpp b/src/creatures/combat/spells.cpp index d684b5433ff..152bc37c496 100644 --- a/src/creatures/combat/spells.cpp +++ b/src/creatures/combat/spells.cpp @@ -565,7 +565,7 @@ bool Spell::playerRuneSpellCheck(std::shared_ptr player, const Position player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); g_game().addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; - } else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { + } else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID) && !topVisibleCreature) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); g_game().addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index fe7657e3f1f..35365a609dc 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -1088,7 +1088,7 @@ void Monster::pushItems(std::shared_ptr tile, const Direction &nextDirecti auto it = items->begin(); while (it != items->end()) { std::shared_ptr item = *it; - if (item && item->hasProperty(CONST_PROP_MOVEABLE) && (item->hasProperty(CONST_PROP_BLOCKPATH) || item->hasProperty(CONST_PROP_BLOCKSOLID)) && item->getAttribute(ItemAttribute_t::ACTIONID) != IMMOVABLE_ACTION_ID) { + if (item && item->hasProperty(CONST_PROP_MOVEABLE) && (item->hasProperty(CONST_PROP_BLOCKPATH) || item->hasProperty(CONST_PROP_BLOCKSOLID)) && item->canBeMoved()) { if (moveCount < 20 && pushItem(item, nextDirection)) { ++moveCount; } else if (!item->isCorpse() && g_game().internalRemoveItem(item) == RETURNVALUE_NOERROR) { diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index c478e15cec0..867b32dbefb 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -2969,8 +2969,7 @@ void Player::notifyStatusChange(std::shared_ptr loginPlayer, VipStatus_t return; } - auto it = VIPList.find(loginPlayer->guid); - if (it == VIPList.end()) { + if (!VIPList.contains(loginPlayer->guid)) { return; } @@ -2986,10 +2985,11 @@ void Player::notifyStatusChange(std::shared_ptr loginPlayer, VipStatus_t } bool Player::removeVIP(uint32_t vipGuid) { - if (VIPList.erase(vipGuid) == 0) { + if (!VIPList.erase(vipGuid)) { return false; } + VIPList.erase(vipGuid); if (account) { IOLoginData::removeVIPEntry(account->getID(), vipGuid); } @@ -3003,8 +3003,7 @@ bool Player::addVIP(uint32_t vipGuid, const std::string &vipName, VipStatus_t st return false; } - auto result = VIPList.insert(vipGuid); - if (!result.second) { + if (!VIPList.insert(vipGuid).second) { sendTextMessage(MESSAGE_FAILURE, "This player is already in your list."); return false; } @@ -3086,6 +3085,9 @@ ReturnValue Player::queryAdd(int32_t index, const std::shared_ptr &thing, g_logger().error("[Player::queryAdd] - Item is nullptr"); return RETURNVALUE_NOTPOSSIBLE; } + if (item->hasOwner() && !item->isOwner(getPlayer())) { + return RETURNVALUE_ITEMISNOTYOURS; + } bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags); if (childIsOwner) { @@ -4636,8 +4638,7 @@ bool Player::onKilledPlayer(const std::shared_ptr &target, bool lastHit) for (auto &kill : target->unjustifiedKills) { if (kill.target == getGUID() && kill.unavenged) { kill.unavenged = false; - auto it = attackedSet.find(target->guid); - attackedSet.erase(it); + attackedSet.erase(target->guid); break; } } @@ -5045,7 +5046,7 @@ bool Player::hasAttacked(std::shared_ptr attacked) const { return false; } - return attackedSet.find(attacked->guid) != attackedSet.end(); + return attackedSet.contains(attacked->guid); } void Player::addAttacked(std::shared_ptr attacked) { @@ -5053,7 +5054,7 @@ void Player::addAttacked(std::shared_ptr attacked) { return; } - attackedSet.insert(attacked->guid); + attackedSet.emplace(attacked->guid); } void Player::removeAttacked(std::shared_ptr attacked) { @@ -5061,10 +5062,7 @@ void Player::removeAttacked(std::shared_ptr attacked) { return; } - auto it = attackedSet.find(attacked->guid); - if (it != attackedSet.end()) { - attackedSet.erase(it); - } + attackedSet.erase(attacked->guid); } void Player::clearAttacked() { @@ -7759,6 +7757,7 @@ const std::unique_ptr &Player::wheel() const { } void Player::sendLootMessage(const std::string &message) const { + auto party = getParty(); if (!party) { sendTextMessage(MESSAGE_LOOT, message); return; diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 517715dccd9..d267f118049 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -2519,7 +2519,7 @@ class Player final : public Creature, public Cylinder, public Bankable { } bool checkAutoLoot() const { - const bool autoLoot = g_configManager().getBoolean(AUTOLOOT, __FUNCTION__) && getStorageValue(STORAGEVALUE_AUTO_LOOT) > 0; + 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(); } diff --git a/src/creatures/players/wheel/player_wheel.cpp b/src/creatures/players/wheel/player_wheel.cpp index cf07955afe7..a60d8124a76 100644 --- a/src/creatures/players/wheel/player_wheel.cpp +++ b/src/creatures/players/wheel/player_wheel.cpp @@ -1400,9 +1400,7 @@ void PlayerWheel::loadDedicationAndConvictionPerks() { if (it != wheelFunctions.end()) { internalData = it->second; } - if (internalData == nullptr) { - g_logger().warn("[{}] 'internalData' cannot be null on slot type: {}, for player: {}", __FUNCTION__, i, m_player.getName()); - } else { + if (internalData) { internalData(m_player.getPlayer(), points, vocationCipId, m_playerBonusData); } } @@ -1816,7 +1814,7 @@ bool PlayerWheel::checkDivineEmpowerment() { int32_t damageBonus = 0; bool isOwner = false; for (const auto &item : *items) { - if (item->getID() == ITEM_DIVINE_EMPOWERMENT && item->getAttribute(ItemAttribute_t::OWNER) == m_player.getID()) { + if (item->getID() == ITEM_DIVINE_EMPOWERMENT && item->isOwner(m_player.getGUID())) { isOwner = true; break; } diff --git a/src/game/game.cpp b/src/game/game.cpp index 546ac979fb6..9d8d89315ce 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -111,7 +111,7 @@ namespace InternalGame { auto realItemParent = item->getRealParent(); auto isItemInGuestInventory = realItemParent && (realItemParent == player || realItemParent->getContainer()); - if (isGuest && !isItemInGuestInventory && !item->isLadder()) { + if (isGuest && !isItemInGuestInventory && !item->isLadder() && !item->canBeUsedByGuests()) { return false; } } @@ -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 && !targetItem->isDummy() && invitedCheckUseWith) { + if (targetId != 0 && targetItem && invitedCheckUseWith && !item->canBeUsedByGuests()) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return false; } @@ -1559,7 +1559,7 @@ void Game::playerMoveItem(std::shared_ptr player, const Position &fromPo } } - if (item->isWrapable()) { + if (item->isWrapable() || item->isStoreItem() || (item->hasOwner() && !item->isOwner(player))) { auto toHouseTile = map.getTile(mapToPos)->dynamic_self_cast(); auto fromHouseTile = map.getTile(mapFromPos)->dynamic_self_cast(); if (fromHouseTile && (!toHouseTile || toHouseTile->getHouse()->getId() != fromHouseTile->getHouse()->getId())) { @@ -1614,7 +1614,7 @@ ReturnValue Game::checkMoveItemToCylinder(std::shared_ptr player, std::s } // prevent move up - if (!item->isStoreItem() && fromCylinder->getContainer() && fromCylinder->getContainer()->getID() == ITEM_GOLD_POUCH) { + if (!item->canBeMovedToStore() && fromCylinder->getContainer() && fromCylinder->getContainer()->getID() == ITEM_GOLD_POUCH) { return RETURNVALUE_CONTAINERNOTENOUGHROOM; } @@ -1624,7 +1624,7 @@ ReturnValue Game::checkMoveItemToCylinder(std::shared_ptr player, std::s std::shared_ptr topParentContainer = toCylinderContainer->getRootContainer(); const auto parentContainer = topParentContainer->getParent() ? topParentContainer->getParent()->getContainer() : nullptr; auto isStoreInbox = parentContainer && parentContainer->isStoreInbox(); - if (!item->isStoreItem() && (containerID == ITEM_STORE_INBOX || isStoreInbox)) { + if (!item->canBeMovedToStore() && (containerID == ITEM_STORE_INBOX || isStoreInbox)) { return RETURNVALUE_CONTAINERNOTENOUGHROOM; } @@ -1651,6 +1651,10 @@ ReturnValue Game::checkMoveItemToCylinder(std::shared_ptr player, std::s if (!isValidMoveItem) { return RETURNVALUE_NOTPOSSIBLE; } + + if (item->hasOwner() && !item->isOwner(player)) { + return RETURNVALUE_ITEMISNOTYOURS; + } } if (item->getContainer() && !item->isStoreItem()) { @@ -1731,8 +1735,15 @@ ReturnValue Game::internalMoveItem(std::shared_ptr fromCylinder, std:: count = item->getItemCount(); } + // check if we can remove this item (using count of 1 since we don't know how + // much we can move yet) + ReturnValue ret = fromCylinder->queryRemove(item, 1, flags, actor); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + // check if we can add this item - ReturnValue ret = toCylinder->queryAdd(index, item, count, flags, actor); + ret = toCylinder->queryAdd(index, item, count, flags, actor); if (ret == RETURNVALUE_NEEDEXCHANGE) { // check if we can add it to source cylinder ret = fromCylinder->queryAdd(fromCylinder->getThingIndex(item), toItem, toItem->getItemCount(), 0); @@ -3296,6 +3307,11 @@ void Game::playerUseItemEx(uint32_t playerId, const Position &fromPos, uint8_t f return; } + if (item->hasOwner() && !item->isOwner(player)) { + player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS); + return; + } + Position walkToPos = fromPos; ReturnValue ret = g_actions().canUse(player, fromPos); if (ret == RETURNVALUE_NOERROR) { @@ -3421,6 +3437,11 @@ void Game::playerUseItem(uint32_t playerId, const Position &pos, uint8_t stackPo return; } + if (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)) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return; @@ -3528,6 +3549,11 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position &fromPos, uin return; } + if (item->hasOwner() && !item->isOwner(player)) { + player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS); + return; + } + if (g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__)) { if (std::shared_ptr houseTile = std::dynamic_pointer_cast(item->getTile())) { const auto &house = houseTile->getHouse(); @@ -3667,6 +3693,11 @@ void Game::playerMoveUpContainer(uint32_t playerId, uint8_t cid) { } } + if (parentContainer->hasOwner() && !parentContainer->isOwner(player)) { + player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS); + return; + } + if (parentContainer->hasPagination() && parentContainer->hasParent()) { uint16_t indexContainer = std::floor(parentContainer->getThingIndex(container) / parentContainer->capacity()) * parentContainer->capacity(); player->addContainer(cid, parentContainer); @@ -3710,6 +3741,16 @@ void Game::playerRotateItem(uint32_t playerId, const Position &pos, uint8_t stac return; } + if (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)) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return; + } + if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { stdext::arraylist listDir(128); if (player->getPathTo(pos, listDir, 0, 1, true, true)) { @@ -3750,6 +3791,16 @@ void Game::playerConfigureShowOffSocket(uint32_t playerId, const Position &pos, return; } + if (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)) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return; + } + bool isPodiumOfRenown = itemId == ITEM_PODIUM_OF_RENOWN1 || itemId == ITEM_PODIUM_OF_RENOWN2; if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) { stdext::arraylist listDir(128); @@ -3792,6 +3843,16 @@ void Game::playerSetShowOffSocket(uint32_t playerId, Outfit_t &outfit, const Pos return; } + if (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)) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return; + } + const auto tile = item->getParent() ? item->getParent()->getTile() : nullptr; if (!tile) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); @@ -3899,26 +3960,26 @@ void Game::playerWrapableItem(uint32_t playerId, const Position &pos, uint8_t st return; } - const auto &house = map.houses.getHouseByPlayerId(player->getGUID()); - if (!house) { - player->sendCancelMessage("You don't own a house, you need own a house to use this."); - return; - } - std::shared_ptr thing = internalGetThing(player, pos, stackPos, itemId, STACKPOS_FIND_THING); if (!thing) { return; } - std::shared_ptr item = thing->getItem(); - std::shared_ptr tile = map.getTile(item->getPosition()); - std::shared_ptr houseTile = std::dynamic_pointer_cast(tile); + const auto item = thing->getItem(); + const auto tile = map.getTile(item->getPosition()); + const auto houseTile = tile->dynamic_self_cast(); if (!tile->hasFlag(TILESTATE_PROTECTIONZONE) || !houseTile) { player->sendCancelMessage("You may construct this only inside a house."); return; } - if (houseTile->getHouse() != house) { - player->sendCancelMessage("Only owners can wrap/unwrap inside a house."); + const auto house = houseTile->getHouse(); + if (!house) { + player->sendCancelMessage("You may construct this only inside a house."); + return; + } + + if (house->getHouseAccessLevel(player) < HOUSE_OWNER) { + player->sendCancelMessage("You are not allowed to construct this here."); return; } @@ -3927,6 +3988,16 @@ void Game::playerWrapableItem(uint32_t playerId, const Position &pos, uint8_t st return; } + if (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)) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return; + } + if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { stdext::arraylist listDir(128); if (player->getPathTo(pos, listDir, 0, 1, true, true)) { @@ -4002,6 +4073,10 @@ std::shared_ptr Game::wrapItem(std::shared_ptr item, std::shared_ptr } void Game::unwrapItem(std::shared_ptr item, uint16_t unWrapId, std::shared_ptr house, std::shared_ptr player) { + if (item->hasOwner() && !item->isOwner(player)) { + player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS); + return; + } auto hiddenCharges = item->getAttribute(DATE); const ItemType &newiType = Item::items.getItemType(unWrapId); if (player != nullptr && house != nullptr && newiType.isBed() && house->getMaxBeds() > -1 && house->getBedCount() >= house->getMaxBeds()) { @@ -4045,6 +4120,11 @@ void Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std:: return; } + if (writeItem->hasOwner() && !writeItem->isOwner(player)) { + player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS); + return; + } + std::shared_ptr topParent = writeItem->getTopParent(); std::shared_ptr owner = std::dynamic_pointer_cast(topParent); @@ -4165,6 +4245,11 @@ void Game::playerStowItem(uint32_t playerId, const Position &pos, uint16_t itemI return; } + if (item->hasOwner() && !item->isOwner(player)) { + player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS); + return; + } + if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; @@ -4331,6 +4416,10 @@ void Game::playerRequestTrade(uint32_t playerId, const Position &pos, uint8_t st player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } + if (tradeItem->isStoreItem() || tradeItem->hasOwner()) { + player->sendCancelMessage(RETURNVALUE_ITEMUNTRADEABLE); + return; + } if (g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__)) { if (std::shared_ptr houseTile = std::dynamic_pointer_cast(tradeItem->getTile())) { @@ -4437,6 +4526,10 @@ bool Game::internalStartTrade(std::shared_ptr player, std::shared_ptrsendCancelMessage(RETURNVALUE_THISPLAYERISALREADYTRADING); return false; } + if (tradeItem->isStoreItem() || tradeItem->hasOwner()) { + player->sendCancelMessage(RETURNVALUE_ITEMUNTRADEABLE); + return false; + } player->tradePartner = tradePartner; player->tradeItem = tradeItem; diff --git a/src/game/movement/teleport.hpp b/src/game/movement/teleport.hpp index ed2d752ad08..998e844b2d6 100644 --- a/src/game/movement/teleport.hpp +++ b/src/game/movement/teleport.hpp @@ -20,6 +20,10 @@ class Teleport final : public Item, public Cylinder { return static_self_cast(); } + std::shared_ptr getCylinder() override final { + return getTeleport(); + } + // serialization Attr_ReadValue readAttr(AttrTypes_t attr, PropStream &propStream) override; void serializeAttr(PropWriteStream &propWriteStream) const override; diff --git a/src/io/iomapserialize.cpp b/src/io/iomapserialize.cpp index db1bac1e563..7f351d2b617 100644 --- a/src/io/iomapserialize.cpp +++ b/src/io/iomapserialize.cpp @@ -46,11 +46,21 @@ void IOMapSerialize::loadHouseItems(Map* map) { } while (item_count--) { + if (auto houseTile = std::dynamic_pointer_cast(tile)) { + const auto house = houseTile->getHouse(); + if (house->getOwner() == 0) { + g_logger().trace("Skipping load item from house id: {}, position: {}, house does not have owner", house->getId(), house->getEntryPosition().toString()); + house->clearHouseInfo(false); + continue; + } + } + loadItem(propStream, tile, true); } } while (result->next()); g_logger().info("Loaded house items in {} milliseconds", bm_context.duration()); } + bool IOMapSerialize::saveHouseItems() { bool success = DBTransaction::executeWithinTransaction([]() { return SaveHouseItemsGuard(); @@ -269,7 +279,7 @@ bool IOMapSerialize::loadHouseInfo() { do { auto houseId = result->getNumber("id"); - const auto &house = g_game().map.houses.getHouse(houseId); + const auto house = g_game().map.houses.getHouse(houseId); if (house) { uint32_t owner = result->getNumber("owner"); int32_t newOwner = result->getNumber("new_owner"); diff --git a/src/items/containers/container.cpp b/src/items/containers/container.cpp index cfc77c4416d..f5ad4cfab60 100644 --- a/src/items/containers/container.cpp +++ b/src/items/containers/container.cpp @@ -450,6 +450,18 @@ ReturnValue Container::queryAdd(int32_t addIndex, const std::shared_ptr & if (item == getItem()) { return RETURNVALUE_THISISIMPOSSIBLE; } + if (item->hasOwner()) { + // a non-owner can move the item around but not pick it up + auto toPlayer = getTopParent()->getPlayer(); + if (toPlayer && !item->isOwner(toPlayer)) { + return RETURNVALUE_ITEMISNOTYOURS; + } + + // a container cannot have items of different owners + if (hasOwner() && getOwnerId() != item->getOwnerId()) { + return RETURNVALUE_ITEMISNOTYOURS; + } + } std::shared_ptr cylinder = getParent(); auto noLimit = hasBitSet(FLAG_NOLIMIT, flags); @@ -473,8 +485,8 @@ ReturnValue Container::queryAdd(int32_t addIndex, const std::shared_ptr & return RETURNVALUE_CONTAINERNOTENOUGHROOM; } - if (std::shared_ptr topParentContainer = getTopParentContainer()) { - if (std::shared_ptr addContainer = item->getContainer()) { + if (const auto topParentContainer = getTopParentContainer()) { + if (const auto addContainer = item->getContainer()) { uint32_t addContainerCount = addContainer->getContainerHoldingCount() + 1; uint32_t maxContainer = static_cast(g_configManager().getNumber(MAX_CONTAINER, __FUNCTION__)); if (addContainerCount + topParentContainer->getContainerHoldingCount() > maxContainer) { @@ -485,10 +497,10 @@ ReturnValue Container::queryAdd(int32_t addIndex, const std::shared_ptr & if (addItemCount + topParentContainer->getItemHoldingCount() > m_maxItems) { return RETURNVALUE_CONTAINERISFULL; } - } - - if (topParentContainer->getItemHoldingCount() + 1 > m_maxItems) { - return RETURNVALUE_CONTAINERISFULL; + } else { + if (topParentContainer->getItemHoldingCount() + 1 > m_maxItems) { + return RETURNVALUE_CONTAINERISFULL; + } } } @@ -950,3 +962,17 @@ void ContainerIterator::advance() { } } } + +uint32_t Container::getOwnerId() const { + uint32_t ownerId = Item::getOwnerId(); + if (ownerId > 0) { + return ownerId; + } + for (const auto &item : itemlist) { + ownerId = item->getOwnerId(); + if (ownerId > 0) { + return ownerId; + } + } + return 0; +} diff --git a/src/items/containers/container.hpp b/src/items/containers/container.hpp index 70d4571bafd..0f3f1df4398 100644 --- a/src/items/containers/container.hpp +++ b/src/items/containers/container.hpp @@ -59,6 +59,10 @@ class Container : public Item, public Cylinder { return static_self_cast(); } + std::shared_ptr getCylinder() override final { + return getContainer(); + } + std::shared_ptr getRootContainer(); virtual std::shared_ptr getDepotLocker() { @@ -165,6 +169,8 @@ class Container : public Item, public Cylinder { virtual void removeItem(std::shared_ptr thing, bool sendUpdateToClient = false); + uint32_t getOwnerId() const override final; + bool isAnyKindOfRewardChest(); bool isAnyKindOfRewardContainer(); bool isBrowseFieldAndHoldsRewardChest(); diff --git a/src/items/containers/depot/depotchest.cpp b/src/items/containers/depot/depotchest.cpp index 01191579840..198ec3e14f0 100644 --- a/src/items/containers/depot/depotchest.cpp +++ b/src/items/containers/depot/depotchest.cpp @@ -24,6 +24,9 @@ ReturnValue DepotChest::queryAdd(int32_t index, const std::shared_ptr &th if (item == nullptr) { return RETURNVALUE_NOTPOSSIBLE; } + if (actor && item->hasOwner() && !item->isOwner(actor)) { + return RETURNVALUE_ITEMISNOTYOURS; + } bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags); if (!skipLimit) { diff --git a/src/items/containers/mailbox/mailbox.cpp b/src/items/containers/mailbox/mailbox.cpp index 9253bdd7417..19b3db5367a 100644 --- a/src/items/containers/mailbox/mailbox.cpp +++ b/src/items/containers/mailbox/mailbox.cpp @@ -138,5 +138,5 @@ bool Mailbox::getReceiver(std::shared_ptr item, std::string &name) const { } bool Mailbox::canSend(std::shared_ptr item) { - return item->getID() == ITEM_PARCEL || item->getID() == ITEM_LETTER; + return !item->hasOwner() && (item->getID() == ITEM_PARCEL || item->getID() == ITEM_LETTER); } diff --git a/src/items/containers/mailbox/mailbox.hpp b/src/items/containers/mailbox/mailbox.hpp index 3d57c72b5ef..5f47b61b290 100644 --- a/src/items/containers/mailbox/mailbox.hpp +++ b/src/items/containers/mailbox/mailbox.hpp @@ -21,6 +21,10 @@ class Mailbox final : public Item, public Cylinder { return static_self_cast(); } + std::shared_ptr getCylinder() override final { + return getMailbox(); + } + // cylinder implementations ReturnValue queryAdd(int32_t index, const std::shared_ptr &thing, uint32_t count, uint32_t flags, std::shared_ptr actor = nullptr) override; ReturnValue queryMaxCount(int32_t index, const std::shared_ptr &thing, uint32_t count, uint32_t &maxQueryCount, uint32_t flags) override; diff --git a/src/items/functions/item/item_parse.cpp b/src/items/functions/item/item_parse.cpp index 8d29dfb71a9..415aa5acb99 100644 --- a/src/items/functions/item/item_parse.cpp +++ b/src/items/functions/item/item_parse.cpp @@ -73,6 +73,7 @@ void ItemParse::initParse(const std::string &tmpStrValue, pugi::xml_node attribu ItemParse::parseReflectDamage(tmpStrValue, valueAttribute, itemType); ItemParse::parseTransformOnUse(tmpStrValue, valueAttribute, itemType); ItemParse::parsePrimaryType(tmpStrValue, valueAttribute, itemType); + ItemParse::parseHouseRelated(tmpStrValue, valueAttribute, itemType); } void ItemParse::parseDummyRate(pugi::xml_node attributeNode, ItemType &itemType) { @@ -572,6 +573,8 @@ void ItemParse::parseAbsorbPercent(const std::string &tmpStrValue, pugi::xml_att itemType.getAbilities().absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); } else if (stringValue == "absorbpercentpoison" || stringValue == "absorbpercentearth") { itemType.getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); + } else if (stringValue == "absorbpercentearth") { + itemType.getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); } else if (stringValue == "absorbpercentice") { itemType.getAbilities().absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += pugi::cast(valueAttribute.value()); } else if (stringValue == "absorbpercentholy") { @@ -954,3 +957,10 @@ void ItemParse::parsePrimaryType(const std::string_view &tmpStrValue, pugi::xml_ itemType.m_primaryType = asLowerCaseString(valueAttribute.as_string()); } } + +void ItemParse::parseHouseRelated(const std::string_view &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType) { + if (tmpStrValue == "usedbyhouseguests") { + g_logger().debug("[{}] item {}, used by guests {}", __FUNCTION__, itemType.id, valueAttribute.as_bool()); + itemType.m_canBeUsedByGuests = valueAttribute.as_bool(); + } +} diff --git a/src/items/functions/item/item_parse.hpp b/src/items/functions/item/item_parse.hpp index f29fc79971a..2ebffc94266 100644 --- a/src/items/functions/item/item_parse.hpp +++ b/src/items/functions/item/item_parse.hpp @@ -156,6 +156,7 @@ const phmap::flat_hash_map ItemParseAttribut { "reflectdamage", ITEM_PARSE_REFLECTDAMAGE }, { "reflectpercentall", ITEM_PARSE_REFLECTPERCENTALL }, { "primarytype", ITEM_PARSE_PRIMARYTYPE }, + { "usedbyhouseguests", ITEM_PARSE_USEDBYGUESTS }, }; const phmap::flat_hash_map ItemTypesMap = { @@ -310,6 +311,7 @@ class ItemParse : public Items { static void parseReflectDamage(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); static void parseTransformOnUse(const std::string_view &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); static void parsePrimaryType(const std::string_view &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); + static void parseHouseRelated(const std::string_view &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); private: // Parent of the function: static void parseField diff --git a/src/items/item.cpp b/src/items/item.cpp index 1632cba95dc..a3f0b3f1a4b 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -238,7 +238,15 @@ bool Item::equals(std::shared_ptr compareItem) const { return false; } + if (getOwnerId() != compareItem->getOwnerId()) { + return false; + } + for (const auto &attribute : getAttributeVector()) { + if (attribute.getAttributeType() == ItemAttribute_t::STORE) { + continue; + } + for (const auto &compareAttribute : compareItem->getAttributeVector()) { if (attribute.getAttributeType() != compareAttribute.getAttributeType()) { continue; @@ -305,6 +313,20 @@ void Item::setID(uint16_t newid) { } } +bool Item::isOwner(uint32_t ownerId) { + if (getOwnerId() == ownerId) { + return true; + } + if (ownerId >= Player::getFirstID() && ownerId <= Player::getLastID()) { + const auto &player = g_game().getPlayerByID(ownerId); + return player && player->getGUID() == getOwnerId(); + } + if (auto player = g_game().getPlayerByGUID(ownerId); player) { + return player->getID() == getOwnerId(); + } + return false; +} + std::shared_ptr Item::getTopParent() { std::shared_ptr aux = getParent(); std::shared_ptr prevaux = std::dynamic_pointer_cast(shared_from_this()); @@ -357,6 +379,9 @@ std::shared_ptr Item::getHoldingPlayer() { } bool Item::isItemStorable() const { + if (isStoreItem() || hasOwner()) { + return false; + } auto isContainerAndHasSomethingInside = (getContainer() != NULL) && (getContainer()->getItemList().size() > 0); return (isStowable() || isContainerAndHasSomethingInside); } @@ -775,6 +800,16 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream &propStream) { break; } + case ATTR_OWNER: { + uint32_t ownerId; + if (!propStream.read(ownerId)) { + g_logger().error("[{}] failed to read amount", __FUNCTION__); + return ATTR_READ_ERROR; + } + + setAttribute(OWNER, ownerId); + break; + } default: return ATTR_READ_ERROR; } @@ -935,11 +970,17 @@ void Item::serializeAttr(PropWriteStream &propWriteStream) const { propWriteStream.write(ATTR_AMOUNT); propWriteStream.write(getAttribute(AMOUNT)); } + if (hasAttribute(STORE_INBOX_CATEGORY)) { propWriteStream.write(ATTR_STORE_INBOX_CATEGORY); propWriteStream.writeString(getString(ItemAttribute_t::STORE_INBOX_CATEGORY)); } + if (hasAttribute(OWNER)) { + propWriteStream.write(ATTR_OWNER); + propWriteStream.write(getAttribute(ItemAttribute_t::OWNER)); + } + // Serialize custom attributes, only serialize if the map not is empty if (hasCustomAttribute()) { auto customAttributeMap = getCustomAttributeMap(); @@ -954,6 +995,50 @@ void Item::serializeAttr(PropWriteStream &propWriteStream) const { } } +void Item::setOwner(std::shared_ptr owner) { + auto id = owner->getID(); + if (owner->getPlayer()) { + id = owner->getPlayer()->getGUID(); + } + setOwner(id); +} + +bool Item::isOwner(std::shared_ptr owner) { + if (!owner) { + return false; + } + auto id = owner->getID(); + if (isOwner(id)) { + return true; + } + if (owner->getPlayer()) { + id = owner->getPlayer()->getGUID(); + } + return isOwner(id); +} + +uint32_t Item::getOwnerId() const { + if (hasAttribute(ItemAttribute_t::OWNER)) { + return getAttribute(ItemAttribute_t::OWNER); + } + return 0; +} + +std::string Item::getOwnerName() { + if (!hasOwner()) { + return ""; + } + + auto creature = g_game().getCreatureByID(getOwnerId()); + if (creature) { + return creature->getName(); + } + if (auto name = g_game().getPlayerNameByGUID(getOwnerId()); !name.empty()) { + return name; + } + return "someone else"; +} + bool Item::hasProperty(ItemProperty prop) const { const ItemType &it = items[id]; switch (prop) { @@ -987,7 +1072,16 @@ bool Item::hasProperty(ItemProperty prop) const { } bool Item::canBeMoved() const { - return isMoveable() && !hasAttribute(UNIQUEID) && (!hasAttribute(ACTIONID) || getAttribute(ItemAttribute_t::ACTIONID) != IMMOVABLE_ACTION_ID); + static std::unordered_set immovableActionIds = { + IMMOVABLE_ACTION_ID, + }; + if (hasAttribute(ItemAttribute_t::UNIQUEID)) { + return false; + } + if (hasAttribute(ItemAttribute_t::ACTIONID) && immovableActionIds.contains(static_cast(getAttribute(ItemAttribute_t::ACTIONID)))) { + return false; + } + return isMoveable(); } void Item::checkDecayMapItemOnMove() { @@ -3142,11 +3236,7 @@ bool Item::hasMarketAttributes() const { } } - if (hasImbuements()) { - return false; - } - - return true; + return !hasImbuements() && !isStoreItem() && !hasOwner(); } bool Item::isInsideDepot(bool includeInbox /* = false*/) { diff --git a/src/items/item.hpp b/src/items/item.hpp index 5dce067504f..956adad61cf 100644 --- a/src/items/item.hpp +++ b/src/items/item.hpp @@ -269,6 +269,28 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { return isLootTrackeable; } + void setOwner(uint32_t owner) { + setAttribute(ItemAttribute_t::OWNER, owner); + } + + void setOwner(std::shared_ptr owner); + + virtual uint32_t getOwnerId() const; + + bool isOwner(uint32_t ownerId); + + std::string getOwnerName(); + + bool isOwner(std::shared_ptr owner); + + bool hasOwner() const { + return getOwnerId() != 0; + } + + bool canBeMovedToStore() const { + return isStoreItem() || hasOwner(); + } + static std::string parseImbuementDescription(std::shared_ptr item); static std::string parseShowDurationSpeed(int32_t speed, bool &begin); static std::string parseShowDuration(std::shared_ptr item); @@ -473,6 +495,9 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { bool canReceiveAutoCarpet() const { return isBlocking() && isAlwaysOnTop() && !items[id].hasHeight; } + bool canBeUsedByGuests() const { + return isDummy() || items[id].m_canBeUsedByGuests; + } bool isDecayDisabled() const { return decayDisabled; diff --git a/src/items/items.hpp b/src/items/items.hpp index 16971801960..5320ec86ab5 100644 --- a/src/items/items.hpp +++ b/src/items/items.hpp @@ -342,6 +342,7 @@ class ItemType { bool loaded = false; bool spellbook = false; bool isWrapKit = false; + bool m_canBeUsedByGuests = false; }; class Items { diff --git a/src/items/items_definitions.hpp b/src/items/items_definitions.hpp index c6326acd28b..4e8fffd3a37 100644 --- a/src/items/items_definitions.hpp +++ b/src/items/items_definitions.hpp @@ -119,6 +119,8 @@ enum ReturnValue { RETURNVALUE_REWARDCHESTISEMPTY, RETURNVALUE_REWARDCONTAINERISEMPTY, RETURNVALUE_CONTACTADMINISTRATOR, + RETURNVALUE_ITEMISNOTYOURS, + RETURNVALUE_ITEMUNTRADEABLE, }; enum ItemGroup_t { @@ -236,6 +238,7 @@ enum AttrTypes_t { ATTR_TIER = 40, ATTR_CUSTOM = 41, ATTR_STORE_INBOX_CATEGORY = 42, + ATTR_OWNER = 43, // Always the last ATTR_NONE = 0 @@ -597,6 +600,7 @@ enum ItemParseAttributes_t { ITEM_PARSE_REFLECTPERCENTALL, ITEM_PARSE_REFLECTDAMAGE, ITEM_PARSE_PRIMARYTYPE, + ITEM_PARSE_USEDBYGUESTS, }; struct ImbuementInfo { diff --git a/src/items/thing.hpp b/src/items/thing.hpp index 05f961ec9a1..bc7d7f3af78 100644 --- a/src/items/thing.hpp +++ b/src/items/thing.hpp @@ -69,6 +69,9 @@ class Thing { virtual std::shared_ptr getCreature() const { return nullptr; } + virtual std::shared_ptr getCylinder() { + return nullptr; + } virtual bool isRemoved() { return true; diff --git a/src/items/tile.cpp b/src/items/tile.cpp index 2b9527095dd..cc9d1160211 100644 --- a/src/items/tile.cpp +++ b/src/items/tile.cpp @@ -65,6 +65,10 @@ bool Tile::hasProperty(std::shared_ptr exclude, ItemProperty prop) const { if (const TileItemVector* items = getItemList()) { for (auto &item : *items) { + if (!item) { + g_logger().error("Tile::hasProperty: tile {} has an item which is nullptr", tilePos.toString()); + continue; + } if (item != exclude && item->hasProperty(prop)) { return true; } @@ -857,7 +861,7 @@ ReturnValue Tile::queryRemove(const std::shared_ptr &thing, uint32_t coun return RETURNVALUE_NOERROR; } -std::shared_ptr Tile::queryDestination(int32_t &, const std::shared_ptr &, std::shared_ptr* destItem, uint32_t &tileFlags) { +std::shared_ptr Tile::queryDestination(int32_t &, const std::shared_ptr &thing, std::shared_ptr* destItem, uint32_t &tileFlags) { std::shared_ptr destTile = nullptr; *destItem = nullptr; @@ -948,6 +952,12 @@ std::shared_ptr Tile::queryDestination(int32_t &, const std::shared_pt std::shared_ptr destThing = destTile->getTopDownItem(); if (destThing) { *destItem = destThing->getItem(); + if (thing->getItem()) { + auto destCylinder = destThing->getCylinder(); + if (destCylinder && !destCylinder->getContainer()) { + return destThing->getCylinder(); + } + } } } return destTile; @@ -1563,7 +1573,7 @@ void Tile::internalAddThing(uint32_t, std::shared_ptr thing) { zone->thingAdded(thing); } - thing->setParent(static_self_cast()); + thing->setParent(getTile()); std::shared_ptr creature = thing->getCreature(); if (creature) { diff --git a/src/items/tile.hpp b/src/items/tile.hpp index bb10b309147..2493974c950 100644 --- a/src/items/tile.hpp +++ b/src/items/tile.hpp @@ -131,6 +131,11 @@ class Tile : public Cylinder, public SharedObject { std::shared_ptr getTile() override final { return static_self_cast(); } + + std::shared_ptr getCylinder() override final { + return getTile(); + } + std::shared_ptr getFieldItem() const; std::shared_ptr getTeleportItem() const; std::shared_ptr getTrashHolder() const; diff --git a/src/items/trashholder.cpp b/src/items/trashholder.cpp index 024bb31fcd0..835558d6244 100644 --- a/src/items/trashholder.cpp +++ b/src/items/trashholder.cpp @@ -12,7 +12,14 @@ #include "items/trashholder.hpp" #include "game/game.hpp" -ReturnValue TrashHolder::queryAdd(int32_t, const std::shared_ptr &, uint32_t, uint32_t, std::shared_ptr) { +ReturnValue TrashHolder::queryAdd(int32_t, const std::shared_ptr &thing, uint32_t, uint32_t, std::shared_ptr actor) { + std::shared_ptr item = thing->getItem(); + if (item == nullptr) { + return RETURNVALUE_NOERROR; + } + if (item->hasOwner() && !item->isOwner(actor)) { + return RETURNVALUE_ITEMISNOTYOURS; + } return RETURNVALUE_NOERROR; } diff --git a/src/items/trashholder.hpp b/src/items/trashholder.hpp index 5043e897d1e..0aa52ed7bee 100644 --- a/src/items/trashholder.hpp +++ b/src/items/trashholder.hpp @@ -21,6 +21,10 @@ class TrashHolder final : public Item, public Cylinder { return static_self_cast(); } + std::shared_ptr getCylinder() override final { + return getTrashHolder(); + } + // cylinder implementations ReturnValue queryAdd(int32_t index, const std::shared_ptr &thing, uint32_t count, uint32_t flags, std::shared_ptr actor = nullptr) override; ReturnValue queryMaxCount(int32_t index, const std::shared_ptr &thing, uint32_t count, uint32_t &maxQueryCount, uint32_t flags) override; diff --git a/src/kv/kv.hpp b/src/kv/kv.hpp index 99bc1f695a6..ea76276c80c 100644 --- a/src/kv/kv.hpp +++ b/src/kv/kv.hpp @@ -43,7 +43,7 @@ class KV : public std::enable_shared_from_this { class KVStore : public KV { public: - static constexpr size_t MAX_SIZE = 10000; + static constexpr size_t MAX_SIZE = 1000000; static KVStore &getInstance(); explicit KVStore(Logger &logger) : diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp index 5d566920c7b..306283b0b6e 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -1200,6 +1200,8 @@ void LuaEnums::initReturnValueEnums(lua_State* L) { registerEnum(L, RETURNVALUE_NOTENOUGHFISHLEVEL); registerEnum(L, RETURNVALUE_REWARDCHESTISEMPTY); registerEnum(L, RETURNVALUE_CONTACTADMINISTRATOR); + registerEnum(L, RETURNVALUE_ITEMISNOTYOURS); + registerEnum(L, RETURNVALUE_ITEMUNTRADEABLE); } // Reload diff --git a/src/lua/functions/core/network/network_message_functions.cpp b/src/lua/functions/core/network/network_message_functions.cpp index fc1c1688613..630cd185d1b 100644 --- a/src/lua/functions/core/network/network_message_functions.cpp +++ b/src/lua/functions/core/network/network_message_functions.cpp @@ -232,7 +232,7 @@ int NetworkMessageFunctions::luaNetworkMessageAddDouble(lua_State* L) { int NetworkMessageFunctions::luaNetworkMessageAddItem(lua_State* L) { // networkMessage:addItem(item, player) - const auto &item = getUserdataShared(L, 2); + std::shared_ptr item = getUserdataShared(L, 2); if (!item) { reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); lua_pushnil(L); @@ -289,7 +289,7 @@ int NetworkMessageFunctions::luaNetworkMessageSendToPlayer(lua_State* L) { return 1; } - const auto &player = getPlayer(L, 2); + std::shared_ptr player = getPlayer(L, 2); if (!player) { reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); return 1; diff --git a/src/lua/functions/creatures/combat/combat_functions.cpp b/src/lua/functions/creatures/combat/combat_functions.cpp index 6c1529726bf..3661536aea6 100644 --- a/src/lua/functions/creatures/combat/combat_functions.cpp +++ b/src/lua/functions/creatures/combat/combat_functions.cpp @@ -88,7 +88,7 @@ int CombatFunctions::luaCombatSetArea(lua_State* L) { int CombatFunctions::luaCombatSetCondition(lua_State* L) { // combat:addCondition(condition) std::shared_ptr condition = getUserdataShared(L, 2); - const auto &combat = getUserdataShared(L, 1); + Combat* combat = getUserdata(L, 1); if (combat && condition) { combat->addCondition(condition->clone()); pushBoolean(L, true); diff --git a/src/lua/functions/creatures/creature_functions.cpp b/src/lua/functions/creatures/creature_functions.cpp index 5ef10be08bf..8315a690f36 100644 --- a/src/lua/functions/creatures/creature_functions.cpp +++ b/src/lua/functions/creatures/creature_functions.cpp @@ -705,7 +705,11 @@ int CreatureFunctions::luaCreatureRemoveCondition(lua_State* L) { const std::shared_ptr condition = creature->getCondition(conditionType, conditionId, subId); if (condition) { bool force = getBoolean(L, 5, false); - creature->removeCondition(conditionType, conditionId, force); + if (subId == 0) { + creature->removeCondition(conditionType, conditionId, force); + } else { + creature->removeCondition(condition); + } pushBoolean(L, true); } else { lua_pushnil(L); diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index b50d62c7d2a..203b24249c8 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -461,10 +461,25 @@ int PlayerFunctions::luaPlayerGetPreyLootPercentage(lua_State* L) { return 1; } +int PlayerFunctions::luaPlayerisMonsterPrey(lua_State* L) { + // player:isMonsterPrey(raceid) + if (std::shared_ptr player = getUserdataShared(L, 1)) { + if (const std::unique_ptr &slot = player->getPreyWithMonster(getNumber(L, 2, 0)); + slot && slot->isOccupied()) { + pushBoolean(L, true); + } else { + pushBoolean(L, false); + } + } else { + lua_pushnil(L); + } + return 1; +} + int PlayerFunctions::luaPlayerPreyThirdSlot(lua_State* L) { // get: player:preyThirdSlot() set: player:preyThirdSlot(bool) - if (const auto &player = getUserdataShared(L, 1)) { - const auto &slot = player->getPreySlotById(PreySlot_Three); + if (std::shared_ptr player = getUserdataShared(L, 1); + const auto &slot = player->getPreySlotById(PreySlot_Three)) { if (!slot) { lua_pushnil(L); } else if (lua_gettop(L) == 1) { @@ -1891,6 +1906,22 @@ int PlayerFunctions::luaPlayerAddItemEx(lua_State* L) { return 1; } +int PlayerFunctions::luaPlayerAddItemStash(lua_State* L) { + // player:addItemStash(itemId, count = 1) + std::shared_ptr player = getUserdataShared(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + auto itemId = getNumber(L, 2); + auto count = getNumber(L, 3, 1); + + player->addItemOnStash(itemId, count); + pushBoolean(L, true); + return 1; +} + int PlayerFunctions::luaPlayerRemoveStashItem(lua_State* L) { // player:removeStashItem(itemId, count) std::shared_ptr player = getUserdataShared(L, 1); diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index 60458816487..80e1e84c98a 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -41,6 +41,7 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "charmExpansion", PlayerFunctions::luaPlayercharmExpansion); registerMethod(L, "Player", "getCharmMonsterType", PlayerFunctions::luaPlayergetCharmMonsterType); + registerMethod(L, "Player", "isMonsterPrey", PlayerFunctions::luaPlayerisMonsterPrey); registerMethod(L, "Player", "getPreyCards", PlayerFunctions::luaPlayerGetPreyCards); registerMethod(L, "Player", "getPreyLootPercentage", PlayerFunctions::luaPlayerGetPreyLootPercentage); registerMethod(L, "Player", "getPreyExperiencePercentage", PlayerFunctions::luaPlayerGetPreyExperiencePercentage); @@ -175,6 +176,7 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "addItem", PlayerFunctions::luaPlayerAddItem); registerMethod(L, "Player", "addItemEx", PlayerFunctions::luaPlayerAddItemEx); + registerMethod(L, "Player", "addItemStash", PlayerFunctions::luaPlayerAddItemStash); registerMethod(L, "Player", "removeStashItem", PlayerFunctions::luaPlayerRemoveStashItem); registerMethod(L, "Player", "removeItem", PlayerFunctions::luaPlayerRemoveItem); registerMethod(L, "Player", "sendContainer", PlayerFunctions::luaPlayerSendContainer); @@ -382,6 +384,7 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayercharmExpansion(lua_State* L); static int luaPlayergetCharmMonsterType(lua_State* L); + static int luaPlayerisMonsterPrey(lua_State* L); static int luaPlayerGetPreyCards(lua_State* L); static int luaPlayerGetPreyLootPercentage(lua_State* L); static int luaPlayerPreyThirdSlot(lua_State* L); @@ -516,6 +519,7 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerAddItem(lua_State* L); static int luaPlayerAddItemEx(lua_State* L); + static int luaPlayerAddItemStash(lua_State* L); static int luaPlayerRemoveStashItem(lua_State* L); static int luaPlayerRemoveItem(lua_State* L); static int luaPlayerSendContainer(lua_State* L); diff --git a/src/lua/functions/items/item_functions.cpp b/src/lua/functions/items/item_functions.cpp index c1f25361253..c8b7f26ed75 100644 --- a/src/lua/functions/items/item_functions.cpp +++ b/src/lua/functions/items/item_functions.cpp @@ -553,6 +553,17 @@ int ItemFunctions::luaItemRemoveCustomAttribute(lua_State* L) { return 1; } +int ItemFunctions::luaItemCanBeMoved(lua_State* L) { + // item:canBeMoved() + std::shared_ptr item = getUserdataShared(L, 1); + if (item) { + pushBoolean(L, item->canBeMoved()); + } else { + lua_pushnil(L); + } + return 1; +} + int ItemFunctions::luaItemSerializeAttributes(lua_State* L) { // item:serializeAttributes() std::shared_ptr item = getUserdataShared(L, 1); @@ -911,3 +922,107 @@ int ItemFunctions::luaItemCanReceiveAutoCarpet(lua_State* L) { pushBoolean(L, item->canReceiveAutoCarpet()); return 1; } + +int ItemFunctions::luaItemSetOwner(lua_State* L) { + // item:setOwner(creature|creatureId) + std::shared_ptr item = getUserdataShared(L, 1); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + return 0; + } + + if (isUserdata(L, 2)) { + std::shared_ptr creature = getUserdataShared(L, 2); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + return 0; + } + item->setOwner(creature); + pushBoolean(L, true); + return 1; + } + + auto creatureId = getNumber(L, 2); + if (creatureId != 0) { + item->setOwner(creatureId); + pushBoolean(L, true); + return 1; + } + + pushBoolean(L, false); + return 1; +} + +int ItemFunctions::luaItemGetOwnerId(lua_State* L) { + // item:getOwner() + std::shared_ptr item = getUserdataShared(L, 1); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + return 0; + } + + if (auto ownerId = item->getOwnerId()) { + lua_pushnumber(L, ownerId); + return 1; + } + + lua_pushnil(L); + return 1; +} + +int ItemFunctions::luaItemIsOwner(lua_State* L) { + // item:isOwner(creature|creatureId) + std::shared_ptr item = getUserdataShared(L, 1); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + return 0; + } + + if (isUserdata(L, 2)) { + std::shared_ptr creature = getUserdataShared(L, 2); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + return 0; + } + pushBoolean(L, item->isOwner(creature)); + return 1; + } + + auto creatureId = getNumber(L, 2); + if (creatureId != 0) { + pushBoolean(L, item->isOwner(creatureId)); + return 1; + } + + pushBoolean(L, false); + return 1; +} + +int ItemFunctions::luaItemGetOwnerName(lua_State* L) { + // item:getOwnerName() + std::shared_ptr item = getUserdataShared(L, 1); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + return 0; + } + + if (auto ownerName = item->getOwnerName(); !ownerName.empty()) { + pushString(L, ownerName); + return 1; + } + + lua_pushnil(L); + return 1; +} + +int ItemFunctions::luaItemHasOwner(lua_State* L) { + // item:hasOwner() + std::shared_ptr item = getUserdataShared(L, 1); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + return 1; + } + + pushBoolean(L, item->hasOwner()); + return 1; +} diff --git a/src/lua/functions/items/item_functions.hpp b/src/lua/functions/items/item_functions.hpp index b5b3b62ebed..5e8c81187a9 100644 --- a/src/lua/functions/items/item_functions.hpp +++ b/src/lua/functions/items/item_functions.hpp @@ -57,6 +57,13 @@ class ItemFunctions final : LuaScriptInterface { registerMethod(L, "Item", "getCustomAttribute", ItemFunctions::luaItemGetCustomAttribute); registerMethod(L, "Item", "setCustomAttribute", ItemFunctions::luaItemSetCustomAttribute); registerMethod(L, "Item", "removeCustomAttribute", ItemFunctions::luaItemRemoveCustomAttribute); + registerMethod(L, "Item", "canBeMoved", ItemFunctions::luaItemCanBeMoved); + + registerMethod(L, "Item", "setOwner", ItemFunctions::luaItemSetOwner); + registerMethod(L, "Item", "getOwnerId", ItemFunctions::luaItemGetOwnerId); + registerMethod(L, "Item", "isOwner", ItemFunctions::luaItemIsOwner); + registerMethod(L, "Item", "getOwnerName", ItemFunctions::luaItemGetOwnerName); + registerMethod(L, "Item", "hasOwner", ItemFunctions::luaItemHasOwner); registerMethod(L, "Item", "moveTo", ItemFunctions::luaItemMoveTo); registerMethod(L, "Item", "transform", ItemFunctions::luaItemTransform); @@ -128,6 +135,7 @@ class ItemFunctions final : LuaScriptInterface { static int luaItemGetCustomAttribute(lua_State* L); static int luaItemSetCustomAttribute(lua_State* L); static int luaItemRemoveCustomAttribute(lua_State* L); + static int luaItemCanBeMoved(lua_State* L); static int luaItemMoveTo(lua_State* L); static int luaItemTransform(lua_State* L); @@ -153,4 +161,10 @@ class ItemFunctions final : LuaScriptInterface { static int luaItemGetClassification(lua_State* L); static int luaItemCanReceiveAutoCarpet(lua_State* L); + + static int luaItemSetOwner(lua_State* L); + static int luaItemGetOwnerId(lua_State* L); + static int luaItemIsOwner(lua_State* L); + static int luaItemGetOwnerName(lua_State* L); + static int luaItemHasOwner(lua_State* L); }; diff --git a/src/lua/functions/items/item_type_functions.cpp b/src/lua/functions/items/item_type_functions.cpp index bc14c36968d..81711978c88 100644 --- a/src/lua/functions/items/item_type_functions.cpp +++ b/src/lua/functions/items/item_type_functions.cpp @@ -105,6 +105,17 @@ int ItemTypeFunctions::luaItemTypeIsStackable(lua_State* L) { return 1; } +int ItemTypeFunctions::luaItemTypeIsStowable(lua_State* L) { + // itemType:isStowable() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->stackable && itemType->wareId > 0); + } else { + lua_pushnil(L); + } + return 1; +} + int ItemTypeFunctions::luaItemTypeIsReadable(lua_State* L) { // itemType:isReadable() const ItemType* itemType = getUserdata(L, 1); @@ -260,10 +271,12 @@ int ItemTypeFunctions::luaItemTypeGetArticle(lua_State* L) { } int ItemTypeFunctions::luaItemTypeGetDescription(lua_State* L) { - // itemType:getDescription() - const ItemType* itemType = getUserdata(L, 1); + // itemType:getDescription([count]) + auto itemType = getUserdata(L, 1); if (itemType) { - pushString(L, itemType->description); + auto count = getNumber(L, 2, -1); + auto description = Item::getDescription(*itemType, 1, nullptr, count); + pushString(L, description); } else { lua_pushnil(L); } diff --git a/src/lua/functions/items/item_type_functions.hpp b/src/lua/functions/items/item_type_functions.hpp index 7ee9032e5d7..46eaa646217 100644 --- a/src/lua/functions/items/item_type_functions.hpp +++ b/src/lua/functions/items/item_type_functions.hpp @@ -25,6 +25,7 @@ class ItemTypeFunctions final : LuaScriptInterface { registerMethod(L, "ItemType", "isMovable", ItemTypeFunctions::luaItemTypeIsMovable); registerMethod(L, "ItemType", "isRune", ItemTypeFunctions::luaItemTypeIsRune); registerMethod(L, "ItemType", "isStackable", ItemTypeFunctions::luaItemTypeIsStackable); + registerMethod(L, "ItemType", "isStowable", ItemTypeFunctions::luaItemTypeIsStowable); registerMethod(L, "ItemType", "isReadable", ItemTypeFunctions::luaItemTypeIsReadable); registerMethod(L, "ItemType", "isWritable", ItemTypeFunctions::luaItemTypeIsWritable); registerMethod(L, "ItemType", "isBlocking", ItemTypeFunctions::luaItemTypeIsBlocking); @@ -91,6 +92,7 @@ class ItemTypeFunctions final : LuaScriptInterface { static int luaItemTypeIsMovable(lua_State* L); static int luaItemTypeIsRune(lua_State* L); static int luaItemTypeIsStackable(lua_State* L); + static int luaItemTypeIsStowable(lua_State* L); static int luaItemTypeIsReadable(lua_State* L); static int luaItemTypeIsWritable(lua_State* L); static int luaItemTypeIsBlocking(lua_State* L); diff --git a/src/lua/functions/map/tile_functions.cpp b/src/lua/functions/map/tile_functions.cpp index 1081014ab57..448671c3b81 100644 --- a/src/lua/functions/map/tile_functions.cpp +++ b/src/lua/functions/map/tile_functions.cpp @@ -655,3 +655,40 @@ int TileFunctions::luaTileGetHouse(lua_State* L) { } return 1; } + +int TileFunctions::luaTileSweep(lua_State* L) { + // tile:sweep(actor) + std::shared_ptr tile = getUserdataShared(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + auto actor = getPlayer(L, 2); + if (!actor) { + lua_pushnil(L); + return 1; + } + + auto house = tile->getHouse(); + if (!house) { + g_logger().debug("TileFunctions::luaTileSweep: tile has no house"); + lua_pushnil(L); + return 1; + } + + if (house->getHouseAccessLevel(actor) < HOUSE_OWNER) { + g_logger().debug("TileFunctions::luaTileSweep: player is not owner of house"); + lua_pushnil(L); + return 1; + } + + auto houseTile = std::dynamic_pointer_cast(tile); + if (!houseTile) { + g_logger().debug("TileFunctions::luaTileSweep: tile is not a house tile"); + lua_pushnil(L); + return 1; + } + + pushBoolean(L, house->transferToDepot(actor, houseTile)); + return 1; +} diff --git a/src/lua/functions/map/tile_functions.hpp b/src/lua/functions/map/tile_functions.hpp index 24e0a87d864..a1956171c54 100644 --- a/src/lua/functions/map/tile_functions.hpp +++ b/src/lua/functions/map/tile_functions.hpp @@ -55,6 +55,7 @@ class TileFunctions final : LuaScriptInterface { registerMethod(L, "Tile", "addItemEx", TileFunctions::luaTileAddItemEx); registerMethod(L, "Tile", "getHouse", TileFunctions::luaTileGetHouse); + registerMethod(L, "Tile", "sweep", TileFunctions::luaTileSweep); } private: @@ -98,4 +99,5 @@ class TileFunctions final : LuaScriptInterface { static int luaTileAddItemEx(lua_State* L); static int luaTileGetHouse(lua_State* L); + static int luaTileSweep(lua_State* L); }; diff --git a/src/map/house/house.cpp b/src/map/house/house.cpp index fb2430e05a6..5090ff86bc5 100644 --- a/src/map/house/house.cpp +++ b/src/map/house/house.cpp @@ -43,23 +43,7 @@ void House::setNewOwnerGuid(int32_t newOwnerGuid, bool serverStartup) { } } -bool House::tryTransferOwnership(std::shared_ptr player, bool serverStartup) { - bool transferSuccess = false; - if (player) { - transferSuccess = transferToDepot(player); - } else { - transferSuccess = transferToDepot(); - } - - for (auto tile : houseTiles) { - if (const CreatureVector* creatures = tile->getCreatures()) { - for (int32_t i = creatures->size(); --i >= 0;) { - const auto creature = (*creatures)[i]; - kickPlayer(nullptr, creature->getPlayer()); - } - } - } - +void House::clearHouseInfo(bool preventOwnerDeletion) { // Remove players from beds for (auto bed : bedsList) { if (bed->getSleeper() != 0) { @@ -68,16 +52,37 @@ bool House::tryTransferOwnership(std::shared_ptr player, bool serverStar } // Clean access lists - if (!serverStartup) { + if (!preventOwnerDeletion) { owner = 0; ownerAccountId = 0; } + // Clean access lists setAccessList(SUBOWNER_LIST, ""); setAccessList(GUEST_LIST, ""); for (auto door : doorList) { door->setAccessList(""); } +} + +bool House::tryTransferOwnership(std::shared_ptr player, bool serverStartup) { + bool transferSuccess = false; + if (player) { + transferSuccess = transferToDepot(player); + } else { + transferSuccess = transferToDepot(); + } + + for (auto tile : houseTiles) { + if (const CreatureVector* creatures = tile->getCreatures()) { + for (int32_t i = creatures->size(); --i >= 0;) { + const auto creature = (*creatures)[i]; + kickPlayer(nullptr, creature->getPlayer()); + } + } + } + + clearHouseInfo(serverStartup); return transferSuccess; } @@ -267,27 +272,54 @@ bool House::transferToDepot(std::shared_ptr player) const { if (townId == 0 || !player) { return false; } + for (std::shared_ptr tile : houseTiles) { + if (!transferToDepot(player, tile)) { + return false; + } + } + return true; +} + +bool House::transferToDepot(std::shared_ptr player, std::shared_ptr tile) const { + if (townId == 0 || !player) { + return false; + } + if (tile->getHouse().get() != this) { + g_logger().debug("[{}] tile house is not this house", __FUNCTION__); + return false; + } ItemList moveItemList; - for (std::shared_ptr tile : houseTiles) { - if (const TileItemVector* items = tile->getItemList()) { - for (const std::shared_ptr &item : *items) { - if (item->isWrapable()) { - handleWrapableItem(moveItemList, item, player, tile); - } else if (item->isPickupable()) { - moveItemList.push_back(item); - } else { - handleContainer(moveItemList, item); - } + if (const TileItemVector* items = tile->getItemList()) { + for (const std::shared_ptr &item : *items) { + if (item->isWrapable()) { + handleWrapableItem(moveItemList, item, player, tile); + } else if (item->isPickupable()) { + moveItemList.push_back(item); + } else { + handleContainer(moveItemList, item); } } } + std::unordered_set> playersToSave = { player }; + for (std::shared_ptr item : moveItemList) { g_logger().debug("[{}] moving item '{}' to depot", __FUNCTION__, item->getName()); - g_game().internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT); + auto targetPlayer = player; + if (item->hasOwner() && !item->isOwner(targetPlayer)) { + targetPlayer = g_game().getPlayerByGUID(item->getOwnerId()); + if (!targetPlayer) { + g_game().internalRemoveItem(item, item->getItemCount()); + continue; + } + playersToSave.insert(targetPlayer); + } + g_game().internalMoveItem(item->getParent(), targetPlayer->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT); + } + for (auto playerToSave : playersToSave) { + g_saveManager().savePlayer(playerToSave); } - g_saveManager().savePlayer(player); return true; } @@ -762,7 +794,7 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const { auto player = g_game().getPlayerByGUID(ownerId, true); if (!player) { // Player doesn't exist, reset house owner - house->setOwner(0); + house->tryTransferOwnership(nullptr, true); continue; } diff --git a/src/map/house/house.hpp b/src/map/house/house.hpp index 2e8427a57a0..9b70f444524 100644 --- a/src/map/house/house.hpp +++ b/src/map/house/house.hpp @@ -147,6 +147,7 @@ class House : public SharedObject { * @note The actual transfer of ownership will occur upon server restart if `serverStartup` is set to false. */ void setNewOwnerGuid(int32_t newOwnerGuid, bool serverStartup); + void clearHouseInfo(bool preventOwnerDeletion); bool tryTransferOwnership(std::shared_ptr player, bool serverStartup); void setOwner(uint32_t guid, bool updateDatabase = true, std::shared_ptr player = nullptr); uint32_t getOwner() const { @@ -226,6 +227,7 @@ class House : public SharedObject { } bool transferToDepot(std::shared_ptr player) const; + bool transferToDepot(std::shared_ptr player, std::shared_ptr tile) const; bool hasItemOnTile() const; bool hasNewOwnership() const; diff --git a/src/map/utils/qtreenode.hpp b/src/map/utils/qtreenode.hpp index 45c95e554fc..d4131ccfe49 100644 --- a/src/map/utils/qtreenode.hpp +++ b/src/map/utils/qtreenode.hpp @@ -19,11 +19,7 @@ class QTreeNode { public: constexpr QTreeNode() = default; - virtual ~QTreeNode() { - for (auto* ptr : child) { - delete ptr; - } - }; + virtual ~QTreeNode() {}; // non-copyable QTreeNode(const QTreeNode &) = delete; diff --git a/src/utils/tools.cpp b/src/utils/tools.cpp index 39ca687f06a..150d1d2238c 100644 --- a/src/utils/tools.cpp +++ b/src/utils/tools.cpp @@ -1448,6 +1448,12 @@ const char* getReturnMessage(ReturnValue value) { case RETURNVALUE_CONTACTADMINISTRATOR: return "An error has occurred, please contact your administrator."; + case RETURNVALUE_ITEMISNOTYOURS: + return "This item is not yours."; + + case RETURNVALUE_ITEMUNTRADEABLE: + return "This item is untradeable."; + // Any unhandled ReturnValue will go enter here default: return "Unknown error."; From 4054751a0818f69d22250b2dfc3bb7818757dfab Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 28 Dec 2023 12:49:21 -0800 Subject: [PATCH 19/28] fix: include CheckIPOSupported (#2064) --- .github/workflows/build-docker-dummy.yml | 22 +++++++++++++ .github/workflows/build-ubuntu-dummy.yml | 31 +++++++++++++++++++ .../workflows/build-windows-cmake-dummy.yml | 25 +++++++++++++++ .../build-windows-solution-dummy.yml | 26 ++++++++++++++++ .github/workflows/clang-lint-dummy.yml | 12 +++++++ .github/workflows/lua-format-dummy.yml | 13 ++++++++ cmake/modules/CanaryLib.cmake | 3 +- 7 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-docker-dummy.yml create mode 100644 .github/workflows/build-ubuntu-dummy.yml create mode 100644 .github/workflows/build-windows-cmake-dummy.yml create mode 100644 .github/workflows/build-windows-solution-dummy.yml create mode 100644 .github/workflows/clang-lint-dummy.yml create mode 100644 .github/workflows/lua-format-dummy.yml diff --git a/.github/workflows/build-docker-dummy.yml b/.github/workflows/build-docker-dummy.yml new file mode 100644 index 00000000000..7c160ea2245 --- /dev/null +++ b/.github/workflows/build-docker-dummy.yml @@ -0,0 +1,22 @@ +--- +name: Build - Docker (dummy) + +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths-ignore: + - "src/**" + +jobs: + build_docker_x86: + if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} + runs-on: ubuntu-latest + steps: + - run: echo "This is a dummy job to satisfy branch protection checks" + + build_docker_arm: + if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} + runs-on: ubuntu-latest + steps: + - run: echo "This is a dummy job to satisfy branch protection checks" diff --git a/.github/workflows/build-ubuntu-dummy.yml b/.github/workflows/build-ubuntu-dummy.yml new file mode 100644 index 00000000000..00c4efdef87 --- /dev/null +++ b/.github/workflows/build-ubuntu-dummy.yml @@ -0,0 +1,31 @@ +--- +name: Build - Ubuntu (dummy) + +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths-ignore: + - "src/**" + +jobs: + job: + if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} + name: ${{ matrix.os }}-${{ matrix.buildtype }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-20.04, ubuntu-22.04] + buildtype: [linux-release, linux-debug] + include: + - os: ubuntu-20.04 + triplet: x64-linux + - os: ubuntu-22.04 + triplet: x64-linux + + steps: + - run: echo "This is a dummy job to satisfy branch protection checks" + - name: Checkout repository + uses: actions/checkout@main diff --git a/.github/workflows/build-windows-cmake-dummy.yml b/.github/workflows/build-windows-cmake-dummy.yml new file mode 100644 index 00000000000..35c66277314 --- /dev/null +++ b/.github/workflows/build-windows-cmake-dummy.yml @@ -0,0 +1,25 @@ +--- +name: Build - Windows - CMake (dummy) +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths-ignore: + - "src/**" + +jobs: + job: + if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} + name: ${{ matrix.os }}-${{ matrix.buildtype }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-2022] + buildtype: [windows-release] + include: + - os: windows-2022 + triplet: x64-windows-static + packages: > + sccache + steps: + - run: echo "This is a dummy job to satisfy branch protection checks" diff --git a/.github/workflows/build-windows-solution-dummy.yml b/.github/workflows/build-windows-solution-dummy.yml new file mode 100644 index 00000000000..6df2e27ec8e --- /dev/null +++ b/.github/workflows/build-windows-solution-dummy.yml @@ -0,0 +1,26 @@ +--- +name: Build - Windows - Solution (dummy) + +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths-ignore: + - "src/**" + +jobs: + job: + if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} + name: ${{ matrix.os }}-${{ matrix.buildtype }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-2022] + buildtype: [Debug] + include: + - os: windows-2022 + triplet: x64-windows + packages: > + sccache + steps: + - run: echo "This is a dummy job to satisfy branch protection checks" diff --git a/.github/workflows/clang-lint-dummy.yml b/.github/workflows/clang-lint-dummy.yml new file mode 100644 index 00000000000..41d488b4d0b --- /dev/null +++ b/.github/workflows/clang-lint-dummy.yml @@ -0,0 +1,12 @@ +--- +name: Clang-format (dummy) +on: + pull_request: + paths-ignore: + - "src/**" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "This is a dummy job to satisfy branch protection checks" diff --git a/.github/workflows/lua-format-dummy.yml b/.github/workflows/lua-format-dummy.yml new file mode 100644 index 00000000000..9a52797a3ca --- /dev/null +++ b/.github/workflows/lua-format-dummy.yml @@ -0,0 +1,13 @@ +--- +name: Lua-format (dumy) + +on: + pull_request: + paths-ignore: + - "data*/**" + +jobs: + lua-formatter: + runs-on: ubuntu-latest + steps: + - run: echo "This is a dummy job to satisfy branch protection checks" diff --git a/cmake/modules/CanaryLib.cmake b/cmake/modules/CanaryLib.cmake index 3185bfcd2c9..957a83c7a3e 100644 --- a/cmake/modules/CanaryLib.cmake +++ b/cmake/modules/CanaryLib.cmake @@ -52,7 +52,8 @@ if(MSVC) MODULE_LINKER_FLAGS "/LTCG" EXE_LINKER_FLAGS "/LTCG") else() - check_ipo_supported(RESULT result OUTPUT output) + include(CheckIPOSupported) + check_ipo_supported(RESULT result) if(result) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto=auto") message(STATUS "IPO/LTO enabled with -flto=auto for non-MSVC compiler.") From 5b54200858243c42c4025f32c833fb1790087a69 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 28 Dec 2023 19:09:47 -0300 Subject: [PATCH 20/28] ci/cl: fix to run cancel previous build only on pull request branchs (#2065) --- .github/workflows/build-docker.yml | 9 +-------- .github/workflows/build-ubuntu.yml | 2 +- .github/workflows/build-windows-cmake.yml | 2 +- .github/workflows/build-windows-solution.yml | 2 +- .github/workflows/clang-lint.yml | 2 +- .github/workflows/cron-stale.yml | 2 +- 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index c1a4a77af7b..d99ba82878c 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -16,7 +16,7 @@ on: jobs: cancel-runs: - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main' runs-on: ubuntu-latest steps: - name: Cancel Previous Runs @@ -90,13 +90,6 @@ jobs: if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} runs-on: ubuntu-latest steps: - - name: Cancel Previous Runs - if: github.ref != 'refs/heads/main' - uses: fkirc/skip-duplicate-actions@master - with: - concurrent_skipping: "same_content" - cancel_others: true - - name: Checkout uses: actions/checkout@main with: diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index e0ea11220fb..cdc54dc33f2 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -20,7 +20,7 @@ env: jobs: cancel-runs: - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main' runs-on: ubuntu-latest steps: - name: Cancel Previous Runs diff --git a/.github/workflows/build-windows-cmake.yml b/.github/workflows/build-windows-cmake.yml index caba8a0aa90..ab0accc857b 100644 --- a/.github/workflows/build-windows-cmake.yml +++ b/.github/workflows/build-windows-cmake.yml @@ -17,7 +17,7 @@ env: MAKEFLAGS: "-j 2" jobs: cancel-runs: - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main' runs-on: ubuntu-latest steps: - name: Cancel Previous Runs diff --git a/.github/workflows/build-windows-solution.yml b/.github/workflows/build-windows-solution.yml index 9e3addb6e74..58dcf97f159 100644 --- a/.github/workflows/build-windows-solution.yml +++ b/.github/workflows/build-windows-solution.yml @@ -20,7 +20,7 @@ env: jobs: cancel-runs: - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main' runs-on: ubuntu-latest steps: - name: Cancel Previous Runs diff --git a/.github/workflows/clang-lint.yml b/.github/workflows/clang-lint.yml index 85699e408c9..67e6427d853 100644 --- a/.github/workflows/clang-lint.yml +++ b/.github/workflows/clang-lint.yml @@ -10,7 +10,7 @@ on: - "src/**" jobs: cancel-runs: - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main' runs-on: ubuntu-latest steps: - name: Cancel Previous Runs diff --git a/.github/workflows/cron-stale.yml b/.github/workflows/cron-stale.yml index b4231144943..3c68d50a247 100644 --- a/.github/workflows/cron-stale.yml +++ b/.github/workflows/cron-stale.yml @@ -6,7 +6,7 @@ on: jobs: cancel-runs: - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main' runs-on: ubuntu-latest steps: - name: Cancel Previous Runs From 960936836e8cd312c0e90377f0194005d021894d Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Thu, 28 Dec 2023 19:43:02 -0300 Subject: [PATCH 21/28] feat: skulled players lose store items config (#2046) Created config to enable/disable players with red/black lose store items and moved the validation from script `blessing.lua` to config. Fixes #2014 --------- Co-authored-by: GitHub Actions Co-authored-by: Luan Santos Co-authored-by: Luan Santos --- config.lua.dist | 1 + data/modules/scripts/blessings/blessings.lua | 2 +- src/config/config_definitions.hpp | 1 + src/config/configmanager.cpp | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/config.lua.dist b/config.lua.dist index 4d2436cfd32..bb23322bdcc 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -374,6 +374,7 @@ showScriptsLogInConsole = false criticalChance = 10 inventoryGlowOnFiveBless = false adventurersBlessingLevel = 21 +skulledDeathLoseStoreItem = false experienceDisplayRates = true -- configure attack base on Fist Fighting skill/experience -- multiplierSpeedOnFist * 5 (multiplies the value obtained from the player fist skill and multiplies it * 5) max 25 is recommended due minTicks limits else player stop attack diff --git a/data/modules/scripts/blessings/blessings.lua b/data/modules/scripts/blessings/blessings.lua index 94eaede5fbd..5a8e5e56c9c 100644 --- a/data/modules/scripts/blessings/blessings.lua +++ b/data/modules/scripts/blessings/blessings.lua @@ -23,7 +23,7 @@ Blessings.Config = { AdventurerBlessingLevel = configManager.getNumber(configKeys.ADVENTURERSBLESSING_LEVEL), -- Free full bless until level HasToF = false, -- Enables/disables twist of fate InquisitonBlessPriceMultiplier = 1.1, -- Bless price multiplied by henricus - SkulledDeathLoseStoreItem = true, -- Destroy all items on store when dying with red/blackskull + SkulledDeathLoseStoreItem = configManager.getBoolean(configKeys.SKULLED_DEATH_LOSE_STORE_ITEM), -- Destroy all items on store when dying with red/blackskull InventoryGlowOnFiveBless = configManager.getBoolean(configKeys.INVENTORY_GLOW), -- Glow in yellow inventory items when the player has 5 or more bless, Debug = false, -- Prin debug messages in console if enabled } diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp index 361e9c52c94..34af1dca190 100644 --- a/src/config/config_definitions.hpp +++ b/src/config/config_definitions.hpp @@ -224,6 +224,7 @@ enum ConfigKey_t : uint16_t { SCRIPTS_CONSOLE_LOGS, SERVER_MOTD, SERVER_NAME, + SKULLED_DEATH_LOSE_STORE_ITEM, SORT_LOOT_BY_CHANCE, SQL_PORT, STAIRHOP_DELAY, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index f82f3a66425..6895571ece4 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -219,6 +219,7 @@ bool ConfigManager::load() { loadIntConfig(L, CRITICALCHANCE, "criticalChance", 10); loadIntConfig(L, ADVENTURERSBLESSING_LEVEL, "adventurersBlessingLevel", 21); + loadBoolConfig(L, SKULLED_DEATH_LOSE_STORE_ITEM, "skulledDeathLoseStoreItem", false); loadIntConfig(L, FORGE_MAX_ITEM_TIER, "forgeMaxItemTier", 10); loadIntConfig(L, FORGE_COST_ONE_SLIVER, "forgeCostOneSliver", 20); loadIntConfig(L, FORGE_SLIVER_AMOUNT, "forgeSliverAmount", 3); From 458eb99f1e4ea214c9047e6e75667ad168df5e87 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 28 Dec 2023 19:10:12 -0800 Subject: [PATCH 22/28] feat: move multiple raids to new system (#2002) New config: `disableLegacyRaids` to disable the old XML based system. New command: `/simraid` you can "simulate" raid parameters to see how often your raid would run: ``` 18:13 Raid triggered 10 times in 31931 rolls (23.138405797101 days or once every 2.3138405797101 days) ``` Raids added can be seen in `scripts/raids`: ![CleanShot 2023-12-09 at 18 45 34@2x](https://github.com/opentibiabr/canary/assets/223760/fe43b632-da3b-4ed5-8bac-37cbce8bd38f) --- config.lua.dist | 1 + data-otservbr-global/raids/raids.xml | 34 +----- .../raids/bosses/arachir_the_ancient_one.lua | 23 ++++ .../scripts/raids/bosses/diblis_the_fair.lua | 23 ++++ .../scripts/raids/bosses/furyosa.lua | 32 ++++++ .../scripts/raids/bosses/hirintror.lua | 23 ++++ .../scripts/raids/bosses/mawhawk.lua | 22 ++++ .../scripts/raids/bosses/sir_valorcrest.lua | 23 ++++ .../scripts/raids/bosses/the_old_widow.lua | 38 +++++++ .../scripts/raids/bosses/the_pale_count.lua | 23 ++++ .../scripts/raids/bosses/the_welter.lua | 22 ++++ .../scripts/raids/bosses/tyrn.lua | 22 ++++ .../scripts/raids/bosses/weakened_shlorg.lua | 23 ++++ .../scripts/raids/bosses/white_pale.lua | 23 ++++ .../scripts/raids/monsters/draptor.lua | 47 ++++++++ .../raids/monsters/midnight_panther.lua | 29 +++++ .../scripts/raids/monsters/rats.lua | 42 +++++++ .../raids/monsters/undead_cavebear.lua | 25 +++++ .../scripts/raids/monsters/wild_horses.lua | 27 +++++ .../scripts/raids/monsters/yeti.lua | 29 +++++ data/libs/encounters_lib.lua | 96 ++++++++++++---- data/libs/raids_lib.lua | 84 +++++++------- data/scripts/talkactions/god/raids.lua | 104 ++++++++++++++++++ src/config/config_definitions.hpp | 1 + src/config/configmanager.cpp | 1 + src/lua/creature/raids.cpp | 7 +- 26 files changed, 733 insertions(+), 91 deletions(-) create mode 100644 data-otservbr-global/scripts/raids/bosses/arachir_the_ancient_one.lua create mode 100644 data-otservbr-global/scripts/raids/bosses/diblis_the_fair.lua create mode 100644 data-otservbr-global/scripts/raids/bosses/furyosa.lua create mode 100644 data-otservbr-global/scripts/raids/bosses/hirintror.lua create mode 100644 data-otservbr-global/scripts/raids/bosses/mawhawk.lua create mode 100644 data-otservbr-global/scripts/raids/bosses/sir_valorcrest.lua create mode 100644 data-otservbr-global/scripts/raids/bosses/the_old_widow.lua create mode 100644 data-otservbr-global/scripts/raids/bosses/the_pale_count.lua create mode 100644 data-otservbr-global/scripts/raids/bosses/the_welter.lua create mode 100644 data-otservbr-global/scripts/raids/bosses/tyrn.lua create mode 100644 data-otservbr-global/scripts/raids/bosses/weakened_shlorg.lua create mode 100644 data-otservbr-global/scripts/raids/bosses/white_pale.lua create mode 100644 data-otservbr-global/scripts/raids/monsters/draptor.lua create mode 100644 data-otservbr-global/scripts/raids/monsters/midnight_panther.lua create mode 100644 data-otservbr-global/scripts/raids/monsters/rats.lua create mode 100644 data-otservbr-global/scripts/raids/monsters/undead_cavebear.lua create mode 100644 data-otservbr-global/scripts/raids/monsters/wild_horses.lua create mode 100644 data-otservbr-global/scripts/raids/monsters/yeti.lua create mode 100644 data/scripts/talkactions/god/raids.lua diff --git a/config.lua.dist b/config.lua.dist index bb23322bdcc..7471d5cd768 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -382,6 +382,7 @@ experienceDisplayRates = true toggleAttackSpeedOnFist = false multiplierSpeedOnFist = 5 maxSpeedOnFist = 500 +disableLegacyRaids = false -- disable legacy XML raids disableMonsterArmor = false combatChainDelay = 50 -- minimum: 50 miliseconds minElementalResistance = -200 diff --git a/data-otservbr-global/raids/raids.xml b/data-otservbr-global/raids/raids.xml index 035aab91e6c..51aae3a0165 100644 --- a/data-otservbr-global/raids/raids.xml +++ b/data-otservbr-global/raids/raids.xml @@ -14,68 +14,41 @@ - - - - - - - - - - - - - - - - - - + - - - - - - - - - - @@ -85,14 +58,12 @@ - - @@ -103,10 +74,9 @@ - + - diff --git a/data-otservbr-global/scripts/raids/bosses/arachir_the_ancient_one.lua b/data-otservbr-global/scripts/raids/bosses/arachir_the_ancient_one.lua new file mode 100644 index 00000000000..c3eed4ab17d --- /dev/null +++ b/data-otservbr-global/scripts/raids/bosses/arachir_the_ancient_one.lua @@ -0,0 +1,23 @@ +local zone = Zone("drefia.arachir") +zone:addArea(Position(32963, 32399, 12), Position(32965, 32401, 12)) + +local raid = Raid("drefia.arachir", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 3, + initialChance = 0.02, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.8, +}) + +raid + :addSpawnMonsters({ + { + name = "Arachir the Ancient One", + amount = 1, + position = Position(32964, 32400, 12), + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/bosses/diblis_the_fair.lua b/data-otservbr-global/scripts/raids/bosses/diblis_the_fair.lua new file mode 100644 index 00000000000..a510439bdfc --- /dev/null +++ b/data-otservbr-global/scripts/raids/bosses/diblis_the_fair.lua @@ -0,0 +1,23 @@ +local zone = Zone("nargor.diblis") +zone:addArea(Position(32008, 32794, 10), Position(32010, 32797, 10)) + +local raid = Raid("nargor.diblis", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 3, + initialChance = 0.02, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.8, +}) + +raid + :addSpawnMonsters({ + { + name = "Diblis The Fair", + amount = 1, + position = Position(32009, 32795, 10), + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/bosses/furyosa.lua b/data-otservbr-global/scripts/raids/bosses/furyosa.lua new file mode 100644 index 00000000000..083e0b23afa --- /dev/null +++ b/data-otservbr-global/scripts/raids/bosses/furyosa.lua @@ -0,0 +1,32 @@ +local zone = Zone("fury-gates.furiosa") +zone:addArea(Position(33257, 32659, 14), Position(33342, 31867, 15)) + +local raid = Raid("fury-gates.furiosa", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 3, + initialChance = 0.01, + targetChancePerDay = 0.01, + maxChancePerCheck = 0.6, +}) + +raid + :addSpawnMonsters({ + { + name = "Demon", + amount = 80, + }, + }) + :autoAdvance("1m") + +raid + :addSpawnMonsters({ + { + name = "Furyosa", + amount = 1, + position = Position(33281, 31804, 15), + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/bosses/hirintror.lua b/data-otservbr-global/scripts/raids/bosses/hirintror.lua new file mode 100644 index 00000000000..a2343d80a37 --- /dev/null +++ b/data-otservbr-global/scripts/raids/bosses/hirintror.lua @@ -0,0 +1,23 @@ +local zone = Zone("svargrond.hirintror") +zone:addArea(Position(32100, 31166, 9), Position(32102, 31168, 9)) + +local raid = Raid("svargrond.hirintror", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 3, + initialChance = 0.02, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.8, +}) + +raid + :addSpawnMonsters({ + { + name = "Hirintror", + amount = 1, + position = Position(32101, 31167, 9), + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/bosses/mawhawk.lua b/data-otservbr-global/scripts/raids/bosses/mawhawk.lua new file mode 100644 index 00000000000..f71a64eb380 --- /dev/null +++ b/data-otservbr-global/scripts/raids/bosses/mawhawk.lua @@ -0,0 +1,22 @@ +local zone = Zone("roshamuul.mawhawk") +zone:addArea(Position(33702, 32460, 7), Position(33704, 32462, 7)) + +local raid = Raid("roshamuul.mawhawk", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 2, + initialChance = 0.04, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.4, +}) + +raid + :addSpawnMonsters({ + { + name = "Mawhawk", + amount = 1, + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/bosses/sir_valorcrest.lua b/data-otservbr-global/scripts/raids/bosses/sir_valorcrest.lua new file mode 100644 index 00000000000..ddb86dc7e57 --- /dev/null +++ b/data-otservbr-global/scripts/raids/bosses/sir_valorcrest.lua @@ -0,0 +1,23 @@ +local zone = Zone("edron.valorcrest") +zone:addArea(Position(33263, 31767, 10), Position(33265, 31769, 10)) + +local raid = Raid("edron.valorcrest", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 3, + initialChance = 0.02, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.8, +}) + +raid + :addSpawnMonsters({ + { + name = "Sir Valorcrest", + amount = 1, + position = Position(33264, 31768, 10), + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/bosses/the_old_widow.lua b/data-otservbr-global/scripts/raids/bosses/the_old_widow.lua new file mode 100644 index 00000000000..ff82c9fca57 --- /dev/null +++ b/data-otservbr-global/scripts/raids/bosses/the_old_widow.lua @@ -0,0 +1,38 @@ +local zone = Zone("venore.the-old-widow") +zone:addArea(Position(32292, 32292, 12), Position(32796, 32306, 12)) + +local raid = Raid("venore.the-old-widow", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 3, + initialChance = 0.02, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.8, +}) + +raid:addBroadcast("The mating season of the giant spiders is at hand. Leave the plains of havoc as fast as you can."):autoAdvance("30s") + +raid:addBroadcast("Giant spiders have gathered on the plains of havoc for their mating season. Beware!"):autoAdvance("3m") + +for _ = 1, 4 do + raid + :addSpawnMonsters({ + { + name = "Giant Spider", + amount = 8, + }, + }) + :autoAdvance("10s") +end + +raid + :addSpawnMonsters({ + { + name = "The Old Widow", + amount = 1, + position = Position(32776, 32296, 7), + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/bosses/the_pale_count.lua b/data-otservbr-global/scripts/raids/bosses/the_pale_count.lua new file mode 100644 index 00000000000..268636736bd --- /dev/null +++ b/data-otservbr-global/scripts/raids/bosses/the_pale_count.lua @@ -0,0 +1,23 @@ +local zone = Zone("drefia.the-pale-count") +zone:addArea(Position(32968, 32419, 15), Position(32970, 32421, 15)) + +local raid = Raid("drefia.the-pale-count", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 3, + initialChance = 0.01, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.7, +}) + +raid + :addSpawnMonsters({ + { + name = "The Pale Count", + amount = 1, + position = Position(32969, 32420, 15), + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/bosses/the_welter.lua b/data-otservbr-global/scripts/raids/bosses/the_welter.lua new file mode 100644 index 00000000000..3daadde51f8 --- /dev/null +++ b/data-otservbr-global/scripts/raids/bosses/the_welter.lua @@ -0,0 +1,22 @@ +local zone = Zone("ankrahmun.the-welter") +zone:addArea(Position(33025, 32659, 5), Position(33027, 32661, 5)) + +local raid = Raid("ankrahmun.the-welter", { + zone = zone, + allowedDays = { "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 3, + initialChance = 0.01, + targetChancePerDay = 0.01, + maxChancePerCheck = 0.6, +}) + +raid + :addSpawnMonsters({ + { + name = "The Welter", + amount = 1, + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/bosses/tyrn.lua b/data-otservbr-global/scripts/raids/bosses/tyrn.lua new file mode 100644 index 00000000000..839f462872d --- /dev/null +++ b/data-otservbr-global/scripts/raids/bosses/tyrn.lua @@ -0,0 +1,22 @@ +local zone = Zone("darashia.tyrn") +zone:addArea(Position(33055, 32392, 14), Position(33057, 32394, 14)) + +local raid = Raid("darashia.tyrn", { + zone = zone, + allowedDays = { "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 2, + initialChance = 0.04, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.4, +}) + +raid + :addSpawnMonsters({ + { + name = "Tyrn", + amount = 1, + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/bosses/weakened_shlorg.lua b/data-otservbr-global/scripts/raids/bosses/weakened_shlorg.lua new file mode 100644 index 00000000000..78ef336a953 --- /dev/null +++ b/data-otservbr-global/scripts/raids/bosses/weakened_shlorg.lua @@ -0,0 +1,23 @@ +local zone = Zone("edron.weakened-shlorg") +zone:addArea(Position(33163, 31715, 9), Position(33165, 31717, 9)) + +local raid = Raid("edron.weakened-shlorg", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 3, + initialChance = 0.02, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.8, +}) + +raid + :addSpawnMonsters({ + { + name = "Weakened Shlorg", + amount = 1, + position = Position(33164, 31716, 9), + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/bosses/white_pale.lua b/data-otservbr-global/scripts/raids/bosses/white_pale.lua new file mode 100644 index 00000000000..880660d5c99 --- /dev/null +++ b/data-otservbr-global/scripts/raids/bosses/white_pale.lua @@ -0,0 +1,23 @@ +local zone = Zone("edron.white-pale") +zone:addArea(Position(33263, 31874, 11), Position(33265, 31876, 11)) + +local raid = Raid("edron.white-pale", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 3, + initialChance = 0.02, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.8, +}) + +raid + :addSpawnMonsters({ + { + name = "White Pale", + amount = 1, + position = Position(33264, 31875, 11), + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/monsters/draptor.lua b/data-otservbr-global/scripts/raids/monsters/draptor.lua new file mode 100644 index 00000000000..002e06ce8de --- /dev/null +++ b/data-otservbr-global/scripts/raids/monsters/draptor.lua @@ -0,0 +1,47 @@ +local zone = Zone("farmine.draptor") +zone:addArea(Position(33195, 31160, 7), Position(33286, 31247, 7)) + +local raid = Raid("farmine.draptor", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 2, + initialChance = 0.02, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.6, + minGapBetween = "12h", +}) + +raid:addBroadcast("The dragons of the Dragonblaze Mountains have descended to Zao to protect the lizardkin!"):autoAdvance("30s") + +for i = 1, 3 do + raid + :addSpawnMonsters({ + { + name = "Dragon", + amount = 50, + }, + }) + :autoAdvance("2m") +end + +for i = 1, 8 do + raid + :addSpawnMonsters({ + { + name = "Draptor", + amount = 1, + }, + }) + :autoAdvance("10s") +end + +raid + :addSpawnMonsters({ + { + name = "Grand Mother Foulscale", + amount = 1, + }, + }) + :autoAdvance("10s") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/monsters/midnight_panther.lua b/data-otservbr-global/scripts/raids/monsters/midnight_panther.lua new file mode 100644 index 00000000000..d4bb999f881 --- /dev/null +++ b/data-otservbr-global/scripts/raids/monsters/midnight_panther.lua @@ -0,0 +1,29 @@ +local zone = Zone("tiquanda.midnight-panther") +zone:addArea(Position(32847, 32697, 7), Position(32871, 32738, 7)) + +local raid = Raid("tiquanda.midnight-panther", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 3, + initialChance = 0.03, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.9, +}) + +local possiblePositions = { + Position(32847, 32697, 7), + Position(32871, 32717, 7), + Position(32856, 32738, 7), +} + +raid + :addSpawnMonsters({ + { + name = "Midnight Panther", + amount = 1, + position = possiblePositions[math.random(1, #possiblePositions)], + }, + }) + :autoAdvance("24h") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/monsters/rats.lua b/data-otservbr-global/scripts/raids/monsters/rats.lua new file mode 100644 index 00000000000..11d1a440bb2 --- /dev/null +++ b/data-otservbr-global/scripts/raids/monsters/rats.lua @@ -0,0 +1,42 @@ +local zone = Zone("thais.rats") +zone:addArea(Position(32331, 32182, 7), Position(32426, 32261, 7)) + +local raid = Raid("thais.rats", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 2, + initialChance = 0.04, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.4, + minGapBetween = "36h", +}) + +raid:addBroadcast("Rat Plague in Thais!"):autoAdvance("5s") + +raid + :addSpawnMonsters({ + { + name = "Rat", + amount = 10, + }, + { + name = "Cave Rat", + amount = 10, + }, + }) + :autoAdvance("10m") + +raid + :addSpawnMonsters({ + { + name = "Rat", + amount = 20, + }, + { + name = "Cave Rat", + amount = 20, + }, + }) + :autoAdvance("10m") + +raid:register() diff --git a/data-otservbr-global/scripts/raids/monsters/undead_cavebear.lua b/data-otservbr-global/scripts/raids/monsters/undead_cavebear.lua new file mode 100644 index 00000000000..b86f23b54c3 --- /dev/null +++ b/data-otservbr-global/scripts/raids/monsters/undead_cavebear.lua @@ -0,0 +1,25 @@ +local zone = Zone("farmine.draptor") +zone:addArea(Position(31909, 32554, 7), Position(31983, 32579, 7)) + +local raid = Raid("farmine.draptor", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 2, + initialChance = 0.02, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.6, + minGapBetween = "12h", +}) + +for i = 1, 3 do + raid + :addSpawnMonsters({ + { + name = "Undead Cavebear", + amount = 3, + }, + }) + :autoAdvance("2m") +end + +raid:register() diff --git a/data-otservbr-global/scripts/raids/monsters/wild_horses.lua b/data-otservbr-global/scripts/raids/monsters/wild_horses.lua new file mode 100644 index 00000000000..b5e6063684e --- /dev/null +++ b/data-otservbr-global/scripts/raids/monsters/wild_horses.lua @@ -0,0 +1,27 @@ +local zone = Zone("thais.wild-horses") +zone:addArea(Position(32456, 32193, 7), Position(32491, 32261, 7)) +zone:addArea(Position(32431, 32240, 7), Position(32464, 32280, 7)) + +local raid = Raid("thais.wild-horses", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 3, + initialChance = 30, + targetChancePerDay = 50, + maxChancePerCheck = 50, + maxChecksPerDay = 2, + minGapBetween = "12h", +}) + +for _ = 1, 7 do + raid + :addSpawnMonsters({ + { + name = "Wild Horse", + amount = 3, + }, + }) + :autoAdvance("3h") +end + +raid:register() diff --git a/data-otservbr-global/scripts/raids/monsters/yeti.lua b/data-otservbr-global/scripts/raids/monsters/yeti.lua new file mode 100644 index 00000000000..0161b7eacd2 --- /dev/null +++ b/data-otservbr-global/scripts/raids/monsters/yeti.lua @@ -0,0 +1,29 @@ +local zone = Zone("folda.yeti") +zone:addArea(Position(31991, 31580, 7), Position(32044, 31616, 7)) + +local raid = Raid("folda.yeti", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 2, + initialChance = 0.02, + targetChancePerDay = 0.02, + maxChancePerCheck = 0.6, + minGapBetween = "48h", +}) + +raid:addBroadcast("Something is moving to the icy grounds of Folda."):autoAdvance("30s") +raid:addBroadcast("Many Yetis are emerging from the icy mountains of Folda."):autoAdvance("30s") +raid:addBroadcast("Numerous Yetis are dominating Folda, beware!"):autoAdvance("60s") + +for i = 1, 20 do + raid + :addSpawnMonsters({ + { + name = "Yeti", + amount = 3, + }, + }) + :autoAdvance("3m") +end + +raid:register() diff --git a/data/libs/encounters_lib.lua b/data/libs/encounters_lib.lua index 6258f399580..e84bd9a8aed 100644 --- a/data/libs/encounters_lib.lua +++ b/data/libs/encounters_lib.lua @@ -17,19 +17,48 @@ setmetatable(EncounterStage, { }, { __index = EncounterStage }) end, }) +---@type Delay number|string The delay time to advance to the next stage + +---@type AutoAdvanceConfig +---@field delay Delay +---@field monstersKilled boolean ---Automatically advances to the next stage after the given delay ----@param delay number|string The delay time to advance to the next stage -function EncounterStage:autoAdvance(delay) +---@param config AutoAdvanceConfig|Delay The configuration for the auto advance +function EncounterStage:autoAdvance(config) + if type(config) == "number" or type(config) == "string" then + config = { delay = config } + end + config = config or {} + local originalStart = self.start + local delay = config.delay + local delayElapsed = false function self.start() delay = delay or 50 -- 50ms is minimum delay; used here for close to instant advance - originalStart() + if originalStart then + originalStart() + end self.encounter:debug("Encounter[{}]:autoAdvance | next stage in: {}", self.encounter.name, delay == 50 and "instant" or delay) self.encounter:addEvent(function() - self.encounter:nextStage() + delayElapsed = true + if not config.monstersKilled then + self.encounter:nextStage() + end end, delay) end + + if config.monstersKilled then + local originalTick = self.tick + function self.tick() + if originaTick then + originalTick() + end + if delayElapsed and self.encounter:countMonsters() == 0 then + self.encounter:nextStage() + end + end + end end ---@class Encounter @@ -76,8 +105,8 @@ setmetatable(Encounter, { ---Resets the encounter configuration ---@param config EncounterConfig The new configuration function Encounter:resetConfig(config) - self.zone = config.zone - self.spawnZone = config.spawnZone or config.zone + self.zone = config.zone:getName() + self.spawnZone = config.spawnZone and config.spawnZone:getName() or config.zone:getName() self.stages = {} self.currentStage = Encounter.unstarted self.registered = false @@ -145,7 +174,7 @@ function Encounter:enterStage(stageNumber, abort) return true end ----@alias SpawnMonsterConfig { name: string, amount: number, event: string?, timeLimit: number?, position: Position|table?, positions: Position|table[]?, spawn: function? } +---@alias SpawnMonsterConfig { name: string|string[], amount: number, event: string?, timeLimit: number?, position: Position|table?, positions: Position|table[]?, spawn: function? } ---Spawns monsters based on the given configuration ---@param config SpawnMonsterConfig The configuration for spawning monsters @@ -164,15 +193,21 @@ function Encounter:spawnMonsters(config) if config.position then table.insert(positions, config.position) else - table.insert(positions, self.spawnZone:randomPosition()) + table.insert(positions, self:getSpawnZone():randomPosition()) end end end for _, position in ipairs(positions) do - for i = 1, self.timeToSpawnMonsters / 1000 do - self:addEvent(function(position) - position:sendMagicEffect(CONST_ME_TELEPORT) - end, i * 1000, position) + if self.timeToSpawnMonsters >= 1000 then + for i = 1, self.timeToSpawnMonsters / 1000 do + self:addEvent(function(position) + position:sendMagicEffect(CONST_ME_TELEPORT) + end, i * 1000, position) + end + end + local name = config.name + if type(name) == "table" then + name = name[math.random(#name)] end self:addEvent(function(name, position, event, spawn, timeLimit) local monster = Game.createMonster(name, position) @@ -199,10 +234,18 @@ function Encounter:spawnMonsters(config) monster:remove() end, config.timeLimit, monster:getID()) end - end, self.timeToSpawnMonsters, config.name, position, config.event, config.spawn, config.timeLimit) + end, self.timeToSpawnMonsters, name, position, config.event, config.spawn, config.timeLimit) end end +function Encounter:getZone() + return Zone(self.zone) +end + +function Encounter:getSpawnZone() + return Zone(self.spawnZone) +end + ---Broadcasts a message to all players function Encounter:broadcast(...) if self.global then @@ -211,25 +254,30 @@ function Encounter:broadcast(...) end return end - self.zone:sendTextMessage(...) + self:getZone():sendTextMessage(...) end ---Counts the number of monsters with the given name in the encounter zone ---@param name string The name of the monster to count ---@return number The number of monsters with the given name function Encounter:countMonsters(name) - return self.zone:countMonsters(name) + return self:getZone():countMonsters(name) end ---Counts the number of players in the encounter zone ---@return number The number of players in the encounter zone function Encounter:countPlayers() - return self.zone:countPlayers(IgnoredByMonsters) + return self:getZone():countPlayers(IgnoredByMonsters) end ---Removes all monsters from the encounter zone function Encounter:removeMonsters() - self.zone:removeMonsters() + self:getZone():removeMonsters() +end + +---Removes all players from the encounter zone +function Encounter:removePlayers() + self:getZone():removePlayers() end ---Resets the encounter to its initial state @@ -249,7 +297,7 @@ end ---@param position Position The position to check ---@return boolean True if the position is inside the encounter zone, false otherwise function Encounter:isInZone(position) - return self.zone:isInZone(position) + return self:getZone():isInZone(position) end ---Enters the previous stage in the encounter @@ -341,9 +389,19 @@ function Encounter:addRemoveMonsters() }) end +---Adds a stage that removes all players from the encounter zone +---@return boolean True if the remove monsters stage is added successfully, false otherwise +function Encounter:addRemovePlayers() + return self:addStage({ + start = function() + self:removePlayers() + end, + }) +end + ---Automatically starts the encounter when players enter the zone function Encounter:startOnEnter() - local zoneEvents = ZoneEvent(self.zone) + local zoneEvents = ZoneEvent(self:getZone()) function zoneEvents.afterEnter(zone, creature) if not self.registered then diff --git a/data/libs/raids_lib.lua b/data/libs/raids_lib.lua index 9b0b9d105a7..9cf3039c43c 100644 --- a/data/libs/raids_lib.lua +++ b/data/libs/raids_lib.lua @@ -47,8 +47,8 @@ end ---Starts the raid if it can be started ---@param self Raid The raid to try to start ---@return boolean True if the raid was started, false otherwise -function Raid:tryStart() - if not self:canStart() then +function Raid:tryStart(force) + if not force and not self:canStart() then return false end logger.info("Starting raid {}", self.name) @@ -65,50 +65,58 @@ function Raid:canStart() logger.debug("Raid {} is already running", self.name) return false end + local forceTrigger = self.kv:get("trigger-when-possible") + if not forceTrigger then + local lastOccurrence = (self.kv:get("last-occurrence") or 0) * 1000 + local currentTime = os.time() * 1000 + if self.minGapBetween and lastOccurrence and currentTime - lastOccurrence < self.minGapBetween then + logger.debug("Raid {} occurred too recently (last: {} ago, min: {})", self.name, FormatDuration(currentTime - lastOccurrence), FormatDuration(self.minGapBetween)) + return false + end + + if not self.targetChancePerDay or not self.maxChancePerCheck then + logger.debug("Raid {} does not have a chance configured (targetChancePerDay: {}, maxChancePerCheck: {})", self.name, self.targetChancePerDay, self.maxChancePerCheck) + return false + end + + local checksToday = tonumber(self.kv:get("checks-today") or 0) + if self.maxChecksPerDay and checksToday >= self.maxChecksPerDay then + logger.debug("Raid {} has already checked today (checks today: {}, max: {})", self.name, checksToday, self.maxChecksPerDay) + return false + end + self.kv:set("checks-today", checksToday + 1) + + local failedAttempts = self.kv:get("failed-attempts") or 0 + local checksPerDay = ParseDuration("23h") / ParseDuration(Raid.checkInterval) + local initialChance = self.initialChance or (self.targetChancePerDay / checksPerDay) + local chanceIncrease = math.max((self.targetChancePerDay - initialChance) / checksPerDay, 0) + local chance = initialChance + (chanceIncrease * failedAttempts) + if chance > self.maxChancePerCheck then + chance = self.maxChancePerCheck + end + chance = chance * 1000 + + -- offset the chance by 1000 to allow for fractional chances + local roll = math.random(100 * 1000) + if roll > chance then + logger.debug("Raid {} failed to start (roll: {}, chance: {}, failed attempts: {})", self.name, roll, chance, failedAttempts) + self.kv:set("failed-attempts", failedAttempts + 1) + return false + end + end + if self.allowedDays and not self:isAllowedDay() then logger.debug("Raid {} is not allowed today ({})", self.name, os.date("%A")) + self.kv:set("trigger-when-possible", true) return false end if self.minActivePlayers and self:getActivePlayerCount() < self.minActivePlayers then logger.debug("Raid {} does not have enough players (active: {}, min: {})", self.name, self:getActivePlayerCount(), self.minActivePlayers) - return false - end - local lastOccurrence = (self.kv:get("last-occurrence") or 0) * 1000 - local currentTime = os.time() * 1000 - if self.minGapBetween and lastOccurrence and currentTime - lastOccurrence < self.minGapBetween then - logger.debug("Raid {} occurred too recently (last: {} ago, min: {})", self.name, FormatDuration(currentTime - lastOccurrence), FormatDuration(self.minGapBetween)) - return false - end - - if not self.targetChancePerDay or not self.maxChancePerCheck then - logger.debug("Raid {} does not have a chance configured (targetChancePerDay: {}, maxChancePerCheck: {})", self.name, self.targetChancePerDay, self.maxChancePerCheck) + self.kv:set("trigger-when-possible", true) return false end - local checksToday = tonumber(self.kv:get("checks-today") or 0) - if self.maxChecksPerDay and checksToday >= self.maxChecksPerDay then - logger.debug("Raid {} has already checked today (checks today: {}, max: {})", self.name, checksToday, self.maxChecksPerDay) - return false - end - self.kv:set("checks-today", checksToday + 1) - - local failedAttempts = self.kv:get("failed-attempts") or 0 - local checksPerDay = ParseDuration("23h") / ParseDuration(Raid.checkInterval) - local initialChance = self.initialChance or (self.targetChancePerDay / checksPerDay) - local chanceIncrease = (self.targetChancePerDay - initialChance) / checksPerDay - local chance = initialChance + (chanceIncrease * failedAttempts) - if chance > self.maxChancePerCheck then - chance = self.maxChancePerCheck - end - chance = chance * 1000 - - -- offset the chance by 1000 to allow for fractional chances - local roll = math.random(100 * 1000) - if roll > chance then - logger.debug("Raid {} failed to start (roll: {}, chance: {}, failed attempts: {})", self.name, roll, chance, failedAttempts) - self.kv:set("failed-attempts", failedAttempts + 1) - return false - end + self.kv:set("trigger-when-possible", false) self.kv:set("failed-attempts", 0) return true end @@ -153,7 +161,7 @@ function Raid:addBroadcast(message, type) return self:addStage({ start = function() self:broadcast(type, message) - Webhook.sendMessage("Incoming raid", message, WEBHOOK_COLOR_RAID) + Webhook.sendMessage(":space_invader: " .. message, announcementChannels["raids"]) end, }) end diff --git a/data/scripts/talkactions/god/raids.lua b/data/scripts/talkactions/god/raids.lua new file mode 100644 index 00000000000..15f21801293 --- /dev/null +++ b/data/scripts/talkactions/god/raids.lua @@ -0,0 +1,104 @@ +local startRaid = TalkAction("/raid") + +function startRaid.onSay(player, words, param) + -- create log + logCommand(player, words, param) + + if param == "" then + player:sendCancelMessage("Command param required.") + return true + end + + if Raid.registry[param] then + local raid = Raid.registry[param] + if raid:tryStart(true) then + player:sendTextMessage(MESSAGE_ADMINISTRADOR, "Raid " .. param .. " started.") + else + player:sendTextMessage(MESSAGE_ADMINISTRADOR, "Raid " .. param .. " could not be started.") + end + return true + end + + local returnValue = Game.startRaid(param) + if returnValue ~= RETURNVALUE_NOERROR then + player:sendTextMessage(MESSAGE_ADMINISTRADOR, Game.getReturnMessage(returnValue)) + else + player:sendTextMessage(MESSAGE_ADMINISTRADOR, "Raid started.") + end + return true +end + +startRaid:separator(" ") +startRaid:groupType("god") +startRaid:register() + +local simulator = TalkAction("/simraid") + +function simulator.onSay(player, words, param) + -- create log + logCommand(player, words, param) + -- initialChance,targetChancePerDay,maxChancePerCheck + local zone = Zone("raid.simzone") + local params = param:split(",") + local initialChance = tonumber(params[1]) + local targetChancePerDay = tonumber(params[2]) + local maxChancePerCheck = tonumber(params[3]) + local raid = Raid("simraid", { + zone = zone, + allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }, + minActivePlayers = 0, + initialChance = initialChance == 0 and nil or initialChance, + targetChancePerDay = targetChancePerDay, + maxChancePerCheck = maxChancePerCheck, + }) + raid.kv:set("failed-attempts", 0) + raid.kv:set("trigger-when-possible", false) + raid.kv:set("last-occurrence", 0) + raid.kv:set("checks-today", 0) + + local triggerCount = 0 + local rolls = 0 + + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Simulating raid with initialChance=" .. initialChance .. ", targetChancePerDay=" .. targetChancePerDay .. ", maxChancePerCheck=" .. maxChancePerCheck .. "...") + + local checksPerDay = ParseDuration("23h") / ParseDuration(Raid.checkInterval) + while triggerCount < 10 do + rolls = rolls + 1 + if raid:tryStart() then + triggerCount = triggerCount + 1 + raid:reset() + end + end + + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Raid triggered " .. triggerCount .. " times in " .. rolls .. " rolls (" .. rolls / checksPerDay .. " days or once every " .. (rolls / checksPerDay) / triggerCount .. " days)") + return true +end + +simulator:separator(" ") +simulator:groupType("god") +simulator:register() + +local listRaid = TalkAction("/listraid") + +function listRaid.onSay(player, words, param) + -- create log + logCommand(player, words, param) + + local raids = {} + for name, raid in pairs(Raid.registry) do + table.insert(raids, name) + end + table.sort(raids) + + local message = "Registered raids: " + for _, name in ipairs(raids) do + message = message .. name .. ", " + end + message = message:sub(1, -3) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, message) + return true +end + +listRaid:separator(" ") +listRaid:groupType("god") +listRaid:register() diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp index 34af1dca190..920494fea9c 100644 --- a/src/config/config_definitions.hpp +++ b/src/config/config_definitions.hpp @@ -49,6 +49,7 @@ enum ConfigKey_t : uint16_t { DEFAULT_PRIORITY, DEPOTCHEST, DEPOT_BOXES, + DISABLE_LEGACY_RAIDS, DISABLE_MONSTER_ARMOR, DISCORD_WEBHOOK_DELAY_MS, DISCORD_WEBHOOK_URL, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index 6895571ece4..090fcaf73e8 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -80,6 +80,7 @@ bool ConfigManager::load() { loadIntConfig(L, STASH_ITEMS, "stashItemCount", 5000); loadBoolConfig(L, OLD_PROTOCOL, "allowOldProtocol", true); + loadBoolConfig(L, DISABLE_LEGACY_RAIDS, "disableLegacyRaids", false); } loadBoolConfig(L, ALLOW_CHANGEOUTFIT, "allowChangeOutfit", true); diff --git a/src/lua/creature/raids.cpp b/src/lua/creature/raids.cpp index bb0d1f1af43..3251f744e4f 100644 --- a/src/lua/creature/raids.cpp +++ b/src/lua/creature/raids.cpp @@ -21,7 +21,7 @@ Raids::Raids() { } bool Raids::loadFromXml() { - if (isLoaded()) { + if (g_configManager().getBoolean(DISABLE_LEGACY_RAIDS, __FUNCTION__) || isLoaded()) { return true; } @@ -96,7 +96,7 @@ bool Raids::loadFromXml() { static constexpr int32_t MAX_RAND_RANGE = 10000000; bool Raids::startup() { - if (!isLoaded() || isStarted()) { + if (!isLoaded() || isStarted() || g_configManager().getBoolean(DISABLE_LEGACY_RAIDS, __FUNCTION__)) { return false; } @@ -109,6 +109,9 @@ bool Raids::startup() { } void Raids::checkRaids() { + if (g_configManager().getBoolean(DISABLE_LEGACY_RAIDS, __FUNCTION__)) { + return; + } if (!getRunning()) { uint64_t now = OTSYS_TIME(); From a5dcb65881225dca721310d70371ff9be8e15c71 Mon Sep 17 00:00:00 2001 From: Luan Luciano Date: Fri, 29 Dec 2023 03:02:34 -0300 Subject: [PATCH 23/28] improve: upgrade from storage to kv in data/ (#1979) - upgrade from storage to kv --------- Co-authored-by: Luan Santos --- .../actions/other/exercise_training.lua | 5 ++- .../creaturescripts/familiar/on_death.lua | 2 +- .../creaturescripts/familiar/on_login.lua | 26 +++---------- data-otservbr-global/lib/core/storages.lua | 6 --- .../actions/other/exercise_training.lua | 5 ++- .../creaturescripts/familiar/on_death.lua | 2 +- .../creaturescripts/familiar/on_login.lua | 7 ++-- .../scripts/creaturescripts/others/login.lua | 13 ++++--- data/chatchannels/scripts/help.lua | 11 +++--- data/events/scripts/creature.lua | 14 +++---- data/events/scripts/player.lua | 3 +- data/libs/core/global_storage.lua | 5 --- data/libs/functions/player.lua | 7 ++-- data/npclib/npc_system/modules.lua | 39 ++++++++++--------- .../scripts/eventcallbacks/player/on_look.lua | 3 +- 15 files changed, 66 insertions(+), 82 deletions(-) diff --git a/data-canary/scripts/actions/other/exercise_training.lua b/data-canary/scripts/actions/other/exercise_training.lua index ecf414a8029..82d13962100 100644 --- a/data-canary/scripts/actions/other/exercise_training.lua +++ b/data-canary/scripts/actions/other/exercise_training.lua @@ -38,7 +38,8 @@ function exerciseTraining.onUse(player, item, fromPosition, target, toPosition, end end - if player:getStorageValue(Storage.IsTraining) > os.time() then + local hasExhaustion = player:kv():get("training-exhaustion") or 0 + if hasExhaustion > os.time() then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "This exercise dummy can only be used after a 30 second cooldown.") return true end @@ -48,7 +49,7 @@ function exerciseTraining.onUse(player, item, fromPosition, target, toPosition, _G.OnExerciseTraining[playerId].event = addEvent(ExerciseEvent, 0, playerId, targetPos, item.itemid, targetId) _G.OnExerciseTraining[playerId].dummyPos = targetPos player:setTraining(true) - player:setStorageValue(Storage.IsTraining, os.time() + 30) + player:kv():set("training-exhaustion", os.time() + 30) end return true end diff --git a/data-canary/scripts/creaturescripts/familiar/on_death.lua b/data-canary/scripts/creaturescripts/familiar/on_death.lua index 02187ef20d9..47d3ba81839 100644 --- a/data-canary/scripts/creaturescripts/familiar/on_death.lua +++ b/data-canary/scripts/creaturescripts/familiar/on_death.lua @@ -9,7 +9,7 @@ function familiarOnDeath.onDeath(creature, corpse, lasthitkiller, mostdamagekill local vocation = FAMILIAR_ID[player:getVocation():getBaseId()] if table.contains(vocation, creature:getName()) then - player:setStorageValue(Global.Storage.FamiliarSummon, os.time()) + player:kv():set("familiar-summon-time", os.time()) for sendMessage = 1, #FAMILIAR_TIMER do stopEvent(player:getStorageValue(FAMILIAR_TIMER[sendMessage].storage)) player:setStorageValue(FAMILIAR_TIMER[sendMessage].storage, -1) diff --git a/data-canary/scripts/creaturescripts/familiar/on_login.lua b/data-canary/scripts/creaturescripts/familiar/on_login.lua index ca0371be77c..e264040dbf9 100644 --- a/data-canary/scripts/creaturescripts/familiar/on_login.lua +++ b/data-canary/scripts/creaturescripts/familiar/on_login.lua @@ -8,7 +8,8 @@ function familiarOnLogin.onLogin(player) local vocation = FAMILIAR_ID[player:getVocation():getBaseId()] local familiarName - local familiarTimeLeft = player:getStorageValue(Global.Storage.FamiliarSummon) - player:getLastLogout() + local familiarSummonTime = player:kv():get("familiar-summon-time") or 0 + local familiarTimeLeft = familiarSummonTime - player:getLastLogout() if vocation then if (not player:isPremium() and player:hasFamiliar(vocation.id)) or player:getLevel() < 200 then @@ -26,27 +27,10 @@ function familiarOnLogin.onLogin(player) end end - if familiarName then - local position = player:getPosition() - local familiarMonster = Game.createMonster(familiarName, position, true, false, player) - if familiarMonster then - familiarMonster:setOutfit({ lookType = player:getFamiliarLooktype() }) - familiarMonster:registerEvent("FamiliarDeath") - position:sendMagicEffect(CONST_ME_MAGIC_BLUE) - - local deltaSpeed = math.max(player:getSpeed() - familiarMonster:getSpeed(), 0) - familiarMonster:changeSpeed(deltaSpeed) - - player:setStorageValue(Global.Storage.FamiliarSummon, os.time() + familiarTimeLeft) - addEvent(RemoveFamiliar, familiarTimeLeft * 1000, familiarMonster:getId(), player:getId()) - - for sendMessage = 1, #FAMILIAR_TIMER do - if player:getStorageValue(FAMILIAR_TIMER[sendMessage].storage) == -1 and familiarTimeLeft >= FAMILIAR_TIMER[sendMessage].countdown then - player:setStorageValue(FAMILIAR_TIMER[sendMessage].storage, addEvent(SendMessageFunction, (familiarTimeLeft - FAMILIAR_TIMER[sendMessage].countdown) * 1000, player:getId(), FAMILIAR_TIMER[sendMessage].message)) - end - end - end + if not familiarName then + return true end + player:createFamiliar(familiarName, familiarTimeLeft) return true end diff --git a/data-otservbr-global/lib/core/storages.lua b/data-otservbr-global/lib/core/storages.lua index 12a7cdd33d1..10e5e305f63 100644 --- a/data-otservbr-global/lib/core/storages.lua +++ b/data-otservbr-global/lib/core/storages.lua @@ -79,8 +79,6 @@ Reserved player action storage key ranges (const.h) Storage = { -- General storages - IsTraining = 30000, - -- Reserved in Global.Storage.NpcExhaust = 30001 Dragonfetish = 30003, EdronRopeQuest = 30004, GhostShipQuest = 30005, @@ -102,10 +100,7 @@ Storage = { RookgaardDestiny = 30020, EruaranGreeting = 30021, MaryzaCookbook = 30022, - -- Reserved in Global.Storage.CombatProtectionStorage = 30023 Factions = 30024, - -- Reserved in Global.Storage.BlockMovementStorage = 30025 - -- Reserved in Global.Storage.FamiliarSummon = 30026 -- unused TrainerRoom = 30027, -- unused NpcSpawn = 30028, ExerciseDummyExhaust = 30029, @@ -130,7 +125,6 @@ Storage = { Navigator = 30048, DwarvenLegs = 30049, PrinceDrazzakTime = 30050, - -- Reserved in Global.Storage.StoreExaust = 30051 LemonCupcake = 30052, BlueberryCupcake = 30053, -- Reserved in Global.Storage.FamiliarSummonEvent10 = 30054 diff --git a/data-otservbr-global/scripts/actions/other/exercise_training.lua b/data-otservbr-global/scripts/actions/other/exercise_training.lua index e7f67c854be..616467aff56 100644 --- a/data-otservbr-global/scripts/actions/other/exercise_training.lua +++ b/data-otservbr-global/scripts/actions/other/exercise_training.lua @@ -54,7 +54,8 @@ function exerciseTraining.onUse(player, item, fromPosition, target, toPosition, end end - if player:getStorageValue(Storage.IsTraining) > os.time() then + local hasExhaustion = player:kv():get("training-exhaustion") or 0 + if hasExhaustion > os.time() then player:sendTextMessage(MESSAGE_FAILURE, "You are already training!") return true end @@ -64,7 +65,7 @@ function exerciseTraining.onUse(player, item, fromPosition, target, toPosition, _G.OnExerciseTraining[playerId].event = addEvent(ExerciseEvent, 0, playerId, targetPos, item.itemid, targetId) _G.OnExerciseTraining[playerId].dummyPos = targetPos player:setTraining(true) - player:setStorageValue(Storage.IsTraining, os.time() + cooldown) + player:kv():set("training-exhaustion", os.time() + cooldown) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have started training on an exercise dummy.") end return true diff --git a/data-otservbr-global/scripts/creaturescripts/familiar/on_death.lua b/data-otservbr-global/scripts/creaturescripts/familiar/on_death.lua index 02187ef20d9..47d3ba81839 100644 --- a/data-otservbr-global/scripts/creaturescripts/familiar/on_death.lua +++ b/data-otservbr-global/scripts/creaturescripts/familiar/on_death.lua @@ -9,7 +9,7 @@ function familiarOnDeath.onDeath(creature, corpse, lasthitkiller, mostdamagekill local vocation = FAMILIAR_ID[player:getVocation():getBaseId()] if table.contains(vocation, creature:getName()) then - player:setStorageValue(Global.Storage.FamiliarSummon, os.time()) + player:kv():set("familiar-summon-time", os.time()) for sendMessage = 1, #FAMILIAR_TIMER do stopEvent(player:getStorageValue(FAMILIAR_TIMER[sendMessage].storage)) player:setStorageValue(FAMILIAR_TIMER[sendMessage].storage, -1) diff --git a/data-otservbr-global/scripts/creaturescripts/familiar/on_login.lua b/data-otservbr-global/scripts/creaturescripts/familiar/on_login.lua index 8e9c7fdbf81..e264040dbf9 100644 --- a/data-otservbr-global/scripts/creaturescripts/familiar/on_login.lua +++ b/data-otservbr-global/scripts/creaturescripts/familiar/on_login.lua @@ -8,12 +8,13 @@ function familiarOnLogin.onLogin(player) local vocation = FAMILIAR_ID[player:getVocation():getBaseId()] local familiarName - local familiarTimeLeft = player:getStorageValue(Global.Storage.FamiliarSummon) - player:getLastLogout() + local familiarSummonTime = player:kv():get("familiar-summon-time") or 0 + local familiarTimeLeft = familiarSummonTime - player:getLastLogout() if vocation then - if (not isPremium(player) and player:hasFamiliar(vocation.id)) or player:getLevel() < 200 then + if (not player:isPremium() and player:hasFamiliar(vocation.id)) or player:getLevel() < 200 then player:removeFamiliar(vocation.id) - elseif isPremium(player) and player:getLevel() >= 200 then + elseif player:isPremium() and player:getLevel() >= 200 then if familiarTimeLeft > 0 then familiarName = vocation.name end diff --git a/data-otservbr-global/scripts/creaturescripts/others/login.lua b/data-otservbr-global/scripts/creaturescripts/others/login.lua index 27bfddd5ddc..852e53c2a63 100644 --- a/data-otservbr-global/scripts/creaturescripts/others/login.lua +++ b/data-otservbr-global/scripts/creaturescripts/others/login.lua @@ -1,16 +1,16 @@ -local function onMovementRemoveProtection(cid, oldPos, time) - local player = Player(cid) +local function onMovementRemoveProtection(playerId, oldPos, time) + local player = Player(playerId) if not player then return true end local playerPos = player:getPosition() if (playerPos.x ~= oldPos.x or playerPos.y ~= oldPos.y or playerPos.z ~= oldPos.z) or player:getTarget() then - player:setStorageValue(Global.Storage.CombatProtectionStorage, 0) + player:kv():remove("combat-protection") return true end - addEvent(onMovementRemoveProtection, 1000, cid, oldPos, time - 1) + addEvent(onMovementRemoveProtection, 1000, playerId, oldPos, time - 1) end local function protectionZoneCheck(playerName) @@ -250,8 +250,9 @@ function playerLogin.onLogin(player) stats.playerId = player:getId() end - if player:getStorageValue(Global.Storage.CombatProtectionStorage) < 1 then - player:setStorageValue(Global.Storage.CombatProtectionStorage, 1) + local isProtected = player:kv():get("combat-protection") or 0 + if isProtected < 1 then + player:kv():set("combat-protection", 1) onMovementRemoveProtection(playerId, player:getPosition(), 10) end diff --git a/data/chatchannels/scripts/help.lua b/data/chatchannels/scripts/help.lua index a5d9e9fe53d..de51a575168 100644 --- a/data/chatchannels/scripts/help.lua +++ b/data/chatchannels/scripts/help.lua @@ -1,5 +1,4 @@ local CHANNEL_HELP = 7 -local storage = 456112 local muted = Condition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT) muted:setParameter(CONDITION_PARAM_SUBID, CHANNEL_HELP) @@ -12,7 +11,8 @@ function onSpeak(player, type, message) return false end - if player:getStorageValue(storage) > os.time() then + local hasExhaustion = player:kv():get("channel-help-exhaustion") or 0 + if hasExhaustion > os.time() then player:sendCancelMessage("You are muted from the Help channel for using it inappropriately.") return false end @@ -25,7 +25,7 @@ function onSpeak(player, type, message) if playerGroupType > target:getAccountType() then if not target:getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) then target:addCondition(muted) - target:setStorageValue(storage, os.time() + 180) + target:kv():set("channel-help-exhaustion", os.time() + 180) -- 3 minutes sendChannelMessage(CHANNEL_HELP, TALKTYPE_CHANNEL_R1, target:getName() .. " has been muted by " .. player:getName() .. " for using Help Channel inappropriately.") else player:sendCancelMessage("That player is already muted.") @@ -42,10 +42,11 @@ function onSpeak(player, type, message) local target = Player(targetName) if target then if playerGroupType > target:getAccountType() then - if target:getStorageValue(storage) > os.time() then + local hasExhaustionTarget = target:kv():get("channel-help-exhaustion") or 0 + if hasExhaustionTarget > os.time() then target:removeCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) sendChannelMessage(CHANNEL_HELP, TALKTYPE_CHANNEL_R1, target:getName() .. " has been unmuted.") - target:setStorageValue(storage, -1) + target:kv():remove("channel-help-exhaustion") else player:sendCancelMessage("That player is not muted.") end diff --git a/data/events/scripts/creature.lua b/data/events/scripts/creature.lua index 4da8217988d..e7a74a33191 100644 --- a/data/events/scripts/creature.lua +++ b/data/events/scripts/creature.lua @@ -13,14 +13,14 @@ local function removeCombatProtection(playerUid) time = 30 end - player:setStorageValue(Global.Storage.CombatProtectionStorage, 2) + player:kv():set("combat-protection", 2) addEvent(function(playerFuncUid) local playerEvent = Player(playerFuncUid) if not playerEvent then return end - playerEvent:setStorageValue(Global.Storage.CombatProtectionStorage, 0) + playerEvent:kv():remove("combat-protection") playerEvent:remove() end, time * 1000, playerUid) end @@ -32,16 +32,16 @@ function Creature:onTargetCombat(target) if target:isPlayer() then if self:isMonster() then - local protectionStorage = target:getStorageValue(Global.Storage.CombatProtectionStorage) + local isProtected = target:kv():get("combat-protection") or 0 if target:getIp() == 0 then -- If player is disconnected, monster shall ignore to attack the player if target:isPzLocked() then return true end - if protectionStorage <= 0 then + if isProtected <= 0 then addEvent(removeCombatProtection, 30 * 1000, target.uid) - target:setStorageValue(Global.Storage.CombatProtectionStorage, 1) - elseif protectionStorage == 1 then + target:kv():set("combat-protection", 1) + elseif isProtected == 1 then self:searchTarget() return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER end @@ -49,7 +49,7 @@ function Creature:onTargetCombat(target) return true end - if protectionStorage >= os.time() then + if isProtected >= os.time() then return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER end end diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua index cf78eeb2bc7..0a719401ed0 100644 --- a/data/events/scripts/player.lua +++ b/data/events/scripts/player.lua @@ -206,8 +206,9 @@ function Player:onLookInBattleList(creature, distance) local master = creature:getMaster() local summons = { "sorcerer familiar", "knight familiar", "druid familiar", "paladin familiar" } if master and table.contains(summons, creature:getName():lower()) then + local familiarSummonTime = master:kv():get("familiar-summon-time") or 0 description = description .. " (Master: " .. master:getName() .. "). \z - It will disappear in " .. getTimeInWords(master:getStorageValue(Global.Storage.FamiliarSummon) - os.time()) + It will disappear in " .. getTimeInWords(familiarSummonTime - os.time()) end end if self:getGroup():getAccess() then diff --git a/data/libs/core/global_storage.lua b/data/libs/core/global_storage.lua index 8576adc54a7..4119be5ebb6 100644 --- a/data/libs/core/global_storage.lua +++ b/data/libs/core/global_storage.lua @@ -23,11 +23,6 @@ Reserved player action storage key ranges (const.hpp) Global = { Storage = { - NpcExhaust = 30001, - CombatProtectionStorage = 30023, - BlockMovementStorage = 30025, - FamiliarSummon = 30026, - StoreExaust = 30051, FamiliarSummonEvent10 = 30054, FamiliarSummonEvent60 = 30055, }, diff --git a/data/libs/functions/player.lua b/data/libs/functions/player.lua index ce9c8177d9d..d494f35edd3 100644 --- a/data/libs/functions/player.lua +++ b/data/libs/functions/player.lua @@ -121,11 +121,12 @@ function Player.getCookiesDelivered(self) end function Player.allowMovement(self, allow) - return self:setStorageValue(Global.Storage.BlockMovementStorage, allow and -1 or 1) + return allow and self:kv():remove("block-movement") or self:kv():set("block-movement", 1) end function Player.hasAllowMovement(self) - return self:getStorageValue(Global.Storage.BlockMovementStorage) ~= 1 + local blockMovement = self:kv():get("block-movement") or 0 + return blockMovement ~= 1 end function Player.checkGnomeRank(self) @@ -431,7 +432,7 @@ function Player:createFamiliar(familiarName, timeLeft) playerPosition:sendMagicEffect(CONST_ME_MAGIC_BLUE) myFamiliar:getPosition():sendMagicEffect(CONST_ME_TELEPORT) -- Divide by 2 to get half the time (the default total time is 30 / 2 = 15) - self:setStorageValue(Global.Storage.FamiliarSummon, os.time() + timeLeft) + self:kv():set("familiar-summon-time", os.time() + timeLeft) addEvent(RemoveFamiliar, timeLeft * 1000, myFamiliar:getId(), self:getId()) for sendMessage = 1, #FAMILIAR_TIMER do self:setStorageValue( diff --git a/data/npclib/npc_system/modules.lua b/data/npclib/npc_system/modules.lua index 68fff0c39df..5e7db715080 100644 --- a/data/npclib/npc_system/modules.lua +++ b/data/npclib/npc_system/modules.lua @@ -223,27 +223,30 @@ if Modules == nil then npcHandler:say("First get rid of those blood stains! You are not going to ruin my vehicle!", npc, player) elseif not player:removeMoneyBank(cost) then npcHandler:say("You don't have enough money.", npc, player) - elseif os.time() < player:getStorageValue(Global.Storage.NpcExhaust) then - npcHandler:say("Sorry, but you need to wait three seconds before travel again.", player) - playerPosition:sendMagicEffect(CONST_ME_POFF) else - npcHandler:removeInteraction(npc, player) - npcHandler:say(parameters.text or "Set the sails!", npc, player) - - local destination = parameters.destination - if type(destination) == "function" then - destination = destination(player) - end + local hasExhaustion = player:kv():get("npc-exhaustion") or 0 + if hasExhaustion > os.time() then + npcHandler:say("Sorry, but you need to wait three seconds before travel again.", player) + playerPosition:sendMagicEffect(CONST_ME_POFF) + else + npcHandler:removeInteraction(npc, player) + npcHandler:say(parameters.text or "Set the sails!", npc, player) - player:setStorageValue(Global.Storage.NpcExhaust, 3 + os.time()) - player:teleportTo(destination) - playerPosition:sendMagicEffect(CONST_ME_TELEPORT) + local destination = parameters.destination + if type(destination) == "function" then + destination = destination(player) + end - -- What a foolish Quest - Mission 3 - if Storage.WhatAFoolish.PieBoxTimer ~= nil then - if player:getStorageValue(Storage.WhatAFoolish.PieBoxTimer) > os.time() then - if destination ~= Position(32660, 31957, 15) then -- kazordoon steamboat - player:setStorageValue(Storage.WhatAFoolish.PieBoxTimer, 1) + player:kv():set("npc-exhaustion", os.time() + 3) -- 3 seconds + player:teleportTo(destination) + playerPosition:sendMagicEffect(CONST_ME_TELEPORT) + + -- What a foolish Quest - Mission 3 + if Storage.WhatAFoolish.PieBoxTimer ~= nil then + if player:getStorageValue(Storage.WhatAFoolish.PieBoxTimer) > os.time() then + if destination ~= Position(32660, 31957, 15) then -- kazordoon steamboat + player:setStorageValue(Storage.WhatAFoolish.PieBoxTimer, 1) + end end end end diff --git a/data/scripts/eventcallbacks/player/on_look.lua b/data/scripts/eventcallbacks/player/on_look.lua index 985bbfcd3a3..f1a35d14565 100644 --- a/data/scripts/eventcallbacks/player/on_look.lua +++ b/data/scripts/eventcallbacks/player/on_look.lua @@ -25,7 +25,8 @@ function callback.playerOnLook(player, thing, position, distance) if thing:isMonster() then local master = thing:getMaster() if master and table.contains({ "sorcerer familiar", "knight familiar", "druid familiar", "paladin familiar" }, thing:getName():lower()) then - description = string.format("%s (Master: %s). \z It will disappear in %s", description, master:getName(), getTimeInWords(master:getStorageValue(Global.Storage.FamiliarSummon) - os.time())) + local familiarSummonTime = master:kv():get("familiar-summon-time") or 0 + description = string.format("%s (Master: %s). \z It will disappear in %s", description, master:getName(), getTimeInWords(familiarSummonTime - os.time())) end end end From b3a85eee3622fc9eceafed96c1f45ce77e1fe403 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 28 Dec 2023 22:38:38 -0800 Subject: [PATCH 24/28] improve: migrate part of daily reward to KV (#2006) Started moving daily reward to KV since it's easier to share that between lua/cpp and it's the direction the project is going. Co-authored-by: Elson Costa --- data/libs/daily_reward/player.lua | 4 ++-- .../scripts/daily_reward/daily_reward.lua | 1 - src/creatures/combat/condition.cpp | 12 ++++++------ src/server/network/protocol/protocolgame.cpp | 16 ++++++++-------- src/utils/const.hpp | 1 - 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/data/libs/daily_reward/player.lua b/data/libs/daily_reward/player.lua index 671f4d118ff..2635e7d377b 100644 --- a/data/libs/daily_reward/player.lua +++ b/data/libs/daily_reward/player.lua @@ -23,11 +23,11 @@ function Player.setDayStreak(self, value) end function Player.getStreakLevel(self) - return math.max(self:getStorageValue(DailyReward.storages.currentStreakLevel), 0) + return self:kv():scoped("daily-reward"):get("streak") or 7 end function Player.setStreakLevel(self, value) - self:setStorageValue(DailyReward.storages.currentStreakLevel, value) + self:kv():scoped("daily-reward"):set("streak", value) end function Player.setNextRewardTime(self, value) diff --git a/data/modules/scripts/daily_reward/daily_reward.lua b/data/modules/scripts/daily_reward/daily_reward.lua index 270aa3189da..177b23cc90b 100644 --- a/data/modules/scripts/daily_reward/daily_reward.lua +++ b/data/modules/scripts/daily_reward/daily_reward.lua @@ -80,7 +80,6 @@ DailyReward = { storages = { -- Player currentDayStreak = 14897, - currentStreakLevel = 14898, -- Cpp uses the same storage value on const.h (STORAGEVALUE_DAILYREWARD) nextRewardTime = 14899, collectionTokens = 14901, staminaBonus = 14902, diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index 2320ad99ee3..8b8ddff7ca4 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -1165,16 +1165,16 @@ bool ConditionRegeneration::executeCondition(std::shared_ptr creature, internalHealthTicks += interval; internalManaTicks += interval; auto player = creature->getPlayer(); - int32_t PlayerdailyStreak = 0; + int32_t dailyStreak = 0; if (player) { - PlayerdailyStreak = player->getStorageValue(STORAGEVALUE_DAILYREWARD); + dailyStreak = static_cast(player->kv()->scoped("daily-reward")->get("streak")->getNumber()); } - if (creature->getZoneType() != ZONE_PROTECTION || PlayerdailyStreak >= DAILY_REWARD_HP_REGENERATION) { + if (creature->getZoneType() != ZONE_PROTECTION || dailyStreak >= DAILY_REWARD_HP_REGENERATION) { if (internalHealthTicks >= getHealthTicks(creature)) { internalHealthTicks = 0; int32_t realHealthGain = creature->getHealth(); - if (creature->getZoneType() == ZONE_PROTECTION && PlayerdailyStreak >= DAILY_REWARD_DOUBLE_HP_REGENERATION) { + if (creature->getZoneType() == ZONE_PROTECTION && dailyStreak >= DAILY_REWARD_DOUBLE_HP_REGENERATION) { creature->changeHealth(healthGain * 2); // Double regen from daily reward } else { creature->changeHealth(healthGain); @@ -1205,10 +1205,10 @@ bool ConditionRegeneration::executeCondition(std::shared_ptr creature, } } - if (creature->getZoneType() != ZONE_PROTECTION || PlayerdailyStreak >= DAILY_REWARD_MP_REGENERATION) { + if (creature->getZoneType() != ZONE_PROTECTION || dailyStreak >= DAILY_REWARD_MP_REGENERATION) { if (internalManaTicks >= getManaTicks(creature)) { internalManaTicks = 0; - if (creature->getZoneType() == ZONE_PROTECTION && PlayerdailyStreak >= DAILY_REWARD_DOUBLE_MP_REGENERATION) { + if (creature->getZoneType() == ZONE_PROTECTION && dailyStreak >= DAILY_REWARD_DOUBLE_MP_REGENERATION) { creature->changeMana(manaGain * 2); // Double regen from daily reward } else { creature->changeMana(manaGain); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 1b55e3e94fa..313bec0d9a4 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -5735,29 +5735,29 @@ void ProtocolGame::sendRestingStatus(uint8_t protection) { NetworkMessage msg; msg.addByte(0xA9); msg.addByte(protection); // 1 / 0 - int32_t PlayerdailyStreak = player->getStorageValue(STORAGEVALUE_DAILYREWARD); - msg.addByte(PlayerdailyStreak < 2 ? 0 : 1); - if (PlayerdailyStreak < 2) { + int32_t dailyStreak = static_cast(player->kv()->scoped("daily-reward")->get("streak")->getNumber()); + msg.addByte(dailyStreak < 2 ? 0 : 1); + if (dailyStreak < 2) { msg.addString("Resting Area (no active bonus)", "ProtocolGame::sendRestingStatus - Resting Area (no active bonus)"); } else { std::ostringstream ss; ss << "Active Resting Area Bonuses: "; - if (PlayerdailyStreak < DAILY_REWARD_DOUBLE_HP_REGENERATION) { + if (dailyStreak < DAILY_REWARD_DOUBLE_HP_REGENERATION) { ss << "\nHit Points Regeneration"; } else { ss << "\nDouble Hit Points Regeneration"; } - if (PlayerdailyStreak >= DAILY_REWARD_MP_REGENERATION) { - if (PlayerdailyStreak < DAILY_REWARD_DOUBLE_MP_REGENERATION) { + if (dailyStreak >= DAILY_REWARD_MP_REGENERATION) { + if (dailyStreak < DAILY_REWARD_DOUBLE_MP_REGENERATION) { ss << ",\nMana Points Regeneration"; } else { ss << ",\nDouble Mana Points Regeneration"; } } - if (PlayerdailyStreak >= DAILY_REWARD_STAMINA_REGENERATION) { + if (dailyStreak >= DAILY_REWARD_STAMINA_REGENERATION) { ss << ",\nStamina Points Regeneration"; } - if (PlayerdailyStreak >= DAILY_REWARD_SOUL_REGENERATION) { + if (dailyStreak >= DAILY_REWARD_SOUL_REGENERATION) { ss << ",\nSoul Points Regeneration"; } ss << "."; diff --git a/src/utils/const.hpp b/src/utils/const.hpp index b9020a177f7..f86ec7aec7b 100644 --- a/src/utils/const.hpp +++ b/src/utils/const.hpp @@ -28,7 +28,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_DAILYREWARD = 14898; static constexpr int32_t STORAGEVALUE_BESTIARYKILLCOUNT = 61305000; // Can get up to 2000 storages! // Hazard system storage From df811d7669e8929e7a733f36f938f900f09ee06f Mon Sep 17 00:00:00 2001 From: roberto furtado Date: Fri, 29 Dec 2023 03:52:42 -0300 Subject: [PATCH 25/28] fix: cyclopedia description (#1923) * double protection information was removed * fixing speed information that showed in half --------- Co-authored-by: Eduardo Dantas Co-authored-by: Luan Santos Co-authored-by: GitHub Actions --- src/items/item.cpp | 51 ++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/items/item.cpp b/src/items/item.cpp index a3f0b3f1a4b..87a3abd7d63 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -1291,7 +1291,7 @@ Item::getDescriptions(const ItemType &it, std::shared_ptr item /*= nullptr if (it.abilities->speed) { ss.str(""); - ss << std::showpos << (it.abilities->speed >> 1) << std::noshowpos; + ss << std::showpos << it.abilities->speed << std::noshowpos; descriptions.emplace_back("Speed", ss.str()); } @@ -1606,7 +1606,7 @@ Item::getDescriptions(const ItemType &it, std::shared_ptr item /*= nullptr ss.str(""); bool skillBoost = false; if (it.abilities->speed) { - ss << std::showpos << "speed " << (it.abilities->speed >> 1) << std::noshowpos; + ss << std::showpos << "speed " << it.abilities->speed << std::noshowpos; skillBoost = true; } @@ -1653,14 +1653,11 @@ Item::getDescriptions(const ItemType &it, std::shared_ptr item /*= nullptr ss << it.abilities->skills[i]; } ss << '%' << std::noshowpos; - skillBoost = true; } - if (it.abilities->stats[STAT_MAGICPOINTS]) { - ss.str(""); - ss << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; - descriptions.emplace_back("Magic Level", ss.str()); + if (skillBoost) { + descriptions.emplace_back("Skill Boost", ss.str()); } for (uint8_t i = 1; i <= 11; i++) { @@ -1692,12 +1689,6 @@ Item::getDescriptions(const ItemType &it, std::shared_ptr item /*= nullptr descriptions.emplace_back("Damage Reflection", ss.str()); } - if (it.abilities->speed) { - ss.str(""); - ss << std::showpos << (it.abilities->speed >> 1) << std::noshowpos; - descriptions.emplace_back("Speed", ss.str()); - } - if (it.abilities->cleavePercent) { ss.str(""); ss << std::showpos << (it.abilities->cleavePercent) << std::noshowpos << "%"; @@ -1728,17 +1719,6 @@ Item::getDescriptions(const ItemType &it, std::shared_ptr item /*= nullptr descriptions.emplace_back("Effect", ss.str()); } - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] == 0) { - continue; - } - - ss.str(""); - ss << getCombatName(indexToCombatType(i)) << ' ' - << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; - descriptions.emplace_back("Protection", ss.str()); - } - for (size_t i = 0; i < COMBAT_COUNT; ++i) { if (it.abilities->fieldAbsorbPercent[i] == 0) { continue; @@ -2587,6 +2567,7 @@ std::string Item::getDescription(const ItemType &it, int32_t lookDistance, std:: if (!begin) { s << ')'; } + // This block refers to the look of the weapons. } else if (it.weaponType != WEAPON_AMMO) { bool begin = true; @@ -2601,6 +2582,28 @@ std::string Item::getDescription(const ItemType &it, int32_t lookDistance, std:: extraDefense = it.extraDefense; } + if (it.isContainer() || (item && item->getContainer())) { + uint32_t volume = 0; + + if (!item || !item->hasAttribute(ItemAttribute_t::UNIQUEID)) { + if (it.isContainer()) { + volume = it.maxItems; + } else if (item) { + volume = item->getContainer()->capacity(); + } + } + + if (volume != 0) { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "Vol:" << volume; + } + } if (attack != 0) { begin = false; s << " (Atk:" << attack; From 28749519252e3f1c123293dde62d611e70a80a59 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Sat, 30 Dec 2023 00:04:58 -0800 Subject: [PATCH 26/28] mv: moveable -> movable (#2067) Just to achieve consistency in the codebase and use the same (more modern) spelling of the word "movable". --- data-canary/lib/core/constants.lua | 2 +- data-canary/lib/core/storages.lua | 2 +- data-canary/scripts/runes/chameleon.lua | 2 +- data-otservbr-global/lib/compat/compat.lua | 8 +- data-otservbr-global/lib/core/storages.lua | 2 +- .../lib/quests/svargrond_arena.lua | 2 +- data-otservbr-global/npc/inigo.lua | 2 +- .../ferumbras_ascendant/habitat_corrupted.lua | 2 +- .../ferumbras_ascendant/habitat_desert.lua | 2 +- .../ferumbras_ascendant/habitat_dimension.lua | 2 +- .../ferumbras_ascendant/habitat_grass.lua | 2 +- .../ferumbras_ascendant/habitat_ice.lua | 2 +- .../ferumbras_ascendant/habitat_mushroom.lua | 2 +- .../ferumbras_ascendant/habitat_roshamuul.lua | 2 +- .../ferumbras_ascendant/habitat_venom.lua | 2 +- .../scripts/globalevents/others/startup.lua | 4 +- .../scripts/spells/runes/chameleon.lua | 2 +- data-otservbr-global/startup/README.md | 144 +++++++++--------- ...item_unmoveable.lua => item_unmovable.lua} | 2 +- data-otservbr-global/startup/tables/load.lua | 2 +- data/events/scripts/player.lua | 2 +- data/items/items.xml | 10 +- data/libs/core/global_storage.lua | 2 +- data/libs/functions/player.lua | 10 +- data/modules/scripts/gamestore/init.lua | 18 +-- data/npclib/npc_system/custom_modules.lua | 2 +- src/creatures/monsters/monster.cpp | 10 +- src/creatures/players/player.cpp | 115 +++++++------- src/creatures/players/player.hpp | 10 +- src/game/game.cpp | 10 +- src/io/iomap.cpp | 8 +- src/io/iomapserialize.cpp | 6 +- src/items/bed.cpp | 2 +- src/items/containers/container.cpp | 8 +- src/items/functions/item/item_parse.cpp | 8 +- src/items/functions/item/item_parse.hpp | 6 +- src/items/item.cpp | 30 ++-- src/items/item.hpp | 12 +- src/items/items.cpp | 2 +- src/items/items.hpp | 2 +- src/items/items_definitions.hpp | 10 +- src/items/tile.cpp | 34 ++--- src/items/tile.hpp | 4 +- src/items/trashholder.cpp | 2 +- src/lua/creature/raids.cpp | 2 +- .../functions/core/game/global_functions.cpp | 4 +- .../functions/core/game/global_functions.hpp | 4 +- src/lua/functions/core/game/lua_enums.cpp | 6 +- src/lua/functions/events/action_functions.cpp | 4 +- src/lua/functions/items/item_functions.cpp | 2 +- .../functions/items/item_type_functions.cpp | 2 +- src/map/house/housetile.cpp | 2 +- src/utils/tools.cpp | 2 +- 53 files changed, 270 insertions(+), 271 deletions(-) rename data-otservbr-global/startup/tables/{item_unmoveable.lua => item_unmovable.lua} (89%) diff --git a/data-canary/lib/core/constants.lua b/data-canary/lib/core/constants.lua index b42d731564d..7b99b309913 100644 --- a/data-canary/lib/core/constants.lua +++ b/data-canary/lib/core/constants.lua @@ -6,7 +6,7 @@ STACKPOS_FOURTH_ITEM_ABOVE_GROUNDTILE = 4 STACKPOS_FIFTH_ITEM_ABOVE_GROUNDTILE = 5 STACKPOS_TOP_CREATURE = 253 STACKPOS_TOP_FIELD = 254 -STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE = 255 +STACKPOS_TOP_MOVABLE_ITEM_OR_CREATURE = 255 THING_TYPE_PLAYER = CREATURETYPE_PLAYER + 1 THING_TYPE_MONSTER = CREATURETYPE_MONSTER + 1 diff --git a/data-canary/lib/core/storages.lua b/data-canary/lib/core/storages.lua index 43e47ef867f..7646a8cc51e 100644 --- a/data-canary/lib/core/storages.lua +++ b/data-canary/lib/core/storages.lua @@ -8,7 +8,7 @@ Reserved player action storage key ranges (const.hpp) [2001 - 2011] Others reserved player action/storages - [100] = unmoveable/untrade/unusable items + [100] = unmovable/untrade/unusable items [101] = use pick floor [102] = well down action [103-120] = others keys action diff --git a/data-canary/scripts/runes/chameleon.lua b/data-canary/scripts/runes/chameleon.lua index 858757b1725..92168645e20 100644 --- a/data-canary/scripts/runes/chameleon.lua +++ b/data-canary/scripts/runes/chameleon.lua @@ -16,7 +16,7 @@ function rune.onCastSpell(creature, variant, isHotkey) item = Tile(position):getTopDownItem() end - if not item or item.itemid == 0 or not isMoveable(item.uid) then + if not item or item.itemid == 0 or not isMovable(item.uid) then creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) creature:getPosition():sendMagicEffect(CONST_ME_POFF) return false diff --git a/data-otservbr-global/lib/compat/compat.lua b/data-otservbr-global/lib/compat/compat.lua index f62945e6b26..cd9b8146763 100644 --- a/data-otservbr-global/lib/compat/compat.lua +++ b/data-otservbr-global/lib/compat/compat.lua @@ -35,7 +35,7 @@ STACKPOS_FOURTH_ITEM_ABOVE_GROUNDTILE = 4 STACKPOS_FIFTH_ITEM_ABOVE_GROUNDTILE = 5 STACKPOS_TOP_CREATURE = 253 STACKPOS_TOP_FIELD = 254 -STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE = 255 +STACKPOS_TOP_MOVABLE_ITEM_OR_CREATURE = 255 THING_TYPE_PLAYER = CREATURETYPE_PLAYER + 1 THING_TYPE_MONSTER = CREATURETYPE_MONSTER + 1 @@ -1094,8 +1094,8 @@ function isCorpse(uid) return i ~= nil and ItemType(i:getId()):isCorpse() or false end -isItemMoveable = isItemMovable -isMoveable = isMovable +isItemMovable = isItemMovable +isMovable = isMovable function getItemName(itemId) return ItemType(itemId):getName() @@ -1380,7 +1380,7 @@ function getThingfromPos(pos) local thing local stackpos = pos.stackpos or 0 - if stackpos == STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE then + if stackpos == STACKPOS_TOP_MOVABLE_ITEM_OR_CREATURE then thing = tile:getTopCreature() if thing == nil then local item = tile:getTopDownItem() diff --git a/data-otservbr-global/lib/core/storages.lua b/data-otservbr-global/lib/core/storages.lua index 10e5e305f63..888da1b113f 100644 --- a/data-otservbr-global/lib/core/storages.lua +++ b/data-otservbr-global/lib/core/storages.lua @@ -8,7 +8,7 @@ Reserved player action storage key ranges (const.h) [2001 - 2011] Others reserved player action/storages - [100] = unmoveable/untrade/unusable items + [100] = unmovable/untrade/unusable items [101] = use pick floor [102] = well down action [103-120] = others keys action diff --git a/data-otservbr-global/lib/quests/svargrond_arena.lua b/data-otservbr-global/lib/quests/svargrond_arena.lua index a32fc0df6b6..660426cb64b 100644 --- a/data-otservbr-global/lib/quests/svargrond_arena.lua +++ b/data-otservbr-global/lib/quests/svargrond_arena.lua @@ -218,7 +218,7 @@ function SvargrondArena.resetPit(pitId) if movableItem and movableItem:isItem() then local itemType = ItemType(movableItem:getId()) if itemType and itemType:isMovable() and not table.contains(SvargrondArena.itemsNotErasable, movableItem:getId()) then - moveableItem:remove() + movableItem:remove() end end diff --git a/data-otservbr-global/npc/inigo.lua b/data-otservbr-global/npc/inigo.lua index e712582ea5e..6a16d1c8158 100644 --- a/data-otservbr-global/npc/inigo.lua +++ b/data-otservbr-global/npc/inigo.lua @@ -40,7 +40,7 @@ local hints = { [8] = "Always eat as much {food} as possible. \z This way, you'll regenerate health points for a longer period of time.", [9] = "After you have killed a monster, you have 10 seconds in which the corpse \z - is not moveable and no one else but you can loot it.", + is not movable and no one else but you can loot it.", [10] = "Be careful when you approach three or more {monsters} because you only can block the attacks of two! \z In such a situation, even a few salamanders can do severe damage or even kill you.", [11] = "There are many ways to gather {food}. Many creatures drop food but you can also pick blueberries or \z diff --git a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_corrupted.lua b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_corrupted.lua index 674b23a1476..74a93dbf5a9 100644 --- a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_corrupted.lua +++ b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_corrupted.lua @@ -378,7 +378,7 @@ local ferumbrasAscendantHabitatCorrupted = Action() function ferumbrasAscendantHabitatCorrupted.onUse(player, item, fromPosition, target, toPosition, isHotkey) if item.itemid == 9125 then if Game.getStorageValue(GlobalStorage.FerumbrasAscendant.Habitats.Corrupted) >= 1 then - player:say("The lever are stuck, need some time to it can be moveable again.", TALKTYPE_MONSTER_SAY) + player:say("The lever are stuck, need some time to it can be movable again.", TALKTYPE_MONSTER_SAY) return true end Game.createMonster("lovely yielothax", Position(33619, 32722, 12), true, true) diff --git a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_desert.lua b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_desert.lua index 9d0e73b65e9..e5b8e22f150 100644 --- a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_desert.lua +++ b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_desert.lua @@ -358,7 +358,7 @@ local ferumbrasAscendantHabitatDesert = Action() function ferumbrasAscendantHabitatDesert.onUse(player, item, fromPosition, target, toPosition, isHotkey) if item.itemid == 9125 then if Game.getStorageValue(GlobalStorage.FerumbrasAscendant.Habitats.Desert) >= 1 then - player:say("The lever are stuck, need some time to it can be moveable again.", TALKTYPE_MONSTER_SAY) + player:say("The lever are stuck, need some time to it can be movable again.", TALKTYPE_MONSTER_SAY) return true end Game.createMonster("lovely rotworm", Position(33641, 32684, 12), true, true) diff --git a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_dimension.lua b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_dimension.lua index 1b9bbadb1eb..1fc14c6646f 100644 --- a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_dimension.lua +++ b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_dimension.lua @@ -394,7 +394,7 @@ local ferumbrasAscendantHabitatDimension = Action() function ferumbrasAscendantHabitatDimension.onUse(player, item, fromPosition, target, toPosition, isHotkey) if item.itemid == 9125 then if Game.getStorageValue(GlobalStorage.FerumbrasAscendant.Habitats.Dimension) >= 1 then - player:say("The lever are stuck, need some time to it can be moveable again.", TALKTYPE_MONSTER_SAY) + player:say("The lever are stuck, need some time to it can be movable again.", TALKTYPE_MONSTER_SAY) return true end Game.createMonster("lovely souleater", Position(33642, 32722, 12), true, true) diff --git a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_grass.lua b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_grass.lua index 0c6c998e4e1..293e65844cc 100644 --- a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_grass.lua +++ b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_grass.lua @@ -426,7 +426,7 @@ local ferumbrasAscendantHabitatGlass = Action() function ferumbrasAscendantHabitatGlass.onUse(player, item, fromPosition, target, toPosition, isHotkey) if item.itemid == 9125 then if Game.getStorageValue(GlobalStorage.FerumbrasAscendant.Habitats.Grass) >= 1 then - player:say("The lever are stuck, need some time to it can be moveable again.", TALKTYPE_MONSTER_SAY) + player:say("The lever are stuck, need some time to it can be movable again.", TALKTYPE_MONSTER_SAY) return true end Game.createMonster("lovely frazzlemaw", Position(33642, 32666, 12), true, true) diff --git a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_ice.lua b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_ice.lua index 2dd56c8abd0..b46a984a00e 100644 --- a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_ice.lua +++ b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_ice.lua @@ -441,7 +441,7 @@ local ferumbrasAscendantHabitatIce = Action() function ferumbrasAscendantHabitatIce.onUse(player, item, fromPosition, target, toPosition, isHotkey) if item.itemid == 9125 then if Game.getStorageValue(GlobalStorage.FerumbrasAscendant.Habitats.Ice) >= 1 then - player:say("The lever are stuck, need some time to it can be moveable again.", TALKTYPE_MONSTER_SAY) + player:say("The lever are stuck, need some time to it can be movable again.", TALKTYPE_MONSTER_SAY) return true end Game.createMonster("lovely snake", Position(33642, 32702, 12), true, true) diff --git a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_mushroom.lua b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_mushroom.lua index 77e288f5e5b..52b0c9b7ac7 100644 --- a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_mushroom.lua +++ b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_mushroom.lua @@ -394,7 +394,7 @@ local ferumbrasAscendantHabitatMushroom = Action() function ferumbrasAscendantHabitatMushroom.onUse(player, item, fromPosition, target, toPosition, isHotkey) if item.itemid == 9125 then if Game.getStorageValue(GlobalStorage.FerumbrasAscendant.Habitats.Mushroom) >= 1 then - player:say("The lever are stuck, need some time to it can be moveable again.", TALKTYPE_MONSTER_SAY) + player:say("The lever are stuck, need some time to it can be movable again.", TALKTYPE_MONSTER_SAY) return true end Game.createMonster("lovely scorpion", Position(33617, 32684, 12), true, true) diff --git a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_roshamuul.lua b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_roshamuul.lua index 1718b1106b8..42da59f0e93 100644 --- a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_roshamuul.lua +++ b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_roshamuul.lua @@ -467,7 +467,7 @@ local ferumbrasAscendantHabitatRoshamuul = Action() function ferumbrasAscendantHabitatRoshamuul.onUse(player, item, fromPosition, target, toPosition, isHotkey) if item.itemid == 9125 then if Game.getStorageValue(GlobalStorage.FerumbrasAscendant.Habitats.Roshamuul) >= 1 then - player:say("The lever are stuck, need some time to it can be moveable again.", TALKTYPE_MONSTER_SAY) + player:say("The lever are stuck, need some time to it can be movable again.", TALKTYPE_MONSTER_SAY) return true end Game.createMonster("lovely deer", Position(33619, 32666, 12), true, true) diff --git a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_venom.lua b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_venom.lua index 2883193b394..2649da5694e 100644 --- a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_venom.lua +++ b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/habitat_venom.lua @@ -449,7 +449,7 @@ local ferumbrasAscendantHabitatVenom = Action() function ferumbrasAscendantHabitatVenom.onUse(player, item, fromPosition, target, toPosition, isHotkey) if item.itemid == 9125 then if Game.getStorageValue(GlobalStorage.FerumbrasAscendant.Habitats.Venom) >= 1 then - player:say("The lever are stuck, need some time to it can be moveable again.", TALKTYPE_MONSTER_SAY) + player:say("The lever are stuck, need some time to it can be movable again.", TALKTYPE_MONSTER_SAY) return true end Game.createMonster("lovely polar bear", Position(33617, 32702, 12), true, true) diff --git a/data-otservbr-global/scripts/globalevents/others/startup.lua b/data-otservbr-global/scripts/globalevents/others/startup.lua index 22af76e94c2..603e50a3ba1 100644 --- a/data-otservbr-global/scripts/globalevents/others/startup.lua +++ b/data-otservbr-global/scripts/globalevents/others/startup.lua @@ -28,8 +28,8 @@ function serverstartup.onStartup() loadLuaMapUnique(ItemUnique) -- Item daily reward table -- This is temporary disabled > loadLuaMapAction(DailyRewardAction) - -- Item unmoveable table - loadLuaMapAction(ItemUnmoveableAction) + -- Item unmovable table + loadLuaMapAction(ItemUnmovableAction) -- Lever table loadLuaMapAction(LeverAction) loadLuaMapUnique(LeverUnique) diff --git a/data-otservbr-global/scripts/spells/runes/chameleon.lua b/data-otservbr-global/scripts/spells/runes/chameleon.lua index 858757b1725..92168645e20 100644 --- a/data-otservbr-global/scripts/spells/runes/chameleon.lua +++ b/data-otservbr-global/scripts/spells/runes/chameleon.lua @@ -16,7 +16,7 @@ function rune.onCastSpell(creature, variant, isHotkey) item = Tile(position):getTopDownItem() end - if not item or item.itemid == 0 or not isMoveable(item.uid) then + if not item or item.itemid == 0 or not isMovable(item.uid) then creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) creature:getPosition():sendMagicEffect(CONST_ME_POFF) return false diff --git a/data-otservbr-global/startup/README.md b/data-otservbr-global/startup/README.md index cfc992c635d..e66c0c1b51a 100644 --- a/data-otservbr-global/startup/README.md +++ b/data-otservbr-global/startup/README.md @@ -39,77 +39,77 @@ Item (unique) = 40001/42000 This folder was created exclusively for tables and functions that are loaded at startup or that cannot be reloaded, thus maintaining greater organization in the files. Action IDS - Use actionID only if you need to create a function that is called multiple times in different locations. - The action is also used as storage, "x" storage is added in the player, - and the same action number gives access to a door, for example. +Use actionID only if you need to create a function that is called multiple times in different locations. +The action is also used as storage, "x" storage is added in the player, +and the same action number gives access to a door, for example. - Reserved player action storage key ranges (const.h at the source) - [10000000 - 20000000] - [1000 - 1500] - [2001 - 2011] + Reserved player action storage key ranges (const.h at the source) + [10000000 - 20000000] + [1000 - 1500] + [2001 - 2011] - Others reserved player action/storages - [100] = unmoveable/untrade/unusable items - [101] = use pick floor - [102] = down floor action - [103] = key 0010 - [103-120] = keys action - [104] = Parchment of the parchment room quest - [303] = key 0303 - [1000] = level door. Here 1 must be used followed by the level. Example: 1010 = level 10, 1100 = level 100] - [3001-3008] = key 3001/3008 - [3012] = key 3012 - [3033] = key 3033 - [3100] = key 3100 - [3142] = key 3142 - [3200] = key 3200 - [3301] = key 3301 - [3302] = key 3302 - [3303] = key 3303 - [3304] = key 3304 - [3350] = key 3350 - [3520] = key 3520 - [3600] = key 3600 - [3610] = key 3610 - [3620] = key 3620 - [3650] = key 3650 - [3666] = key 3666 - [3667] = key 3667 - [3700] = key 3700 - [3701/3703] = key 3701/3703 - [3800/3802] = key 3800/3802 - [3899] = key 3899 - [3900] = key 3900 - [3909/3917] = key 3909/3917 - [3923] = key 3923 - [3925] = key 3925 - [3930] = key 3930 - [3932] = key 3932 - [3934] = key 3934 - [3935] = key 3935 - [3936] = key 3936 - [3938] = key 3938 - [3940] = key 3940 - [3950] = key 3950 - [3960] = key 3960 - [3980] = key 3980 - [3988] = key 3988 - [4001] = key 4001 - [4009] = key 4009 - [4022] = key 4022 - [4023] = key 4023 - [4033] = key 4033 - [4037] = key 4037 - [4055] = key 4055 - [4210] = key 4210 - [4501] = key 4501 - [4502] = key 4502 - [4503] = key 4503 - [4600] = key 4600 - [4601] = key 4601 - [4603] = key 4603 - [5000] = key 5000 - [5002] = key 5002 - [5010] = key 5010 - [5050] = key 5050 - [6010] = key 6010 + Others reserved player action/storages + [100] = unmovable/untrade/unusable items + [101] = use pick floor + [102] = down floor action + [103] = key 0010 + [103-120] = keys action + [104] = Parchment of the parchment room quest + [303] = key 0303 + [1000] = level door. Here 1 must be used followed by the level. Example: 1010 = level 10, 1100 = level 100] + [3001-3008] = key 3001/3008 + [3012] = key 3012 + [3033] = key 3033 + [3100] = key 3100 + [3142] = key 3142 + [3200] = key 3200 + [3301] = key 3301 + [3302] = key 3302 + [3303] = key 3303 + [3304] = key 3304 + [3350] = key 3350 + [3520] = key 3520 + [3600] = key 3600 + [3610] = key 3610 + [3620] = key 3620 + [3650] = key 3650 + [3666] = key 3666 + [3667] = key 3667 + [3700] = key 3700 + [3701/3703] = key 3701/3703 + [3800/3802] = key 3800/3802 + [3899] = key 3899 + [3900] = key 3900 + [3909/3917] = key 3909/3917 + [3923] = key 3923 + [3925] = key 3925 + [3930] = key 3930 + [3932] = key 3932 + [3934] = key 3934 + [3935] = key 3935 + [3936] = key 3936 + [3938] = key 3938 + [3940] = key 3940 + [3950] = key 3950 + [3960] = key 3960 + [3980] = key 3980 + [3988] = key 3988 + [4001] = key 4001 + [4009] = key 4009 + [4022] = key 4022 + [4023] = key 4023 + [4033] = key 4033 + [4037] = key 4037 + [4055] = key 4055 + [4210] = key 4210 + [4501] = key 4501 + [4502] = key 4502 + [4503] = key 4503 + [4600] = key 4600 + [4601] = key 4601 + [4603] = key 4603 + [5000] = key 5000 + [5002] = key 5002 + [5010] = key 5010 + [5050] = key 5050 + [6010] = key 6010 diff --git a/data-otservbr-global/startup/tables/item_unmoveable.lua b/data-otservbr-global/startup/tables/item_unmovable.lua similarity index 89% rename from data-otservbr-global/startup/tables/item_unmoveable.lua rename to data-otservbr-global/startup/tables/item_unmovable.lua index ed7128ad6ad..cd7aa195742 100644 --- a/data-otservbr-global/startup/tables/item_unmoveable.lua +++ b/data-otservbr-global/startup/tables/item_unmovable.lua @@ -1,6 +1,6 @@ -- Look README.md for see the reserved action/unique numbers -ItemUnmoveableAction = { +ItemUnmovableAction = { -- Unmovable action, add new position and it create in-game [100] = { itemId = false, diff --git a/data-otservbr-global/startup/tables/load.lua b/data-otservbr-global/startup/tables/load.lua index fe3b5bbde5e..901dd2fe940 100644 --- a/data-otservbr-global/startup/tables/load.lua +++ b/data-otservbr-global/startup/tables/load.lua @@ -6,7 +6,7 @@ dofile(DATA_DIRECTORY .. "/startup/tables/door_level.lua") dofile(DATA_DIRECTORY .. "/startup/tables/door_quest.lua") dofile(DATA_DIRECTORY .. "/startup/tables/item.lua") dofile(DATA_DIRECTORY .. "/startup/tables/item_daily_reward.lua") -dofile(DATA_DIRECTORY .. "/startup/tables/item_unmoveable.lua") +dofile(DATA_DIRECTORY .. "/startup/tables/item_unmovable.lua") dofile(DATA_DIRECTORY .. "/startup/tables/lever.lua") dofile(DATA_DIRECTORY .. "/startup/tables/teleport.lua") dofile(DATA_DIRECTORY .. "/startup/tables/teleport_item.lua") diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua index 0a719401ed0..c134e784993 100644 --- a/data/events/scripts/player.lua +++ b/data/events/scripts/player.lua @@ -1,6 +1,6 @@ local storeItemID = { -- registered item ids here are not tradable with players - -- these items can be set to moveable at items.xml + -- these items can be set to movable at items.xml -- 500 charges exercise weapons 28552, -- exercise sword 28553, -- exercise axe diff --git a/data/items/items.xml b/data/items/items.xml index bce4281b538..abbe27c060c 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -11607,7 +11607,7 @@ - + @@ -35642,7 +35642,7 @@ - + @@ -35742,7 +35742,7 @@ - + @@ -42625,7 +42625,7 @@ - + @@ -50372,7 +50372,7 @@ - + diff --git a/data/libs/core/global_storage.lua b/data/libs/core/global_storage.lua index 4119be5ebb6..63d17166332 100644 --- a/data/libs/core/global_storage.lua +++ b/data/libs/core/global_storage.lua @@ -8,7 +8,7 @@ Reserved player action storage key ranges (const.hpp) [2001 - 2011] Others reserved player action/storages - [100] = unmoveable/untrade/unusable items + [100] = unmovable/untrade/unusable items [101] = use pick floor [102] = well down action [103-120] = others keys action diff --git a/data/libs/functions/player.lua b/data/libs/functions/player.lua index d494f35edd3..8bb420a5669 100644 --- a/data/libs/functions/player.lua +++ b/data/libs/functions/player.lua @@ -555,9 +555,9 @@ function Player.updateHazard(self) return true end -function Player:addItemStoreInboxEx(item, moveable, setOwner) +function Player:addItemStoreInboxEx(item, movable, setOwner) local inbox = self:getSlotItem(CONST_SLOT_STORE_INBOX) - if not moveable then + if not movable then item:setOwner(self) item:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime()) elseif setOwner then @@ -567,14 +567,14 @@ function Player:addItemStoreInboxEx(item, moveable, setOwner) return item end -function Player:addItemStoreInbox(itemId, amount, moveable, setOwner) +function Player:addItemStoreInbox(itemId, amount, movable, setOwner) local iType = ItemType(itemId) if not iType then return nil end if iType:isStackable() then while amount > iType:getStackSize() do - self:addItemStoreInboxEx(Game.createItem(itemId, iType:getStackSize()), moveable, setOwner) + self:addItemStoreInboxEx(Game.createItem(itemId, iType:getStackSize()), movable, setOwner) amount = amount - iType:getStackSize() end end @@ -582,7 +582,7 @@ function Player:addItemStoreInbox(itemId, amount, moveable, setOwner) if not item then return nil end - return self:addItemStoreInboxEx(item, moveable, setOwner) + return self:addItemStoreInboxEx(item, movable, setOwner) end ---@param monster Monster diff --git a/data/modules/scripts/gamestore/init.lua b/data/modules/scripts/gamestore/init.lua index d33485c7091..f3959c3a1e9 100644 --- a/data/modules/scripts/gamestore/init.lua +++ b/data/modules/scripts/gamestore/init.lua @@ -443,9 +443,9 @@ function parseBuyStoreOffer(playerId, msg) -- Handled errors have a code index and unhandled errors do not local pcallOk, pcallError = pcall(function() if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM then - GameStore.processItemPurchase(player, offer.itemtype, offer.count, offer.moveable, offer.setOwner) + GameStore.processItemPurchase(player, offer.itemtype, offer.count, offer.movable, offer.setOwner) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM_UNIQUE then - GameStore.processItemPurchase(player, offer.itemtype, offer.count, offer.moveable, offer.setOwner) + GameStore.processItemPurchase(player, offer.itemtype, offer.count, offer.movable, offer.setOwner) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_POUCH then GameStore.processItemPurchase(player, offer.itemtype, offer.count) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_INSTANT_REWARD_ACCESS then @@ -459,7 +459,7 @@ function parseBuyStoreOffer(playerId, msg) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREMIUM then GameStore.processPremiumPurchase(player, offer.id) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_STACKABLE then - GameStore.processStackablePurchase(player, offer.itemtype, offer.count, offer.name, offer.moveable, offer.setOwner) + GameStore.processStackablePurchase(player, offer.itemtype, offer.count, offer.name, offer.movable, offer.setOwner) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HOUSE then GameStore.processHouseRelatedPurchase(player, offer) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT then @@ -482,7 +482,7 @@ function parseBuyStoreOffer(playerId, msg) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_TEMPLE then GameStore.processTempleTeleportPurchase(player) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_CHARGES then - GameStore.processChargesPurchase(player, offer.itemtype, offer.name, offer.charges, offer.moveable, offer.setOwner) + GameStore.processChargesPurchase(player, offer.itemtype, offer.name, offer.charges, offer.movable, offer.setOwner) elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HIRELING then local hirelingName = msg:getString() local sex = msg:getByte() @@ -1507,21 +1507,21 @@ end -- take a table {code = ..., message = ...} if the error is handled. When no code -- index is present the error is assumed to be unhandled. -function GameStore.processItemPurchase(player, offerId, offerCount, moveable, setOwner) +function GameStore.processItemPurchase(player, offerId, offerCount, movable, setOwner) if player:getFreeCapacity() < ItemType(offerId):getWeight(offerCount) then return error({ code = 0, message = "Please make sure you have free capacity to hold this item." }) end for t = 1, offerCount do - player:addItemStoreInbox(offerId, offerCount or 1, moveable, setOwner) + player:addItemStoreInbox(offerId, offerCount or 1, movable, setOwner) end end -function GameStore.processChargesPurchase(player, itemtype, name, charges, moveable, setOwner) +function GameStore.processChargesPurchase(player, itemtype, name, charges, movable, setOwner) if player:getFreeCapacity() < ItemType(itemtype):getWeight(1) then return error({ code = 0, message = "Please make sure you have free capacity to hold this item." }) end - player:addItemStoreInbox(itemtype, charges, moveable, setOwner) + player:addItemStoreInbox(itemtype, charges, movable, setOwner) end function GameStore.processSingleBlessingPurchase(player, blessId, count) @@ -1557,7 +1557,7 @@ function GameStore.processPremiumPurchase(player, offerId) end end -function GameStore.processStackablePurchase(player, offerId, offerCount, offerName, moveable, setOwner) +function GameStore.processStackablePurchase(player, offerId, offerCount, offerName, movable, setOwner) local function isKegItem(itemId) return itemId >= ITEM_KEG_START and itemId <= ITEM_KEG_END end diff --git a/data/npclib/npc_system/custom_modules.lua b/data/npclib/npc_system/custom_modules.lua index 10e16b956bd..f1438d39d64 100644 --- a/data/npclib/npc_system/custom_modules.lua +++ b/data/npclib/npc_system/custom_modules.lua @@ -137,7 +137,7 @@ local hints = { health points anymore, eat something.", [6] = "Always eat as much food as possible. This way, you'll regenerate health points for a longer period of time.", [7] = "After you have killed a monster, you have 10 seconds in which the corpse \z - is not moveable and no one else but you can loot it.", + is not movable and no one else but you can loot it.", [8] = "Be careful when you approach three or more monsters because you only can block the attacks of two. \z In such a situation even a few rats can do severe damage or even kill you.", [9] = "There are many ways to gather food. Many creatures drop food but you can also pick blueberries or bake \z diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 35365a609dc..1e08ec3c024 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -552,11 +552,9 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL do { int32_t factionOffset = static_cast((*it)->getFaction()) * 100000; const auto dmg = damageMap.find((*it)->getID()); - if (dmg != damageMap.end()) { - if (dmg->second.total + factionOffset > mostDamage) { - mostDamage = dmg->second.total; - getTarget = *it; - } + if (dmg != damageMap.end() && dmg->second.total + factionOffset > mostDamage) { + mostDamage = dmg->second.total; + getTarget = *it; } } while (++it != resultList.end()); } @@ -1088,7 +1086,7 @@ void Monster::pushItems(std::shared_ptr tile, const Direction &nextDirecti auto it = items->begin(); while (it != items->end()) { std::shared_ptr item = *it; - if (item && item->hasProperty(CONST_PROP_MOVEABLE) && (item->hasProperty(CONST_PROP_BLOCKPATH) || item->hasProperty(CONST_PROP_BLOCKSOLID)) && item->canBeMoved()) { + if (item && item->hasProperty(CONST_PROP_MOVABLE) && (item->hasProperty(CONST_PROP_BLOCKPATH) || item->hasProperty(CONST_PROP_BLOCKSOLID)) && item->canBeMoved()) { if (moveCount < 20 && pushItem(item, nextDirection)) { ++moveCount; } else if (!item->isCorpse() && g_game().internalRemoveItem(item) == RETURNVALUE_NOERROR) { diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 867b32dbefb..7283f82251d 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -159,21 +159,21 @@ std::string Player::getDescription(int32_t lookDistance) { } } - if (party) { + if (m_party) { if (lookDistance == -1) { s << " Your party has "; } else { s << " " << subjectPronoun << " " << getSubjectVerb() << " in a party with "; } - size_t memberCount = party->getMemberCount() + 1; + size_t memberCount = m_party->getMemberCount() + 1; if (memberCount == 1) { s << "1 member and "; } else { s << memberCount << " members and "; } - size_t invitationCount = party->getInvitationCount(); + size_t invitationCount = m_party->getInvitationCount(); if (invitationCount == 1) { s << "1 pending invitation."; } else { @@ -844,7 +844,7 @@ uint16_t Player::getContainerIndex(uint8_t cid) const { } bool Player::canOpenCorpse(uint32_t ownerId) const { - return getID() == ownerId || (party && party->canOpenCorpse(ownerId)); + return getID() == ownerId || (m_party && m_party->canOpenCorpse(ownerId)); } uint16_t Player::getLookCorpse() const { @@ -1129,8 +1129,8 @@ void Player::sendLootStats(std::shared_ptr item, uint8_t count) { client->sendLootStats(item, count); } - if (party) { - party->addPlayerLoot(getPlayer(), item); + if (m_party) { + m_party->addPlayerLoot(getPlayer(), item); } } @@ -1282,8 +1282,8 @@ void Player::updateSupplyTracker(std::shared_ptr item) { client->sendUpdateSupplyTracker(item); } - if (party) { - party->addPlayerSupply(getPlayer(), item); + if (m_party) { + m_party->addPlayerSupply(getPlayer(), item); } } @@ -1776,8 +1776,8 @@ void Player::onRemoveCreature(std::shared_ptr creature, bool isLogout) if (auto player = getPlayer(); player == creature) { if (isLogout) { - if (party) { - party->leaveParty(player); + if (m_party) { + m_party->leaveParty(player); } if (guild) { guild->removeMember(player); @@ -1901,9 +1901,9 @@ void Player::onCreatureMove(const std::shared_ptr &creature, const std inMarket = false; } - if (party) { - party->updateSharedExperience(); - party->updatePlayerStatus(getPlayer(), oldPos, newPos); + if (m_party) { + m_party->updateSharedExperience(); + m_party->updatePlayerStatus(getPlayer(), oldPos, newPos); } if (teleport || oldPos.z != newPos.z) { @@ -2355,8 +2355,8 @@ void Player::addExperience(std::shared_ptr target, uint64_t exp, bool g_game().addCreatureHealth(static_self_cast()); g_game().addPlayerMana(static_self_cast()); - if (party) { - party->updateSharedExperience(); + if (m_party) { + m_party->updateSharedExperience(); } g_creatureEvents().playerAdvance(static_self_cast(), SKILL_LEVEL, prevLevel, level); @@ -2441,8 +2441,8 @@ void Player::removeExperience(uint64_t exp, bool sendText /* = false*/) { g_game().addCreatureHealth(static_self_cast()); g_game().addPlayerMana(static_self_cast()); - if (party) { - party->updateSharedExperience(); + if (m_party) { + m_party->updateSharedExperience(); } std::ostringstream ss; @@ -2483,6 +2483,7 @@ void Player::onBlockHit() { } void Player::onTakeDamage(std::shared_ptr attacker, int32_t damage) { + // nothing here yet } void Player::onAttackedCreatureBlockHit(BlockType_t blockType) { @@ -3378,8 +3379,8 @@ ReturnValue Player::queryRemove(const std::shared_ptr &thing, uint32_t co return RETURNVALUE_NOTPOSSIBLE; } - if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) { - return RETURNVALUE_NOTMOVEABLE; + if (!item->isMovable() && !hasBitSet(FLAG_IGNORENOTMOVABLE, flags)) { + return RETURNVALUE_NOTMOVABLE; } return RETURNVALUE_NOERROR; @@ -4581,8 +4582,8 @@ void Player::onAttacked() { void Player::onIdleStatus() { Creature::onIdleStatus(); - if (party) { - party->clearPlayerPoints(static_self_cast()); + if (m_party) { + m_party->clearPlayerPoints(static_self_cast()); } } @@ -4599,18 +4600,18 @@ void Player::onAttackedCreatureDrainHealth(std::shared_ptr target, int Creature::onAttackedCreatureDrainHealth(target, points); if (target) { - if (party && !Combat::isPlayerCombat(target)) { + if (m_party && !Combat::isPlayerCombat(target)) { auto tmpMonster = target->getMonster(); if (tmpMonster && tmpMonster->isHostile()) { // We have fulfilled a requirement for shared experience - party->updatePlayerTicks(static_self_cast(), points); + m_party->updatePlayerTicks(static_self_cast(), points); } } } } void Player::onTargetCreatureGainHealth(std::shared_ptr target, int32_t points) { - if (target && party) { + if (target && m_party) { std::shared_ptr tmpPlayer = nullptr; if (isPartner(tmpPlayer) && (tmpPlayer != getPlayer())) { @@ -4622,7 +4623,7 @@ void Player::onTargetCreatureGainHealth(std::shared_ptr target, int32_ } if (isPartner(tmpPlayer)) { - party->updatePlayerTicks(static_self_cast(), points); + m_party->updatePlayerTicks(static_self_cast(), points); } } } @@ -4723,8 +4724,8 @@ void Player::onGainExperience(uint64_t gainExp, std::shared_ptr target return; } - if (target && !target->getPlayer() && party && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) { - party->shareExperience(gainExp, target); + if (target && !target->getPlayer() && m_party && m_party->isSharedExperienceActive() && m_party->isSharedExperienceEnabled()) { + m_party->shareExperience(gainExp, target); // We will get a share of the experience through the sharing mechanism return; } @@ -5024,7 +5025,7 @@ Skulls_t Player::getSkullClient(std::shared_ptr creature) { return SKULL_YELLOW; } - if (party && party == player->party) { + if (m_party && m_party == player->m_party) { return SKULL_GREEN; } } @@ -5475,14 +5476,14 @@ PartyShields_t Player::getPartyShield(std::shared_ptr player) { return SHIELD_NONE; } - if (party) { - if (party->getLeader() == player) { - if (party->isSharedExperienceActive()) { - if (party->isSharedExperienceEnabled()) { + if (m_party) { + if (m_party->getLeader() == player) { + if (m_party->isSharedExperienceActive()) { + if (m_party->isSharedExperienceEnabled()) { return SHIELD_YELLOW_SHAREDEXP; } - if (party->canUseSharedExperience(player)) { + if (m_party->canUseSharedExperience(player)) { return SHIELD_YELLOW_NOSHAREDEXP; } @@ -5492,13 +5493,13 @@ PartyShields_t Player::getPartyShield(std::shared_ptr player) { return SHIELD_YELLOW; } - if (player->party == party) { - if (party->isSharedExperienceActive()) { - if (party->isSharedExperienceEnabled()) { + if (player->m_party == m_party) { + if (m_party->isSharedExperienceActive()) { + if (m_party->isSharedExperienceEnabled()) { return SHIELD_BLUE_SHAREDEXP; } - if (party->canUseSharedExperience(player)) { + if (m_party->canUseSharedExperience(player)) { return SHIELD_BLUE_NOSHAREDEXP; } @@ -5517,7 +5518,7 @@ PartyShields_t Player::getPartyShield(std::shared_ptr player) { return SHIELD_WHITEYELLOW; } - if (player->party) { + if (player->m_party) { return SHIELD_GRAY; } @@ -5525,17 +5526,17 @@ PartyShields_t Player::getPartyShield(std::shared_ptr player) { } bool Player::isInviting(std::shared_ptr player) const { - if (!player || !party || party->getLeader().get() != this) { + if (!player || !m_party || m_party->getLeader().get() != this) { return false; } - return party->isPlayerInvited(player); + return m_party->isPlayerInvited(player); } bool Player::isPartner(std::shared_ptr player) const { - if (!player || !party || player.get() == this) { + if (!player || !m_party || player.get() == this) { return false; } - return party == player->party; + return m_party == player->m_party; } bool Player::isGuildMate(std::shared_ptr player) const { @@ -5992,15 +5993,15 @@ void Player::clearModalWindows() { } uint16_t Player::getHelpers() const { - if (guild && party) { + if (guild && m_party) { const auto &guildMembers = guild->getMembersOnline(); stdext::vector_set> helperSet; helperSet.insert(helperSet.end(), guildMembers.begin(), guildMembers.end()); - helperSet.insertAll(party->getMembers()); - helperSet.insertAll(party->getInvitees()); + helperSet.insertAll(m_party->getMembers()); + helperSet.insertAll(m_party->getInvitees()); - helperSet.emplace(party->getLeader()); + helperSet.emplace(m_party->getLeader()); return static_cast(helperSet.size()); } @@ -6009,8 +6010,8 @@ uint16_t Player::getHelpers() const { return static_cast(guild->getMemberCountOnline()); } - if (party) { - return static_cast(party->getMemberCount() + party->getInvitationCount() + 1); + if (m_party) { + return static_cast(m_party->getMemberCount() + m_party->getInvitationCount() + 1); } return 0u; @@ -7644,15 +7645,15 @@ void Player::parseAttackRecvHazardSystem(CombatDamage &damage, std::shared_ptrgetMembers()) { + if (m_party) { + for (const auto partyMember : m_party->getMembers()) { if (partyMember && partyMember->getHazardSystemPoints() < points) { points = partyMember->getHazardSystemPoints(); } } - if (party->getLeader() && party->getLeader()->getHazardSystemPoints() < points) { - points = party->getLeader()->getHazardSystemPoints(); + if (m_party->getLeader() && m_party->getLeader()->getHazardSystemPoints() < points) { + points = m_party->getLeader()->getHazardSystemPoints(); } } @@ -7703,15 +7704,15 @@ void Player::parseAttackDealtHazardSystem(CombatDamage &damage, std::shared_ptr< } auto points = getHazardSystemPoints(); - if (party) { - for (const auto partyMember : party->getMembers()) { + if (m_party) { + for (const auto partyMember : m_party->getMembers()) { if (partyMember && partyMember->getHazardSystemPoints() < points) { points = partyMember->getHazardSystemPoints(); } } - if (party->getLeader() && party->getLeader()->getHazardSystemPoints() < points) { - points = party->getLeader()->getHazardSystemPoints(); + if (m_party->getLeader() && m_party->getLeader()->getHazardSystemPoints() < points) { + points = m_party->getLeader()->getHazardSystemPoints(); } } @@ -7733,7 +7734,7 @@ void Player::parseAttackDealtHazardSystem(CombatDamage &damage, std::shared_ptr< if (monster->getHazardSystemDefenseBoost()) { stage = points * static_cast(g_configManager().getNumber(HAZARD_DEFENSE_MULTIPLIER, __FUNCTION__)); if (stage != 0) { - damage.exString = "(hazard -" + std::to_string(stage / 100) + "%)"; + damage.exString = fmt::format("(hazard -{}%)", stage / 100.); damage.primary.value -= static_cast(std::ceil((static_cast(damage.primary.value) * stage) / 10000)); if (damage.secondary.value != 0) { damage.secondary.value -= static_cast(std::ceil((static_cast(damage.secondary.value) * stage) / 10000)); diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index d267f118049..9d7a555b050 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -362,10 +362,10 @@ class Player final : public Creature, public Cylinder, public Bankable { } void setParty(std::shared_ptr newParty) { - this->party = newParty; + m_party = newParty; } std::shared_ptr getParty() const { - return party; + return m_party; } int32_t getCleavePercent(bool useCharges = false) const; @@ -1821,8 +1821,8 @@ class Player final : public Creature, public Cylinder, public Bankable { } void updatePartyTrackerAnalyzer() const { - if (client && party) { - client->updatePartyTrackerAnalyzer(party); + if (client && m_party) { + client->updatePartyTrackerAnalyzer(m_party); } } @@ -2716,7 +2716,7 @@ class Player final : public Creature, public Cylinder, public Bankable { std::shared_ptr writeItem = nullptr; std::shared_ptr editHouse = nullptr; std::shared_ptr shopOwner = nullptr; - std::shared_ptr party = nullptr; + std::shared_ptr m_party = nullptr; std::shared_ptr tradePartner = nullptr; ProtocolGame_ptr client; std::shared_ptr walkTask; diff --git a/src/game/game.cpp b/src/game/game.cpp index 9d8d89315ce..1cca6b5f388 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -503,7 +503,7 @@ std::shared_ptr Game::internalGetThing(std::shared_ptr player, co case STACKPOS_MOVE: { std::shared_ptr item = tile->getTopDownItem(); - if (item && item->isMoveable()) { + if (item && item->isMovable()) { thing = item; } else { thing = tile->getTopVisibleCreature(player); @@ -1185,7 +1185,7 @@ void Game::playerMoveCreature(std::shared_ptr player, std::shared_ptrisPushable() && !player->hasFlag(PlayerFlags_t::CanPushAllCreatures)) || (movingCreature->isInGhostMode() && !player->isAccessPlayer()))) { - player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE); + player->sendCancelMessage(RETURNVALUE_NOTMOVABLE); return; } @@ -1447,7 +1447,7 @@ void Game::playerMoveItem(std::shared_ptr player, const Position &fromPo } if (!item->isPushable() || item->hasAttribute(ItemAttribute_t::UNIQUEID)) { - player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE); + player->sendCancelMessage(RETURNVALUE_NOTMOVABLE); return; } @@ -2055,7 +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_IGNORENOTMOVEABLE); + ReturnValue ret = cylinder->queryRemove(item, count, flags | FLAG_IGNORENOTMOVABLE); if (!force && ret != RETURNVALUE_NOERROR) { g_logger().debug("{} - Failed to execute query remove", __FUNCTION__); return ret; @@ -4425,7 +4425,7 @@ void Game::playerRequestTrade(uint32_t playerId, const Position &pos, uint8_t st if (std::shared_ptr houseTile = std::dynamic_pointer_cast(tradeItem->getTile())) { const auto &house = houseTile->getHouse(); if (house && tradeItem->getRealParent() != player && (!house->isInvited(player) || house->getHouseAccessLevel(player) == HOUSE_GUEST)) { - player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE); + player->sendCancelMessage(RETURNVALUE_NOTMOVABLE); return; } } diff --git a/src/io/iomap.cpp b/src/io/iomap.cpp index f11e33204ce..457f3eed6bf 100644 --- a/src/io/iomap.cpp +++ b/src/io/iomap.cpp @@ -173,9 +173,9 @@ void IOMap::parseTileArea(FileStream &stream, Map &map, const Position &pos) { const auto item = std::make_shared(); item->id = id; - if (tile->isHouse() && iType.moveable) { + if (tile->isHouse() && iType.movable) { g_logger().warn("[IOMap::loadMap] - " - "Moveable item with ID: {}, in house: {}, " + "Movable item with ID: {}, in house: {}, " "at position: x {}, y {}, z {}", id, tile->houseId, x, y, z); } else if (iType.isGroundTile()) { @@ -207,9 +207,9 @@ void IOMap::parseTileArea(FileStream &stream, Map &map, const Position &pos) { if (tile->isHouse() && iType.isBed()) { // nothing - } else if (tile->isHouse() && iType.moveable) { + } else if (tile->isHouse() && iType.movable) { g_logger().warn("[IOMap::loadMap] - " - "Moveable item with ID: {}, in house: {}, " + "Movable item with ID: {}, in house: {}, " "at position: x {}, y {}, z {}", id, tile->houseId, x, y, z); } else if (iType.isGroundTile()) { diff --git a/src/io/iomapserialize.cpp b/src/io/iomapserialize.cpp index 7f351d2b617..ad1a59e67f6 100644 --- a/src/io/iomapserialize.cpp +++ b/src/io/iomapserialize.cpp @@ -138,14 +138,14 @@ bool IOMapSerialize::loadItem(PropStream &propStream, std::shared_ptr } const ItemType &iType = Item::items[id]; - if (iType.isBed() || iType.moveable || !tile || iType.isCarpet()) { + if (iType.isBed() || iType.movable || !tile || iType.isCarpet()) { // create a new item auto item = Item::CreateItem(id); if (item) { if (item->unserializeAttr(propStream)) { - // Remove only not moveable and not sleeper bed + // Remove only not movable and not sleeper bed auto bed = item->getBed(); - if (isHouseItem && iType.isBed() && bed && bed->getSleeper() == 0 && !iType.moveable) { + if (isHouseItem && iType.isBed() && bed && bed->getSleeper() == 0 && !iType.movable) { return false; } std::shared_ptr container = item->getContainer(); diff --git a/src/items/bed.cpp b/src/items/bed.cpp index 1b1a3a7266a..a1af00b2334 100644 --- a/src/items/bed.cpp +++ b/src/items/bed.cpp @@ -99,7 +99,7 @@ bool BedItem::canUse(std::shared_ptr player) { auto firstPart = keepFirstWordOnly(partName); auto nextPartOf = keepFirstWordOnly(nextPartname); g_logger().debug("First bed part name {}, second part name {}", firstPart, nextPartOf); - if (!isMoveable() || !nextBedItem->isMoveable() || firstPart != nextPartOf) { + if (!isMovable() || !nextBedItem->isMovable() || firstPart != nextPartOf) { return false; } diff --git a/src/items/containers/container.cpp b/src/items/containers/container.cpp index f5ad4cfab60..d4d55ba6cf1 100644 --- a/src/items/containers/container.cpp +++ b/src/items/containers/container.cpp @@ -44,7 +44,7 @@ std::shared_ptr Container::create(std::shared_ptr tile) { TileItemVector* itemVector = tile->getItemList(); if (itemVector) { for (auto &item : *itemVector) { - if (((item->getContainer() || item->hasProperty(CONST_PROP_MOVEABLE)) || (item->isWrapable() && !item->hasProperty(CONST_PROP_MOVEABLE) && !item->hasProperty(CONST_PROP_BLOCKPATH))) && !item->hasAttribute(ItemAttribute_t::UNIQUEID)) { + if (((item->getContainer() || item->hasProperty(CONST_PROP_MOVABLE)) || (item->isWrapable() && !item->hasProperty(CONST_PROP_MOVABLE) && !item->hasProperty(CONST_PROP_BLOCKPATH))) && !item->hasAttribute(ItemAttribute_t::UNIQUEID)) { container->itemlist.push_front(item); item->setParent(container); } @@ -583,9 +583,9 @@ ReturnValue Container::queryRemove(const std::shared_ptr &thing, uint32_t return RETURNVALUE_NOTPOSSIBLE; } - if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) { - g_logger().debug("{} - Item is not moveable", __FUNCTION__); - return RETURNVALUE_NOTMOVEABLE; + if (!item->isMovable() && !hasBitSet(FLAG_IGNORENOTMOVABLE, flags)) { + g_logger().debug("{} - Item is not movable", __FUNCTION__); + return RETURNVALUE_NOTMOVABLE; } std::shared_ptr houseTile = std::dynamic_pointer_cast(getTopParent()); if (houseTile) { diff --git a/src/items/functions/item/item_parse.cpp b/src/items/functions/item/item_parse.cpp index 415aa5acb99..db0c73bdc36 100644 --- a/src/items/functions/item/item_parse.cpp +++ b/src/items/functions/item/item_parse.cpp @@ -26,7 +26,7 @@ void ItemParse::initParse(const std::string &tmpStrValue, pugi::xml_node attribu ItemParse::parseRotateTo(tmpStrValue, valueAttribute, itemType); ItemParse::parseWrapContainer(tmpStrValue, valueAttribute, itemType); ItemParse::parseWrapableTo(tmpStrValue, valueAttribute, itemType); - ItemParse::parseMoveable(tmpStrValue, valueAttribute, itemType); + ItemParse::parseMovable(tmpStrValue, valueAttribute, itemType); ItemParse::parseBlockProjectTile(tmpStrValue, valueAttribute, itemType); ItemParse::parsePickupable(tmpStrValue, valueAttribute, itemType); ItemParse::parseFloorChange(tmpStrValue, valueAttribute, itemType); @@ -196,10 +196,10 @@ void ItemParse::parseWrapableTo(const std::string &tmpStrValue, pugi::xml_attrib } } -void ItemParse::parseMoveable(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType) { +void ItemParse::parseMovable(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType) { std::string stringValue = tmpStrValue; - if (stringValue == "moveable") { - itemType.moveable = valueAttribute.as_bool(); + if (stringValue == "movable") { + itemType.movable = valueAttribute.as_bool(); } } diff --git a/src/items/functions/item/item_parse.hpp b/src/items/functions/item/item_parse.hpp index 2ebffc94266..f60277ad5d7 100644 --- a/src/items/functions/item/item_parse.hpp +++ b/src/items/functions/item/item_parse.hpp @@ -30,8 +30,8 @@ const phmap::flat_hash_map ItemParseAttribut { "wrapcontainer", ITEM_PARSE_WRAPCONTAINER }, { "wrapableto", ITEM_PARSE_WRAPABLETO }, { "unwrapableto", ITEM_PARSE_WRAPABLETO }, - { "moveable", ITEM_PARSE_MOVEABLE }, - { "movable", ITEM_PARSE_MOVEABLE }, + { "movable", ITEM_PARSE_MOVABLE }, + { "movable", ITEM_PARSE_MOVABLE }, { "blockprojectile", ITEM_PARSE_BLOCKPROJECTILE }, { "allowpickupable", ITEM_PARSE_PICKUPABLE }, { "pickupable", ITEM_PARSE_PICKUPABLE }, @@ -264,7 +264,7 @@ class ItemParse : public Items { static void parseRotateTo(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); static void parseWrapContainer(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); static void parseWrapableTo(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); - static void parseMoveable(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); + static void parseMovable(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); static void parseBlockProjectTile(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); static void parsePickupable(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); static void parseFloorChange(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); diff --git a/src/items/item.cpp b/src/items/item.cpp index 87a3abd7d63..2415c2a8d27 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -131,7 +131,7 @@ std::shared_ptr Item::CreateItemAsContainer(const uint16_t type, uint it.id == 0 || it.stackable || it.multiUse - || it.moveable + || it.movable || it.pickupable || it.isDepot() || it.isSplash() @@ -313,7 +313,7 @@ void Item::setID(uint16_t newid) { } } -bool Item::isOwner(uint32_t ownerId) { +bool Item::isOwner(uint32_t ownerId) const { if (getOwnerId() == ownerId) { return true; } @@ -850,7 +850,7 @@ void Item::serializeAttr(PropWriteStream &propWriteStream) const { propWriteStream.write(charges); } - if (it.moveable) { + if (it.movable) { if (auto actionId = getAttribute(ItemAttribute_t::ACTIONID)) { propWriteStream.write(ATTR_ACTION_ID); propWriteStream.write(actionId); @@ -996,25 +996,25 @@ void Item::serializeAttr(PropWriteStream &propWriteStream) const { } void Item::setOwner(std::shared_ptr owner) { - auto id = owner->getID(); + auto ownerId = owner->getID(); if (owner->getPlayer()) { - id = owner->getPlayer()->getGUID(); + ownerId = owner->getPlayer()->getGUID(); } - setOwner(id); + setOwner(ownerId); } -bool Item::isOwner(std::shared_ptr owner) { +bool Item::isOwner(std::shared_ptr owner) const { if (!owner) { return false; } - auto id = owner->getID(); - if (isOwner(id)) { + auto ownerId = owner->getID(); + if (isOwner(ownerId)) { return true; } if (owner->getPlayer()) { - id = owner->getPlayer()->getGUID(); + ownerId = owner->getPlayer()->getGUID(); } - return isOwner(id); + return isOwner(ownerId); } uint32_t Item::getOwnerId() const { @@ -1024,7 +1024,7 @@ uint32_t Item::getOwnerId() const { return 0; } -std::string Item::getOwnerName() { +std::string Item::getOwnerName() const { if (!hasOwner()) { return ""; } @@ -1044,7 +1044,7 @@ bool Item::hasProperty(ItemProperty prop) const { switch (prop) { case CONST_PROP_BLOCKSOLID: return it.blockSolid; - case CONST_PROP_MOVEABLE: + case CONST_PROP_MOVABLE: return canBeMoved(); case CONST_PROP_HASHEIGHT: return it.hasHeight; @@ -1081,7 +1081,7 @@ bool Item::canBeMoved() const { if (hasAttribute(ItemAttribute_t::ACTIONID) && immovableActionIds.contains(static_cast(getAttribute(ItemAttribute_t::ACTIONID)))) { return false; } - return isMoveable(); + return isMovable(); } void Item::checkDecayMapItemOnMove() { @@ -1904,7 +1904,7 @@ std::string Item::parseImbuementDescription(std::shared_ptr item) { bool Item::isSavedToHouses() { const auto &it = items[id]; - return it.moveable || it.isWrappable() || it.isCarpet() || getDoor() || (getContainer() && !getContainer()->empty()) || it.canWriteText || getBed() || it.m_transformOnUse; + return it.movable || it.isWrappable() || it.isCarpet() || getDoor() || (getContainer() && !getContainer()->empty()) || it.canWriteText || getBed() || it.m_transformOnUse; } SoundEffect_t Item::getMovementSound(std::shared_ptr toCylinder) const { diff --git a/src/items/item.hpp b/src/items/item.hpp index 956adad61cf..90f3c3d83ef 100644 --- a/src/items/item.hpp +++ b/src/items/item.hpp @@ -277,11 +277,11 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { virtual uint32_t getOwnerId() const; - bool isOwner(uint32_t ownerId); + bool isOwner(uint32_t ownerId) const; - std::string getOwnerName(); + std::string getOwnerName() const; - bool isOwner(std::shared_ptr owner); + bool isOwner(std::shared_ptr owner) const; bool hasOwner() const { return getOwnerId() != 0; @@ -314,7 +314,7 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { virtual void serializeAttr(PropWriteStream &propWriteStream) const; bool isPushable() override final { - return isMoveable(); + return isMovable(); } int32_t getThrowRange() const override final { return (isPickupable() ? 15 : 2); @@ -447,8 +447,8 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { bool isWrapContainer() const { return items[id].wrapContainer; } - bool isMoveable() const { - return items[id].moveable; + bool isMovable() const { + return items[id].movable; } bool isCorpse() const { return items[id].isCorpse; diff --git a/src/items/items.cpp b/src/items/items.cpp index 5b4a7419ee5..6c517c8a1ed 100644 --- a/src/items/items.cpp +++ b/src/items/items.cpp @@ -166,7 +166,7 @@ void Items::loadFromProtobuf() { iType.wrapable = true; } iType.multiUse = object.flags().multiuse(); - iType.moveable = object.flags().unmove() == false; + iType.movable = object.flags().unmove() == false; iType.canReadText = (object.flags().has_lenshelp() && object.flags().lenshelp().id() == 1112) || (object.flags().has_write() && object.flags().write().max_text_length() != 0) || (object.flags().has_write_once() && object.flags().write_once().max_text_length_once() != 0); iType.canReadText = object.flags().has_write() || object.flags().has_write_once(); iType.isVertical = object.flags().has_hook() && object.flags().hook().direction() == HOOK_TYPE_SOUTH; diff --git a/src/items/items.hpp b/src/items/items.hpp index 5320ec86ab5..b04e128fd7f 100644 --- a/src/items/items.hpp +++ b/src/items/items.hpp @@ -326,7 +326,7 @@ class ItemType { bool wrapable = false; bool wrapContainer = false; bool multiUse = false; - bool moveable = false; + bool movable = false; bool canReadText = false; bool canWriteText = false; bool isVertical = false; diff --git a/src/items/items_definitions.hpp b/src/items/items_definitions.hpp index 4e8fffd3a37..bd5c152d378 100644 --- a/src/items/items_definitions.hpp +++ b/src/items/items_definitions.hpp @@ -19,7 +19,7 @@ enum ItemProperty { CONST_PROP_BLOCKPATH, CONST_PROP_ISVERTICAL, CONST_PROP_ISHORIZONTAL, - CONST_PROP_MOVEABLE, + CONST_PROP_MOVABLE, CONST_PROP_IMMOVABLEBLOCKSOLID, CONST_PROP_IMMOVABLEBLOCKPATH, CONST_PROP_IMMOVABLENOFIELDBLOCKPATH, @@ -43,7 +43,7 @@ enum ReturnValue { RETURNVALUE_THEREISNOWAY, RETURNVALUE_DESTINATIONOUTOFREACH, RETURNVALUE_CREATUREBLOCK, - RETURNVALUE_NOTMOVEABLE, + RETURNVALUE_NOTMOVABLE, RETURNVALUE_DROPTWOHANDEDITEM, RETURNVALUE_BOTHHANDSNEEDTOBEFREE, RETURNVALUE_CANONLYUSEONEWEAPON, @@ -434,7 +434,7 @@ enum TileFlags_t : uint32_t { TILESTATE_IMMOVABLENOFIELDBLOCKPATH = 1 << 21, TILESTATE_NOFIELDBLOCKPATH = 1 << 22, TILESTATE_SUPPORTS_HANGABLE = 1 << 23, - TILESTATE_MOVEABLE = 1 << 24, + TILESTATE_MOVABLE = 1 << 24, TILESTATE_ISHORIZONTAL = 1 << 25, TILESTATE_ISVERTICAL = 1 << 26, TILESTATE_BLOCKPROJECTILE = 1 << 27, @@ -458,7 +458,7 @@ enum CylinderFlags_t { FLAG_CHILDISOWNER = 1 << 3, // Used by containers to query capacity of the carrier (player) FLAG_PATHFINDING = 1 << 4, // An additional check is done for floor changing/teleport items FLAG_IGNOREFIELDDAMAGE = 1 << 5, // Bypass field damage checks - FLAG_IGNORENOTMOVEABLE = 1 << 6, // Bypass check for mobility + FLAG_IGNORENOTMOVABLE = 1 << 6, // Bypass check for mobility FLAG_IGNOREAUTOSTACK = 1 << 7, // queryDestination will not try to stack items together }; @@ -483,7 +483,7 @@ enum ItemParseAttributes_t { ITEM_PARSE_WRAPCONTAINER, ITEM_PARSE_IMBUEMENT, ITEM_PARSE_WRAPABLETO, - ITEM_PARSE_MOVEABLE, + ITEM_PARSE_MOVABLE, ITEM_PARSE_BLOCKPROJECTILE, ITEM_PARSE_PICKUPABLE, ITEM_PARSE_FLOORCHANGE, diff --git a/src/items/tile.cpp b/src/items/tile.cpp index cc9d1160211..35264469171 100644 --- a/src/items/tile.cpp +++ b/src/items/tile.cpp @@ -39,8 +39,8 @@ bool Tile::hasProperty(ItemProperty prop) const { return hasFlag(TILESTATE_ISVERTICAL); case CONST_PROP_ISHORIZONTAL: return hasFlag(TILESTATE_ISHORIZONTAL); - case CONST_PROP_MOVEABLE: - return hasFlag(TILESTATE_MOVEABLE); + case CONST_PROP_MOVABLE: + return hasFlag(TILESTATE_MOVABLE); case CONST_PROP_IMMOVABLEBLOCKSOLID: return hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID); case CONST_PROP_IMMOVABLEBLOCKPATH: @@ -357,7 +357,7 @@ std::shared_ptr Tile::getTopVisibleThing(std::shared_ptr creatu } void Tile::onAddTileItem(std::shared_ptr item) { - if ((item->hasProperty(CONST_PROP_MOVEABLE) || item->getContainer()) || (item->isWrapable() && !item->hasProperty(CONST_PROP_MOVEABLE) && !item->hasProperty(CONST_PROP_BLOCKPATH))) { + if ((item->hasProperty(CONST_PROP_MOVABLE) || item->getContainer()) || (item->isWrapable() && !item->hasProperty(CONST_PROP_MOVABLE) && !item->hasProperty(CONST_PROP_BLOCKPATH))) { auto it = g_game().browseFields.find(static_self_cast()); if (it != g_game().browseFields.end()) { auto lockedCylinder = it->second.lock(); @@ -393,7 +393,7 @@ void Tile::onAddTileItem(std::shared_ptr item) { } } - if (item->isCarpet() && !item->isMoveable()) { + if (item->isCarpet() && !item->isMovable()) { if (getTopTopItem() && getTopTopItem()->canReceiveAutoCarpet()) { return; } @@ -430,7 +430,7 @@ void Tile::onAddTileItem(std::shared_ptr item) { } void Tile::onUpdateTileItem(std::shared_ptr oldItem, const ItemType &oldType, std::shared_ptr newItem, const ItemType &newType) { - if ((newItem->hasProperty(CONST_PROP_MOVEABLE) || newItem->getContainer()) || (newItem->isWrapable() && newItem->hasProperty(CONST_PROP_MOVEABLE) && !oldItem->hasProperty(CONST_PROP_BLOCKPATH))) { + if ((newItem->hasProperty(CONST_PROP_MOVABLE) || newItem->getContainer()) || (newItem->isWrapable() && newItem->hasProperty(CONST_PROP_MOVABLE) && !oldItem->hasProperty(CONST_PROP_BLOCKPATH))) { auto it = g_game().browseFields.find(getTile()); if (it != g_game().browseFields.end()) { auto lockedCylinder = it->second.lock(); @@ -442,7 +442,7 @@ void Tile::onUpdateTileItem(std::shared_ptr oldItem, const ItemType &oldTy } } } - } else if ((oldItem->hasProperty(CONST_PROP_MOVEABLE) || oldItem->getContainer()) || (oldItem->isWrapable() && !oldItem->hasProperty(CONST_PROP_MOVEABLE) && !oldItem->hasProperty(CONST_PROP_BLOCKPATH))) { + } else if ((oldItem->hasProperty(CONST_PROP_MOVABLE) || oldItem->getContainer()) || (oldItem->isWrapable() && !oldItem->hasProperty(CONST_PROP_MOVABLE) && !oldItem->hasProperty(CONST_PROP_BLOCKPATH))) { auto it = g_game().browseFields.find(getTile()); if (it != g_game().browseFields.end()) { auto lockedCylinder = it->second.lock(); @@ -472,7 +472,7 @@ void Tile::onUpdateTileItem(std::shared_ptr oldItem, const ItemType &oldTy } void Tile::onRemoveTileItem(const CreatureVector &spectators, const std::vector &oldStackPosVector, std::shared_ptr item) { - if ((item->hasProperty(CONST_PROP_MOVEABLE) || item->getContainer()) || (item->isWrapable() && !item->hasProperty(CONST_PROP_MOVEABLE) && !item->hasProperty(CONST_PROP_BLOCKPATH))) { + if ((item->hasProperty(CONST_PROP_MOVABLE) || item->getContainer()) || (item->isWrapable() && !item->hasProperty(CONST_PROP_MOVABLE) && !item->hasProperty(CONST_PROP_BLOCKPATH))) { auto it = g_game().browseFields.find(getTile()); if (it != g_game().browseFields.end()) { auto lockedCylinder = it->second.lock(); @@ -523,7 +523,7 @@ void Tile::onRemoveTileItem(const CreatureVector &spectators, const std::vector< } } - if (item->isCarpet() && !item->isMoveable()) { + if (item->isCarpet() && !item->isMovable()) { if (getTopTopItem() && getTopTopItem()->canReceiveAutoCarpet()) { return; } @@ -739,7 +739,7 @@ ReturnValue Tile::queryAdd(int32_t, const std::shared_ptr &thing, uint32_ // FLAG_IGNOREBLOCKITEM is set if (ground) { const ItemType &iiType = Item::items[ground->getID()]; - if (iiType.blockSolid && (!iiType.moveable || ground->hasAttribute(ItemAttribute_t::UNIQUEID))) { + if (iiType.blockSolid && (!iiType.movable || ground->hasAttribute(ItemAttribute_t::UNIQUEID))) { return RETURNVALUE_NOTPOSSIBLE; } } @@ -747,7 +747,7 @@ ReturnValue Tile::queryAdd(int32_t, const std::shared_ptr &thing, uint32_ if (const auto items = getItemList()) { for (auto &item : *items) { const ItemType &iiType = Item::items[item->getID()]; - if (iiType.blockSolid && (!iiType.moveable || item->hasAttribute(ItemAttribute_t::UNIQUEID))) { + if (iiType.blockSolid && (!iiType.movable || item->hasAttribute(ItemAttribute_t::UNIQUEID))) { return RETURNVALUE_NOTPOSSIBLE; } } @@ -854,8 +854,8 @@ ReturnValue Tile::queryRemove(const std::shared_ptr &thing, uint32_t coun return RETURNVALUE_NOTPOSSIBLE; } - if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, tileFlags)) { - return RETURNVALUE_NOTMOVEABLE; + if (!item->isMovable() && !hasBitSet(FLAG_IGNORENOTMOVABLE, tileFlags)) { + return RETURNVALUE_NOTMOVABLE; } return RETURNVALUE_NOERROR; @@ -1684,8 +1684,8 @@ void Tile::setTileFlags(const std::shared_ptr &item) { setFlag(TILESTATE_IMMOVABLEBLOCKPATH); } - if (item->hasProperty(CONST_PROP_MOVEABLE)) { - setFlag(TILESTATE_MOVEABLE); + if (item->hasProperty(CONST_PROP_MOVABLE)) { + setFlag(TILESTATE_MOVABLE); } if (item->hasProperty(CONST_PROP_ISHORIZONTAL)) { @@ -1745,8 +1745,8 @@ void Tile::resetTileFlags(const std::shared_ptr &item) { resetFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH); } - if (item->hasProperty(CONST_PROP_MOVEABLE) && !hasProperty(item, CONST_PROP_MOVEABLE)) { - resetFlag(TILESTATE_MOVEABLE); + if (item->hasProperty(CONST_PROP_MOVABLE) && !hasProperty(item, CONST_PROP_MOVABLE)) { + resetFlag(TILESTATE_MOVABLE); } if (item->hasProperty(CONST_PROP_ISHORIZONTAL) && !hasProperty(item, CONST_PROP_ISHORIZONTAL)) { @@ -1796,7 +1796,7 @@ void Tile::resetTileFlags(const std::shared_ptr &item) { } } -bool Tile::isMoveableBlocking() const { +bool Tile::isMovableBlocking() const { return !ground || hasFlag(TILESTATE_BLOCKSOLID); } diff --git a/src/items/tile.hpp b/src/items/tile.hpp index 2493974c950..83f0350f9aa 100644 --- a/src/items/tile.hpp +++ b/src/items/tile.hpp @@ -132,7 +132,7 @@ class Tile : public Cylinder, public SharedObject { return static_self_cast(); } - std::shared_ptr getCylinder() override final { + std::shared_ptr getCylinder() final { return getTile(); } @@ -149,7 +149,7 @@ class Tile : public Cylinder, public SharedObject { std::shared_ptr getBottomVisibleCreature(std::shared_ptr creature) const; std::shared_ptr getTopTopItem() const; std::shared_ptr getTopDownItem() const; - bool isMoveableBlocking() const; + bool isMovableBlocking() const; std::shared_ptr getTopVisibleThing(std::shared_ptr creature); std::shared_ptr getItemByTopOrder(int32_t topOrder); diff --git a/src/items/trashholder.cpp b/src/items/trashholder.cpp index 835558d6244..40c1f0e49b4 100644 --- a/src/items/trashholder.cpp +++ b/src/items/trashholder.cpp @@ -50,7 +50,7 @@ void TrashHolder::addThing(int32_t, std::shared_ptr thing) { return; } - if (item.get() == this || !item->hasProperty(CONST_PROP_MOVEABLE)) { + if (item.get() == this || !item->hasProperty(CONST_PROP_MOVABLE)) { return; } diff --git a/src/lua/creature/raids.cpp b/src/lua/creature/raids.cpp index 3251f744e4f..aad2fd08350 100644 --- a/src/lua/creature/raids.cpp +++ b/src/lua/creature/raids.cpp @@ -540,7 +540,7 @@ bool AreaSpawnEvent::executeEvent() { bool success = false; for (int32_t tries = 0; tries < MAXIMUM_TRIES_PER_MONSTER; tries++) { std::shared_ptr tile = g_game().map.getTile(static_cast(uniform_random(fromPos.x, toPos.x)), static_cast(uniform_random(fromPos.y, toPos.y)), static_cast(uniform_random(fromPos.z, toPos.z))); - if (tile && !tile->isMoveableBlocking() && !tile->hasFlag(TILESTATE_PROTECTIONZONE) && tile->getTopCreature() == nullptr && g_game().placeCreature(monster, tile->getPosition(), false, true)) { + if (tile && !tile->isMovableBlocking() && !tile->hasFlag(TILESTATE_PROTECTIONZONE) && tile->getTopCreature() == nullptr && g_game().placeCreature(monster, tile->getPosition(), false, true)) { success = true; monster->setForgeMonster(false); break; diff --git a/src/lua/functions/core/game/global_functions.cpp b/src/lua/functions/core/game/global_functions.cpp index e2961e26215..2bf9549ff97 100644 --- a/src/lua/functions/core/game/global_functions.cpp +++ b/src/lua/functions/core/game/global_functions.cpp @@ -124,8 +124,8 @@ int GlobalFunctions::luaIsDepot(lua_State* L) { return 1; } -int GlobalFunctions::luaIsMoveable(lua_State* L) { - // isMoveable(uid) +int GlobalFunctions::luaIsMovable(lua_State* L) { + // isMovable(uid) // isMovable(uid) std::shared_ptr thing = getScriptEnv()->getThingByUID(getNumber(L, -1)); pushBoolean(L, thing && thing->isPushable()); diff --git a/src/lua/functions/core/game/global_functions.hpp b/src/lua/functions/core/game/global_functions.hpp index 3d83e03aa14..6ccb6c45d9d 100644 --- a/src/lua/functions/core/game/global_functions.hpp +++ b/src/lua/functions/core/game/global_functions.hpp @@ -37,7 +37,7 @@ class GlobalFunctions final : LuaScriptInterface { lua_register(L, "getWorldUpTime", GlobalFunctions::luaGetWorldUpTime); lua_register(L, "isDepot", GlobalFunctions::luaIsDepot); lua_register(L, "isInWar", GlobalFunctions::luaIsInWar); - lua_register(L, "isMovable", GlobalFunctions::luaIsMoveable); + lua_register(L, "isMovable", GlobalFunctions::luaIsMovable); lua_register(L, "isValidUID", GlobalFunctions::luaIsValidUID); lua_register(L, "saveServer", GlobalFunctions::luaSaveServer); lua_register(L, "sendChannelMessage", GlobalFunctions::luaSendChannelMessage); @@ -78,7 +78,7 @@ class GlobalFunctions final : LuaScriptInterface { static int luaGetWorldUpTime(lua_State* L); static int luaIsDepot(lua_State* L); static int luaIsInWar(lua_State* L); - static int luaIsMoveable(lua_State* L); + static int luaIsMovable(lua_State* L); static int luaIsValidUID(lua_State* L); static int luaSaveServer(lua_State* L); static int luaSendChannelMessage(lua_State* L); diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp index 306283b0b6e..fd2f26699f4 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -137,7 +137,7 @@ void LuaEnums::initOthersEnums(lua_State* L) { registerEnum(L, FLAG_CHILDISOWNER); registerEnum(L, FLAG_PATHFINDING); registerEnum(L, FLAG_IGNOREFIELDDAMAGE); - registerEnum(L, FLAG_IGNORENOTMOVEABLE); + registerEnum(L, FLAG_IGNORENOTMOVABLE); registerEnum(L, FLAG_IGNOREAUTOSTACK); // Use with house:getAccessList, house:setAccessList @@ -687,7 +687,7 @@ void LuaEnums::initConstPropEnums(lua_State* L) { registerEnum(L, CONST_PROP_BLOCKPATH); registerEnum(L, CONST_PROP_ISVERTICAL); registerEnum(L, CONST_PROP_ISHORIZONTAL); - registerEnum(L, CONST_PROP_MOVEABLE); + registerEnum(L, CONST_PROP_MOVABLE); registerEnum(L, CONST_PROP_IMMOVABLEBLOCKSOLID); registerEnum(L, CONST_PROP_IMMOVABLEBLOCKPATH); registerEnum(L, CONST_PROP_IMMOVABLENOFIELDBLOCKPATH); @@ -1130,7 +1130,7 @@ void LuaEnums::initReturnValueEnums(lua_State* L) { registerEnum(L, RETURNVALUE_THEREISNOWAY); registerEnum(L, RETURNVALUE_DESTINATIONOUTOFREACH); registerEnum(L, RETURNVALUE_CREATUREBLOCK); - registerEnum(L, RETURNVALUE_NOTMOVEABLE); + registerEnum(L, RETURNVALUE_NOTMOVABLE); registerEnum(L, RETURNVALUE_DROPTWOHANDEDITEM); registerEnum(L, RETURNVALUE_BOTHHANDSNEEDTOBEFREE); registerEnum(L, RETURNVALUE_CANONLYUSEONEWEAPON); diff --git a/src/lua/functions/events/action_functions.cpp b/src/lua/functions/events/action_functions.cpp index f31b74618e9..e0e746d4149 100644 --- a/src/lua/functions/events/action_functions.cpp +++ b/src/lua/functions/events/action_functions.cpp @@ -166,8 +166,8 @@ int ActionFunctions::luaActionPosition(lua_State* L) { // If it is an item that can be removed, then it will be set as non-movable. ItemType &itemType = Item::items.getItemType(itemId); - if (itemType.moveable == true) { - itemType.moveable = false; + if (itemType.movable == true) { + itemType.movable = false; } g_game().setCreateLuaItems(position, itemId); diff --git a/src/lua/functions/items/item_functions.cpp b/src/lua/functions/items/item_functions.cpp index c8b7f26ed75..3e9190055c4 100644 --- a/src/lua/functions/items/item_functions.cpp +++ b/src/lua/functions/items/item_functions.cpp @@ -626,7 +626,7 @@ int ItemFunctions::luaItemMoveTo(lua_State* L) { return 1; } - uint32_t flags = getNumber(L, 3, FLAG_NOLIMIT | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE | FLAG_IGNORENOTMOVEABLE); + uint32_t flags = getNumber(L, 3, FLAG_NOLIMIT | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE | FLAG_IGNORENOTMOVABLE); if (item->getParent() == VirtualCylinder::virtualCylinder) { pushBoolean(L, g_game().internalAddItem(toCylinder, item, INDEX_WHEREEVER, flags) == RETURNVALUE_NOERROR); diff --git a/src/lua/functions/items/item_type_functions.cpp b/src/lua/functions/items/item_type_functions.cpp index 81711978c88..4ff757b3dcd 100644 --- a/src/lua/functions/items/item_type_functions.cpp +++ b/src/lua/functions/items/item_type_functions.cpp @@ -76,7 +76,7 @@ int ItemTypeFunctions::luaItemTypeIsMovable(lua_State* L) { // itemType:isMovable() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - pushBoolean(L, itemType->moveable); + pushBoolean(L, itemType->movable); } else { lua_pushnil(L); } diff --git a/src/map/house/housetile.cpp b/src/map/house/housetile.cpp index 386b06a0873..3ed661a1c12 100644 --- a/src/map/house/housetile.cpp +++ b/src/map/house/housetile.cpp @@ -125,7 +125,7 @@ ReturnValue HouseTile::queryRemove(const std::shared_ptr &thing, uint32_t if (house && !house->isInvited(actorPlayer)) { return RETURNVALUE_NOTPOSSIBLE; } else if (house && house->getHouseAccessLevel(actorPlayer) == HOUSE_GUEST) { - return RETURNVALUE_NOTMOVEABLE; + return RETURNVALUE_NOTMOVABLE; } } return Tile::queryRemove(thing, count, flags); diff --git a/src/utils/tools.cpp b/src/utils/tools.cpp index 150d1d2238c..56c76ac0418 100644 --- a/src/utils/tools.cpp +++ b/src/utils/tools.cpp @@ -1209,7 +1209,7 @@ const char* getReturnMessage(ReturnValue value) { case RETURNVALUE_DESTINATIONOUTOFREACH: return "Destination is out of reach."; - case RETURNVALUE_NOTMOVEABLE: + case RETURNVALUE_NOTMOVABLE: return "You cannot move this object."; case RETURNVALUE_DROPTWOHANDEDITEM: From 37bd3dc9ca354a4c39857ef741c2f32c40dbbe1d Mon Sep 17 00:00:00 2001 From: markiluk <106963095+markiluk@users.noreply.github.com> Date: Sat, 30 Dec 2023 21:14:40 +0100 Subject: [PATCH 27/28] feat: Timira The Many-Headed Boss fight mechanic (#2044) # Description Timira The Many-Headed Boss fight mechanic. Previously, boss lever teleported players straight to boss room. This PR covers whole mechanic for this fight. First stage - players have to use bucket full of sparkles on trapped water, End of first stage spawns Reward box and teleport to second stage. Second stage - players have to use corupted water with 8s cooldown, until end of stage is triggered. End of second stage spawn Reward box and teleport to third stage. Third stage is just a boss fight. **Map requires changes:** Lack of water around first stage ![image](https://github.com/opentibiabr/canary/assets/106963095/aec56eff-bd46-48f6-95db-afb96d6e0599) Borders are walkable : 1. vertical borders should have an id : 38126 2. horizontal borders should have an id: 38125 ![image](https://github.com/opentibiabr/canary/assets/106963095/8b4e1350-e35d-463d-a77a-c6cb2dd19388) --------- Co-authored-by: Elson Costa Co-authored-by: Luan Santos Co-authored-by: GitHub Actions --- .../quests/marapur/timira_the_many-headed.lua | 2 + .../quests/marapur/boss_lever_timira.lua | 20 +- .../quests/marapur/boss_timira_fight.lua | 390 ++++++++++++++++++ .../quests/marapur/teleports_timira.lua | 11 +- 4 files changed, 411 insertions(+), 12 deletions(-) create mode 100644 data-otservbr-global/scripts/actions/quests/marapur/boss_timira_fight.lua diff --git a/data-otservbr-global/monster/quests/marapur/timira_the_many-headed.lua b/data-otservbr-global/monster/quests/marapur/timira_the_many-headed.lua index 1351dc4e303..43d0a1502c3 100644 --- a/data-otservbr-global/monster/quests/marapur/timira_the_many-headed.lua +++ b/data-otservbr-global/monster/quests/marapur/timira_the_many-headed.lua @@ -76,9 +76,11 @@ monster.loot = { { name = "giant topaz", chance = 2041 }, { name = "dawnfire sherwani", chance = 200 }, { name = "frostflower boots", chance = 200 }, + { name = "feverbloom boots", chance = 200 }, { id = 39233, chance = 200 }, -- enchanted turtle amulet { name = "midnight tunic", chance = 200 }, { name = "midnight sarong", chance = 200 }, + { name = "naga quiver", chance = 200 }, { name = "naga sword", chance = 200 }, { name = "naga axe", chance = 200 }, { name = "naga club", chance = 200 }, diff --git a/data-otservbr-global/scripts/actions/quests/marapur/boss_lever_timira.lua b/data-otservbr-global/scripts/actions/quests/marapur/boss_lever_timira.lua index 3f9bf934ebf..9c77ed3d849 100644 --- a/data-otservbr-global/scripts/actions/quests/marapur/boss_lever_timira.lua +++ b/data-otservbr-global/scripts/actions/quests/marapur/boss_lever_timira.lua @@ -1,15 +1,13 @@ local config = { - boss = { - name = "Timira The Many-Headed", - position = Position(33815, 32703, 9), - }, + boss = { name = "Timira the Many-Headed" }, + encounter = "Timira the Many-Headed", requiredLevel = 250, playerPositions = { - { pos = Position(33809, 32702, 8), teleport = Position(33816, 32708, 9), effect = CONST_ME_TELEPORT }, - { pos = Position(33808, 32702, 8), teleport = Position(33816, 32708, 9), effect = CONST_ME_TELEPORT }, - { pos = Position(33807, 32702, 8), teleport = Position(33816, 32708, 9), effect = CONST_ME_TELEPORT }, - { pos = Position(33806, 32702, 8), teleport = Position(33816, 32708, 9), effect = CONST_ME_TELEPORT }, - { pos = Position(33805, 32702, 8), teleport = Position(33816, 32708, 9), effect = CONST_ME_TELEPORT }, + { pos = Position(33809, 32702, 8), teleport = Position(33787, 32680, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(33808, 32702, 8), teleport = Position(33787, 32680, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(33807, 32702, 8), teleport = Position(33787, 32680, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(33806, 32702, 8), teleport = Position(33787, 32680, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(33805, 32702, 8), teleport = Position(33787, 32680, 10), effect = CONST_ME_TELEPORT }, }, specPos = { from = Position(33803, 32690, 9), @@ -21,3 +19,7 @@ local config = { local lever = BossLever(config) lever:position({ x = 33810, y = 32702, z = 8 }) lever:register() + +local zone = lever:getZone() +zone:addArea({ x = 33777, y = 32673, z = 10 }, { x = 33801, y = 32700, z = 10 }) +zone:addArea({ x = 33784, y = 32701, z = 9 }, { x = 33806, y = 32716, z = 9 }) diff --git a/data-otservbr-global/scripts/actions/quests/marapur/boss_timira_fight.lua b/data-otservbr-global/scripts/actions/quests/marapur/boss_timira_fight.lua new file mode 100644 index 00000000000..df39a5799be --- /dev/null +++ b/data-otservbr-global/scripts/actions/quests/marapur/boss_timira_fight.lua @@ -0,0 +1,390 @@ +local timiraFightConfig = { + chestEquipmentChance = 200, -- with modifier x4 and lootRate x3 it gives 3,6% for equipment + getLootRandomModifier = 4, -- for more info check getLootRandom function in functions.lua + bucketsRequiredPerPlayerInFight = 2, + corruptedWaterUsePerPlayerInFight = 2, + secondsDelayForUseCorruptedWater = 8, + chestPossibleValuables = { + "crystal coin", + "amber", + "giant sapphire", + "giant amethyst", + "Raw Watermelon Tourmaline", + "Watermelon Tourmaline", + }, + chestPossibleEquipment = { + "dawnfire sherwani", + "dawnfire pantaloons", + "frostflower boots", + "feverbloom boots", + "midnight tunic", + "midnight sarong", + "naga sword", + "naga axe", + "naga club", + "naga wand", + "naga rod", + "naga crossbow", + "naga quiver", + }, +} + +local fightZone = Zone("fight.timira.encounterZone") +local firstStageZone = Zone("fight.timira.encounterZone.firstStage") +local secondStageZone = Zone("fight.timira.encounterZone.secondStage") +local bossZone = Zone("fight.timira.encounterZone.bossStage") + +firstStageZone:addArea({ x = 33787, y = 32684, z = 10 }, { x = 33793, y = 32694, z = 10 }) -- smaller than first stage area, its spawn area +secondStageZone:addArea({ x = 33793, y = 32704, z = 9 }, { x = 33802, y = 32709, z = 9 }) -- smaller than first stage area, its spawn area +bossZone:addArea({ x = 33803, y = 32690, z = 9 }, { x = 33828, y = 32715, z = 9 }) -- whole boss area + +fightZone:addArea({ x = 33777, y = 32673, z = 10 }, { x = 33801, y = 32700, z = 10 }) -- whole first stage area +fightZone:addArea({ x = 33784, y = 32701, z = 9 }, { x = 33805, y = 32711, z = 9 }) -- whole second stage area +fightZone:addArea({ x = 33803, y = 32690, z = 9 }, { x = 33828, y = 32715, z = 9 }) -- whole boss stage area + +local monstersToKeepSpawning = { + { name = "Rogue Naga", amount = 3 }, + { name = "Corrupt Naga", amount = 3 }, +} + +local stages = { + FIRST = 1, + SECOND = 2, + THIRD = 3, +} + +local firstStageConfig = { + sparklingBucketId = 39244, + badWaterBucketId = 39243, + emptyBucketId = 39242, + shatteredWaterId = 39269, + fixedShatteredWaterId = 39268, + shatteredWaterPosition = Position(33798, 32689, 10), + sparklesIds = { 1722, 1723 }, + shallowWaterPositions = { + Position(33793, 32679, 10), + Position(33794, 32679, 10), + Position(33793, 32680, 10), + Position(33794, 32680, 10), + Position(33795, 32680, 10), + Position(33793, 32681, 10), + Position(33794, 32681, 10), + Position(33795, 32681, 10), + Position(33796, 32681, 10), + Position(33794, 32682, 10), + Position(33795, 32682, 10), + Position(33796, 32682, 10), + Position(33794, 32683, 10), + Position(33795, 32683, 10), + Position(33796, 32683, 10), + }, + bucketsPostions = { + Position(33796, 32685, 10), + Position(33797, 32691, 10), + Position(33787, 32687, 10), + Position(33785, 32676, 10), + }, + shallowWaterBorderIds = { + 38125, + 38134, + 38130, + 38126, + 38136, + }, + isWaterSparkling = false, + skipedSparklingIterations = 0, + sparklingIterationsToSkip = 3, + nextStageTeleportPosition = Position(33787, 32699, 10), + rewardChestPosition = Position(33789, 32699, 10), + playersTookReward = {}, +} + +local secondStageConfig = { + nextStageTeleportPosition = Position(33804, 32704, 9), + rewardChestPosition = Position(33802, 32703, 9), + playersTookReward = {}, +} + +local activeTeleportId = 35500 +local inactiveTeleportId = 39238 +local chestId = 39390 +local activeStage = stages.FIRST +local firstStagePoints = 0 +local secondStagePoints = 0 + +local function restartShatteredWater() + local pos = firstStageConfig.shatteredWaterPosition + if pos:hasItem(firstStageConfig.fixedShatteredWaterId) then + Tile(pos):getItemById(firstStageConfig.fixedShatteredWaterId):remove(1) + Game.createItem(firstStageConfig.shatteredWaterId, 1, pos) + end +end + +local function restartBuckets() + for _, pos in ipairs(firstStageConfig.bucketsPostions) do + if not pos:hasItem(firstStageConfig.emptyBucketId) then + Game.createItem(firstStageConfig.emptyBucketId, 1, pos) + end + end +end + +local function restartTeleports() + local teleports = { firstStageConfig.nextStageTeleportPosition, secondStageConfig.nextStageTeleportPosition } + for _, pos in ipairs(teleports) do + if pos:hasItem(activeTeleportId) then + Tile(pos):getItemById(activeTeleportId):remove(1) + Game.createItem(inactiveTeleportId, 1, pos) + end + end +end + +local function removeRewardChests() + local chests = { firstStageConfig.rewardChestPosition, secondStageConfig.rewardChestPosition } + firstStageConfig.playersTookReward = {} + secondStageConfig.playersTookReward = {} + for _, pos in ipairs(chests) do + if pos:hasItem(chestId) then + Tile(pos):getItemById(chestId):remove(1) + end + end +end + +local function restart() + activeStage = stages.FIRST + firstStagePoints = 0 + secondStagePoints = 0 + firstStageConfig.isWaterSparkling = false + firstStageConfig.previousIterSkipedSparkling = false + restartShatteredWater() + restartBuckets() + restartTeleports() + removeRewardChests() +end + +local function activeZone() + local stageZones = { + [stages.FIRST] = firstStageZone, + [stages.SECOND] = secondStageZone, + [stages.THIRD] = {}, + } + return stageZones[activeStage] +end + +local encounter = Encounter("Timira the Many-Headed", { + zone = fightZone, + timeToSpawnMonsters = "10ms", +}) + +encounter + :addStage({ + start = function() + restart() + end, + }) + :autoAdvance() +encounter:addRemoveMonsters():autoAdvance() +encounter:addStage({ start = function() end }) -- FIRST stage, Action timiraBucket.onUse activates next stage + +encounter:addRemoveMonsters():autoAdvance() +encounter:addStage({ start = function() end }) -- SECOND stage, Action corruptedWater.onUse activates next stage + +encounter:addRemoveMonsters():autoAdvance() +encounter:addSpawnMonsters({ -- THIRD stage, boss fight + { + name = "Timira the Many-Headed", + positions = { Position(33815, 32703, 9) }, + }, +}) + +encounter:register() + +local function startSecondStage() + if activeStage == stages.FIRST then + local pos = firstStageConfig.shatteredWaterPosition + if pos:hasItem(firstStageConfig.shatteredWaterId) then + Tile(pos):getItemById(firstStageConfig.shatteredWaterId):remove(1) + Game.createItem(firstStageConfig.fixedShatteredWaterId, 1, pos) + end + local tpPos = firstStageConfig.nextStageTeleportPosition + if tpPos:hasItem(inactiveTeleportId) then + Tile(tpPos):getItemById(inactiveTeleportId):remove(1) + Game.createItem(activeTeleportId, 1, tpPos) + end + Game.createItem(chestId, 1, firstStageConfig.rewardChestPosition) + activeStage = stages.SECOND + encounter:nextStage() + end +end + +local function startThirdStage() + if activeStage == stages.SECOND then + local tpPos = secondStageConfig.nextStageTeleportPosition + if tpPos:hasItem(inactiveTeleportId) then + Tile(tpPos):getItemById(inactiveTeleportId):remove(1) + Game.createItem(activeTeleportId, 1, tpPos) + end + Game.createItem(chestId, 1, secondStageConfig.rewardChestPosition) + activeStage = stages.THIRD + encounter:nextStage() + end +end + +function encounter:onReset(position) + encounter:removeMonsters() + fightZone:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("Timira the Many-Headed has been defeated. You have %i seconds to leave the room.", 60)) + self:addEvent(function(zn) + zn:refresh() + zn:removePlayers() + end, 60 * 1000, fightZone) +end + +local nagaSpawner = GlobalEvent("self.timira.spawner.onThink") +function nagaSpawner.onThink(interval, lastExecution) + if next(fightZone:getPlayers()) == nil then + return true + end + + if activeStage < 3 then + for _, mob in ipairs(monstersToKeepSpawning) do + if fightZone:countMonsters(mob.name) < mob.amount then + encounter:spawnMonsters({ name = mob.name, positions = { activeZone():randomPosition() } }) + end + end + end + + return true +end +nagaSpawner:interval(1000) +nagaSpawner:register() + +local sparklingWater = GlobalEvent("self.timira.water.onThink") +function sparklingWater.onThink(interval, lastExecution) + if firstStageConfig.isWaterSparkling then --Sparkling 2s passed, turn it off + for _, pos in ipairs(firstStageConfig.shallowWaterPositions) do + for _, spark in ipairs(firstStageConfig.sparklesIds) do + if pos:hasItem(spark) then + Tile(pos):getItemById(spark):remove(1) + end + end + end + firstStageConfig.isWaterSparkling = false + firstStageConfig.skipedSparklingIterations = 0 + return true + end + + if next(fightZone:getPlayers()) == nil then + return true + end + + if activeStage == stages.FIRST then + if not firstStageConfig.isWaterSparkling and firstStageConfig.skipedSparklingIterations >= firstStageConfig.sparklingIterationsToSkip then + for _, pos in ipairs(firstStageConfig.shallowWaterPositions) do + local spark = firstStageConfig.sparklesIds[math.random(1, #firstStageConfig.sparklesIds)] + Game.createItem(spark, 1, pos) + end + firstStageConfig.isWaterSparkling = true + else + firstStageConfig.skipedSparklingIterations = firstStageConfig.skipedSparklingIterations + 1 + end + end + + return true +end +sparklingWater:interval(2000) +sparklingWater:register() + +local timiraBucket = Action() +function timiraBucket.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item.itemid == firstStageConfig.sparklingBucketId and target and target.itemid == firstStageConfig.shatteredWaterId then + if player:removeItem(firstStageConfig.sparklingBucketId, 1) then + player:addItem(firstStageConfig.emptyBucketId, 1) + toPosition:sendMagicEffect(CONST_ME_WATER_DROP) + firstStagePoints = firstStagePoints + 1 + + if activeStage == stages.FIRST and firstStagePoints >= fightZone:countPlayers() * timiraFightConfig.bucketsRequiredPerPlayerInFight then + player:say("The water has formed into a shape!", TALKTYPE_MONSTER_SAY, false, 0, firstStageConfig.shatteredWaterPosition) + startSecondStage() + return true + end + player:say("The pieces are shining!", TALKTYPE_MONSTER_SAY, false, 0, firstStageConfig.shatteredWaterPosition) + end + return true + end + if item.itemid == firstStageConfig.badWaterBucketId and target and target.itemid ~= firstStageConfig.shatteredWaterId then + if player:removeItem(firstStageConfig.badWaterBucketId, 1) then + player:addItem(firstStageConfig.emptyBucketId, 1) + local splash = Game.createItem(2886, 0, toPosition) + splash:decay() + end + return true + end + if item.itemid == firstStageConfig.emptyBucketId and target and table.contains(firstStageConfig.shallowWaterBorderIds, target.itemid) then + if player:removeItem(firstStageConfig.emptyBucketId, 1) then + if firstStageConfig.isWaterSparkling then + player:addItem(firstStageConfig.sparklingBucketId, 1) + else + player:addItem(firstStageConfig.badWaterBucketId, 1) + end + end + return true + end + player:getPosition():sendMagicEffect(CONST_ME_POFF) + return false +end +timiraBucket:id(firstStageConfig.sparklingBucketId, firstStageConfig.emptyBucketId, firstStageConfig.badWaterBucketId) +timiraBucket:register() + +local corruptedWater = Action() +function corruptedWater.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if not activeStage == stages.SECOND then + return false + end + local currentIcon = player:getIcon("timira-secondStage") + if not currentIcon or currentIcon.category ~= CreatureIconCategory_Quests or currentIcon.icon ~= CreatureIconQuests_RedShield then + secondStagePoints = secondStagePoints + 1 + if secondStagePoints >= fightZone:countPlayers() * timiraFightConfig.corruptedWaterUsePerPlayerInFight then + startThirdStage() + return true + end + player:setIcon("timira-secondStage", CreatureIconCategory_Quests, CreatureIconQuests_RedShield, 1) + addEvent(function(playerUid) + local player = Player(playerUid) + if player then + player:removeIcon("timira-secondStage") + end + return true + end, timiraFightConfig.secondsDelayForUseCorruptedWater * 1000, player:getGuid()) + return true + end + player:getPosition():sendMagicEffect(CONST_ME_POFF) + return false +end +corruptedWater:id(39267) +corruptedWater:register() + +local timiraChest = Action() +function timiraChest.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local rewards = { + [firstStageConfig.rewardChestPosition.z] = firstStageConfig.playersTookReward, + [secondStageConfig.rewardChestPosition.z] = secondStageConfig.playersTookReward, + } + + if table.contains(rewards[toPosition.z], player:getGuid()) then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The chest is empty.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + return false + end + table.insert(rewards[toPosition.z], player:getGuid()) + local randValue = getLootRandom(timiraFightConfig.getLootRandomModifier) + local itemName = timiraFightConfig.chestPossibleValuables[math.random(1, #timiraFightConfig.chestPossibleValuables)] + if randValue <= timiraFightConfig.chestEquipmentChance then + itemName = timiraFightConfig.chestPossibleEquipment[math.random(1, #timiraFightConfig.chestPossibleEquipment)] + end + + local rewardItem = ItemType(itemName) + player:addItem(rewardItem:getId(), 1) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found a " .. itemName .. ".") + return true +end +timiraChest:id(chestId) +timiraChest:register() diff --git a/data-otservbr-global/scripts/actions/quests/marapur/teleports_timira.lua b/data-otservbr-global/scripts/actions/quests/marapur/teleports_timira.lua index a2ad0773c2e..862da606a05 100644 --- a/data-otservbr-global/scripts/actions/quests/marapur/teleports_timira.lua +++ b/data-otservbr-global/scripts/actions/quests/marapur/teleports_timira.lua @@ -1,9 +1,14 @@ local positions = { - { tptimiraPos = { x = 33803, y = 32700, z = 7 }, tpPos = { x = 33806, y = 32700, z = 8 } }, + { tptimiraPos = { x = 33803, y = 32700, z = 7 }, tpPos = { x = 33806, y = 32700, z = 8 } }, -- Outside to lever room teleport + { tptimiraPos = { x = 33802, y = 32700, z = 8 }, tpPos = { x = 33804, y = 32702, z = 7 } }, -- Lever room to outside teleport - { tptimiraPos = { x = 33802, y = 32700, z = 8 }, tpPos = { x = 33804, y = 32702, z = 7 } }, + { tptimiraPos = { x = 33790, y = 32678, z = 10 }, tpPos = { x = 33810, y = 32699, z = 8 } }, -- First stage to Lever room + { tptimiraPos = { x = 33787, y = 32699, z = 10 }, tpPos = { x = 33790, y = 32706, z = 9 } }, -- First stage to Second stage - { tptimiraPos = { x = 33816, y = 32710, z = 9 }, tpPos = { x = 33810, y = 32699, z = 8 } }, + { tptimiraPos = { x = 33788, y = 32708, z = 9 }, tpPos = { x = 33810, y = 32699, z = 8 } }, -- Second stage to Lever room + { tptimiraPos = { x = 33804, y = 32704, z = 9 }, tpPos = { x = 33810, y = 32705, z = 9 } }, -- Second stage to Third stage + + { tptimiraPos = { x = 33816, y = 32710, z = 9 }, tpPos = { x = 33804, y = 32702, z = 7 } }, -- BossRoom exit to outside teleport } local TpTimira = MoveEvent() From a082e9d2b9688bc8a496510b43a1aadf11512b34 Mon Sep 17 00:00:00 2001 From: Karin Date: Sat, 30 Dec 2023 17:21:42 -0300 Subject: [PATCH 28/28] fix: fuse or transfer error with no slots in bp (#2068) Co-authored-by: GitHub Actions Co-authored-by: Luan Santos --- src/creatures/players/player.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 7283f82251d..2aebb8179c6 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -6914,6 +6914,12 @@ 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; + } + ForgeHistory history; history.actionType = ForgeConversion_t::FORGE_ACTION_FUSION; history.tier = tier; @@ -7122,6 +7128,12 @@ void Player::forgeFuseItems(uint16_t itemId, uint8_t tier, bool success, bool re } 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; + } + ForgeHistory history; history.actionType = ForgeConversion_t::FORGE_ACTION_TRANSFER; history.tier = tier;