From 5c87e81a758651a4c83274fc1c8b05c1ca2d8c60 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Wed, 13 Dec 2023 19:54:41 -0800 Subject: [PATCH 01/28] improve: more reliable quickloot/autoloot (#1997) Instead of trying to quickloot at a location, we quickloot the corpse directly. This makes it so we deterministically pick up all the loot, instead of losing some depending on how and where monsters are dying. --- src/creatures/creature.cpp | 14 +++++++------- src/game/game.cpp | 8 ++++---- src/game/game.hpp | 8 +------- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index 4720b903233..bc77050268d 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -800,23 +800,23 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared corpse->startDecaying(); bool corpses = corpse->isRewardCorpse() || (corpse->getID() == ITEM_MALE_CORPSE || corpse->getID() == ITEM_FEMALE_CORPSE); const auto player = mostDamageCreature ? mostDamageCreature->getPlayer() : nullptr; - if (corpse->getContainer() && player && !corpses) { + auto corpseContainer = corpse->getContainer(); + if (corpseContainer && player && !corpses) { auto monster = getMonster(); if (monster && !monster->isRewardBoss()) { std::ostringstream lootMessage; - lootMessage << "Loot of " << getNameDescription() << ": " << corpse->getContainer()->getContentDescription(player->getProtocolVersion() < 1200) << "."; - auto suffix = corpse->getContainer()->getAttribute(ItemAttribute_t::LOOTMESSAGE_SUFFIX); + lootMessage << "Loot of " << getNameDescription() << ": " << corpseContainer->getContentDescription(player->getProtocolVersion() < 1200) << "."; + auto suffix = corpseContainer->getAttribute(ItemAttribute_t::LOOTMESSAGE_SUFFIX); if (!suffix.empty()) { lootMessage << suffix; } player->sendLootMessage(lootMessage.str()); } - if (player->checkAutoLoot()) { - int32_t pos = tile->getStackposOfItem(player, corpse); + if (player->checkAutoLoot() && corpseContainer && mostDamageCreature->getPlayer()) { g_dispatcher().addEvent( - std::bind(&Game::playerQuickLoot, &g_game(), mostDamageCreature->getID(), this->getPosition(), corpse->getID(), pos - 1, nullptr, false, true), - "Game::playerQuickLoot" + std::bind(&Game::playerQuickLootCorpse, &g_game(), player, corpseContainer), + "Game::playerQuickLootCorpse" ); } } diff --git a/src/game/game.cpp b/src/game/game.cpp index d741b24b1a2..f70af5cf864 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -2545,7 +2545,7 @@ ReturnValue Game::internalTeleport(const std::shared_ptr &thing, const Po return RETURNVALUE_NOTPOSSIBLE; } -void Game::internalQuickLootCorpse(std::shared_ptr player, std::shared_ptr corpse) { +void Game::playerQuickLootCorpse(std::shared_ptr player, std::shared_ptr corpse) { if (!player || !corpse) { return; } @@ -5000,11 +5000,11 @@ void Game::playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t item auto rewardId = corpse->getAttribute(ItemAttribute_t::DATE); auto reward = player->getReward(rewardId, false); if (reward) { - internalQuickLootCorpse(player, reward->getContainer()); + playerQuickLootCorpse(player, reward->getContainer()); } } else { if (!lootAllCorpses) { - internalQuickLootCorpse(player, corpse); + playerQuickLootCorpse(player, corpse); } else { playerLootAllCorpses(player, pos, lootAllCorpses); } @@ -5041,7 +5041,7 @@ void Game::playerLootAllCorpses(std::shared_ptr player, const Position & } corpses++; - internalQuickLootCorpse(player, tileCorpse); + playerQuickLootCorpse(player, tileCorpse); if (corpses >= 30) { break; } diff --git a/src/game/game.hpp b/src/game/game.hpp index f446615e29a..263119d3518 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -350,6 +350,7 @@ class Game { void playerSetFightModes(uint32_t playerId, FightMode_t fightMode, bool chaseMode, bool secureMode); void playerLookAt(uint32_t playerId, uint16_t itemId, const Position &pos, uint8_t stackPos); void playerLookInBattleList(uint32_t playerId, uint32_t creatureId); + void playerQuickLootCorpse(std::shared_ptr player, std::shared_ptr corpse); void playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t itemId, uint8_t stackPos, std::shared_ptr defaultItem = nullptr, bool lootAllCorpses = false, bool autoLoot = false); void playerLootAllCorpses(std::shared_ptr player, const Position &pos, bool lootAllCorpses); void playerSetLootContainer(uint32_t playerId, ObjectCategory_t category, const Position &pos, uint16_t itemId, uint8_t stackPos); @@ -697,13 +698,6 @@ class Game { void playerSpeakToNpc(std::shared_ptr player, const std::string &text); std::shared_ptr createPlayerTask(uint32_t delay, std::function f, std::string context) const; - /** - * Player wants to loot a corpse - * \param player Player pointer - * \param corpse Container pointer to be looted - */ - void internalQuickLootCorpse(std::shared_ptr player, std::shared_ptr corpse); - /** * @brief Finds the container for loot based on the given parameters. * From 8ed72978bce184525a4de7578f0abd009721b264 Mon Sep 17 00:00:00 2001 From: sebbesiren <35768829+sebbesiren@users.noreply.github.com> Date: Fri, 15 Dec 2023 20:41:54 +0100 Subject: [PATCH 02/28] fix: skill food from hireling (#2028) Hireling food for Skill Choose didnt work due to bad if statement. Added if statement so it works as it should --- data-otservbr-global/npc/hireling.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/npc/hireling.lua b/data-otservbr-global/npc/hireling.lua index 11757bff0a5..ca72e1c5bcf 100644 --- a/data-otservbr-global/npc/hireling.lua +++ b/data-otservbr-global/npc/hireling.lua @@ -656,7 +656,7 @@ function createHirelingType(HirelingName) end elseif npcHandler:getTopic(playerId) == TOPIC.BANK then enableBankSystem[playerId] = true - elseif npcHandler:getTopic(playerId) == TOPIC.FOOD then + elseif npcHandler:getTopic(playerId) == TOPIC.FOOD or npcHandler:getTopic(playerId) == TOPIC_FOOD.SKILL_CHOOSE then handleFoodActions(npc, creature, message) elseif npcHandler:getTopic(playerId) == TOPIC.GOODS then -- Ensures players cannot access other shop categories From dd1beeff8efd3d8e41c48d47b7272a096375d67d Mon Sep 17 00:00:00 2001 From: sebbesiren <35768829+sebbesiren@users.noreply.github.com> Date: Sat, 16 Dec 2023 22:10:22 +0100 Subject: [PATCH 03/28] fix: paladin speed on sharpshooter and swift foot (#2030) The changes to speed didnt take into account the paladin spells. --- data-canary/scripts/spells/support/sharpshooter.lua | 4 ++-- data-canary/scripts/spells/support/swift_foot.lua | 2 +- data-otservbr-global/scripts/spells/support/sharpshooter.lua | 4 ++-- data-otservbr-global/scripts/spells/support/swift_foot.lua | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data-canary/scripts/spells/support/sharpshooter.lua b/data-canary/scripts/spells/support/sharpshooter.lua index 0a419b6b07e..595c1e6db80 100644 --- a/data-canary/scripts/spells/support/sharpshooter.lua +++ b/data-canary/scripts/spells/support/sharpshooter.lua @@ -10,9 +10,9 @@ skill:setParameter(CONDITION_PARAM_DISABLE_DEFENSE, true) skill:setParameter(CONDITION_PARAM_BUFF_SPELL, true) combat:addCondition(skill) -local speed = Condition(CONDITION_PARALYZE) +local speed = Condition(CONDITION_HASTE) speed:setParameter(CONDITION_PARAM_TICKS, 10000) -speed:setFormula(-0.73, 0, -0.73, 0) +speed:setFormula(0.7, 0, 0.7, 0) combat:addCondition(speed) local exhaustHealGroup = Condition(CONDITION_SPELLGROUPCOOLDOWN) diff --git a/data-canary/scripts/spells/support/swift_foot.lua b/data-canary/scripts/spells/support/swift_foot.lua index 803f6980cd8..249494f38ca 100644 --- a/data-canary/scripts/spells/support/swift_foot.lua +++ b/data-canary/scripts/spells/support/swift_foot.lua @@ -8,7 +8,7 @@ combat:addCondition(exhaust) local condition = Condition(CONDITION_HASTE) condition:setParameter(CONDITION_PARAM_TICKS, 10000) -condition:setFormula(0.8, -72, 0.8, -72) +condition:setFormula(1.8, 72, 1.8, 72) combat:addCondition(condition) local exhaustAttackGroup = Condition(CONDITION_SPELLGROUPCOOLDOWN) diff --git a/data-otservbr-global/scripts/spells/support/sharpshooter.lua b/data-otservbr-global/scripts/spells/support/sharpshooter.lua index 1003128a6c4..568fec86e00 100644 --- a/data-otservbr-global/scripts/spells/support/sharpshooter.lua +++ b/data-otservbr-global/scripts/spells/support/sharpshooter.lua @@ -4,9 +4,9 @@ local combat = Combat() combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_GREEN) combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) -local speed = Condition(CONDITION_PARALYZE) +local speed = Condition(CONDITION_HASTE) speed:setParameter(CONDITION_PARAM_TICKS, spellDuration) -speed:setFormula(-0.7, 56, -0.7, 56) +speed:setFormula(0.7, 0, 0.7, 0) combat:addCondition(speed) local exhaustHealGroup = Condition(CONDITION_SPELLGROUPCOOLDOWN) diff --git a/data-otservbr-global/scripts/spells/support/swift_foot.lua b/data-otservbr-global/scripts/spells/support/swift_foot.lua index 14e3e5b85f4..5984e2cf250 100644 --- a/data-otservbr-global/scripts/spells/support/swift_foot.lua +++ b/data-otservbr-global/scripts/spells/support/swift_foot.lua @@ -6,7 +6,7 @@ combat:setParameter(COMBAT_PARAM_AGGRESSIVE, 0) local condition = Condition(CONDITION_HASTE) condition:setParameter(CONDITION_PARAM_TICKS, spellDuration) -condition:setFormula(0.8, -72, 0.8, -72) +condition:setFormula(1.8, 72, 1.8, 72) combat:addCondition(condition) local spell = Spell("instant") From 6b1d52a7e08fd62d4b40561d32a8c9224bdcbeaa Mon Sep 17 00:00:00 2001 From: Sorairei <79299476+Sorairei@users.noreply.github.com> Date: Sat, 16 Dec 2023 15:12:05 -0600 Subject: [PATCH 04/28] fix: oberon arguments (#2021) When we reply to oberon ULTAH SALID'AR, ESDO LO! we respond with: SEHWO ASIMO, TOLIDO ESD this argument does not have the ! ![oberon](https://github.com/opentibiabr/canary/assets/79299476/6e150ef1-95bd-4f44-8d9f-abdbb0aae356) --- .../the_secret_library/bosses/grand_master_oberon_functions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/monster/quests/the_secret_library/bosses/grand_master_oberon_functions.lua b/data-otservbr-global/monster/quests/the_secret_library/bosses/grand_master_oberon_functions.lua index af9c6f0218a..86714879320 100644 --- a/data-otservbr-global/monster/quests/the_secret_library/bosses/grand_master_oberon_functions.lua +++ b/data-otservbr-global/monster/quests/the_secret_library/bosses/grand_master_oberon_functions.lua @@ -24,7 +24,7 @@ GrandMasterOberonResponses = { [6] = { msg = "Excuse me but I still do not get the message!", msg2 = oberonOthersMessages[2] }, [7] = { msg = "Dare strike up a Minnesang and you will receive your last accolade!", msg2 = oberonOthersMessages[1] }, [8] = { msg = "Then why are we fighting alone right now?", msg2 = oberonOthersMessages[2] }, - [9] = { msg = "SEHWO ASIMO, TOLIDO ESD!", msg2 = oberonOthersMessages[2] }, + [9] = { msg = "SEHWO ASIMO, TOLIDO ESD", msg2 = oberonOthersMessages[2] }, } GrandMasterOberonConfig = { From 6219e9ec8324d5f8d465fe550e8a390d6027b043 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Sat, 16 Dec 2023 13:13:38 -0800 Subject: [PATCH 05/28] feat: the monster fight mechanics (#2001) --- .../scripts/movements/teleport/oskayaat.lua | 33 +++ .../cradle_of_monsters/the_monster_fight.lua | 234 ++++++++++++++++++ .../cradle_of_monsters/the_monster_lever.lua | 28 +++ 3 files changed, 295 insertions(+) create mode 100644 data-otservbr-global/scripts/movements/teleport/oskayaat.lua create mode 100644 data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_fight.lua create mode 100644 data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_lever.lua diff --git a/data-otservbr-global/scripts/movements/teleport/oskayaat.lua b/data-otservbr-global/scripts/movements/teleport/oskayaat.lua new file mode 100644 index 00000000000..4a5ac5683ac --- /dev/null +++ b/data-otservbr-global/scripts/movements/teleport/oskayaat.lua @@ -0,0 +1,33 @@ +SimpleTeleport(Position(33028, 32953, 8), Position(33042, 32950, 9)) +SimpleTeleport(Position(33043, 32950, 9), Position(33028, 32952, 8)) + +local wallTeleport = Action() +function wallTeleport.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if player:getPosition().y < 32915 then + player:teleportTo(Position(33038, 32916, 9)) + else + player:teleportTo(Position(33038, 32914, 9)) + end + return true +end + +wallTeleport:position(Position(33038, 32915, 9)) +wallTeleport:register() + +local boatExit = Action() +function boatExit.onUse(player, item, fromPosition, target, toPosition, isHotkey) + player:teleportTo(Position(33176, 32882, 7)) + return true +end + +boatExit:position(Position(33069, 32915, 7)) +boatExit:register() + +local boatEntry = Action() +function boatEntry.onUse(player, item, fromPosition, target, toPosition, isHotkey) + player:teleportTo(Position(33069, 32916, 7)) + return true +end + +boatEntry:position(Position(33176, 32883, 7)) +boatEntry:register() diff --git a/data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_fight.lua b/data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_fight.lua new file mode 100644 index 00000000000..ac10eead616 --- /dev/null +++ b/data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_fight.lua @@ -0,0 +1,234 @@ +local bossZone = Zone("boss.the-monster") + +local puddleId = 42075 +local jailBarsId = 2184 + +local encounter = Encounter("The Monster", { + zone = bossZone, + timeToSpawnMonsters = "10ms", +}) + +local function freeMonster() + local tile = Tile(Position(33844, 32591, 12)) + if tile then + while true do + local item = tile:getItemById(jailBarsId) + if item then + item:remove() + else + break + end + end + end +end + +function encounter:onReset(position) + encounter:removeMonsters() + freeMonster() +end + +encounter:addRemoveMonsters():autoAdvance() +encounter + :addStage({ + start = function() + Game.createItem(jailBarsId, 1, Position(33844, 32591, 12)) + end, + }) + :autoAdvance() + +encounter:addSpawnMonsters({ + { + name = "Doctor Marrow", + event = "fight.the-monster.DoctorMarrowHealth", + positions = { + Position(33838, 32591, 12), + }, + spawn = function(monster) + monster:setInvulnerable() + end, + }, + { + name = "The Monster", + event = { "fight.the-monster.TheMonsterHealth", "fight.the-monster.TheMonsterDeath" }, + positions = { + Position(33845, 32591, 12), + }, + spawn = function(monster) + monster:setIcon("the-monster", CreatureIconCategory_Quests, CreatureIconQuests_PurpleShield, 20) + end, + }, + { + name = "Antenna", + event = "fight.the-monster.AntennaDeath", + positions = { + Position(33834, 32589, 12), + Position(33840, 32589, 12), + Position(33834, 32593, 12), + Position(33840, 32593, 12), + }, + }, +}) + +encounter:addStage({ + start = function() + local monsters = encounter:getZone():getMonstersByName("Doctor Marrow") + if not monsters or #monsters == 0 then + return false + end + local doctor = monsters[1] + doctor:removeInvulnerable() + end, +}) + +encounter:addStage({ + start = function() + freeMonster() + end, +}) + +encounter:register() + +local spawnContainers = GlobalEvent("fight.the-monster.containers.onThink") +function spawnContainers.onThink(interval, lastExecution) + return true +end + +spawnContainers:interval(4000) +spawnContainers:register() + +local doctorHealth = CreatureEvent("fight.the-monster.DoctorMarrowHealth") +function doctorHealth.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType) + if not creature then + return primaryDamage, primaryType, secondaryDamage, secondaryType + end + local newHealth = creature:getHealth() - primaryDamage - secondaryDamage + if newHealth <= creature:getMaxHealth() * 0.5 then + creature:setHealth(creature:getMaxHealth()) + creature:remove() + encounter:nextStage() + return false + end + return primaryDamage, primaryType, secondaryDamage, secondaryType +end + +doctorHealth:register() + +local antennaDeath = CreatureEvent("fight.the-monster.AntennaDeath") +function antennaDeath.onDeath() + -- The monster count is only updated AFTER the event is called, so we need to subtract 1 + local count = encounter:countMonsters("antenna") - 1 + if count == 0 then + encounter:nextStage() + end +end + +antennaDeath:register() + +local alchemistContainerDeath = CreatureEvent("fight.the-monster.AlchemistContainerDeath") +function alchemistContainerDeath.onDeath(creature) + local directions = { DIRECTION_NORTH, DIRECTION_EAST, DIRECTION_SOUTH, DIRECTION_WEST } + for _, direction in ipairs(directions) do + local position = creature:getPosition() + position:getNextPosition(direction) + local tile = Tile(position) + if tile:isWalkable(false, false, false, true, true) then + local item = Game.createItem(puddleId, 1, position) + item:decay() + break + end + end +end + +alchemistContainerDeath:register() + +local alchemistContainerSpawns = GlobalEvent("fight.the-monster.containers.alchemist.onThink") +local alchemistContainerPositions = { + { x = 33834, y = 32585, z = 12 }, + { x = 33840, y = 32585, z = 12 }, + { x = 33845, y = 32587, z = 12 }, + { x = 33845, y = 32595, z = 12 }, + { x = 33840, y = 32597, z = 12 }, + { x = 33834, y = 32597, z = 12 }, + { x = 33829, y = 32595, z = 12 }, + { x = 33829, y = 32592, z = 12 }, + { x = 33829, y = 32590, z = 12 }, + { x = 33829, y = 32587, z = 12 }, +} + +function alchemistContainerSpawns.onThink() + for _, position in ipairs(alchemistContainerPositions) do + local tile = Tile(position) + if tile and tile:getCreatureCount() == 0 then + local corpse = tile:getItemById(39949) + if corpse then + corpse:remove() + end + local monster = Game.createMonster("alchemist container", position) + if monster then + monster:registerEvent("fight.the-monster.AlchemistContainerDeath") + end + end + end + return true +end + +alchemistContainerSpawns:interval(10000) +alchemistContainerSpawns:register() + +local function getShields(creature) + local currentIcon = creature:getIcon("the-monster") + if not currentIcon or currentIcon.category ~= CreatureIconCategory_Quests or currentIcon.icon ~= CreatureIconQuests_PurpleShield then + return 0 + end + if currentIcon.count <= 0 then + creature:removeIcon("magma-bubble") + return 0 + end + return currentIcon.count +end + +local function setShields(creature, count) + if count <= 0 then + creature:removeIcon("the-monster") + return + end + creature:setIcon("the-monster", CreatureIconCategory_Quests, CreatureIconQuests_PurpleShield, count) +end + +local monsterHealth = CreatureEvent("fight.the-monster.TheMonsterHealth") +function monsterHealth.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType) + if not creature then + return primaryDamage, primaryType, secondaryDamage, secondaryType + end + local shields = getShields(creature) + local multiplier = 1 - shields * 0.05 + return primaryDamage * multiplier, primaryType, secondaryDamage * multiplier, secondaryType +end + +monsterHealth:register() + +local monsterDeath = CreatureEvent("fight.the-monster.TheMonsterDeath") +function monsterDeath.onDeath(creature) + encounter:nextStage() +end + +monsterDeath:register() + +local puddleStepIn = MoveEvent("fight.the-monster.PuddleStepIn") +function puddleStepIn.onStepIn(creature, item, position, fromPosition) + if not creature or creature:getName() ~= "The Monster" then + return true + end + item:remove() + local current = getShields(creature) + if current <= 0 then + return true + end + setShields(creature, current - 1) + creature:getPosition():sendMagicEffect(CONST_ME_ORANGE_ENERGY_SPARK) + return true +end + +puddleStepIn:type("stepin") +puddleStepIn:id(puddleId) +puddleStepIn:register() diff --git a/data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_lever.lua b/data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_lever.lua new file mode 100644 index 00000000000..71528272d4b --- /dev/null +++ b/data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_lever.lua @@ -0,0 +1,28 @@ +local config = { + boss = { name = "The Monster" }, + encounter = "The Monster", + requiredLevel = 250, + + playerPositions = { + { pos = { x = 33812, y = 32584, z = 12 }, teleport = { x = 33831, y = 32591, z = 12 }, effect = CONST_ME_TELEPORT }, + { pos = { x = 33811, y = 32584, z = 12 }, teleport = { x = 33831, y = 32591, z = 12 }, effect = CONST_ME_TELEPORT }, + { pos = { x = 33810, y = 32584, z = 12 }, teleport = { x = 33831, y = 32591, z = 12 }, effect = CONST_ME_TELEPORT }, + { pos = { x = 33809, y = 32584, z = 12 }, teleport = { x = 33831, y = 32591, z = 12 }, effect = CONST_ME_TELEPORT }, + { pos = { x = 33808, y = 32584, z = 12 }, teleport = { x = 33831, y = 32591, z = 12 }, effect = CONST_ME_TELEPORT }, + }, + specPos = { + from = { x = 33828, y = 32584, z = 12 }, + to = { x = 33846, y = 32598, z = 12 }, + }, + exitTeleporter = { x = 33829, y = 32591, z = 12 }, + exit = { x = 33810, y = 32587, z = 12 }, +} + +local lever = BossLever(config) +lever:position({ x = 33813, y = 32584, z = 12 }) +lever:register() + +-- Entrance to lever room +SimpleTeleport({ x = 33792, y = 32581, z = 12 }, { x = 33806, y = 32584, z = 12 }) +-- Exit from lever room +SimpleTeleport({ x = 33804, y = 32584, z = 12 }, { x = 33792, y = 32579, z = 12 }) From 3561589d18ddb4bb48031064d52f88ffa6ae6b22 Mon Sep 17 00:00:00 2001 From: sebbesiren <35768829+sebbesiren@users.noreply.github.com> Date: Sun, 17 Dec 2023 06:16:54 +0100 Subject: [PATCH 06/28] fix: speed change issues (#2032) Speed changes was set to -800 on all places. I replaced them with -100 to not make people stand still. These values may not be correct but will be better than -800. --- data-canary/monster/demons/fury.lua | 2 +- data-canary/monster/magicals/guzzlemaw.lua | 2 +- data-otservbr-global/monster/bosses/the_abomination.lua | 2 +- data-otservbr-global/monster/bosses/the_collector.lua | 2 +- data-otservbr-global/monster/constructs/ice_golem.lua | 2 +- data-otservbr-global/monster/constructs/infected_weeper.lua | 2 +- data-otservbr-global/monster/constructs/lava_golem.lua | 2 +- data-otservbr-global/monster/constructs/magma_crawler.lua | 2 +- data-otservbr-global/monster/constructs/orewalker.lua | 2 +- data-otservbr-global/monster/constructs/weeper.lua | 2 +- data-otservbr-global/monster/demons/dawnfire_asura.lua | 2 +- data-otservbr-global/monster/demons/fury.lua | 2 +- data-otservbr-global/monster/demons/midnight_asura.lua | 2 +- data-otservbr-global/monster/demons/plaguesmith.lua | 2 +- data-otservbr-global/monster/dragons/ghastly_dragon.lua | 2 +- data-otservbr-global/monster/elementals/ironblight.lua | 2 +- data-otservbr-global/monster/humanoids/dworc_voodoomaster.lua | 2 +- data-otservbr-global/monster/humanoids/lost_berserker.lua | 2 +- data-otservbr-global/monster/magicals/choking_fear.lua | 2 +- data-otservbr-global/monster/magicals/guzzlemaw.lua | 2 +- data-otservbr-global/monster/magicals/shock_head.lua | 2 +- data-otservbr-global/monster/plants/carniphila.lua | 2 +- data-otservbr-global/monster/quests/ancient_tombs/thalas.lua | 2 +- .../monster/quests/cults_of_tibia/animated_guzzlemaw.lua | 2 +- .../monster/quests/cults_of_tibia/bosses/essence_of_malice.lua | 2 +- .../monster/quests/forgotten_knowledge/thorn_minion.lua | 2 +- .../monster/quests/pits_of_inferno/the_plasmother.lua | 2 +- .../monster/quests/the_dream_courts/lucifuga_aranea.lua | 2 +- .../monster/quests/the_elemental_spheres/ice_overlord.lua | 2 +- .../the_secret_library/bosses/the_scourge_of_oblivion.lua | 2 +- data-otservbr-global/monster/undeads/lost_soul.lua | 2 +- data-otservbr-global/monster/vermins/crystal_spider.lua | 2 +- 32 files changed, 32 insertions(+), 32 deletions(-) diff --git a/data-canary/monster/demons/fury.lua b/data-canary/monster/demons/fury.lua index f12b4ba628f..7c1946d7991 100644 --- a/data-canary/monster/demons/fury.lua +++ b/data-canary/monster/demons/fury.lua @@ -108,7 +108,7 @@ monster.attacks = { -- {name ="fury skill reducer", interval = 2000, chance = 5, target = false}, { name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = -120, maxDamage = -300, radius = 3, effect = CONST_ME_HITAREA, target = false }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -125, maxDamage = -250, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_SMALLCLOUDS, target = false }, - { name = "speed", interval = 2000, chance = 15, speedChange = -800, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_SMALLCLOUDS, target = false, duration = 30000 }, + { name = "speed", interval = 2000, chance = 15, speedChange = -100, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_SMALLCLOUDS, target = false, duration = 30000 }, } monster.defenses = { diff --git a/data-canary/monster/magicals/guzzlemaw.lua b/data-canary/monster/magicals/guzzlemaw.lua index 8d3902203d1..8e38d459779 100644 --- a/data-canary/monster/magicals/guzzlemaw.lua +++ b/data-canary/monster/magicals/guzzlemaw.lua @@ -114,7 +114,7 @@ monster.attacks = { { name = "condition", type = CONDITION_BLEEDING, interval = 2000, chance = 10, minDamage = -500, maxDamage = -1000, radius = 3, target = false }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -900, length = 8, spread = 3, effect = CONST_ME_EXPLOSIONAREA, target = false }, { name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -500, radius = 2, shootEffect = CONST_ANI_LARGEROCK, effect = CONST_ME_STONES, target = true }, - { name = "speed", interval = 2000, chance = 15, speedChange = -800, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 }, + { name = "speed", interval = 2000, chance = 15, speedChange = -100, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -800, length = 8, spread = 3, effect = CONST_ME_MAGIC_RED, target = false }, } diff --git a/data-otservbr-global/monster/bosses/the_abomination.lua b/data-otservbr-global/monster/bosses/the_abomination.lua index e6ba0b1ae06..5d0c02c4b14 100644 --- a/data-otservbr-global/monster/bosses/the_abomination.lua +++ b/data-otservbr-global/monster/bosses/the_abomination.lua @@ -92,7 +92,7 @@ monster.loot = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, skill = 90, attack = 120 }, - { name = "speed", interval = 1000, chance = 12, speedChange = -800, radius = 6, effect = CONST_ME_POISONAREA, target = false, duration = 10000 }, + { name = "speed", interval = 1000, chance = 12, speedChange = -100, radius = 6, effect = CONST_ME_POISONAREA, target = false, duration = 10000 }, { name = "combat", interval = 1000, chance = 9, type = COMBAT_EARTHDAMAGE, minDamage = -200, maxDamage = -650, radius = 4, effect = CONST_ME_POISONAREA, target = false }, { name = "combat", interval = 1000, chance = 11, type = COMBAT_LIFEDRAIN, minDamage = -400, maxDamage = -900, radius = 4, shootEffect = CONST_ANI_POISON, effect = CONST_ME_SOUND_GREEN, target = true }, { name = "combat", interval = 2000, chance = 19, type = COMBAT_PHYSICALDAMAGE, minDamage = -350, maxDamage = -850, length = 7, spread = 0, shootEffect = CONST_ANI_POISON, effect = CONST_ME_HITBYPOISON, target = false }, diff --git a/data-otservbr-global/monster/bosses/the_collector.lua b/data-otservbr-global/monster/bosses/the_collector.lua index b3fa70f85c6..b63f6c8ace3 100644 --- a/data-otservbr-global/monster/bosses/the_collector.lua +++ b/data-otservbr-global/monster/bosses/the_collector.lua @@ -67,7 +67,7 @@ monster.loot = {} monster.attacks = { { name = "melee", interval = 2000, chance = 100, skill = 100, attack = 40 }, - { name = "speed", interval = 1000, chance = 13, speedChange = -800, length = 8, spread = 0, effect = CONST_ME_ENERGYHIT, target = false, duration = 20000 }, + { name = "speed", interval = 1000, chance = 13, speedChange = -100, length = 8, spread = 0, effect = CONST_ME_ENERGYHIT, target = false, duration = 20000 }, { name = "combat", interval = 1000, chance = 15, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -85, range = 7, shootEffect = CONST_ANI_LARGEROCK, target = false }, { name = "melee", interval = 2000, chance = 15, minDamage = -10, maxDamage = -80 }, } diff --git a/data-otservbr-global/monster/constructs/ice_golem.lua b/data-otservbr-global/monster/constructs/ice_golem.lua index e5c226bae5c..0ab245558c1 100644 --- a/data-otservbr-global/monster/constructs/ice_golem.lua +++ b/data-otservbr-global/monster/constructs/ice_golem.lua @@ -94,7 +94,7 @@ monster.loot = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -220 }, - { name = "speed", interval = 1000, chance = 13, speedChange = -800, length = 8, spread = 0, effect = CONST_ME_ENERGYHIT, target = false, duration = 20000 }, + { name = "speed", interval = 1000, chance = 13, speedChange = -100, length = 8, spread = 0, effect = CONST_ME_ENERGYHIT, target = false, duration = 20000 }, { name = "combat", interval = 1000, chance = 15, type = COMBAT_ICEDAMAGE, minDamage = -50, maxDamage = -85, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = false }, { name = "ice golem skill reducer", interval = 2000, chance = 10, target = false }, } diff --git a/data-otservbr-global/monster/constructs/infected_weeper.lua b/data-otservbr-global/monster/constructs/infected_weeper.lua index e302618f3eb..4bcccd5b9fa 100644 --- a/data-otservbr-global/monster/constructs/infected_weeper.lua +++ b/data-otservbr-global/monster/constructs/infected_weeper.lua @@ -91,7 +91,7 @@ monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -280 }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -250, maxDamage = -700, length = 5, spread = 3, effect = CONST_ME_MAGIC_RED, target = false }, { name = "combat", interval = 2000, chance = 30, type = COMBAT_FIREDAMAGE, minDamage = -80, maxDamage = -250, radius = 3, effect = CONST_ME_HITBYFIRE, target = false }, - { name = "speed", interval = 2000, chance = 10, speedChange = -800, length = 5, spread = 3, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 }, + { name = "speed", interval = 2000, chance = 10, speedChange = -100, length = 5, spread = 3, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/constructs/lava_golem.lua b/data-otservbr-global/monster/constructs/lava_golem.lua index aed9ad864c0..38db564d494 100644 --- a/data-otservbr-global/monster/constructs/lava_golem.lua +++ b/data-otservbr-global/monster/constructs/lava_golem.lua @@ -112,7 +112,7 @@ monster.attacks = { { name = "combat", interval = 2000, chance = 10, type = COMBAT_MANADRAIN, minDamage = -600, maxDamage = -1300, length = 8, spread = 3, effect = CONST_ME_MORTAREA, target = false }, { name = "lava golem soulfire", interval = 2000, chance = 15, target = false }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_FIREDAMAGE, minDamage = -220, maxDamage = -350, radius = 4, effect = CONST_ME_FIREAREA, target = true }, - { name = "speed", interval = 2000, chance = 10, speedChange = -800, length = 5, spread = 3, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 }, + { name = "speed", interval = 2000, chance = 10, speedChange = -100, length = 5, spread = 3, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 }, { name = "combat", interval = 2000, chance = 30, type = COMBAT_FIREDAMAGE, minDamage = -280, maxDamage = -350, radius = 3, effect = CONST_ME_HITBYFIRE, target = false }, } diff --git a/data-otservbr-global/monster/constructs/magma_crawler.lua b/data-otservbr-global/monster/constructs/magma_crawler.lua index 39c58ab8838..84ad15af13b 100644 --- a/data-otservbr-global/monster/constructs/magma_crawler.lua +++ b/data-otservbr-global/monster/constructs/magma_crawler.lua @@ -111,7 +111,7 @@ monster.attacks = { { name = "magma crawler soulfire", interval = 2000, chance = 20, target = false }, { name = "soulfire rune", interval = 2000, chance = 10, target = false }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_FIREDAMAGE, minDamage = -140, maxDamage = -180, radius = 3, effect = CONST_ME_HITBYFIRE, target = false }, - { name = "speed", interval = 2000, chance = 10, speedChange = -800, radius = 2, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 }, + { name = "speed", interval = 2000, chance = 10, speedChange = -100, radius = 2, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/constructs/orewalker.lua b/data-otservbr-global/monster/constructs/orewalker.lua index 767b8a049ae..0ebf08181ff 100644 --- a/data-otservbr-global/monster/constructs/orewalker.lua +++ b/data-otservbr-global/monster/constructs/orewalker.lua @@ -113,7 +113,7 @@ monster.attacks = { -- poison { name = "condition", type = CONDITION_POISON, interval = 2000, chance = 10, minDamage = -800, maxDamage = -1080, radius = 3, shootEffect = CONST_ANI_SMALLEARTH, effect = CONST_ME_SMALLPLANTS, target = true }, { name = "drunk", interval = 2000, chance = 15, radius = 4, effect = CONST_ME_SOUND_PURPLE, target = false, duration = 6000 }, - { name = "speed", interval = 2000, chance = 15, speedChange = -800, radius = 2, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 }, + { name = "speed", interval = 2000, chance = 15, speedChange = -100, radius = 2, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/constructs/weeper.lua b/data-otservbr-global/monster/constructs/weeper.lua index 75a8d7ebb42..63dfa572727 100644 --- a/data-otservbr-global/monster/constructs/weeper.lua +++ b/data-otservbr-global/monster/constructs/weeper.lua @@ -101,7 +101,7 @@ monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -450 }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_FIREDAMAGE, minDamage = -400, maxDamage = -1000, length = 8, spread = 0, effect = CONST_ME_FIREATTACK, target = false }, { name = "combat", interval = 3000, chance = 100, type = COMBAT_FIREDAMAGE, minDamage = -80, maxDamage = -250, radius = 3, effect = CONST_ME_HITBYFIRE, target = false }, - { name = "speed", interval = 2000, chance = 10, speedChange = -800, length = 5, spread = 0, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 }, + { name = "speed", interval = 2000, chance = 10, speedChange = -100, length = 5, spread = 0, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/demons/dawnfire_asura.lua b/data-otservbr-global/monster/demons/dawnfire_asura.lua index ac6fb0d5308..a3b50cfef84 100644 --- a/data-otservbr-global/monster/demons/dawnfire_asura.lua +++ b/data-otservbr-global/monster/demons/dawnfire_asura.lua @@ -110,7 +110,7 @@ monster.attacks = { { name = "combat", interval = 3700, chance = 17, type = COMBAT_LIFEDRAIN, minDamage = -100, maxDamage = -300, length = 8, spread = 0, effect = CONST_ME_PURPLEENERGY, target = false }, { name = "combat", interval = 3200, chance = 25, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -350, radius = 4, range = 5, target = true, effect = CONST_ME_MORTAREA }, { name = "combat", interval = 2700, chance = 20, type = COMBAT_FIREDAMAGE, minDamage = -95, maxDamage = -180, range = 3, shootEffect = CONST_ANI_FIRE, target = true }, - { name = "speed", interval = 2000, chance = 20, speedChange = -800, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true, duration = 15000 }, + { name = "speed", interval = 2000, chance = 20, speedChange = -100, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true, duration = 15000 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/demons/fury.lua b/data-otservbr-global/monster/demons/fury.lua index cdaad15cf8f..cfa187717a8 100644 --- a/data-otservbr-global/monster/demons/fury.lua +++ b/data-otservbr-global/monster/demons/fury.lua @@ -107,7 +107,7 @@ monster.attacks = { { name = "fury skill reducer", interval = 2000, chance = 5, target = false }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = -120, maxDamage = -300, radius = 3, effect = CONST_ME_HITAREA, target = false }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -125, maxDamage = -250, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_SMALLCLOUDS, target = false }, - { name = "speed", interval = 2000, chance = 15, speedChange = -800, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_SMALLCLOUDS, target = false, duration = 30000 }, + { name = "speed", interval = 2000, chance = 15, speedChange = -100, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_SMALLCLOUDS, target = false, duration = 30000 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/demons/midnight_asura.lua b/data-otservbr-global/monster/demons/midnight_asura.lua index c3b0b3791e6..f73ceb8e519 100644 --- a/data-otservbr-global/monster/demons/midnight_asura.lua +++ b/data-otservbr-global/monster/demons/midnight_asura.lua @@ -118,7 +118,7 @@ monster.attacks = { { name = "combat", interval = 4100, chance = 27, type = COMBAT_DEATHDAMAGE, minDamage = -150, maxDamage = -300, length = 8, spread = 0, effect = CONST_ME_MORTAREA, target = false }, { name = "combat", interval = 2700, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -50, maxDamage = -200, range = 5, shootEffect = CONST_ANI_SUDDENDEATH, target = true }, { name = "combat", interval = 3100, chance = 15, type = COMBAT_ENERGYDAMAGE, minDamage = -50, maxDamage = -100, range = 1, shootEffect = CONST_ANI_ENERGY, target = true }, - { name = "speed", interval = 2000, chance = 20, speedChange = -800, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true, duration = 15000 }, + { name = "speed", interval = 2000, chance = 20, speedChange = -100, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true, duration = 15000 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/demons/plaguesmith.lua b/data-otservbr-global/monster/demons/plaguesmith.lua index 6fbc96b533d..28470a0500a 100644 --- a/data-otservbr-global/monster/demons/plaguesmith.lua +++ b/data-otservbr-global/monster/demons/plaguesmith.lua @@ -115,7 +115,7 @@ monster.attacks = { { name = "melee", interval = 1500, chance = 100, minDamage = 0, maxDamage = -539, condition = { type = CONDITION_POISON, totalDamage = 200, interval = 4000 } }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -60, maxDamage = -114, radius = 4, effect = CONST_ME_POISONAREA, target = false }, { name = "plaguesmith wave", interval = 2000, chance = 10, minDamage = -100, maxDamage = -350, target = false }, - { name = "speed", interval = 2000, chance = 15, speedChange = -800, radius = 4, effect = CONST_ME_POISONAREA, target = false, duration = 30000 }, + { name = "speed", interval = 2000, chance = 15, speedChange = -100, radius = 4, effect = CONST_ME_POISONAREA, target = false, duration = 30000 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/dragons/ghastly_dragon.lua b/data-otservbr-global/monster/dragons/ghastly_dragon.lua index a4e989ea21f..11ade52fea9 100644 --- a/data-otservbr-global/monster/dragons/ghastly_dragon.lua +++ b/data-otservbr-global/monster/dragons/ghastly_dragon.lua @@ -115,7 +115,7 @@ monster.attacks = { { name = "combat", interval = 2000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -80, maxDamage = -230, range = 7, effect = CONST_ME_MAGIC_RED, target = true }, { name = "ghastly dragon wave", interval = 2000, chance = 10, minDamage = -120, maxDamage = -250, target = false }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -110, maxDamage = -180, radius = 4, effect = CONST_ME_MORTAREA, target = false }, - { name = "speed", interval = 2000, chance = 20, speedChange = -800, range = 7, effect = CONST_ME_SMALLCLOUDS, target = true, duration = 30000 }, + { name = "speed", interval = 2000, chance = 20, speedChange = -100, range = 7, effect = CONST_ME_SMALLCLOUDS, target = true, duration = 30000 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/elementals/ironblight.lua b/data-otservbr-global/monster/elementals/ironblight.lua index 593069318ed..a7fba4ab036 100644 --- a/data-otservbr-global/monster/elementals/ironblight.lua +++ b/data-otservbr-global/monster/elementals/ironblight.lua @@ -110,7 +110,7 @@ monster.attacks = { { name = "condition", type = CONDITION_POISON, interval = 2000, chance = 10, minDamage = -460, maxDamage = -480, radius = 6, shootEffect = CONST_ANI_POISON, effect = CONST_ME_POISONAREA, target = false }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_ICEDAMAGE, minDamage = -260, maxDamage = -350, length = 7, spread = 0, shootEffect = CONST_ANI_ICE, effect = CONST_ME_ICEATTACK, target = false }, { name = "combat", interval = 2000, chance = 20, type = COMBAT_EARTHDAMAGE, minDamage = -180, maxDamage = -250, radius = 2, shootEffect = CONST_ANI_GREENSTAR, effect = CONST_ME_BIGPLANTS, target = true }, - { name = "speed", interval = 2000, chance = 10, speedChange = -800, length = 5, spread = 0, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 }, + { name = "speed", interval = 2000, chance = 10, speedChange = -100, length = 5, spread = 0, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/humanoids/dworc_voodoomaster.lua b/data-otservbr-global/monster/humanoids/dworc_voodoomaster.lua index 9a9770186f3..c614fc2cbbc 100644 --- a/data-otservbr-global/monster/humanoids/dworc_voodoomaster.lua +++ b/data-otservbr-global/monster/humanoids/dworc_voodoomaster.lua @@ -93,7 +93,7 @@ monster.loot = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -20 }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -40, range = 1, effect = CONST_ME_MAGIC_RED, target = false }, - { name = "speed", interval = 2000, chance = 10, speedChange = -800, range = 7, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 }, + { name = "speed", interval = 2000, chance = 10, speedChange = -100, range = 7, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 }, { name = "drunk", interval = 2000, chance = 10, range = 7, shootEffect = CONST_ANI_ENERGY, effect = CONST_ME_TELEPORT, target = false }, { name = "outfit", interval = 2000, chance = 10, range = 7, effect = CONST_ME_MAGIC_BLUE, target = false, duration = 5000, outfitMonster = "chicken" }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_EARTHDAMAGE, minDamage = -6, maxDamage = -18, radius = 6, effect = CONST_ME_GREEN_RINGS, target = false }, diff --git a/data-otservbr-global/monster/humanoids/lost_berserker.lua b/data-otservbr-global/monster/humanoids/lost_berserker.lua index cd48787df95..fb161e8cd63 100644 --- a/data-otservbr-global/monster/humanoids/lost_berserker.lua +++ b/data-otservbr-global/monster/humanoids/lost_berserker.lua @@ -112,7 +112,7 @@ monster.attacks = { { name = "combat", interval = 2000, chance = 15, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -300, range = 7, shootEffect = CONST_ANI_WHIRLWINDAXE, target = false }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -250, range = 7, radius = 3, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_EXPLOSIONAREA, target = true }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_MANADRAIN, minDamage = -150, maxDamage = -250, radius = 5, effect = CONST_ME_MAGIC_RED, target = false }, - { name = "speed", interval = 2000, chance = 10, speedChange = -800, radius = 2, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 }, + { name = "speed", interval = 2000, chance = 10, speedChange = -100, radius = 2, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 }, { name = "drunk", interval = 2000, chance = 10, radius = 4, effect = CONST_ME_STUN, target = true, duration = 6000 }, } diff --git a/data-otservbr-global/monster/magicals/choking_fear.lua b/data-otservbr-global/monster/magicals/choking_fear.lua index 16361bab0e9..9e95ff23aa9 100644 --- a/data-otservbr-global/monster/magicals/choking_fear.lua +++ b/data-otservbr-global/monster/magicals/choking_fear.lua @@ -109,7 +109,7 @@ monster.attacks = { -- poison { name = "condition", type = CONDITION_POISON, interval = 2000, chance = 10, minDamage = -700, maxDamage = -900, length = 5, spread = 0, effect = CONST_ME_HITBYPOISON, target = false }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -300, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true }, - { name = "speed", interval = 2000, chance = 20, speedChange = -800, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true, duration = 15000 }, + { name = "speed", interval = 2000, chance = 20, speedChange = -100, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true, duration = 15000 }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_MANADRAIN, minDamage = -130, maxDamage = -300, radius = 4, effect = CONST_ME_SOUND_RED, target = false }, { name = "choking fear drown", interval = 2000, chance = 20, target = false }, { name = "combat", interval = 2000, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -250, maxDamage = -500, radius = 4, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_MORTAREA, target = true }, diff --git a/data-otservbr-global/monster/magicals/guzzlemaw.lua b/data-otservbr-global/monster/magicals/guzzlemaw.lua index d402a6921da..9ea1f726827 100644 --- a/data-otservbr-global/monster/magicals/guzzlemaw.lua +++ b/data-otservbr-global/monster/magicals/guzzlemaw.lua @@ -113,7 +113,7 @@ monster.attacks = { { name = "condition", type = CONDITION_BLEEDING, interval = 2000, chance = 10, minDamage = -500, maxDamage = -1000, radius = 3, effect = CONST_ME_DRAWBLOOD, target = false }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -900, length = 8, spread = 0, effect = CONST_ME_EXPLOSIONAREA, target = false }, { name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -500, radius = 2, shootEffect = CONST_ANI_LARGEROCK, effect = CONST_ME_STONES, target = true }, - { name = "speed", interval = 2000, chance = 15, speedChange = -800, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 }, + { name = "speed", interval = 2000, chance = 15, speedChange = -100, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -800, length = 8, spread = 0, effect = CONST_ME_MAGIC_RED, target = false }, } diff --git a/data-otservbr-global/monster/magicals/shock_head.lua b/data-otservbr-global/monster/magicals/shock_head.lua index 22c1e30890e..0394008118d 100644 --- a/data-otservbr-global/monster/magicals/shock_head.lua +++ b/data-otservbr-global/monster/magicals/shock_head.lua @@ -86,7 +86,7 @@ monster.loot = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -798 }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -200, maxDamage = -300, length = 5, spread = 2, effect = CONST_ME_BLACKSMOKE, target = false }, - { name = "speed", interval = 2000, chance = 15, speedChange = -800, length = 8, spread = 0, effect = CONST_ME_PURPLEENERGY, target = false, duration = 7500 }, + { name = "speed", interval = 2000, chance = 15, speedChange = -100, length = 8, spread = 0, effect = CONST_ME_PURPLEENERGY, target = false, duration = 7500 }, { name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -350, radius = 4, shootEffect = CONST_ANI_EARTH, effect = CONST_ME_STONES, target = true }, { name = "shock head skill reducer 1", interval = 2000, chance = 5, range = 5, target = false }, { name = "shock head skill reducer 2", interval = 2000, chance = 5, target = false }, diff --git a/data-otservbr-global/monster/plants/carniphila.lua b/data-otservbr-global/monster/plants/carniphila.lua index 4251cde140a..ab52039d6a9 100644 --- a/data-otservbr-global/monster/plants/carniphila.lua +++ b/data-otservbr-global/monster/plants/carniphila.lua @@ -88,7 +88,7 @@ monster.loot = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -100, condition = { type = CONDITION_POISON, totalDamage = 100, interval = 4000 } }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -60, maxDamage = -95, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = false }, - { name = "speed", interval = 2000, chance = 15, speedChange = -800, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = false, duration = 30000 }, + { name = "speed", interval = 2000, chance = 15, speedChange = -100, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = false, duration = 30000 }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_EARTHDAMAGE, minDamage = -40, maxDamage = -130, radius = 3, effect = CONST_ME_POISONAREA, target = false }, } diff --git a/data-otservbr-global/monster/quests/ancient_tombs/thalas.lua b/data-otservbr-global/monster/quests/ancient_tombs/thalas.lua index d6ad2f68864..6f27b9f17f2 100644 --- a/data-otservbr-global/monster/quests/ancient_tombs/thalas.lua +++ b/data-otservbr-global/monster/quests/ancient_tombs/thalas.lua @@ -92,7 +92,7 @@ monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -900 }, { name = "combat", interval = 2000, chance = 25, type = COMBAT_EARTHDAMAGE, minDamage = -150, maxDamage = -650, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_POISONAREA, target = false }, { name = "melee", interval = 3000, chance = 20, minDamage = -150, maxDamage = -650 }, - { name = "speed", interval = 1000, chance = 6, speedChange = -800, range = 7, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 }, + { name = "speed", interval = 1000, chance = 6, speedChange = -100, range = 7, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 }, -- poison { name = "condition", type = CONDITION_POISON, interval = 1000, chance = 15, minDamage = -34, maxDamage = -35, radius = 5, effect = CONST_ME_POISONAREA, target = false }, { name = "combat", interval = 3000, chance = 17, type = COMBAT_EARTHDAMAGE, minDamage = -55, maxDamage = -550, length = 8, spread = 3, effect = CONST_ME_POISONAREA, target = false }, diff --git a/data-otservbr-global/monster/quests/cults_of_tibia/animated_guzzlemaw.lua b/data-otservbr-global/monster/quests/cults_of_tibia/animated_guzzlemaw.lua index 009f8a83209..6d723c594ea 100644 --- a/data-otservbr-global/monster/quests/cults_of_tibia/animated_guzzlemaw.lua +++ b/data-otservbr-global/monster/quests/cults_of_tibia/animated_guzzlemaw.lua @@ -99,7 +99,7 @@ monster.attacks = { { name = "condition", type = CONDITION_BLEEDING, interval = 2000, chance = 10, minDamage = -500, maxDamage = -1000, radius = 3, effect = CONST_ME_DRAWBLOOD, target = false }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -900, length = 8, spread = 0, effect = CONST_ME_EXPLOSIONAREA, target = true }, { name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -500, radius = 2, shootEffect = CONST_ANI_LARGEROCK, effect = CONST_ME_STONES, target = true }, - { name = "speed", interval = 2000, chance = 15, speedChange = -800, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 }, + { name = "speed", interval = 2000, chance = 15, speedChange = -100, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -800, length = 8, spread = 0, effect = CONST_ME_MAGIC_RED, target = false }, } diff --git a/data-otservbr-global/monster/quests/cults_of_tibia/bosses/essence_of_malice.lua b/data-otservbr-global/monster/quests/cults_of_tibia/bosses/essence_of_malice.lua index 26aceb32010..3c9b9b0bba4 100644 --- a/data-otservbr-global/monster/quests/cults_of_tibia/bosses/essence_of_malice.lua +++ b/data-otservbr-global/monster/quests/cults_of_tibia/bosses/essence_of_malice.lua @@ -101,7 +101,7 @@ monster.attacks = { { name = "combat", interval = 2000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -80, maxDamage = -230, range = 7, effect = CONST_ME_MAGIC_RED, target = true }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -120, maxDamage = -250, length = 8, spread = 0, effect = CONST_ME_LOSEENERGY, target = false }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -110, maxDamage = -180, radius = 4, effect = CONST_ME_MORTAREA, target = false }, - { name = "speed", interval = 2000, chance = 20, speedChange = -800, range = 7, effect = CONST_ME_SMALLCLOUDS, target = true, duration = 30000 }, + { name = "speed", interval = 2000, chance = 20, speedChange = -100, range = 7, effect = CONST_ME_SMALLCLOUDS, target = true, duration = 30000 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/quests/forgotten_knowledge/thorn_minion.lua b/data-otservbr-global/monster/quests/forgotten_knowledge/thorn_minion.lua index 8d4bebd6665..abfc779efc1 100644 --- a/data-otservbr-global/monster/quests/forgotten_knowledge/thorn_minion.lua +++ b/data-otservbr-global/monster/quests/forgotten_knowledge/thorn_minion.lua @@ -67,7 +67,7 @@ monster.loot = {} monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = -0, maxDamage = -264 }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -100, maxDamage = -195, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = false }, - { name = "speed", interval = 2000, chance = 15, speedChange = -800, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = false, duration = 30000 }, + { name = "speed", interval = 2000, chance = 15, speedChange = -100, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = false, duration = 30000 }, { name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -200, maxDamage = -280, radius = 4, effect = CONST_ME_GROUNDSHAKER, target = false }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -200, maxDamage = -400, length = 4, spread = 0, effect = CONST_ME_CARNIPHILA, target = false }, } diff --git a/data-otservbr-global/monster/quests/pits_of_inferno/the_plasmother.lua b/data-otservbr-global/monster/quests/pits_of_inferno/the_plasmother.lua index b530d1e96c1..fd98f95e5b0 100644 --- a/data-otservbr-global/monster/quests/pits_of_inferno/the_plasmother.lua +++ b/data-otservbr-global/monster/quests/pits_of_inferno/the_plasmother.lua @@ -89,7 +89,7 @@ monster.loot = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, skill = 30, attack = 50 }, - { name = "speed", interval = 1000, chance = 8, speedChange = -800, radius = 6, effect = CONST_ME_POISONAREA, target = false, duration = 10000 }, + { name = "speed", interval = 1000, chance = 8, speedChange = -100, radius = 6, effect = CONST_ME_POISONAREA, target = false, duration = 10000 }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -200, maxDamage = -350, radius = 4, effect = CONST_ME_POISONAREA, target = false }, { name = "combat", interval = 3000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -200, maxDamage = -530, radius = 4, shootEffect = CONST_ANI_POISON, effect = CONST_ME_HITBYPOISON, target = true }, } diff --git a/data-otservbr-global/monster/quests/the_dream_courts/lucifuga_aranea.lua b/data-otservbr-global/monster/quests/the_dream_courts/lucifuga_aranea.lua index 717a1fe0814..5fbd324c0fd 100644 --- a/data-otservbr-global/monster/quests/the_dream_courts/lucifuga_aranea.lua +++ b/data-otservbr-global/monster/quests/the_dream_courts/lucifuga_aranea.lua @@ -73,7 +73,7 @@ monster.loot = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -250, condition = { type = CONDITION_POISON, totalDamage = 160, interval = 4000 } }, - { name = "speed", interval = 2000, chance = 15, speedChange = -800, range = 7, radius = 6, effect = CONST_ME_POFF, target = false, duration = 15000 }, + { name = "speed", interval = 2000, chance = 15, speedChange = -100, range = 7, radius = 6, effect = CONST_ME_POFF, target = false, duration = 15000 }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_ICEDAMAGE, minDamage = -50, maxDamage = -100, range = 7, shootEffect = CONST_ANI_ICE, effect = CONST_ME_ICEAREA, target = true }, { name = "speed", interval = 2000, chance = 20, speedChange = -600, range = 7, shootEffect = CONST_ANI_SNOWBALL, target = true, duration = 10000 }, } diff --git a/data-otservbr-global/monster/quests/the_elemental_spheres/ice_overlord.lua b/data-otservbr-global/monster/quests/the_elemental_spheres/ice_overlord.lua index 9a65a1c8ef2..44bf1a162e1 100644 --- a/data-otservbr-global/monster/quests/the_elemental_spheres/ice_overlord.lua +++ b/data-otservbr-global/monster/quests/the_elemental_spheres/ice_overlord.lua @@ -74,7 +74,7 @@ monster.loot = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -400 }, - { name = "speed", interval = 2000, chance = 18, speedChange = -800, radius = 6, effect = CONST_ME_ICETORNADO, target = false, duration = 5000 }, + { name = "speed", interval = 2000, chance = 18, speedChange = -100, radius = 6, effect = CONST_ME_ICETORNADO, target = false, duration = 5000 }, { name = "combat", interval = 1000, chance = 9, type = COMBAT_ICEDAMAGE, minDamage = -50, maxDamage = -400, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = true }, } diff --git a/data-otservbr-global/monster/quests/the_secret_library/bosses/the_scourge_of_oblivion.lua b/data-otservbr-global/monster/quests/the_secret_library/bosses/the_scourge_of_oblivion.lua index 491b946adba..bd4f897e03a 100644 --- a/data-otservbr-global/monster/quests/the_secret_library/bosses/the_scourge_of_oblivion.lua +++ b/data-otservbr-global/monster/quests/the_secret_library/bosses/the_scourge_of_oblivion.lua @@ -134,7 +134,7 @@ monster.attacks = { { name = "choking fear drown", interval = 2000, chance = 20, target = false }, { name = "combat", interval = 2000, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -450, maxDamage = -1400, radius = 4, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_MORTAREA, target = true }, { name = "combat", interval = 1000, chance = 10, type = COMBAT_MANADRAIN, minDamage = -800, maxDamage = -2300, radius = 8, effect = CONST_ME_MAGIC_GREEN, target = false }, - { name = "speed", interval = 1000, chance = 12, speedChange = -800, radius = 6, effect = CONST_ME_POISONAREA, target = false, duration = 60000 }, + { name = "speed", interval = 1000, chance = 12, speedChange = -100, radius = 6, effect = CONST_ME_POISONAREA, target = false, duration = 60000 }, { name = "strength", interval = 1000, chance = 8, radius = 5, effect = CONST_ME_HITAREA, target = false }, { name = "combat", interval = 1000, chance = 34, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -700, range = 7, radius = 7, shootEffect = CONST_ANI_FIRE, effect = CONST_ME_FIREAREA, target = true }, { name = "combat", interval = 1000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -300, maxDamage = -950, length = 8, spread = 0, effect = CONST_ME_MAGIC_RED, target = false }, diff --git a/data-otservbr-global/monster/undeads/lost_soul.lua b/data-otservbr-global/monster/undeads/lost_soul.lua index d00e9eb9d18..99002595639 100644 --- a/data-otservbr-global/monster/undeads/lost_soul.lua +++ b/data-otservbr-global/monster/undeads/lost_soul.lua @@ -103,7 +103,7 @@ monster.loot = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -420 }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -40, maxDamage = -210, length = 3, spread = 0, effect = CONST_ME_MAGIC_RED, target = false }, - { name = "speed", interval = 2000, chance = 20, speedChange = -800, radius = 6, effect = CONST_ME_SMALLCLOUDS, target = false, duration = 4000 }, + { name = "speed", interval = 2000, chance = 20, speedChange = -100, radius = 6, effect = CONST_ME_SMALLCLOUDS, target = false, duration = 4000 }, } monster.defenses = { diff --git a/data-otservbr-global/monster/vermins/crystal_spider.lua b/data-otservbr-global/monster/vermins/crystal_spider.lua index ecb4b9ee62c..b6dd122b188 100644 --- a/data-otservbr-global/monster/vermins/crystal_spider.lua +++ b/data-otservbr-global/monster/vermins/crystal_spider.lua @@ -100,7 +100,7 @@ monster.loot = { monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -250, condition = { type = CONDITION_POISON, totalDamage = 160, interval = 4000 } }, - { name = "speed", interval = 2000, chance = 15, speedChange = -800, range = 7, radius = 6, effect = CONST_ME_POFF, target = false, duration = 15000 }, + { name = "speed", interval = 2000, chance = 15, speedChange = -100, range = 7, radius = 6, effect = CONST_ME_POFF, target = false, duration = 15000 }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_ICEDAMAGE, minDamage = -50, maxDamage = -100, range = 7, shootEffect = CONST_ANI_ICE, effect = CONST_ME_ICEAREA, target = true }, { name = "speed", interval = 2000, chance = 20, speedChange = -600, range = 7, shootEffect = CONST_ANI_SNOWBALL, target = true, duration = 10000 }, } From 282e73e886d64e07787ce36619803d40040d7916 Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Sun, 17 Dec 2023 14:15:14 -0300 Subject: [PATCH 07/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 08/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 09/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 10/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 11/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 12/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 13/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 14/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 15/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 16/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 17/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 18/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 19/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 20/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 21/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 22/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 23/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 24/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 25/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 26/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 27/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 28/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();