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);