diff --git a/config.lua.dist b/config.lua.dist index 7c4b2ce62..85bfc4691 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -203,7 +203,6 @@ onlyPremiumAccount = false -- NOTE: buyBlessCommandFee will add fee when player buy bless by command (!bless), active changing value between 1 and 100 (fee percent. ex: 3 = 3%, 30 = 30%) -- NOTE: teleportPlayerToVocationRoom will enable oressa to teleport player to his/her room vocation -- NOTE: toggleReceiveReward = true, will enable players to choose one of reward exercise weapon by command !reward --- NOTE: randomMonsterSpawn = true, will enable monsters from the same spawn to be randomized between them, thus making a variable hunt weatherRain = false thunderEffect = false allConsoleLog = false @@ -219,7 +218,6 @@ buyAolCommandFee = 0 buyBlessCommandFee = 0 teleportPlayerToVocationRoom = true toggleReceiveReward = false -randomMonsterSpawn = false -- Teleport summon -- Set to true will never remove the summon diff --git a/data-otxserver/lib/core/storages.lua b/data-otxserver/lib/core/storages.lua index 202bb290a..78ff88388 100644 --- a/data-otxserver/lib/core/storages.lua +++ b/data-otxserver/lib/core/storages.lua @@ -1705,8 +1705,6 @@ Storage = { -- Reserved storage 52396-52410 (TheOrderOfTheLion) Drume = { Commander = 52396, -- Global - TotalLionCommanders = 52397, -- Global - TotalUsurperCommanders = 52398, -- Global }, }, -- News quest development @@ -3067,6 +3065,13 @@ GlobalStorage = { DiprathSwitchesGlobalStorage = 60161, AshmunrahSwitchesGlobalStorage = 60162, }, + TheOrderOfTheLion = { + -- Reserved storage from 60170 - 60171 + Drume = { + TotalLionCommanders = 60170, -- Global + TotalUsurperCommanders = 60171, -- Global + }, + }, FuryGates = 65000, Yakchal = 65001, PitsOfInfernoLevers = 65002, diff --git a/data-otxserver/monster/mammals/white_deer.lua b/data-otxserver/monster/mammals/white_deer.lua index 303955ac7..f1da3a873 100644 --- a/data-otxserver/monster/mammals/white_deer.lua +++ b/data-otxserver/monster/mammals/white_deer.lua @@ -13,6 +13,10 @@ monster.outfit = { lookMount = 0, } +monster.events = { + "WhiteDeerDeath", +} + monster.raceId = 720 monster.Bestiary = { class = "Mammal", diff --git a/data-otxserver/monster/quests/the_order_of_lion/bosses/kesar.lua b/data-otxserver/monster/quests/the_order_of_lion/bosses/kesar.lua index 7b984fc7f..a3db694f7 100644 --- a/data-otxserver/monster/quests/the_order_of_lion/bosses/kesar.lua +++ b/data-otxserver/monster/quests/the_order_of_lion/bosses/kesar.lua @@ -32,6 +32,10 @@ monster.strategiesTarget = { nearest = 100, } +monster.events = { + "KesarImmortal", +} + monster.flags = { summonable = false, attackable = true, diff --git a/data-otxserver/monster/quests/the_order_of_lion/lion_commander.lua b/data-otxserver/monster/quests/the_order_of_lion/lion_commander.lua index 9a5cd22c7..f409fca3c 100644 --- a/data-otxserver/monster/quests/the_order_of_lion/lion_commander.lua +++ b/data-otxserver/monster/quests/the_order_of_lion/lion_commander.lua @@ -32,6 +32,10 @@ monster.strategiesTarget = { nearest = 100, } +monster.events = { + "LionCommanderDeath", +} + monster.flags = { summonable = false, attackable = true, diff --git a/data-otxserver/monster/quests/the_order_of_lion/usurper_commander.lua b/data-otxserver/monster/quests/the_order_of_lion/usurper_commander.lua index ca4c5b36b..fd31fa628 100644 --- a/data-otxserver/monster/quests/the_order_of_lion/usurper_commander.lua +++ b/data-otxserver/monster/quests/the_order_of_lion/usurper_commander.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, @@ -33,7 +33,7 @@ monster.strategiesTarget = { } monster.events = { - "usurperCommanderDeath", + "UsurperCommanderDeath", } monster.flags = { diff --git a/data-otxserver/scripts/actions/other/construction_kits.lua b/data-otxserver/scripts/actions/other/construction_kits.lua index 0989fff6b..176ffa0f2 100644 --- a/data-otxserver/scripts/actions/other/construction_kits.lua +++ b/data-otxserver/scripts/actions/other/construction_kits.lua @@ -7,8 +7,7 @@ local constructionKits = { [2780] = 2418, [2781] = 2422, [2782] = 2319, - [2812] = 11812, - [10207] = 2986, + [2812] = 2986, [2785] = 2314, [2786] = 2347, [2787] = 2348, @@ -36,7 +35,6 @@ local constructionKits = { [2809] = 2426, [2810] = 2352, [2811] = 2982, - [2812] = 2353, [5086] = 5046, [5087] = 5055, [5088] = 5056, diff --git a/data-otxserver/scripts/globalevents/others/startup.lua b/data-otxserver/scripts/globalevents/others/startup.lua index 2b779b9de..22af76e94 100644 --- a/data-otxserver/scripts/globalevents/others/startup.lua +++ b/data-otxserver/scripts/globalevents/others/startup.lua @@ -47,9 +47,7 @@ function serverstartup.onStartup() -- Create new item on map CreateMapItem(CreateItemOnMap) -- Update old quest storage keys ---[[ updateKeysStorage(QuestKeysUpdate) -]] logger.debug("Loaded all actions in the map") logger.debug("Loaded all uniques in the map") @@ -107,6 +105,12 @@ function serverstartup.onStartup() Result.free(banResultId) end + -- Ferumbras Ascendant quest + for i = 1, #GlobalStorage.FerumbrasAscendant.Habitats do + local storage = GlobalStorage.FerumbrasAscendant.Habitats[i] + Game.setStorageValue(storage, 0) + end + -- Check house auctions local resultId = db.storeQuery("SELECT `id`, `highest_bidder`, `last_bid`, (SELECT `balance` FROM \z `players` WHERE `players`.`id` = `highest_bidder`) AS `balance` FROM `houses` WHERE `owner` = 0 AND \z diff --git a/data-otxserver/scripts/globalevents/spawn/raids.lua b/data-otxserver/scripts/globalevents/spawn/raids.lua index 4a0703075..83d065349 100644 --- a/data-otxserver/scripts/globalevents/spawn/raids.lua +++ b/data-otxserver/scripts/globalevents/spawn/raids.lua @@ -1,5 +1,4 @@ local raids = { ---[[ --Terça-Feira ["Tuesday"] = { ["16:00"] = { raidName = "Midnight Panther" }, @@ -35,7 +34,6 @@ local raids = { ["31/10"] = { ["16:00"] = { raidName = "Halloween Hare" }, }, -]] } local spawnRaids = GlobalEvent("spawn raids") diff --git a/data-otxserver/scripts/lib/register_actions.lua b/data-otxserver/scripts/lib/register_actions.lua index e3b58c8e3..7777ee014 100644 --- a/data-otxserver/scripts/lib/register_actions.lua +++ b/data-otxserver/scripts/lib/register_actions.lua @@ -383,10 +383,6 @@ function onUsePick(player, item, fromPosition, target, toPosition, isHotkey) end target:getPosition():sendMagicEffect(CONST_ME_BLOCKHIT) target:remove(1) - elseif target.itemid == 10310 then - target:remove(1) - toPosition:sendMagicEffect(CONST_ME_POFF) - player:addItem(3035, 10) elseif target.itemid == 7200 then target:transform(7236) target:decay() diff --git a/data-otxserver/scripts/movements/teleport/citizen.lua b/data-otxserver/scripts/movements/teleport/citizen.lua index 505358261..fa12efa5d 100644 --- a/data-otxserver/scripts/movements/teleport/citizen.lua +++ b/data-otxserver/scripts/movements/teleport/citizen.lua @@ -1,9 +1,21 @@ local config = { - [9059] = TOWNS_LIST.TREKOLT, - [9056] = TOWNS_LIST.RHYVES, - [9060] = TOWNS_LIST.VARAK, - [9057] = TOWNS_LIST.JORVIK, - [9058] = TOWNS_LIST.SAUND + [9059] = TOWNS_LIST.AB_DENDRIEL, + [9056] = TOWNS_LIST.CARLIN, + [9060] = TOWNS_LIST.KAZORDOON, + [9057] = TOWNS_LIST.THAIS, + [9058] = TOWNS_LIST.VENORE, + [9061] = TOWNS_LIST.DARASHIA, + [9062] = TOWNS_LIST.ANKRAHMUN, + [9063] = TOWNS_LIST.EDRON, + [9068] = TOWNS_LIST.FARMINE, + [9064] = TOWNS_LIST.LIBERTY_BAY, + [9065] = TOWNS_LIST.PORT_HOPE, + [9066] = TOWNS_LIST.SVARGROND, + [9067] = TOWNS_LIST.YALAHAR, + [9240] = TOWNS_LIST.GRAY_BEACH, + [9510] = TOWNS_LIST.RATHLETON, + [9500] = TOWNS_LIST.ROSHAMUUL, + [9515] = TOWNS_LIST.ISSAVI, } local citizen = MoveEvent() @@ -24,6 +36,13 @@ function citizen.onStepIn(creature, item, position, fromPosition) return true end + if town:getId() == TOWNS_LIST.SVARGROND and player:getStorageValue(Storage.BarbarianTest.Questline) < 8 then + player:sendTextMessage(MESSAGE_GAME_HIGHLIGHT, "You first need to absolve the Barbarian Test Quest to become citizen!") + player:teleportTo(town:getTemplePosition()) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return true + end + player:setTown(town) player:teleportTo(town:getTemplePosition()) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) diff --git a/data-otxserver/scripts/quests/the_order_of_lion/action-bounac_entrance.lua b/data-otxserver/scripts/quests/the_order_of_lion/action-bounac_entrance.lua new file mode 100644 index 000000000..803bfc1b1 --- /dev/null +++ b/data-otxserver/scripts/quests/the_order_of_lion/action-bounac_entrance.lua @@ -0,0 +1,22 @@ +local bounacEntrance = Action() +function bounacEntrance.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item:getActionId() == 59602 then + if player:getLevel() < 250 then + player:sendCancelMessage("You need at least level 250.") + toPosition:sendMagicEffect(CONST_ME_POFF) + else + player:teleportTo({ x = 32423, y = 32448, z = 7 }) + toPosition:sendMagicEffect(CONST_ME_WATERSPLASH) + Position({ x = 32423, y = 32448, z = 7 }):sendMagicEffect(CONST_ME_WATERSPLASH) + end + elseif item:getActionId() == 59603 then + player:teleportTo({ x = 33183, y = 31756, z = 7 }) + Position({ x = 33183, y = 31756, z = 7 }):sendMagicEffect(CONST_ME_WATERSPLASH) + toPosition:sendMagicEffect(CONST_ME_WATERSPLASH) + end + return true +end + +bounacEntrance:aid(59602) +bounacEntrance:aid(59603) +bounacEntrance:register() diff --git a/data-otxserver/scripts/quests/the_order_of_lion/action-drume.lua b/data-otxserver/scripts/quests/the_order_of_lion/action-drume.lua new file mode 100644 index 000000000..2cdca69a4 --- /dev/null +++ b/data-otxserver/scripts/quests/the_order_of_lion/action-drume.lua @@ -0,0 +1,120 @@ +local config = { + lionPosition = { + Position(32444, 32512, 7), + Position(32449, 32516, 7), + Position(32444, 32520, 7), + }, + usurperPosition = { + Position(32450, 32520, 7), + Position(32444, 32516, 7), + Position(32448, 32512, 7), + }, + firstPlayerPosition = Position(32457, 32508, 6), + centerPosition = Position(32439, 32523, 7), -- Center Room + exitPosition = Position(32453, 32503, 7), -- Exit Position + newPosition = Position(32453, 32510, 7), + rangeX = 22, + rangeY = 16, + timeToKill = 20, -- time in minutes to remove the player +} + +local currentEvent = nil + +local function clearRoomDrume(centerPosition, rangeX, rangeY, resetGlobalStorage) + local spectators, spectator = Game.getSpectators(centerPosition, false, false, rangeX, rangeX, rangeY, rangeY) + for i = 1, #spectators do + spectator = spectators[i] + if spectator:isMonster() then + spectator:remove() + end + if spectator:isPlayer() then + spectator:teleportTo(config.exitPosition) + spectator:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your time is over.") + end + end + if Game.getStorageValue(resetGlobalStorage) == 1 then + Game.setStorageValue(resetGlobalStorage, -1) + end + currentEvent = nil +end + +local drumeAction = Action() +function drumeAction.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if player:getPosition() ~= config.firstPlayerPosition then + return false + end + + local spectators = Game.getSpectators(config.centerPosition, false, true, config.rangeX, config.rangeX, config.rangeY, config.rangeY) + if #spectators ~= 0 then + player:sendCancelMessage("There's someone already in the skirmish.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + return true + end + + local tempPos, tempTile, tempCreature + local players = {} + for x = config.firstPlayerPosition.x, config.firstPlayerPosition.x + 4 do + tempPos = Position(x, config.firstPlayerPosition.y, config.firstPlayerPosition.z) + tempTile = Tile(tempPos) + if tempTile then + tempCreature = tempTile:getTopCreature() + if tempCreature and tempCreature:isPlayer() then + table.insert(players, tempCreature) + end + end + end + if #players == 0 then + return false + end + for _, pi in pairs(players) do + if not pi:canFightBoss("Drume") then + player:sendCancelMessage("Someone of your team has already fought in the skirmish in the last 10h.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + return true + end + end + local spectators = Game.getSpectators(config.centerPosition, false, false, config.rangeX, config.rangeX, config.rangeY, config.rangeY) + for _, creature in pairs(spectators) do + if creature:isMonster() then + creature:remove() + end + end + local totalLion = 0 + local totalUsurper = 0 + local tempMonster + for _, pos in pairs(config.lionPosition) do + tempMonster = Game.createMonster("Lion Commander", pos) + if not tempMonster then + player:sendCancelMessage("There was an error, contact an admin.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + return true + end + totalLion = totalLion + 1 + end + for _, pos in pairs(config.usurperPosition) do + tempMonster = Game.createMonster("Usurper Commander", pos) + if not tempMonster then + player:sendCancelMessage("There was an error, contact an admin.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + return true + end + totalUsurper = totalUsurper + 1 + end + for _, pi in pairs(players) do + pi:setBossCooldown("Drume", os.time() + (configManager.getNumber(configKeys.BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN))) + pi:teleportTo(config.newPosition) + pi:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have " .. config.timeToKill .. " minutes to defeat Drume.") + end + if currentEvent then + stopEvent(currentEvent) + end + currentEvent = addEvent(clearRoomDrume, config.timeToKill * 60 * 1000, config.centerPosition, config.rangeX, config.rangeY, resetGlobalStorage) + config.newPosition:sendMagicEffect(CONST_ME_TELEPORT) + toPosition:sendMagicEffect(CONST_ME_POFF) + Game.setStorageValue(GlobalStorage.TheOrderOfTheLion.Drume.TotalLionCommanders, totalLion) + Game.setStorageValue(GlobalStorage.TheOrderOfTheLion.Drume.TotalUsurperCommanders, totalUsurper) + return true +end + +drumeAction:aid(59601) +drumeAction:register() diff --git a/data-otxserver/scripts/quests/the_order_of_lion/action-elevator.lua b/data-otxserver/scripts/quests/the_order_of_lion/action-elevator.lua new file mode 100644 index 000000000..08cfedf00 --- /dev/null +++ b/data-otxserver/scripts/quests/the_order_of_lion/action-elevator.lua @@ -0,0 +1,25 @@ +local elevatorBounacAction = Action() +function elevatorBounacAction.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if player:getPosition() ~= Position(32371, 32496, 7) then + Position(32371, 32496, 7):sendMagicEffect(CONST_ME_POFF) + else + player:teleportTo(Position(32374, 32497, 3)) + Position(32374, 32497, 3):sendMagicEffect(CONST_ME_POFF) + end + return true +end + +elevatorBounacAction:aid(59604) +elevatorBounacAction:register() + +local elevatorBounacMoveEvent = MoveEvent() +function elevatorBounacMoveEvent.onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:teleportTo(Position(32371, 32497, 7)) + Position(32371, 32497, 7):sendMagicEffect(CONST_ME_POFF) + end + return true +end + +elevatorBounacMoveEvent:aid(59605) +elevatorBounacMoveEvent:register() diff --git a/data-otxserver/scripts/quests/the_order_of_lion/creatureevent-commander_kills.lua b/data-otxserver/scripts/quests/the_order_of_lion/creatureevent-commander_kills.lua new file mode 100644 index 000000000..4cedd4ec4 --- /dev/null +++ b/data-otxserver/scripts/quests/the_order_of_lion/creatureevent-commander_kills.lua @@ -0,0 +1,50 @@ +local config = { + centerPosition = Position(32439, 32523, 7), -- Center Room + exitPosition = Position(32453, 32503, 7), -- Exit Position + rangeX = 22, + rangeY = 16, +} + +local lionCommanderDeath = CreatureEvent("LionCommanderDeath") +function lionCommanderDeath.onPrepareDeath(creature) + local totalCommanders = Game.getStorageValue(GlobalStorage.TheOrderOfTheLion.Drume.TotalLionCommanders) + if totalCommanders > 1 then + Game.setStorageValue(GlobalStorage.TheOrderOfTheLion.Drume.TotalLionCommanders, totalCommanders - 1) + else + local spectators = Game.getSpectators(config.centerPosition, false, false, config.rangeX, config.rangeX, config.rangeY, config.rangeY) + for _, spectator in pairs(spectators) do + if spectator:isMonster() and not spectator:getMaster() then + spectator:remove() + elseif spectator:isPlayer() then + spectator:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You lost the skirmish.") + spectator:teleportTo(config.exitPosition) + end + end + config.exitPosition:sendMagicEffect(CONST_ME_TELEPORT) + end + return true +end + +lionCommanderDeath:register() + +local usurperCommanderDeath = CreatureEvent("UsurperCommanderDeath") +function usurperCommanderDeath.onPrepareDeath(creature) + local totalCommanders = Game.getStorageValue(GlobalStorage.TheOrderOfTheLion.Drume.TotalUsurperCommanders) + if totalCommanders > 0 then + Game.setStorageValue(GlobalStorage.TheOrderOfTheLion.Drume.TotalUsurperCommanders, totalCommanders - 1) + if totalCommanders == 1 then + Game.createMonster("Kesar", Position(32444, 32515, 7), false, true) + Game.createMonster("Drume", Position(32444, 32516, 7), false, true) + end + end + return true +end + +usurperCommanderDeath:register() + +local kesarHealthChange = CreatureEvent("KesarImmortal") +function kesarHealthChange.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) + return 0, 0, 0, 0 +end + +kesarHealthChange:register() diff --git a/data-otxserver/scripts/quests/the_order_of_lion/moveevent-drume_entrance.lua b/data-otxserver/scripts/quests/the_order_of_lion/moveevent-drume_entrance.lua new file mode 100644 index 000000000..7300c40eb --- /dev/null +++ b/data-otxserver/scripts/quests/the_order_of_lion/moveevent-drume_entrance.lua @@ -0,0 +1,11 @@ +local drumeEntrance = MoveEvent() +function drumeEntrance.onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and not creature:canFightBoss("Drume") then + creature:teleportTo(fromPosition, true) + creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You've been into the skirmish in the last 10 hours.") + end + return true +end + +drumeEntrance:aid(59601) +drumeEntrance:register() diff --git a/data/chatchannels/scripts/advertising-rook.lua b/data/chatchannels/scripts/advertising-rook.lua index e652a2423..1cd206791 100644 --- a/data/chatchannels/scripts/advertising-rook.lua +++ b/data/chatchannels/scripts/advertising-rook.lua @@ -1,5 +1,5 @@ function canJoin(player) - return player:getVocation():getId() == VOCATION_NONE or player:getAccountType() >= ACCOUNT_TYPE_SENIORTUTOR + return player:getVocation():getId() == VOCATION_NONE or player:getGroup():getId() >= GROUP_TYPE_SENIORTUTOR end local CHANNEL_ADVERTISING_ROOK = 6 @@ -9,7 +9,7 @@ muted:setParameter(CONDITION_PARAM_SUBID, CHANNEL_ADVERTISING_ROOK) muted:setParameter(CONDITION_PARAM_TICKS, 120000) function onSpeak(player, type, message) - if player:getAccountType() >= ACCOUNT_TYPE_GAMEMASTER then + if player:getGroup():getId() >= GROUP_TYPE_GAMEMASTER then if type == TALKTYPE_CHANNEL_Y then return TALKTYPE_CHANNEL_O end @@ -28,7 +28,7 @@ function onSpeak(player, type, message) player:addCondition(muted) if type == TALKTYPE_CHANNEL_O then - if player:getAccountType() < ACCOUNT_TYPE_GAMEMASTER then + if player:getGroup():getId() < GROUP_TYPE_GAMEMASTER then type = TALKTYPE_CHANNEL_Y end elseif type == TALKTYPE_CHANNEL_R1 then diff --git a/data/chatchannels/scripts/advertising.lua b/data/chatchannels/scripts/advertising.lua index 9b9791dd1..ac121430e 100644 --- a/data/chatchannels/scripts/advertising.lua +++ b/data/chatchannels/scripts/advertising.lua @@ -1,5 +1,5 @@ function canJoin(player) - return player:getVocation():getId() ~= VOCATION_NONE or player:getAccountType() >= ACCOUNT_TYPE_SENIORTUTOR + return player:getVocation():getId() ~= VOCATION_NONE or player:getGroup():getId() >= GROUP_TYPE_SENIORTUTOR end local CHANNEL_ADVERTISING = 5 @@ -9,7 +9,7 @@ muted:setParameter(CONDITION_PARAM_SUBID, CHANNEL_ADVERTISING) muted:setParameter(CONDITION_PARAM_TICKS, 120000) function onSpeak(player, type, message) - if player:getAccountType() >= ACCOUNT_TYPE_GAMEMASTER then + if player:getGroup():getId() >= GROUP_TYPE_GAMEMASTER then if type == TALKTYPE_CHANNEL_Y then return TALKTYPE_CHANNEL_O end @@ -28,7 +28,7 @@ function onSpeak(player, type, message) player:addCondition(muted) if type == TALKTYPE_CHANNEL_O then - if player:getAccountType() < ACCOUNT_TYPE_GAMEMASTER then + if player:getGroup():getId() < GROUP_TYPE_GAMEMASTER then type = TALKTYPE_CHANNEL_Y end elseif type == TALKTYPE_CHANNEL_R1 then diff --git a/data/chatchannels/scripts/english_chat.lua b/data/chatchannels/scripts/english_chat.lua index 7bedb4665..5ac89d8dd 100644 --- a/data/chatchannels/scripts/english_chat.lua +++ b/data/chatchannels/scripts/english_chat.lua @@ -4,17 +4,17 @@ function onSpeak(player, type, message) return false end - local playerAccountType = player:getAccountType() + local playerGroupType = player:getGroup():getId() if type == TALKTYPE_CHANNEL_Y then - if playerAccountType >= ACCOUNT_TYPE_GAMEMASTER then + if playerGroupType >= GROUP_TYPE_GAMEMASTER then type = TALKTYPE_CHANNEL_O end elseif type == TALKTYPE_CHANNEL_O then - if playerAccountType < ACCOUNT_TYPE_GAMEMASTER then + if playerGroupType < GROUP_TYPE_GAMEMASTER then type = TALKTYPE_CHANNEL_Y end elseif type == TALKTYPE_CHANNEL_R1 then - if playerAccountType < ACCOUNT_TYPE_GAMEMASTER and not player:hasFlag(PlayerFlag_CanTalkRedChannel) then + if playerGroupType < GROUP_TYPE_GAMEMASTER and not player:hasFlag(PlayerFlag_CanTalkRedChannel) then type = TALKTYPE_CHANNEL_Y end end diff --git a/data/chatchannels/scripts/help.lua b/data/chatchannels/scripts/help.lua index e8da2f5d0..a5d9e9fe5 100644 --- a/data/chatchannels/scripts/help.lua +++ b/data/chatchannels/scripts/help.lua @@ -6,8 +6,8 @@ muted:setParameter(CONDITION_PARAM_SUBID, CHANNEL_HELP) muted:setParameter(CONDITION_PARAM_TICKS, 3600000) function onSpeak(player, type, message) - local playerAccountType = player:getAccountType() - if player:getLevel() == 1 and playerAccountType == ACCOUNT_TYPE_NORMAL then + local playerGroupType = player:getGroup():getId() + if player:getLevel() == 1 and playerGroupType == GROUP_TYPE_NORMAL then player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") return false end @@ -17,12 +17,12 @@ function onSpeak(player, type, message) return false end - if playerAccountType >= ACCOUNT_TYPE_TUTOR then + if playerGroupType >= GROUP_TYPE_TUTOR then if string.sub(message, 1, 6) == "!mute " then local targetName = string.sub(message, 7) local target = Player(targetName) if target then - if playerAccountType > target:getAccountType() then + if playerGroupType > target:getAccountType() then if not target:getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) then target:addCondition(muted) target:setStorageValue(storage, os.time() + 180) @@ -41,7 +41,7 @@ function onSpeak(player, type, message) local targetName = string.sub(message, 9) local target = Player(targetName) if target then - if playerAccountType > target:getAccountType() then + if playerGroupType > target:getAccountType() then if target:getStorageValue(storage) > os.time() then target:removeCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) sendChannelMessage(CHANNEL_HELP, TALKTYPE_CHANNEL_R1, target:getName() .. " has been unmuted.") @@ -60,16 +60,16 @@ function onSpeak(player, type, message) end if type == TALKTYPE_CHANNEL_Y then - if playerAccountType >= ACCOUNT_TYPE_TUTOR or player:hasFlag(PlayerFlag_TalkOrangeHelpChannel) then + if playerGroupType >= GROUP_TYPE_TUTOR or player:hasFlag(PlayerFlag_TalkOrangeHelpChannel) then type = TALKTYPE_CHANNEL_O end elseif type == TALKTYPE_CHANNEL_O then - if playerAccountType < ACCOUNT_TYPE_TUTOR and not player:hasFlag(PlayerFlag_TalkOrangeHelpChannel) then + if playerGroupType < GROUP_TYPE_TUTOR and not player:hasFlag(PlayerFlag_TalkOrangeHelpChannel) then type = TALKTYPE_CHANNEL_Y end elseif type == TALKTYPE_CHANNEL_R1 then - if playerAccountType < ACCOUNT_TYPE_GAMEMASTER and not player:hasFlag(PlayerFlag_CanTalkRedChannel) then - if playerAccountType >= ACCOUNT_TYPE_TUTOR or player:hasFlag(PlayerFlag_TalkOrangeHelpChannel) then + if playerGroupType < GROUP_TYPE_GAMEMASTER and not player:hasFlag(PlayerFlag_CanTalkRedChannel) then + if playerGroupType >= GROUP_TYPE_TUTOR or player:hasFlag(PlayerFlag_TalkOrangeHelpChannel) then type = TALKTYPE_CHANNEL_O else type = TALKTYPE_CHANNEL_Y diff --git a/data/chatchannels/scripts/tutor.lua b/data/chatchannels/scripts/tutor.lua index 9928fef9e..9112093be 100644 --- a/data/chatchannels/scripts/tutor.lua +++ b/data/chatchannels/scripts/tutor.lua @@ -1,19 +1,19 @@ function canJoin(player) - return player:getAccountType() >= ACCOUNT_TYPE_TUTOR + return player:getGroup():getId() >= GROUP_TYPE_TUTOR end function onSpeak(player, type, message) - local playerAccountType = player:getAccountType() + local playerGroupType = player:getGroup():getId() if type == TALKTYPE_CHANNEL_Y then - if playerAccountType >= ACCOUNT_TYPE_SENIORTUTOR then + if playerGroupType >= GROUP_TYPE_SENIORTUTOR then type = TALKTYPE_CHANNEL_O end elseif type == TALKTYPE_CHANNEL_O then - if playerAccountType < ACCOUNT_TYPE_SENIORTUTOR then + if playerGroupType < GROUP_TYPE_SENIORTUTOR then type = TALKTYPE_CHANNEL_Y end elseif type == TALKTYPE_CHANNEL_R1 then - if playerAccountType < ACCOUNT_TYPE_GAMEMASTER and not player:hasFlag(PlayerFlag_CanTalkRedChannel) then + if playerGroupType < GROUP_TYPE_GAMEMASTER and not player:hasFlag(PlayerFlag_CanTalkRedChannel) then type = TALKTYPE_CHANNEL_Y end end diff --git a/data/chatchannels/scripts/world_chat.lua b/data/chatchannels/scripts/world_chat.lua index 42517d2a5..6715fa769 100644 --- a/data/chatchannels/scripts/world_chat.lua +++ b/data/chatchannels/scripts/world_chat.lua @@ -1,20 +1,20 @@ function onSpeak(player, type, message) - local playerAccountType = player:getAccountType() - if player:getLevel() == 1 and playerAccountType < ACCOUNT_TYPE_GAMEMASTER then + local playerGroupType = player:getGroup():getId() + if player:getLevel() == 1 and playerGroupType < GROUP_TYPE_GAMEMASTER then player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") return false end if type == TALKTYPE_CHANNEL_Y then - if playerAccountType >= ACCOUNT_TYPE_GAMEMASTER then + if playerGroupType >= GROUP_TYPE_GAMEMASTER then type = TALKTYPE_CHANNEL_O end elseif type == TALKTYPE_CHANNEL_O then - if playerAccountType < ACCOUNT_TYPE_GAMEMASTER then + if playerGroupType < GROUP_TYPE_GAMEMASTER then type = TALKTYPE_CHANNEL_Y end elseif type == TALKTYPE_CHANNEL_R1 then - if playerAccountType < ACCOUNT_TYPE_GAMEMASTER and not player:hasFlag(PlayerFlag_CanTalkRedChannel) then + if playerGroupType < GROUP_TYPE_GAMEMASTER and not player:hasFlag(PlayerFlag_CanTalkRedChannel) then type = TALKTYPE_CHANNEL_Y end end diff --git a/data/items/items.xml b/data/items/items.xml index c46348b55..5542b35d4 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -46155,6 +46155,7 @@ + @@ -46164,6 +46165,7 @@ + @@ -46173,6 +46175,7 @@ + @@ -46182,6 +46185,7 @@ + diff --git a/data/libs/functions/functions.lua b/data/libs/functions/functions.lua index 616d25b84..2cdf4868a 100644 --- a/data/libs/functions/functions.lua +++ b/data/libs/functions/functions.lua @@ -229,8 +229,26 @@ function setPlayerMarriageStatus(id, val) db.query("UPDATE `players` SET `marriage_status` = " .. val .. " WHERE `id` = " .. id) end -function clearBossRoom(playerId, bossId, centerPosition, rangeX, rangeY, exitPosition) - local spectators, spectator = Game.getSpectators(centerPosition, false, false, rangeX, rangeX, rangeY, rangeY) +function checkBoss(centerPosition, rangeX, rangeY, bossName, bossPos) + local spectators, found = Game.getSpectators(centerPosition, false, false, rangeX, rangeX, rangeY, rangeY), false + for i = 1, #spectators do + local spec = spectators[i] + if spec:isMonster() then + if spec:getName() == bossName then + found = true + break + end + end + end + if not found then + local boss = Game.createMonster(bossName, bossPos, true, true) + boss:setReward(true) + end + return found +end + +function clearBossRoom(playerId, centerPosition, onlyPlayers, rangeX, rangeY, exitPosition) + local spectators, spectator = Game.getSpectators(centerPosition, false, onlyPlayers, rangeX, rangeX, rangeY, rangeY) for i = 1, #spectators do spectator = spectators[i] if spectator:isPlayer() and spectator.uid == playerId then @@ -252,13 +270,13 @@ function clearRoom(centerPosition, rangeX, rangeY, resetGlobalStorage) spectator:remove() end end - if Game.getStorageValue(resetGlobalStorage) == 1 then + if resetGlobalStorage ~= nil and Game.getStorageValue(resetGlobalStorage) == 1 then Game.setStorageValue(resetGlobalStorage, -1) end end -function roomIsOccupied(centerPosition, rangeX, rangeY) - local spectators = Game.getSpectators(centerPosition, false, false, rangeX, rangeX, rangeY, rangeY) +function roomIsOccupied(centerPosition, onlyPlayers, rangeX, rangeY) + local spectators = Game.getSpectators(centerPosition, false, onlyPlayers, rangeX, rangeX, rangeY, rangeY) if #spectators ~= 0 then return true end diff --git a/data/libs/functions/position.lua b/data/libs/functions/position.lua index d3ac6667b..2ba38968f 100644 --- a/data/libs/functions/position.lua +++ b/data/libs/functions/position.lua @@ -127,23 +127,8 @@ function Position:compare(position) return self.x == position.x and self.y == position.y and self.z == position.z end -function Position.hasPlayer(centerPosition, rangeX, rangeY) - local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) - if #spectators ~= 0 then - return true - end - return false -end - function Position.removeMonster(centerPosition, rangeX, rangeY) - local spectators = Game.getSpectators(centerPosition, false, false, rangeX, rangeX, rangeY, rangeY) - local spectators, spectator = Game.getSpectators(centerPosition, false, false, rangeX, rangeX, rangeY, rangeY) - for i = 1, #spectators do - spectator = spectators[i] - if spectator:isMonster() then - spectator:remove() - end - end + clearRoom(centerPosition, false, false) end function Position.getFreePosition(from, to) diff --git a/data/modules/scripts/blessings/blessings.lua b/data/modules/scripts/blessings/blessings.lua index b4494c240..0582c6154 100644 --- a/data/modules/scripts/blessings/blessings.lua +++ b/data/modules/scripts/blessings/blessings.lua @@ -246,7 +246,7 @@ Blessings.useCharm = function(player, item) end Blessings.checkBless = function(player) - local result, bless = "Received blessings:" + local result = "Received blessings:" for k, v in pairs(Blessings.All) do result = player:hasBlessing(k) and result .. "\n" .. v.name or result end diff --git a/data/modules/scripts/gamestore/init.lua b/data/modules/scripts/gamestore/init.lua index 7f0f2a80c..86c2c60d4 100644 --- a/data/modules/scripts/gamestore/init.lua +++ b/data/modules/scripts/gamestore/init.lua @@ -234,9 +234,6 @@ local function queueSendStoreAlertToUser(message, delay, playerId, storeErrorCod end function onRecvbyte(player, msg, byte) - if not configManager.getBoolean(STOREMODULES) then - return true - end if player:getVocation():getId() == 0 and not GameStore.haveCategoryRook() then return player:sendCancelMessage("Store don't have offers for rookgaard citizen.") end diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp index 4880b0728..417a5348a 100644 --- a/src/config/config_definitions.hpp +++ b/src/config/config_definitions.hpp @@ -80,7 +80,6 @@ enum booleanConfig_t { PARTY_AUTO_SHARE_EXPERIENCE, PARTY_SHARE_LOOT_BOOSTS, RESET_SESSIONS_ON_STARTUP, - RANDOM_MONSTER_SPAWN, TOGGLE_WHEELSYSTEM, TOGGLE_ATTACK_SPEED_ONFIST, VIP_SYSTEM_ENABLED, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index a404a2308..8f46b9b3f 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -100,7 +100,6 @@ bool ConfigManager::load() { boolean[OPTIMIZE_DATABASE] = getGlobalBoolean(L, "startupDatabaseOptimization", true); boolean[TOGGLE_MAP_CUSTOM] = getGlobalBoolean(L, "toggleMapCustom", true); boolean[TOGGLE_MAINTAIN_MODE] = getGlobalBoolean(L, "toggleMaintainMode", false); - boolean[RANDOM_MONSTER_SPAWN] = getGlobalBoolean(L, "randomMonsterSpawn", false); string[MAINTAIN_MODE_MESSAGE] = getGlobalString(L, "maintainModeMessage", ""); string[IP] = getGlobalString(L, "ip", "127.0.0.1"); diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index 22763139d..46804d6b5 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -1022,9 +1022,7 @@ void Creature::goToFollowCreature() { } } - if (followCreature->getPlayer() && followCreature->getPlayer()->isDisconnected()) { - hasFollowPath = false; - } else if (listDir.empty()) { + if (listDir.empty()) { hasFollowPath = getPathTo(followCreature->getPosition(), listDir, fpp); } diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index d29845864..b2660933a 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -225,13 +225,9 @@ void Monster::onCreatureMove(const std::shared_ptr &creature, const st } updateIdleStatus(); - if (!m_attackedCreature.expired()) { - return; - } if (!isSummon()) { - auto followCreature = getFollowCreature(); - if (followCreature) { + if (const auto &followCreature = getFollowCreature()) { const Position &followPosition = followCreature->getPosition(); const Position &pos = getPosition(); @@ -239,12 +235,11 @@ void Monster::onCreatureMove(const std::shared_ptr &creature, const st int32_t offset_y = Position::getDistanceY(followPosition, pos); if ((offset_x > 1 || offset_y > 1) && mType->info.changeTargetChance > 0) { Direction dir = getDirectionTo(pos, followPosition); - const Position &checkPosition = getNextPosition(dir, pos); + const auto &checkPosition = getNextPosition(dir, pos); - auto nextTile = g_game().map.getTile(checkPosition); - if (nextTile) { - auto topCreature = nextTile->getTopCreature(); - if (topCreature && followCreature != topCreature && isOpponent(topCreature)) { + if (const auto &nextTile = g_game().map.getTile(checkPosition)) { + const auto &topCreature = nextTile->getTopCreature(); + if (followCreature != topCreature && isOpponent(topCreature)) { selectTarget(topCreature); } } @@ -289,77 +284,68 @@ void Monster::onCreatureSay(std::shared_ptr creature, SpeakClasses typ } } -void Monster::addFriend(std::shared_ptr creature) { +void Monster::addFriend(const std::shared_ptr &creature) { assert(creature.get() != this); friendList.try_emplace(creature->getID(), creature); } -void Monster::removeFriend(std::shared_ptr creature) { - friendList.erase(creature->getID()); +void Monster::removeFriend(const std::shared_ptr &creature) { + std::erase_if(friendList, [id = creature->getID()](const auto &it) { + const auto &target = it.second.lock(); + return !target || target->getID() == id; + }); } -void Monster::addTarget(std::shared_ptr creature, bool pushFront /* = false*/) { +bool Monster::addTarget(const std::shared_ptr &creature, bool pushFront /* = false*/) { assert(creature.get() != this); - auto cid = creature->getID(); - targetListMap.try_emplace(cid, creature); - if (std::find(targetIDList.begin(), targetIDList.end(), cid) == targetIDList.end()) { - if (pushFront) { - targetIDList.push_front(cid); - } else { - targetIDList.push_back(cid); - } - if (!getMaster() && getFaction() != FACTION_DEFAULT && creature->getPlayer()) { - totalPlayersOnScreen++; - } + + const auto &it = getTargetIterator(creature); + if (it != targetList.end()) { + return false; + } + + if (pushFront) { + targetList.emplace_front(creature); + } else { + targetList.emplace_back(creature); + } + + if (!getMaster() && getFaction() != FACTION_DEFAULT && creature->getPlayer()) { + totalPlayersOnScreen++; } + + return true; } -void Monster::removeTarget(std::shared_ptr creature) { +bool Monster::removeTarget(const std::shared_ptr &creature) { if (!creature) { - return; + return false; } - auto it = std::find(targetIDList.begin(), targetIDList.end(), creature->getID()); - if (it != targetIDList.end()) { - if (!getMaster() && getFaction() != FACTION_DEFAULT && creature->getPlayer()) { - totalPlayersOnScreen--; - } - - targetIDList.erase(it); - targetListMap.erase(creature->getID()); + const auto &it = getTargetIterator(creature); + if (it == targetList.end()) { + return false; } -} -void Monster::updateTargetList() { - auto friendIterator = friendList.begin(); - while (friendIterator != friendList.end()) { - auto creature = (*friendIterator).second.lock(); - if (!creature || creature->getHealth() <= 0 || !canSee(creature->getPosition())) { - friendIterator = friendList.erase(friendIterator); - } else { - ++friendIterator; - } + if (!getMaster() && getFaction() != FACTION_DEFAULT && creature->getPlayer()) { + totalPlayersOnScreen--; } - auto targetIterator = targetIDList.begin(); - while (targetIterator != targetIDList.end()) { - const uint32_t targetId = *targetIterator; + targetList.erase(it); + + return true; +} - auto itTLM = targetListMap.find(targetId); - const bool existTarget = itTLM != targetListMap.end(); +void Monster::updateTargetList() { + std::erase_if(friendList, [this](const auto &it) { + const auto &target = it.second.lock(); + return !target || target->getHealth() <= 0 || !canSee(target->getPosition()); + }); - if (existTarget) { - const auto &creature = itTLM->second.lock(); - if (!creature || creature->getHealth() <= 0 || !canSee(creature->getPosition())) { - targetIterator = targetIDList.erase(targetIterator); - targetListMap.erase(itTLM); - } else { - ++targetIterator; - } - } else { - targetIterator = targetIDList.erase(targetIterator); - } - } + std::erase_if(targetList, [this](const std::weak_ptr &ref) { + const auto &target = ref.lock(); + return !target || target->getHealth() <= 0 || !canSee(target->getPosition()); + }); for (const auto &spectator : Spectators().find(position, true)) { if (spectator.get() != this && canSee(spectator->getPosition())) { @@ -369,8 +355,7 @@ void Monster::updateTargetList() { } void Monster::clearTargetList() { - targetIDList.clear(); - targetListMap.clear(); + targetList.clear(); } void Monster::clearFriendList() { @@ -393,16 +378,13 @@ void Monster::onCreatureEnter(std::shared_ptr creature) { onCreatureFound(creature, true); } -bool Monster::isFriend(std::shared_ptr creature) const { +bool Monster::isFriend(const std::shared_ptr &creature) const { if (isSummon() && getMaster()->getPlayer()) { - std::shared_ptr masterPlayer = getMaster()->getPlayer(); - std::shared_ptr tmpPlayer = nullptr; - - if (creature->getPlayer()) { - tmpPlayer = creature->getPlayer(); - } else { - std::shared_ptr creatureMaster = creature->getMaster(); + const auto &masterPlayer = getMaster()->getPlayer(); + auto tmpPlayer = creature->getPlayer(); + if (!tmpPlayer) { + const auto &creatureMaster = creature->getMaster(); if (creatureMaster && creatureMaster->getPlayer()) { tmpPlayer = creatureMaster->getPlayer(); } @@ -411,27 +393,30 @@ bool Monster::isFriend(std::shared_ptr creature) const { if (tmpPlayer && (tmpPlayer == getMaster() || masterPlayer->isPartner(tmpPlayer))) { return true; } - } else if (creature->getMonster() && !creature->isSummon()) { - return true; } - return false; + return creature->getMonster() && !creature->isSummon(); } -bool Monster::isOpponent(std::shared_ptr creature) const { +bool Monster::isOpponent(const std::shared_ptr &creature) const { + if (!creature) { + return false; + } + if (isSummon() && getMaster()->getPlayer()) { - if (creature != getMaster()) { - return true; - } - } else if (creature->getPlayer() && creature->getPlayer()->hasFlag(PlayerFlags_t::IgnoredByMonsters)) { + return creature != getMaster(); + } + + if (creature->getPlayer() && creature->getPlayer()->hasFlag(PlayerFlags_t::IgnoredByMonsters)) { return false; - } else { - if (getFaction() != FACTION_DEFAULT) { - return isEnemyFaction(creature->getFaction()) || creature->getFaction() == FACTION_PLAYER; - } - if ((creature->getPlayer()) || (creature->getMaster() && creature->getMaster()->getPlayer())) { - return true; - } + } + + if (getFaction() != FACTION_DEFAULT) { + return isEnemyFaction(creature->getFaction()) || creature->getFaction() == FACTION_PLAYER; + } + + if ((creature->getPlayer()) || (creature->getMaster() && creature->getMaster()->getPlayer())) { + return true; } return false; @@ -446,7 +431,7 @@ void Monster::onCreatureLeave(std::shared_ptr creature) { // update targetList if (isOpponent(creature)) { removeTarget(creature); - if (targetIDList.empty()) { + if (targetList.empty()) { updateIdleStatus(); } } @@ -473,11 +458,11 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL } } - std::list> resultList; + std::vector> resultList; const Position &myPos = getPosition(); - for (auto cid : targetIDList) { - auto creature = targetListMap[cid].lock(); + for (const auto &cref : targetList) { + const auto &creature = cref.lock(); if (creature && isTarget(creature)) { if ((static_self_cast()->targetDistance == 1) || canUseAttack(myPos, creature)) { resultList.push_back(creature); @@ -513,7 +498,7 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL } } else { int32_t minRange = std::numeric_limits::max(); - for (auto creature : getTargetList()) { + for (const auto &creature : getTargetList()) { if (!isTarget(creature)) { continue; } @@ -588,42 +573,14 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL } // lets just pick the first target in the list - for (auto target : getTargetList()) { - if (selectTarget(target)) { - return true; - } - } - return false; + return std::ranges::any_of(getTargetList(), [this](const std::shared_ptr &creature) { + return selectTarget(creature); + }); } void Monster::onFollowCreatureComplete(const std::shared_ptr &creature) { - if (!creature) { - return; - } - - const auto &it = std::find(targetIDList.begin(), targetIDList.end(), creature->getID()); - if (it != targetIDList.end()) { - if (const auto &target = targetListMap[*it].lock()) { - targetIDList.erase(it); - - if (hasFollowPath) { - targetIDList.push_front(target->getID()); - } else if (!isSummon()) { - targetIDList.push_back(target->getID()); - } else { - targetListMap.erase(target->getID()); - } - } - } - - // Change the target if necessary - if (!hasFollowPath && !isSummon() && !targetIDList.empty() && targetIDList.front() != creature->getID()) { - const auto &itMap = targetListMap.find(targetIDList.front()); - if (itMap != targetListMap.end()) { - if (const auto &target = itMap->second.lock()) { - selectTarget(target); - } - } + if (removeTarget(creature) && (hasFollowPath || !isSummon())) { + addTarget(creature, hasFollowPath); } } @@ -663,20 +620,27 @@ bool Monster::isTarget(std::shared_ptr creature) { if (creature->getPosition().z != getPosition().z) { return false; } - Faction_t targetFaction = creature->getFaction(); - if (getFaction() != FACTION_DEFAULT && !isSummon()) { - return isEnemyFaction(targetFaction); + + if (!isSummon()) { + if (creature->getPlayer() && creature->getPlayer()->isDisconnected()) { + return false; + } + + if (getFaction() != FACTION_DEFAULT) { + return isEnemyFaction(creature->getFaction()); + } } + return true; } -bool Monster::selectTarget(std::shared_ptr creature) { +bool Monster::selectTarget(const std::shared_ptr &creature) { if (!isTarget(creature)) { return false; } - auto it = std::find(targetIDList.begin(), targetIDList.end(), creature->getID()); - if (it == targetIDList.end()) { + const auto &it = getTargetIterator(creature); + if (it == targetList.end()) { // Target not found in our target list. return false; } @@ -708,13 +672,13 @@ void Monster::setIdle(bool idle) { void Monster::updateIdleStatus() { bool idle = false; - auto master = getMaster(); - if (conditions.empty()) { - if (!isSummon() && targetIDList.empty()) { - idle = true; - } else if (master && (!isSummon() && totalPlayersOnScreen == 0 || isSummon() && master->getMonster() && master->getMonster()->totalPlayersOnScreen == 0) && getFaction() != FACTION_DEFAULT) { + if (!isSummon() && targetList.empty()) { idle = true; + } else if (const auto &master = getMaster()) { + if ((!isSummon() && totalPlayersOnScreen == 0 || isSummon() && master->getMonster() && master->getMonster()->totalPlayersOnScreen == 0) && getFaction() != FACTION_DEFAULT) { + idle = true; + } } } @@ -781,45 +745,47 @@ void Monster::onThink(uint32_t interval) { if (!isInSpawnRange(position)) { g_game().internalTeleport(static_self_cast(), masterPos); setIdle(true); - } else { - updateIdleStatus(); + return; + } - if (!isIdle) { - addEventWalk(); - - auto attackedCreature = getAttackedCreature(); - auto followCreature = getFollowCreature(); - if (isSummon()) { - if (!attackedCreature) { - if (getMaster() && getMaster()->getAttackedCreature()) { - // This happens if the monster is summoned during combat - selectTarget(getMaster()->getAttackedCreature()); - } else if (getMaster() != followCreature) { - // Our master has not ordered us to attack anything, lets follow him around instead. - setFollowCreature(getMaster()); - } - } else if (attackedCreature.get() == this) { - setFollowCreature(nullptr); - } else if (followCreature != attackedCreature) { - // This happens just after a master orders an attack, so lets follow it aswell. - setFollowCreature(attackedCreature); - } - } else if (!attackedCreature && !targetIDList.empty()) { - if (!followCreature || !hasFollowPath) { - searchTarget(TARGETSEARCH_NEAREST); - } else if (isFleeing()) { - if (attackedCreature && !canUseAttack(getPosition(), attackedCreature)) { - searchTarget(TARGETSEARCH_DEFAULT); - } - } - } + updateIdleStatus(); - onThinkTarget(interval); - onThinkYell(interval); - onThinkDefense(interval); - onThinkSound(interval); - } + if (isIdle) { + return; } + + addEventWalk(); + + const auto &attackedCreature = getAttackedCreature(); + const auto &followCreature = getFollowCreature(); + if (isSummon()) { + if (attackedCreature.get() == this) { + setFollowCreature(nullptr); + } else if (attackedCreature && followCreature != attackedCreature) { + // This happens just after a master orders an attack, so lets follow it aswell. + setFollowCreature(attackedCreature); + } else if (getMaster() && getMaster()->getAttackedCreature()) { + // This happens if the monster is summoned during combat + selectTarget(getMaster()->getAttackedCreature()); + } else if (getMaster() != followCreature) { + // Our master has not ordered us to attack anything, lets follow him around instead. + setFollowCreature(getMaster()); + } + } else if (!targetList.empty()) { + const bool attackedCreatureIsDisconnected = attackedCreature && attackedCreature->getPlayer() && attackedCreature->getPlayer()->isDisconnected(); + if (!attackedCreature || attackedCreatureIsDisconnected) { + if (!followCreature || !hasFollowPath || attackedCreatureIsDisconnected) { + searchTarget(TARGETSEARCH_NEAREST); + } else if (attackedCreature && isFleeing() && !canUseAttack(getPosition(), attackedCreature)) { + searchTarget(TARGETSEARCH_DEFAULT); + } + } + } + + onThinkTarget(interval); + onThinkYell(interval); + onThinkDefense(interval); + onThinkSound(interval); } void Monster::doAttacking(uint32_t interval) { diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index 29182fe68..2422ae767 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -17,11 +17,6 @@ class Creature; class Game; class Spawn; -using CreatureList = std::list>; - -using CreatureWeakHashMap = phmap::flat_hash_map>; -using CreatureIDList = std::list; - class Monster final : public Creature { public: static std::shared_ptr createMonster(const std::string &name); @@ -187,34 +182,37 @@ class Monster final : public Creature { } bool searchTarget(TargetSearchType_t searchType = TARGETSEARCH_DEFAULT); - bool selectTarget(std::shared_ptr creature); - - CreatureList getTargetList() { - std::list> list; - for (auto it = targetIDList.begin(); it != targetIDList.end();) { - auto cid = *it; - if (auto targetCreature = targetListMap[cid].lock()) { - list.push_back(targetCreature); - ++it; - } else { - it = targetIDList.erase(it); - targetListMap.erase(cid); + bool selectTarget(const std::shared_ptr &creature); + + auto getTargetList() { + CreatureVector list; + list.reserve(targetList.size()); + + std::erase_if(targetList, [&list](const std::weak_ptr &ref) { + if (const auto &creature = ref.lock()) { + list.emplace_back(creature); + return false; } - } + + return true; + }); + return list; } - std::vector> getFriendList() { - std::vector> list; + auto getFriendList() { + CreatureVector list; + list.reserve(friendList.size()); - for (auto it = friendList.begin(); it != friendList.end();) { - if (auto friendCreature = it->second.lock()) { - list.emplace_back(friendCreature); - ++it; - } else { - it = friendList.erase(it); + std::erase_if(friendList, [&list](const auto &it) { + if (const auto &creature = it.second.lock()) { + list.emplace_back(creature); + return false; } - } + + return true; + }); + return list; } @@ -340,9 +338,15 @@ class Monster final : public Creature { } private: - CreatureWeakHashMap friendList; - CreatureIDList targetIDList; - CreatureWeakHashMap targetListMap; + auto getTargetIterator(const std::shared_ptr &creature) { + return std::ranges::find_if(targetList.begin(), targetList.end(), [id = creature->getID()](const std::weak_ptr &ref) { + const auto &target = ref.lock(); + return target && target->getID() == id; + }); + } + + std::unordered_map> friendList; + std::deque> targetList; time_t timeToChangeFiendish = 0; @@ -391,10 +395,10 @@ class Monster final : public Creature { void updateLookDirection(); - void addFriend(std::shared_ptr creature); - void removeFriend(std::shared_ptr creature); - void addTarget(std::shared_ptr creature, bool pushFront = false); - void removeTarget(std::shared_ptr creature); + void addFriend(const std::shared_ptr &creature); + void removeFriend(const std::shared_ptr &creature); + bool addTarget(const std::shared_ptr &creature, bool pushFront = false); + bool removeTarget(const std::shared_ptr &creature); void death(std::shared_ptr lastHitCreature) override; std::shared_ptr getCorpse(std::shared_ptr lastHitCreature, std::shared_ptr mostDamageCreature) override; @@ -425,8 +429,8 @@ class Monster final : public Creature { void onThinkDefense(uint32_t interval); void onThinkSound(uint32_t interval); - bool isFriend(std::shared_ptr creature) const; - bool isOpponent(std::shared_ptr creature) const; + bool isFriend(const std::shared_ptr &creature) const; + bool isOpponent(const std::shared_ptr &creature) const; uint64_t getLostExperience() const override { return skillLoss ? mType->info.experience : 0; diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp index a86f43ed1..aa9bcb14c 100644 --- a/src/creatures/monsters/spawns/spawn_monster.cpp +++ b/src/creatures/monsters/spawns/spawn_monster.cpp @@ -198,12 +198,7 @@ void SpawnMonster::startup() { for (const auto &it : spawnMonsterMap) { uint32_t spawnMonsterId = it.first; const spawnBlock_t &sb = it.second; - if (g_configManager().getBoolean(RANDOM_MONSTER_SPAWN)) { - const spawnBlock_t &randSb = std::next(spawnMonsterMap.begin(), uniform_random(0, spawnMonsterMap.size() - 1))->second; - spawnMonster(spawnMonsterId, randSb.monsterType, sb.pos, sb.direction, true); - } else { - spawnMonster(spawnMonsterId, sb.monsterType, sb.pos, sb.direction, true); - } + spawnMonster(spawnMonsterId, sb.monsterType, sb.pos, sb.direction, true); } } @@ -220,35 +215,27 @@ void SpawnMonster::checkSpawnMonster() { continue; } - const spawnBlock_t &sb = it.second; - + spawnBlock_t &sb = it.second; if (!sb.monsterType->canSpawn(sb.pos)) { - spawnMonsterMap[spawnMonsterId].lastSpawn = OTSYS_TIME(); - continue; - } - - if (sb.monsterType->info.isBlockable && findPlayer(sb.pos)) { - spawnMonsterMap[spawnMonsterId].lastSpawn = OTSYS_TIME(); + sb.lastSpawn = OTSYS_TIME(); continue; } - spawnBlock_t currentSb; - if (g_configManager().getBoolean(RANDOM_MONSTER_SPAWN)) { - currentSb = std::next(spawnMonsterMap.begin(), uniform_random(0, spawnMonsterMap.size() - 1))->second; - currentSb.pos = sb.pos; - currentSb.direction = sb.direction; - } else { - currentSb = sb; - } + if (OTSYS_TIME() >= sb.lastSpawn + sb.interval) { + if (sb.monsterType->info.isBlockable && findPlayer(sb.pos)) { + sb.lastSpawn = OTSYS_TIME(); + continue; + } - if (currentSb.monsterType->info.isBlockable) { - spawnMonster(spawnMonsterId, currentSb.monsterType, currentSb.pos, currentSb.direction, true); - } else { - scheduleSpawn(spawnMonsterId, currentSb, 3 * NONBLOCKABLE_SPAWN_MONSTER_INTERVAL); - } + if (sb.monsterType->info.isBlockable) { + spawnMonster(spawnMonsterId, sb.monsterType, sb.pos, sb.direction); + } else { + scheduleSpawn(spawnMonsterId, sb, 3 * NONBLOCKABLE_SPAWN_MONSTER_INTERVAL); + } - if (++spawnMonsterCount >= static_cast(g_configManager().getNumber(RATE_SPAWN))) { - break; + if (++spawnMonsterCount >= static_cast(g_configManager().getNumber(RATE_SPAWN))) { + break; + } } } diff --git a/src/game/game.cpp b/src/game/game.cpp index 5e4e593dd..8d16fd624 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -3956,7 +3956,7 @@ std::shared_ptr Game::wrapItem(std::shared_ptr item, std::shared_ptr auto itemName = item->getName(); std::shared_ptr newItem = transformItem(item, ITEM_DECORATION_KIT); newItem->setCustomAttribute("unWrapId", static_cast(oldItemID)); - item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap it in your own house to create a <" + itemName + ">."); + newItem->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap it in your own house to create a <" + itemName + ">."); if (hiddenCharges > 0) { newItem->setAttribute(DATE, hiddenCharges); } diff --git a/src/io/iomapserialize.cpp b/src/io/iomapserialize.cpp index 8e35f5acb..7862e1a11 100644 --- a/src/io/iomapserialize.cpp +++ b/src/io/iomapserialize.cpp @@ -116,8 +116,6 @@ bool IOMapSerialize::loadContainer(PropStream &propStream, std::shared_ptr parent, bool isHouseItem /*= false*/) { uint16_t id; if (!propStream.read(id)) { @@ -130,14 +128,16 @@ bool IOMapSerialize::loadItem(PropStream &propStream, std::shared_ptr } const ItemType &iType = Item::items[id]; - if (isHouseItem && iType.isBed() && id < NEW_BEDS_START_ID) { - return false; - } - if (iType.moveable || !tile || iType.isCarpet() || iType.isBed()) { + if (iType.isBed() || iType.moveable || !tile || iType.isCarpet()) { // create a new item - std::shared_ptr item = Item::CreateItem(id); + auto item = Item::CreateItem(id); if (item) { if (item->unserializeAttr(propStream)) { + // Remove only not moveable and not sleeper bed + auto bed = item->getBed(); + if (isHouseItem && iType.isBed() && bed && bed->getSleeper() == 0 && !iType.moveable) { + return false; + } std::shared_ptr container = item->getContainer(); if (container && !loadContainer(propStream, container)) { return false; diff --git a/src/items/bed.cpp b/src/items/bed.cpp index 289ae210a..2e0baa9e7 100644 --- a/src/items/bed.cpp +++ b/src/items/bed.cpp @@ -84,11 +84,22 @@ bool BedItem::canUse(std::shared_ptr player) { return false; } - if (getNextBedItem() == nullptr) { + auto nextBedItem = getNextBedItem(); + if (nextBedItem == nullptr) { return false; } - if (Item::items[id].bedPart != BED_PILLOW_PART) { + const auto &itemType = Item::items[id]; + if (itemType.bedPart != BED_PILLOW_PART) { + return false; + } + + auto partName = itemType.name; + auto nextPartname = nextBedItem->getName(); + auto firstPart = keepFirstWordOnly(partName); + auto nextPartOf = keepFirstWordOnly(nextPartname); + g_logger().debug("First bed part name {}, second part name {}", firstPart, nextPartOf); + if (!isMoveable() || !nextBedItem->isMoveable() || firstPart != nextPartOf) { return false; } diff --git a/src/lua/functions/core/game/config_functions.cpp b/src/lua/functions/core/game/config_functions.cpp index 663bd430d..bdeaa226f 100644 --- a/src/lua/functions/core/game/config_functions.cpp +++ b/src/lua/functions/core/game/config_functions.cpp @@ -128,7 +128,6 @@ void ConfigFunctions::init(lua_State* L) { registerEnumIn(L, "configKeys", RED_SKULL_DURATION); registerEnumIn(L, "configKeys", BLACK_SKULL_DURATION); registerEnumIn(L, "configKeys", ORANGE_SKULL_DURATION); - registerEnumIn(L, "configKeys", RANDOM_MONSTER_SPAWN); registerEnumIn(L, "configKeys", RATE_MONSTER_HEALTH); registerEnumIn(L, "configKeys", RATE_MONSTER_ATTACK); registerEnumIn(L, "configKeys", RATE_MONSTER_DEFENSE); diff --git a/src/utils/tools.cpp b/src/utils/tools.cpp index 8d7da61e4..a4b9f8d31 100644 --- a/src/utils/tools.cpp +++ b/src/utils/tools.cpp @@ -257,6 +257,15 @@ void trim_left(std::string &source, char t) { source.erase(0, source.find_first_not_of(t)); } +std::string keepFirstWordOnly(std::string &str) { + size_t spacePos = str.find(' '); + if (spacePos != std::string::npos) { + str.erase(spacePos); + } + + return str; +} + void toLowerCaseString(std::string &source) { std::transform(source.begin(), source.end(), source.begin(), tolower); } diff --git a/src/utils/tools.hpp b/src/utils/tools.hpp index 14e9fd394..099f77afc 100644 --- a/src/utils/tools.hpp +++ b/src/utils/tools.hpp @@ -25,6 +25,8 @@ std::string generateToken(const std::string &secret, uint32_t ticks); void replaceString(std::string &str, const std::string &sought, const std::string &replacement); void trim_right(std::string &source, char t); void trim_left(std::string &source, char t); +std::string keepFirstWordOnly(std::string &str); + void toLowerCaseString(std::string &source); std::string asLowerCaseString(std::string source); std::string asUpperCaseString(std::string source);