diff --git a/CMakePresets.json b/CMakePresets.json
index 35ba674b484..0dbcece6773 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -28,7 +28,7 @@
},
"vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": {
- "hostOS": [ "Windows" ]
+ "hostOS": ["Windows"]
}
},
"condition": {
diff --git a/README.md b/README.md
index 16b6d73cb14..d3d83eecc48 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ source of the Canary, so that it will be the first repository to use this engine
To connect to the server and to take a stable experience, you can
use [mehah's otclient](https://github.com/mehah/otclient)
or [tibia client](https://github.com/dudantas/tibia-client/releases/latest) and if you want to edit something, check
-our [customized tools](https://docs.opentibiabr.com/others/downloads/tools).
+our [customized tools](https://docs.opentibiabr.com/opentibiabr/downloads/tools).
If you want edit the map, use the [own remere's map editor](https://github.com/opentibiabr/remeres-map-editor/).
diff --git a/config.lua.dist b/config.lua.dist
index 76508f6d7d7..a00bb061f81 100644
--- a/config.lua.dist
+++ b/config.lua.dist
@@ -186,6 +186,7 @@ onlyPremiumAccount = false
-- NOTE: buyAolCommandFee will add fee when player buy aol by command (!aol), active changing value more than 0 (fee value. ex: 1 = 1gp aol will be 50001)
-- 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
weatherRain = false
thunderEffect = false
allConsoleLog = false
@@ -200,6 +201,7 @@ toggleTravelsFree = false
buyAolCommandFee = 0
buyBlessCommandFee = 0
teleportPlayerToVocationRoom = true
+toggleReceiveReward = false
-- Teleport summon
-- Set to true will never remove the summon
diff --git a/data-otservbr-global/lib/core/storages.lua b/data-otservbr-global/lib/core/storages.lua
index d5f434274b0..b1795ce1dde 100644
--- a/data-otservbr-global/lib/core/storages.lua
+++ b/data-otservbr-global/lib/core/storages.lua
@@ -140,7 +140,7 @@ Storage = {
PremiumAccount = 30058,
BattleAxeQuest = 30059,
ShrineEntrance = 30060,
-
+ PlayerWeaponReward = 30061,
--[[
Old storages
Over time, this will be dropped and replaced by the table above
diff --git a/data-otservbr-global/monster/bosses/doctor_marrow.lua b/data-otservbr-global/monster/bosses/doctor_marrow.lua
new file mode 100644
index 00000000000..3de9bc41825
--- /dev/null
+++ b/data-otservbr-global/monster/bosses/doctor_marrow.lua
@@ -0,0 +1,110 @@
+local mType = Game.createMonsterType("Doctor Marrow")
+local monster = {}
+
+monster.description = "Doctor Marrow"
+monster.experience = 0
+monster.outfit = {
+ lookType = 1611,
+ lookHead = 57,
+ lookBody = 0,
+ lookLegs = 0,
+ lookFeet = 95,
+ lookAddons = 0,
+ lookMount = 0,
+}
+
+monster.health = 1800
+monster.maxHealth = 1800
+monster.race = "blood"
+monster.corpse = 18074
+monster.speed = 125
+monster.manaCost = 0
+
+monster.changeTarget = {
+ interval = 4000,
+ chance = 10,
+}
+
+monster.strategiesTarget = {
+ nearest = 80,
+ health = 10,
+ damage = 10,
+}
+
+monster.flags = {
+ summonable = false,
+ attackable = true,
+ hostile = true,
+ convinceable = false,
+ pushable = false,
+ rewardBoss = true,
+ illusionable = false,
+ canPushItems = true,
+ canPushCreatures = true,
+ critChance = 10,
+ staticAttackChance = 90,
+ targetDistance = 1,
+ runHealth = 0,
+ healthHidden = false,
+ isBlockable = false,
+ canWalkOnEnergy = true,
+ canWalkOnFire = true,
+ canWalkOnPoison = true,
+}
+
+monster.light = {
+ level = 0,
+ color = 0,
+}
+
+monster.voices = {
+ interval = 5000,
+ chance = 10,
+ { text = "You can't stop the future!", yell = false },
+}
+
+monster.attacks = {
+ { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -2800 },
+}
+
+monster.defenses = {
+ defense = 54,
+ armor = 59,
+ mitigation = 3.7,
+}
+
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = 0 },
+ { type = COMBAT_ENERGYDAMAGE, percent = 0 },
+ { type = COMBAT_EARTHDAMAGE, percent = 0 },
+ { type = COMBAT_FIREDAMAGE, percent = 0 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = 0 },
+ { type = COMBAT_HOLYDAMAGE, percent = 0 },
+ { type = COMBAT_DEATHDAMAGE, percent = 0 },
+}
+
+monster.immunities = {
+ { type = "paralyze", condition = true },
+ { type = "outfit", condition = false },
+ { type = "invisible", condition = true },
+ { type = "bleed", condition = false },
+}
+
+mType.onThink = function(monster, interval) end
+
+mType.onAppear = function(monster, creature)
+ if monster:getType():isRewardBoss() then
+ monster:setReward(true)
+ end
+end
+
+mType.onDisappear = function(monster, creature) end
+
+mType.onMove = function(monster, creature, fromPosition, toPosition) end
+
+mType.onSay = function(monster, creature, type, message) end
+
+mType:register(monster)
diff --git a/data-otservbr-global/scripts/creaturescripts/customs/reward_exercise.lua b/data-otservbr-global/scripts/creaturescripts/customs/reward_exercise.lua
new file mode 100644
index 00000000000..f17304cec00
--- /dev/null
+++ b/data-otservbr-global/scripts/creaturescripts/customs/reward_exercise.lua
@@ -0,0 +1,13 @@
+local winReward = CreatureEvent("WinReward")
+
+function winReward.onLogin(player)
+ if configManager.getBoolean(configKeys.TOGGLE_RECEIVE_REWARD) and player:getTown():getId() >= TOWNS_LIST.AB_DENDRIEL then
+ -- check user won exercise weapon and send message
+ if player:getStorageValue(tonumber(Storage.PlayerWeaponReward)) ~= 1 then
+ player:sendTextMessage(MESSAGE_LOGIN, "You can receive an exercise weapon using command !reward")
+ end
+ end
+ return true
+end
+
+winReward:register()
diff --git a/data-otservbr-global/scripts/globalevents/worldchanges/their_masters_voice.lua b/data-otservbr-global/scripts/globalevents/worldchanges/their_masters_voice.lua
index fa1b9adbe33..2061c254cf9 100644
--- a/data-otservbr-global/scripts/globalevents/worldchanges/their_masters_voice.lua
+++ b/data-otservbr-global/scripts/globalevents/worldchanges/their_masters_voice.lua
@@ -12,7 +12,7 @@ function theirmastersvoice.onStartup()
if item then
local slimeChance = math.random(100)
if slimeChance <= 30 then
- item:transform(math.random(13585, 13589))
+ item:transform(math.random(12059, 12063))
position:sendMagicEffect(CONST_ME_YELLOW_RINGS)
end
end
diff --git a/data-otservbr-global/scripts/lib/register_monster_type.lua b/data-otservbr-global/scripts/lib/register_monster_type.lua
index 68983418159..21a10d94aac 100644
--- a/data-otservbr-global/scripts/lib/register_monster_type.lua
+++ b/data-otservbr-global/scripts/lib/register_monster_type.lua
@@ -199,6 +199,9 @@ registerMonsterType.flags = function(mtype, mask)
if mask.flags.canPushCreatures ~= nil then
mtype:canPushCreatures(mask.flags.canPushCreatures)
end
+ if mask.flags.critChance ~= nil then
+ mtype:critChance(mask.flags.critChance)
+ end
if mask.flags.targetDistance then
mtype:targetDistance(math.max(1, mask.flags.targetDistance))
end
diff --git a/data-otservbr-global/world/world_changes/fury_gates/abdendriel.otbm b/data-otservbr-global/world/world_changes/fury_gates/abdendriel.otbm
index 9e6b2698eee..c41c56415e9 100644
Binary files a/data-otservbr-global/world/world_changes/fury_gates/abdendriel.otbm and b/data-otservbr-global/world/world_changes/fury_gates/abdendriel.otbm differ
diff --git a/data-otservbr-global/world/world_changes/fury_gates/ankrahmun.otbm b/data-otservbr-global/world/world_changes/fury_gates/ankrahmun.otbm
index 04b378fa578..d426bf44223 100644
Binary files a/data-otservbr-global/world/world_changes/fury_gates/ankrahmun.otbm and b/data-otservbr-global/world/world_changes/fury_gates/ankrahmun.otbm differ
diff --git a/data-otservbr-global/world/world_changes/fury_gates/carlin.otbm b/data-otservbr-global/world/world_changes/fury_gates/carlin.otbm
index a112b2e45e6..15f3fc688b6 100644
Binary files a/data-otservbr-global/world/world_changes/fury_gates/carlin.otbm and b/data-otservbr-global/world/world_changes/fury_gates/carlin.otbm differ
diff --git a/data-otservbr-global/world/world_changes/fury_gates/darashia.otbm b/data-otservbr-global/world/world_changes/fury_gates/darashia.otbm
index 5f96244586b..4e259396824 100644
Binary files a/data-otservbr-global/world/world_changes/fury_gates/darashia.otbm and b/data-otservbr-global/world/world_changes/fury_gates/darashia.otbm differ
diff --git a/data-otservbr-global/world/world_changes/fury_gates/edron.otbm b/data-otservbr-global/world/world_changes/fury_gates/edron.otbm
index 72d3239b4bb..3ad2c77ca4a 100644
Binary files a/data-otservbr-global/world/world_changes/fury_gates/edron.otbm and b/data-otservbr-global/world/world_changes/fury_gates/edron.otbm differ
diff --git a/data-otservbr-global/world/world_changes/fury_gates/kazordoon.otbm b/data-otservbr-global/world/world_changes/fury_gates/kazordoon.otbm
index c1aa4744aac..4a6e4386f29 100644
Binary files a/data-otservbr-global/world/world_changes/fury_gates/kazordoon.otbm and b/data-otservbr-global/world/world_changes/fury_gates/kazordoon.otbm differ
diff --git a/data-otservbr-global/world/world_changes/fury_gates/libertybay.otbm b/data-otservbr-global/world/world_changes/fury_gates/libertybay.otbm
index ac1df9d0950..c9a57e0f45f 100644
Binary files a/data-otservbr-global/world/world_changes/fury_gates/libertybay.otbm and b/data-otservbr-global/world/world_changes/fury_gates/libertybay.otbm differ
diff --git a/data-otservbr-global/world/world_changes/fury_gates/porthope.otbm b/data-otservbr-global/world/world_changes/fury_gates/porthope.otbm
index f0b1b943c76..2232ecbc988 100644
Binary files a/data-otservbr-global/world/world_changes/fury_gates/porthope.otbm and b/data-otservbr-global/world/world_changes/fury_gates/porthope.otbm differ
diff --git a/data-otservbr-global/world/world_changes/fury_gates/thais.otbm b/data-otservbr-global/world/world_changes/fury_gates/thais.otbm
index dfb1039d07b..a131dd8d096 100644
Binary files a/data-otservbr-global/world/world_changes/fury_gates/thais.otbm and b/data-otservbr-global/world/world_changes/fury_gates/thais.otbm differ
diff --git a/data-otservbr-global/world/world_changes/fury_gates/venore.otbm b/data-otservbr-global/world/world_changes/fury_gates/venore.otbm
index 1ca21d586d1..1922f21ab60 100644
Binary files a/data-otservbr-global/world/world_changes/fury_gates/venore.otbm and b/data-otservbr-global/world/world_changes/fury_gates/venore.otbm differ
diff --git a/data/events/scripts/creature.lua b/data/events/scripts/creature.lua
index 99d9f189d01..4da8217988d 100644
--- a/data/events/scripts/creature.lua
+++ b/data/events/scripts/creature.lua
@@ -1,5 +1,5 @@
-local function removeCombatProtection(cid)
- local player = Player(cid)
+local function removeCombatProtection(playerUid)
+ local player = Player(playerUid)
if not player then
return true
end
@@ -14,30 +14,22 @@ local function removeCombatProtection(cid)
end
player:setStorageValue(Global.Storage.CombatProtectionStorage, 2)
- addEvent(function(cid)
- local player = Player(cid)
- if not player then
+ addEvent(function(playerFuncUid)
+ local playerEvent = Player(playerFuncUid)
+ if not playerEvent then
return
end
- player:setStorageValue(Global.Storage.CombatProtectionStorage, 0)
- player:remove()
- end, time * 1000, cid)
+ playerEvent:setStorageValue(Global.Storage.CombatProtectionStorage, 0)
+ playerEvent:remove()
+ end, time * 1000, playerUid)
end
-picIf = {}
function Creature:onTargetCombat(target)
if not self then
return true
end
- if not picIf[target.uid] then
- if target:isMonster() then
- target:registerEvent("RewardSystemSlogan")
- picIf[target.uid] = {}
- end
- end
-
if target:isPlayer() then
if self:isMonster() then
local protectionStorage = target:getStorageValue(Global.Storage.CombatProtectionStorage)
diff --git a/data/events/scripts/party.lua b/data/events/scripts/party.lua
index 16ed0b733d9..b27965994b8 100644
--- a/data/events/scripts/party.lua
+++ b/data/events/scripts/party.lua
@@ -1,38 +1,38 @@
function Party:onJoin(player)
- local playerId = player:getId()
- addEvent(function()
- player = Player(playerId)
- if not player then
+ local playerUid = player:getGuid()
+ addEvent(function(playerFuncUid)
+ local playerEvent = Player(playerFuncUid)
+ if not playerEvent then
return
end
- local party = player:getParty()
+ local party = playerEvent:getParty()
if not party then
return
end
party:refreshHazard()
- end, 100)
+ end, 100, playerUid)
return true
end
function Party:onLeave(player)
- local playerId = player:getId()
+ local playerUid = player:getGuid()
local members = self:getMembers()
table.insert(members, self:getLeader())
- local memberIds = {}
+ local memberUids = {}
for _, member in ipairs(members) do
- if member:getId() ~= playerId then
- table.insert(memberIds, member:getId())
+ if member:getGuid() ~= playerUid then
+ table.insert(memberUids, member:getGuid())
end
end
- addEvent(function()
- player = Player(playerId)
- if player then
- player:updateHazard()
+ addEvent(function(playerFuncUid, memberUidsTableEvent)
+ local playerEvent = Player(playerFuncUid)
+ if playerEvent then
+ playerEvent:updateHazard()
end
- for _, memberId in ipairs(memberIds) do
- local member = Player(memberId)
+ for _, memberUid in ipairs(memberUidsTableEvent) do
+ local member = Player(memberUid)
if member then
local party = member:getParty()
if party then
@@ -41,7 +41,7 @@ function Party:onLeave(player)
end
end
end
- end, 100)
+ end, 100, playerUid, memberUids)
return true
end
@@ -86,5 +86,5 @@ function Party:onShareExperience(exp)
sharedExperienceMultiplier = 1.0 + ((size * (5 * (size - 1) + 10)) / 100)
end
- return (exp * sharedExperienceMultiplier) / (#self:getMembers() + 1)
+ return math.ceil((exp * sharedExperienceMultiplier) / (#self:getMembers() + 1))
end
diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua
index 49ab18e8a07..816aa523b70 100644
--- a/data/events/scripts/player.lua
+++ b/data/events/scripts/player.lua
@@ -40,64 +40,52 @@ local storeItemID = {
-- Players cannot throw items on teleports if set to true
local blockTeleportTrashing = true
-local titles = {
- { storageID = 14960, title = " Scout" },
- { storageID = 14961, title = " Sentinel" },
- { storageID = 14962, title = " Steward" },
- { storageID = 14963, title = " Warden" },
- { storageID = 14964, title = " Squire" },
- { storageID = 14965, title = " Warrior" },
- { storageID = 14966, title = " Keeper" },
- { storageID = 14967, title = " Guardian" },
- { storageID = 14968, title = " Sage" },
- { storageID = 14969, title = " Tutor" },
- { storageID = 14970, title = " Senior Tutor" },
- { storageID = 14971, title = " King" },
-}
-
local config = {
maxItemsPerSeconds = 1,
exhaustTime = 2000,
}
-if not pushDelay then
- pushDelay = {}
-end
+local pushDelay = {}
+
+local function antiPush(player, item, count, fromPosition, toPosition, fromCylinder, toCylinder)
+ if not player then
+ player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
+ return false
+ end
-local function antiPush(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder)
if toPosition.x == CONTAINER_POSITION then
return true
end
local tile = Tile(toPosition)
if not tile then
- self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
+ player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
return false
end
- local cid = self:getId()
- if not pushDelay[cid] then
- pushDelay[cid] = { items = 0, time = 0 }
+ local playerId = player:getId()
+ if not pushDelay[playerId] then
+ pushDelay[playerId] = { items = 0, time = 0 }
end
- pushDelay[cid].items = pushDelay[cid].items + 1
+ pushDelay[playerId].items = pushDelay[playerId].items + 1
local currentTime = systemTime()
- if pushDelay[cid].time == 0 then
- pushDelay[cid].time = currentTime
- elseif pushDelay[cid].time == currentTime then
- pushDelay[cid].items = pushDelay[cid].items + 1
- elseif currentTime > pushDelay[cid].time then
- pushDelay[cid].time = 0
- pushDelay[cid].items = 0
+ if pushDelay[playerId].time == 0 then
+ pushDelay[playerId].time = currentTime
+ elseif pushDelay[playerId].time == currentTime then
+ pushDelay[playerId].items = pushDelay[playerId].items + 1
+ elseif currentTime > pushDelay[playerId].time then
+ pushDelay[playerId].time = 0
+ pushDelay[playerId].items = 0
end
- if pushDelay[cid].items > config.maxItemsPerSeconds then
- pushDelay[cid].time = currentTime + config.exhaustTime
+ if pushDelay[playerId].items > config.maxItemsPerSeconds then
+ pushDelay[playerId].time = currentTime + config.exhaustTime
end
- if pushDelay[cid].time > currentTime then
- self:sendCancelMessage("You can't move that item so fast.")
+ if pushDelay[playerId].time > currentTime then
+ player:sendCancelMessage("You can't move that item so fast.")
return false
end
@@ -263,13 +251,12 @@ function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder,
if exhaust[pid] then
self:sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED)
return false
- else
- exhaust[pid] = true
- addEvent(function()
- exhaust[pid] = false
- end, 2000, pid)
- return true
end
+ exhaust[playerId] = true
+ addEvent(function()
+ exhaust[playerId] = false
+ end, 2000, playerId)
+ return true
end
-- Bath tube
@@ -304,9 +291,8 @@ function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder,
if parent:getSize() == parent:getCapacity() then
self:sendTextMessage(MESSAGE_FAILURE, Game.getReturnMessage(RETURNVALUE_CONTAINERNOTENOUGHROOM))
return false
- else
- return moveItem:moveTo(parent)
end
+ return moveItem:moveTo(parent)
end
end
@@ -370,24 +356,27 @@ function Player:onItemMoved(item, count, fromPosition, toPosition, fromCylinder,
local topos = Position(33052, 31932, 15) -- Checagem
local removeItem = false
if self:getPosition():isInRange(frompos, topos) and item:getId() == 23729 then
- local tileBoss = Tile(toPosition)
- if tileBoss and tileBoss:getTopCreature() and tileBoss:getTopCreature():isMonster() then
- if tileBoss:getTopCreature():getName():lower() == "the remorseless corruptor" then
- tileBoss:getTopCreature():addHealth(-17000)
- tileBoss:getTopCreature():remove()
- local monster = Game.createMonster("The Corruptor of Souls", toPosition)
- if not monster then
- return false
- end
- removeItem = true
- monster:registerEvent("CheckTile")
- if Game.getStorageValue("healthSoul") > 0 then
- monster:addHealth(-(monster:getHealth() - Game.getStorageValue("healthSoul")))
+ local tile = Tile(toPosition)
+ if tile then
+ local tileBoss = tile:getTopCreature()
+ if tileBoss and tileBoss:isMonster() then
+ if tileBoss:getName():lower() == "the remorseless corruptor" then
+ tileBoss:addHealth(-17000)
+ tileBoss:remove()
+ local monster = Game.createMonster("The Corruptor of Souls", toPosition)
+ if not monster then
+ return false
+ end
+ removeItem = true
+ monster:registerEvent("CheckTile")
+ if Game.getStorageValue("healthSoul") > 0 then
+ monster:addHealth(-(monster:getHealth() - Game.getStorageValue("healthSoul")))
+ end
+ Game.setStorageValue("CheckTile", os.time() + 30)
+ elseif tileBoss:getName():lower() == "the corruptor of souls" then
+ Game.setStorageValue("CheckTile", os.time() + 30)
+ removeItem = true
end
- Game.setStorageValue("CheckTile", os.time() + 30)
- elseif tileBoss:getTopCreature():getName():lower() == "the corruptor of souls" then
- Game.setStorageValue("CheckTile", os.time() + 30)
- removeItem = true
end
end
if removeItem then
@@ -401,7 +390,7 @@ end
function Player:onMoveCreature(creature, fromPosition, toPosition)
local player = creature:getPlayer()
- if player and onExerciseTraining[player:getId()] and self:getGroup():hasFlag(PlayerFlag_CanPushAllCreatures) == false then
+ if player and onExerciseTraining[player:getId()] and not self:getGroup():hasFlag(PlayerFlag_CanPushAllCreatures) then
self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
return false
end
@@ -409,13 +398,12 @@ function Player:onMoveCreature(creature, fromPosition, toPosition)
end
local function hasPendingReport(name, targetName, reportType)
- local f = io.open(string.format("%s/reports/players/%s-%s-%d.txt", CORE_DIRECTORY, name, targetName, reportType), "r")
- if f then
- io.close(f)
+ local file = io.open(string.format("%s/reports/players/%s-%s-%d.txt", CORE_DIRECTORY, name, targetName, reportType), "r")
+ if file then
+ io.close(file)
return true
- else
- return false
end
+ return false
end
function Player:onReportRuleViolation(targetName, reportType, reportReason, comment, translation)
@@ -457,6 +445,7 @@ end
function Player:onReportBug(message, position, category)
local name = self:getName()
+ FS.mkdir_p(string.format("%s/reports/bugs/%s", CORE_DIRECTORY, name))
local file = io.open(string.format("%s/reports/bugs/%s/report.txt", CORE_DIRECTORY, name), "a")
if not file then
@@ -483,7 +472,6 @@ function Player:onTurn(direction)
if self:getGroup():getAccess() and self:getDirection() == direction then
local nextPosition = self:getPosition()
nextPosition:getNextPosition(direction)
-
self:teleportTo(nextPosition, true)
end
@@ -569,31 +557,28 @@ function Player:onGainSkillTries(skill, tries)
if IsRunningGlobalDatapack() and isSkillGrowthLimited(self, skill) then
return 0
end
- if APPLY_SKILL_MULTIPLIER == false then
+ if not APPLY_SKILL_MULTIPLIER then
return tries
end
-- Event scheduler skill rate
+ local STAGES_DEFAULT = nil
if configManager.getBoolean(configKeys.RATE_USE_STAGES) then
STAGES_DEFAULT = skillsStages
- else
- STAGES_DEFAULT = nil
end
- SKILL_DEFAULT = self:getSkillLevel(skill)
- RATE_DEFAULT = configManager.getNumber(configKeys.RATE_SKILL)
+ local SKILL_DEFAULT = self:getSkillLevel(skill)
+ local RATE_DEFAULT = configManager.getNumber(configKeys.RATE_SKILL)
if skill == SKILL_MAGLEVEL then
-- Magic Level
if configManager.getBoolean(configKeys.RATE_USE_STAGES) then
STAGES_DEFAULT = magicLevelStages
- else
- STAGES_DEFAULT = nil
end
SKILL_DEFAULT = self:getBaseMagicLevel()
RATE_DEFAULT = configManager.getNumber(configKeys.RATE_MAGIC)
end
- skillOrMagicRate = getRateFromTable(STAGES_DEFAULT, SKILL_DEFAULT, RATE_DEFAULT)
+ local skillOrMagicRate = getRateFromTable(STAGES_DEFAULT, SKILL_DEFAULT, RATE_DEFAULT)
if SCHEDULE_SKILL_RATE ~= 100 then
skillOrMagicRate = math.max(0, (skillOrMagicRate * SCHEDULE_SKILL_RATE) / 100)
@@ -618,9 +603,8 @@ function Player:onCombat(target, item, primaryDamage, primaryType, secondaryDama
if ItemType(item:getId()):getWeaponType() == WEAPON_AMMO then
if table.contains({ ITEM_OLD_DIAMOND_ARROW, ITEM_DIAMOND_ARROW }, item:getId()) then
return primaryDamage, primaryType, secondaryDamage, secondaryType
- else
- item = self:getSlotItem(CONST_SLOT_LEFT)
end
+ item = self:getSlotItem(CONST_SLOT_LEFT)
end
return primaryDamage, primaryType, secondaryDamage, secondaryType
diff --git a/data/global.lua b/data/global.lua
index 88d1d8103c0..9fad23e8787 100644
--- a/data/global.lua
+++ b/data/global.lua
@@ -43,8 +43,6 @@ DIRECTIONS_TABLE = {
DIRECTION_NORTHEAST,
}
-STORAGEVALUE_PROMOTION = 30018
-
SERVER_NAME = configManager.getString(configKeys.SERVER_NAME)
SERVER_MOTD = configManager.getString(configKeys.SERVER_MOTD)
@@ -85,16 +83,16 @@ ropeSpots = { 386, 421, 386, 7762, 12202, 12936, 14238, 17238, 23363, 21965, 219
specialRopeSpots = { 12935 }
-- Impact Analyser
--- Every 2 seconds
-updateInterval = 2
if not GlobalBosses then
GlobalBosses = {}
end
+
-- Healing
-- Global table to insert data
if healingImpact == nil then
healingImpact = {}
end
+
-- Damage
-- Global table to insert data
if damageImpact == nil then
diff --git a/data/items/items.xml b/data/items/items.xml
index 8c826d3fd64..d23468cabf3 100644
--- a/data/items/items.xml
+++ b/data/items/items.xml
@@ -15098,7 +15098,6 @@
-
-
-
diff --git a/data/libs/functions/creature.lua b/data/libs/functions/creature.lua
index 86245cc2491..d43a31f287d 100644
--- a/data/libs/functions/creature.lua
+++ b/data/libs/functions/creature.lua
@@ -193,6 +193,37 @@ function Creature.checkCreatureInsideDoor(player, toPosition)
end
end
+function Creature:canAccessPz()
+ if self:isMonster() or (self:isPlayer() and self:isPzLocked()) then
+ return false
+ end
+ return true
+end
+
+function Creature.getKillers(self, onlyPlayers)
+ local killers = {}
+ local inFightTicks = configManager.getNumber(configKeys.PZ_LOCKED)
+ local timeNow = os.mtime()
+ local getCreature = onlyPlayers and Player or Creature
+ for cid, cb in pairs(self:getDamageMap()) do
+ local creature = getCreature(cid)
+ if creature and creature ~= self and (timeNow - cb.ticks) <= inFightTicks then
+ killers[#killers + 1] = {
+ creature = creature,
+ damage = cb.total,
+ }
+ end
+ end
+
+ table.sort(killers, function(a, b)
+ return a.damage > b.damage
+ end)
+ for i, killer in pairs(killers) do
+ killers[i] = killer.creature
+ end
+ return killers
+end
+
function Creature:addEventStamina(target)
local player = self:getPlayer()
local monster = target:getMonster()
diff --git a/data/libs/functions/fs.lua b/data/libs/functions/fs.lua
new file mode 100644
index 00000000000..f95fba51b05
--- /dev/null
+++ b/data/libs/functions/fs.lua
@@ -0,0 +1,32 @@
+FS = {}
+
+function FS.exists(path)
+ local file = io.open(path, "r")
+ if file then
+ file:close()
+ return true
+ end
+ return false
+end
+
+function FS.mkdir(path)
+ if FS.exists(path) then
+ return true
+ end
+ local success, err = os.execute("mkdir " .. path)
+ if not success then
+ return false, err
+ end
+ return true
+end
+
+function FS.mkdir_p(path)
+ if path == "" then
+ return true
+ end
+ if FS.exists(path) then
+ return true
+ end
+ FS.mkdir_p(path:match("(.*[/\\])"))
+ return FS.mkdir(path)
+end
diff --git a/data/libs/functions/load.lua b/data/libs/functions/load.lua
index 705fdf712d6..0ab33f5de11 100644
--- a/data/libs/functions/load.lua
+++ b/data/libs/functions/load.lua
@@ -5,6 +5,7 @@ dofile(CORE_DIRECTORY .. "/libs/functions/constants.lua")
dofile(CORE_DIRECTORY .. "/libs/functions/container.lua")
dofile(CORE_DIRECTORY .. "/libs/functions/creature.lua")
dofile(CORE_DIRECTORY .. "/libs/functions/functions.lua")
+dofile(CORE_DIRECTORY .. "/libs/functions/fs.lua")
dofile(CORE_DIRECTORY .. "/libs/functions/game.lua")
dofile(CORE_DIRECTORY .. "/libs/functions/item.lua")
dofile(CORE_DIRECTORY .. "/libs/functions/itemtype.lua")
diff --git a/data/libs/functions/player.lua b/data/libs/functions/player.lua
index ad35e6851f6..c2ae610cfd2 100644
--- a/data/libs/functions/player.lua
+++ b/data/libs/functions/player.lua
@@ -132,6 +132,10 @@ function Player.allowMovement(self, allow)
return self:setStorageValue(Global.Storage.BlockMovementStorage, allow and -1 or 1)
end
+function Player.hasAllowMovement(self)
+ return self:getStorageValue(Global.Storage.BlockMovementStorage) ~= 1
+end
+
function Player.checkGnomeRank(self)
if not IsRunningGlobalDatapack() then
return true
@@ -236,19 +240,14 @@ function Player:removeMoneyBank(amount)
self:sendTextMessage(MESSAGE_TRADE, ("Paid %s from inventory and %s gold from bank account. Your account balance is now %s gold."):format(FormatNumber(moneyCount), FormatNumber(amount - moneyCount), FormatNumber(self:getBankBalance())))
return true
- else
- self:setBankBalance(bankCount - amount)
- self:sendTextMessage(MESSAGE_TRADE, ("Paid %s gold from bank account. Your account balance is now %s gold."):format(FormatNumber(amount), FormatNumber(self:getBankBalance())))
- return true
end
+ self:setBankBalance(bankCount - amount)
+ self:sendTextMessage(MESSAGE_TRADE, ("Paid %s gold from bank account. Your account balance is now %s gold."):format(FormatNumber(amount), FormatNumber(self:getBankBalance())))
+ return true
end
return false
end
-function Player.hasAllowMovement(self)
- return self:getStorageValue(Global.Storage.BlockMovementStorage) ~= 1
-end
-
function Player.hasRookgaardShield(self)
-- Wooden Shield, Studded Shield, Brass Shield, Plate Shield, Copper Shield
return self:getItemCount(3412) > 0 or self:getItemCount(3426) > 0 or self:getItemCount(3411) > 0 or self:getItemCount(3410) > 0 or self:getItemCount(3430) > 0
diff --git a/data/libs/functions/position.lua b/data/libs/functions/position.lua
index 36ddc337aed..d3ac6667be3 100644
--- a/data/libs/functions/position.lua
+++ b/data/libs/functions/position.lua
@@ -36,7 +36,8 @@ function Position:moveUpstairs()
direction = DIRECTION_WEST
end
- local position = self + Position.directionOffset[direction]
+ local position = Position(self)
+ position:getNextPosition(direction)
toTile = Tile(position)
if toTile and toTile:isWalkable(false, false, false, false, true) then
swap(self, position)
diff --git a/data/libs/functions/tile.lua b/data/libs/functions/tile.lua
index 70690c65f7a..0201459018c 100644
--- a/data/libs/functions/tile.lua
+++ b/data/libs/functions/tile.lua
@@ -78,7 +78,7 @@ end
-- Functions from OTServbr-Global
function Tile.isHouse(self)
local house = self:getHouse()
- return not not house
+ return house and true or false
end
function Tile.isPz(self)
diff --git a/data/scripts/talkactions/player/reward.lua b/data/scripts/talkactions/player/reward.lua
new file mode 100644
index 00000000000..7c2999a587e
--- /dev/null
+++ b/data/scripts/talkactions/player/reward.lua
@@ -0,0 +1,65 @@
+local config = {
+ items = {
+ { id = 35284, charges = 64000 },
+ { id = 35279, charges = 64000 },
+ { id = 35281, charges = 64000 },
+ { id = 35283, charges = 64000 },
+ { id = 35282, charges = 64000 },
+ { id = 35280, charges = 64000 },
+ },
+ storage = tonumber(Storage.PlayerWeaponReward), -- storage key, player can only win once
+}
+
+local function sendExerciseRewardModal(player)
+ local window = ModalWindow({
+ title = "Exercise Reward",
+ message = "choose a item",
+ })
+ for _, it in pairs(config.items) do
+ local iType = ItemType(it.id)
+ if iType then
+ window:addChoice(iType:getName(), function(player, button, choice)
+ if button.name ~= "Select" then
+ return true
+ end
+
+ local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
+ if inbox and inbox:getEmptySlots() > 0 then
+ local item = inbox:addItem(it.id, it.charges)
+ if item then
+ item:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime())
+ else
+ player:sendTextMessage(MESSAGE_LOOK, "You need to have capacity and empty slots to receive.")
+ return
+ end
+ player:sendTextMessage(MESSAGE_LOOK, string.format("Congratulations, you received a %s with %i charges in your store inbox.", iType:getName(), it.charges))
+ player:setStorageValue(config.storage, 1)
+ else
+ player:sendTextMessage(MESSAGE_LOOK, "You need to have capacity and empty slots to receive.")
+ end
+ end)
+ end
+ end
+ window:addButton("Select")
+ window:addButton("Close")
+ window:setDefaultEnterButton(0)
+ window:setDefaultEscapeButton(1)
+ window:sendToPlayer(player)
+end
+
+local exerciseRewardModal = TalkAction("!reward")
+function exerciseRewardModal.onSay(player, words, param)
+ if not configManager.getBoolean(configKeys.TOGGLE_RECEIVE_REWARD) or player:getTown():getId() < TOWNS_LIST.AB_DENDRIEL then
+ return true
+ end
+ if player:getStorageValue(config.storage) > 0 then
+ player:sendTextMessage(MESSAGE_LOOK, "You already received your exercise weapon reward!")
+ return true
+ end
+ sendExerciseRewardModal(player)
+ return true
+end
+
+exerciseRewardModal:separator(" ")
+exerciseRewardModal:groupType("normal")
+exerciseRewardModal:register()
diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp
index 6a785280e56..d3bf0b87eec 100644
--- a/src/config/config_definitions.hpp
+++ b/src/config/config_definitions.hpp
@@ -91,6 +91,8 @@ enum booleanConfig_t {
TOGGLE_MOUNT_IN_PZ,
TOGGLE_HOUSE_TRANSFER_ON_SERVER_RESTART,
+ TOGGLE_RECEIVE_REWARD,
+
LAST_BOOLEAN_CONFIG
};
diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp
index 940ea29eb51..d0abcc9352b 100644
--- a/src/config/configmanager.cpp
+++ b/src/config/configmanager.cpp
@@ -12,7 +12,6 @@
#include "config/configmanager.hpp"
#include "declarations.hpp"
#include "game/game.hpp"
-#include "lua/scripts/luajit_sync.hpp"
#include "server/network/webhook/webhook.hpp"
#if LUA_VERSION_NUM >= 502
@@ -393,6 +392,8 @@ bool ConfigManager::load() {
boolean[TOGGLE_HOUSE_TRANSFER_ON_SERVER_RESTART] = getGlobalBoolean(L, "togglehouseTransferOnRestart", false);
+ boolean[TOGGLE_RECEIVE_REWARD] = getGlobalBoolean(L, "toggleReceiveReward", false);
+
loaded = true;
lua_close(L);
return true;
diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp
index 30b0208ad0a..75cec14c30f 100644
--- a/src/creatures/combat/combat.cpp
+++ b/src/creatures/combat/combat.cpp
@@ -18,6 +18,7 @@
#include "creatures/monsters/monster.hpp"
#include "creatures/monsters/monsters.hpp"
#include "items/weapons/weapons.hpp"
+#include "map/spectators.hpp"
int32_t Combat::getLevelFormula(std::shared_ptr player, const std::shared_ptr wheelSpell, const CombatDamage &damage) const {
if (!player) {
@@ -72,7 +73,7 @@ CombatDamage Combat::getCombatDamage(std::shared_ptr creature, std::sh
);
} else if (formulaType == COMBAT_FORMULA_SKILL) {
std::shared_ptr
- tool = player->getWeapon();
- const Weapon* weapon = g_weapons().getWeapon(tool);
+ const WeaponShared_ptr weapon = g_weapons().getWeapon(tool);
if (weapon) {
damage.primary.value = normal_random(
static_cast(minb),
@@ -754,7 +755,7 @@ void Combat::CombatNullFunc(std::shared_ptr caster, std::shared_ptr caster, std::shared_ptr tile, const CombatParams ¶ms) {
+void Combat::combatTileEffects(const CreatureVector &spectators, std::shared_ptr caster, std::shared_ptr tile, const CombatParams ¶ms) {
if (params.itemId != 0) {
uint16_t itemId = params.itemId;
switch (itemId) {
@@ -998,7 +999,6 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin
getCombatArea(pos, pos, area, tileList);
}
- SpectatorHashSet spectators;
uint32_t maxX = 0;
uint32_t maxY = 0;
@@ -1019,7 +1019,6 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin
const int32_t rangeX = maxX + MAP_MAX_VIEW_PORT_X;
const int32_t rangeY = maxY + MAP_MAX_VIEW_PORT_Y;
- g_game().map.getSpectators(spectators, pos, true, true, rangeX, rangeX, rangeY, rangeY);
int affected = 0;
for (std::shared_ptr tile : tileList) {
@@ -1071,6 +1070,7 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin
}
// Wheel of destiny get beam affected total
+ auto spectators = Spectators().find(pos, true, rangeX, rangeX, rangeY, rangeY);
std::shared_ptr casterPlayer = caster ? caster->getPlayer() : nullptr;
uint8_t beamAffectedTotal = casterPlayer ? casterPlayer->wheel()->getBeamAffectedTotal(tmpDamage) : 0;
uint8_t beamAffectedCurrent = 0;
@@ -1110,7 +1110,7 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin
}
}
}
- combatTileEffects(spectators, caster, tile, params);
+ combatTileEffects(spectators.data(), caster, tile, params);
}
// Wheel of destiny update beam mastery damage
@@ -1139,41 +1139,7 @@ void Combat::doCombatHealth(std::shared_ptr caster, std::shared_ptrgetPlayer()) {
- // Critical damage
- uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance;
- // Charm low blow rune)
- if (target && target->getMonster() && damage.primary.type != COMBAT_HEALING) {
- uint16_t playerCharmRaceid = caster->getPlayer()->parseRacebyCharm(CHARM_LOW, false, 0);
- if (playerCharmRaceid != 0) {
- const auto mType = g_monsters().getMonsterType(target->getName());
- if (mType && playerCharmRaceid == mType->info.raceid) {
- const auto charm = g_iobestiary().getBestiaryCharm(CHARM_LOW);
- if (charm) {
- chance += charm->percent;
- g_game().sendDoubleSoundEffect(target->getPosition(), charm->soundCastEffect, charm->soundImpactEffect, caster);
- }
- }
- }
- }
- if (chance != 0 && uniform_random(1, 100) <= chance) {
- damage.critical = true;
- damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100;
- damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100;
- }
-
- // Fatal hit (onslaught)
- if (auto playerWeapon = caster->getPlayer()->getInventoryItem(CONST_SLOT_LEFT);
- playerWeapon != nullptr && playerWeapon->getTier()) {
- double_t fatalChance = playerWeapon->getFatalChance();
- double_t randomChance = uniform_random(0, 10000) / 100;
- if (damage.primary.type != COMBAT_HEALING && fatalChance > 0 && randomChance < fatalChance) {
- damage.fatal = true;
- damage.primary.value += static_cast(std::round(damage.primary.value * 0.6));
- damage.secondary.value += static_cast(std::round(damage.secondary.value * 0.6));
- }
- }
- }
+ applyExtensions(caster, target, damage, params);
if (canCombat) {
if (target && caster && params.distanceEffect != CONST_ANI_NONE) {
@@ -1194,27 +1160,7 @@ void Combat::doCombatHealth(std::shared_ptr caster, std::shared_ptr caster, const Position &position, const std::unique_ptr &area, CombatDamage &damage, const CombatParams ¶ms) {
- if (caster && caster->getPlayer()) {
- // Critical damage
- uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance;
- if (damage.primary.type != COMBAT_HEALING && chance != 0 && uniform_random(1, 100) <= chance) {
- damage.critical = true;
- damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100;
- damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100;
- }
-
- // Fatal hit (onslaught)
- if (auto playerWeapon = caster->getPlayer()->getInventoryItem(CONST_SLOT_LEFT);
- playerWeapon != nullptr && playerWeapon->getTier() > 0) {
- double_t fatalChance = playerWeapon->getFatalChance();
- double_t randomChance = uniform_random(0, 10000) / 100;
- if (damage.primary.type != COMBAT_HEALING && fatalChance > 0 && randomChance < fatalChance) {
- damage.fatal = true;
- damage.primary.value += static_cast(std::round(damage.primary.value * 0.6));
- damage.secondary.value += static_cast(std::round(damage.secondary.value * 0.6));
- }
- }
- }
+ applyExtensions(caster, nullptr, damage, params);
const auto origin = caster ? caster->getPosition() : Position();
CombatFunc(caster, origin, position, area, params, CombatHealthFunc, &damage);
}
@@ -1231,15 +1177,7 @@ void Combat::doCombatMana(std::shared_ptr caster, std::shared_ptrgetPosition(), params.impactEffect);
}
- if (caster && caster->getPlayer()) {
- // Critical damage
- uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance;
- if (chance != 0 && uniform_random(1, 100) <= chance) {
- damage.critical = true;
- damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100;
- damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100;
- }
- }
+ applyExtensions(caster, target, damage, params);
if (canCombat) {
if (caster && target && params.distanceEffect != CONST_ANI_NONE) {
@@ -1260,15 +1198,7 @@ void Combat::doCombatMana(std::shared_ptr caster, std::shared_ptr caster, const Position &position, const std::unique_ptr &area, CombatDamage &damage, const CombatParams ¶ms) {
- if (caster && caster->getPlayer()) {
- // Critical damage
- uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance;
- if (chance != 0 && uniform_random(1, 100) <= chance) {
- damage.critical = true;
- damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100;
- damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100;
- }
- }
+ applyExtensions(caster, nullptr, damage, params);
const auto origin = caster ? caster->getPosition() : Position();
CombatFunc(caster, origin, position, area, params, CombatManaFunc, &damage);
}
@@ -1339,11 +1269,10 @@ void Combat::doCombatDefault(std::shared_ptr caster, std::shared_ptr caster, std::shared_ptr target, const Position &origin, const CombatParams ¶ms) {
if (!params.aggressive || (caster != target && Combat::canDoCombat(caster, target, params.aggressive) == RETURNVALUE_NOERROR)) {
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, target->getPosition(), true, true);
+ auto spectators = Spectators().find(target->getPosition(), true);
CombatNullFunc(caster, target, params, nullptr);
- combatTileEffects(spectators, caster, target->getTile(), params);
+ combatTileEffects(spectators.data(), caster, target->getTile(), params);
if (params.targetCallback) {
params.targetCallback->onTargetCombat(caster, target);
@@ -1396,13 +1325,12 @@ std::vector>> Combat::pickChainTargets
const int maxBacktrackingAttempts = 10; // Can be adjusted as needed
while (!targets.empty() && targets.size() <= maxTargets) {
auto currentTarget = targets.back();
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, currentTarget->getPosition(), false, false, chainDistance, chainDistance, chainDistance, chainDistance);
+ auto spectators = Spectators().find(currentTarget->getPosition(), false, chainDistance, chainDistance, chainDistance, chainDistance);
g_logger().debug("Combat::pickChainTargets: currentTarget: {}, spectators: {}", currentTarget->getName(), spectators.size());
double closestDistance = std::numeric_limits::max();
std::shared_ptr closestSpectator = nullptr;
- for (std::shared_ptr spectator : spectators) {
+ for (const auto &spectator : spectators) {
if (!spectator || visited.contains(spectator->getID())) {
continue;
}
@@ -1516,7 +1444,7 @@ void ValueCallback::getMinMaxValues(std::shared_ptr player, CombatDamage
case COMBAT_FORMULA_SKILL: {
// onGetPlayerMinMaxValues(player, attackSkill, attackValue, attackFactor)
std::shared_ptr
- tool = player->getWeapon();
- const Weapon* weapon = g_weapons().getWeapon(tool);
+ const WeaponShared_ptr weapon = g_weapons().getWeapon(tool);
std::shared_ptr
- item = nullptr;
if (weapon) {
@@ -1770,13 +1698,15 @@ bool ChainPickerCallback::onChainCombat(std::shared_ptr creature, std:
//**********************************************************//
void AreaCombat::clear() {
- areas.clear();
+ std::ranges::fill(areas, nullptr);
}
AreaCombat::AreaCombat(const AreaCombat &rhs) {
hasExtArea = rhs.hasExtArea;
- for (const auto &it : rhs.areas) {
- areas[it.first] = it.second->clone();
+ for (uint_fast8_t i = 0; i <= Direction::DIRECTION_LAST; ++i) {
+ if (const auto &area = rhs.areas[i]) {
+ areas[i] = area->clone();
+ }
}
}
@@ -2080,3 +2010,64 @@ void MagicField::onStepInField(const std::shared_ptr &creature) {
creature->addCondition(conditionCopy);
}
}
+
+void Combat::applyExtensions(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms) {
+ if (damage.extension || !caster || damage.primary.type == COMBAT_HEALING) {
+ return;
+ }
+
+ g_logger().debug("[Combat::applyExtensions] - Applying extensions for {} on {}. Initial damage: {}", caster->getName(), target ? target->getName() : "null", damage.primary.value);
+
+ // Critical hit
+ uint16_t chance = 0;
+ int32_t multiplier = 50;
+ auto player = caster->getPlayer();
+ auto monster = caster->getMonster();
+ if (player) {
+ chance = player->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE);
+ multiplier = player->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE);
+
+ if (target) {
+ uint16_t playerCharmRaceid = player->parseRacebyCharm(CHARM_LOW, false, 0);
+ if (playerCharmRaceid != 0) {
+ const auto mType = g_monsters().getMonsterType(target->getName());
+ if (mType && playerCharmRaceid == mType->info.raceid) {
+ const auto charm = g_iobestiary().getBestiaryCharm(CHARM_LOW);
+ if (charm) {
+ chance += charm->percent;
+ g_game().sendDoubleSoundEffect(target->getPosition(), charm->soundCastEffect, charm->soundImpactEffect, caster);
+ }
+ }
+ }
+ }
+ } else if (monster) {
+ chance = monster->critChance();
+ }
+
+ multiplier += damage.criticalDamage;
+ multiplier = 1 + multiplier / 100;
+ chance += (uint16_t)damage.criticalChance;
+
+ if (chance != 0 && uniform_random(1, 100) <= chance) {
+ damage.critical = true;
+ damage.primary.value *= multiplier;
+ damage.secondary.value *= multiplier;
+ }
+
+ if (player) {
+ // Fatal hit (onslaught)
+ if (auto playerWeapon = player->getInventoryItem(CONST_SLOT_LEFT);
+ playerWeapon != nullptr && playerWeapon->getTier() > 0) {
+ double_t fatalChance = playerWeapon->getFatalChance();
+ double_t randomChance = uniform_random(0, 10000) / 100;
+ if (fatalChance > 0 && randomChance < fatalChance) {
+ damage.fatal = true;
+ damage.primary.value += static_cast(std::round(damage.primary.value * 0.6));
+ damage.secondary.value += static_cast(std::round(damage.secondary.value * 0.6));
+ }
+ }
+ } else if (monster) {
+ damage.primary.value *= monster->getAttackMultiplier();
+ damage.secondary.value *= monster->getAttackMultiplier();
+ }
+}
diff --git a/src/creatures/combat/combat.hpp b/src/creatures/combat/combat.hpp
index 56096b9fd71..2bb2d0a3589 100644
--- a/src/creatures/combat/combat.hpp
+++ b/src/creatures/combat/combat.hpp
@@ -21,8 +21,6 @@ class Spell;
class Player;
class MatrixArea;
-static const std::unique_ptr &MatrixAreaNull {};
-
// for luascript callback
class ValueCallback final : public CallBack {
public:
@@ -247,15 +245,10 @@ class AreaCombat {
}
}
- auto it = areas.find(dir);
- if (it == areas.end()) {
- return MatrixAreaNull;
- }
-
- return it->second;
+ return areas[dir];
}
- std::map> areas;
+ std::array, Direction::DIRECTION_LAST + 1> areas {};
bool hasExtArea = false;
};
@@ -267,6 +260,8 @@ class Combat {
Combat(const Combat &) = delete;
Combat &operator=(const Combat &) = delete;
+ static void applyExtensions(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms);
+
static void doCombatHealth(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms);
static void doCombatHealth(std::shared_ptr caster, const Position &position, const std::unique_ptr &area, CombatDamage &damage, const CombatParams ¶ms);
@@ -368,7 +363,7 @@ class Combat {
static void CombatDispelFunc(std::shared_ptr caster, std::shared_ptr target, const CombatParams ¶ms, CombatDamage* data);
static void CombatNullFunc(std::shared_ptr caster, std::shared_ptr target, const CombatParams ¶ms, CombatDamage* data);
- static void combatTileEffects(const SpectatorHashSet &spectators, std::shared_ptr caster, std::shared_ptr tile, const CombatParams ¶ms);
+ static void combatTileEffects(const CreatureVector &spectators, std::shared_ptr caster, std::shared_ptr tile, const CombatParams ¶ms);
/**
* @brief Calculate the level formula for combat.
diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp
index 8b58e6ff0fa..fc6ee1206f1 100644
--- a/src/creatures/combat/condition.cpp
+++ b/src/creatures/combat/condition.cpp
@@ -13,6 +13,7 @@
#include "game/game.hpp"
#include "game/scheduling/dispatcher.hpp"
#include "io/fileloader.hpp"
+#include "map/spectators.hpp"
/**
* Condition
@@ -1190,13 +1191,12 @@ bool ConditionRegeneration::executeCondition(std::shared_ptr creature,
message.primary.color = TEXTCOLOR_PASTELRED;
player->sendTextMessage(message);
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, player->getPosition(), false, true);
+ auto spectators = Spectators().find(player->getPosition());
spectators.erase(player);
if (!spectators.empty()) {
message.type = MESSAGE_HEALED_OTHERS;
message.text = player->getName() + " was healed for " + healString;
- for (std::shared_ptr spectator : spectators) {
+ for (const auto &spectator : spectators) {
spectator->getPlayer()->sendTextMessage(message);
}
}
diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp
index bab02aec428..d5c634eed7f 100644
--- a/src/creatures/creature.cpp
+++ b/src/creatures/creature.cpp
@@ -16,6 +16,7 @@
#include "creatures/monsters/monster.hpp"
#include "game/scheduling/scheduler.hpp"
#include "game/zones/zone.hpp"
+#include "map/spectators.hpp"
double Creature::speedA = 857.36;
double Creature::speedB = 261.29;
@@ -1206,8 +1207,7 @@ void Creature::onGainExperience(uint64_t gainExp, std::shared_ptr targ
master->onGainExperience(gainExp, target);
if (!m->isFamiliar()) {
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, position, false, true);
+ auto spectators = Spectators().find(position);
if (spectators.empty()) {
return;
}
@@ -1217,7 +1217,7 @@ void Creature::onGainExperience(uint64_t gainExp, std::shared_ptr targ
message.primary.color = TEXTCOLOR_WHITE_EXP;
message.primary.value = gainExp;
- for (std::shared_ptr spectator : spectators) {
+ for (const auto &spectator : spectators) {
spectator->getPlayer()->sendTextMessage(message);
}
}
@@ -1824,17 +1824,8 @@ void Creature::iconChanged() {
return;
}
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, tile->getPosition(), true);
- for (auto spectator : spectators) {
- if (!spectator) {
- continue;
- }
-
- auto player = spectator->getPlayer();
- if (player) {
- player->sendCreatureIcon(getCreature());
- }
+ for (const auto &spectator : Spectators().find(tile->getPosition(), true)) {
+ spectator->getPlayer()->sendCreatureIcon(getCreature());
}
}
diff --git a/src/creatures/interactions/chat.cpp b/src/creatures/interactions/chat.cpp
index d58da460728..3dba27371a3 100644
--- a/src/creatures/interactions/chat.cpp
+++ b/src/creatures/interactions/chat.cpp
@@ -262,7 +262,7 @@ bool ChatChannel::executeOnSpeakEvent(const std::shared_ptr &player, Spe
Chat::Chat() :
scriptInterface("Chat Interface"),
- dummyPrivate(CHANNEL_PRIVATE, "Private Chat Channel") {
+ dummyPrivate(std::make_shared(CHANNEL_PRIVATE, "Private Chat Channel")) {
scriptInterface.initState();
}
@@ -284,48 +284,48 @@ bool Chat::load() {
auto it = normalChannels.find(channelId);
if (it != normalChannels.end()) {
- ChatChannel &channel = it->second;
- channel.publicChannel = isPublic;
- channel.name = channelName;
+ const auto &channel = it->second;
+ channel->publicChannel = isPublic;
+ channel->name = channelName;
if (scriptAttribute) {
if (scriptInterface.loadFile(coreFolder + "/chatchannels/scripts/" + std::string(scriptAttribute.as_string()), scriptAttribute.as_string()) == 0) {
- channel.onSpeakEvent = scriptInterface.getEvent("onSpeak");
- channel.canJoinEvent = scriptInterface.getEvent("canJoin");
- channel.onJoinEvent = scriptInterface.getEvent("onJoin");
- channel.onLeaveEvent = scriptInterface.getEvent("onLeave");
+ channel->onSpeakEvent = scriptInterface.getEvent("onSpeak");
+ channel->canJoinEvent = scriptInterface.getEvent("canJoin");
+ channel->onJoinEvent = scriptInterface.getEvent("onJoin");
+ channel->onLeaveEvent = scriptInterface.getEvent("onLeave");
} else {
g_logger().warn("[Chat::load] - Can not load script: {}", scriptAttribute.as_string());
}
}
- UsersMap tempUserMap = std::move(channel.users);
+ UsersMap tempUserMap = std::move(channel->users);
for (const auto &pair : tempUserMap) {
- channel.addUser(pair.second);
+ channel->addUser(pair.second);
}
continue;
}
- ChatChannel channel(channelId, channelName);
- channel.publicChannel = isPublic;
+ const auto &channel(std::make_shared(channelId, channelName));
+ channel->publicChannel = isPublic;
if (scriptAttribute) {
if (scriptInterface.loadFile(coreFolder + "/chatchannels/scripts/" + std::string(scriptAttribute.as_string()), scriptAttribute.as_string()) == 0) {
- channel.onSpeakEvent = scriptInterface.getEvent("onSpeak");
- channel.canJoinEvent = scriptInterface.getEvent("canJoin");
- channel.onJoinEvent = scriptInterface.getEvent("onJoin");
- channel.onLeaveEvent = scriptInterface.getEvent("onLeave");
+ channel->onSpeakEvent = scriptInterface.getEvent("onSpeak");
+ channel->canJoinEvent = scriptInterface.getEvent("canJoin");
+ channel->onJoinEvent = scriptInterface.getEvent("onJoin");
+ channel->onLeaveEvent = scriptInterface.getEvent("onLeave");
} else {
g_logger().warn("[Chat::load] Can not load script: {}", scriptAttribute.as_string());
}
}
- normalChannels[channel.id] = channel;
+ normalChannels[channel->id] = channel;
}
return true;
}
-ChatChannel* Chat::createChannel(const std::shared_ptr &player, uint16_t channelId) {
+std::shared_ptr Chat::createChannel(const std::shared_ptr &player, uint16_t channelId) {
if (getChannel(player, channelId) != nullptr) {
return nullptr;
}
@@ -334,8 +334,8 @@ ChatChannel* Chat::createChannel(const std::shared_ptr &player, uint16_t
case CHANNEL_GUILD: {
const auto guild = player->getGuild();
if (guild != nullptr) {
- auto ret = guildChannels.emplace(std::make_pair(guild->getId(), ChatChannel(channelId, guild->getName())));
- return &ret.first->second;
+ auto ret = guildChannels.emplace(std::make_pair(guild->getId(), std::make_shared(channelId, guild->getName())));
+ return ret.first->second;
}
break;
}
@@ -343,8 +343,8 @@ ChatChannel* Chat::createChannel(const std::shared_ptr &player, uint16_t
case CHANNEL_PARTY: {
auto party = player->getParty();
if (party != nullptr) {
- auto ret = partyChannels.emplace(std::make_pair(party, ChatChannel(channelId, "Party")));
- return &ret.first->second;
+ auto ret = partyChannels.emplace(std::make_pair(party, std::make_shared(channelId, "Party")));
+ return ret.first->second;
}
break;
}
@@ -357,11 +357,11 @@ ChatChannel* Chat::createChannel(const std::shared_ptr &player, uint16_t
// find a free private channel slot
for (uint16_t i = 100; i < 10000; ++i) {
- auto ret = privateChannels.emplace(std::make_pair(i, PrivateChatChannel(i, player->getName() + "'s Channel")));
+ auto ret = privateChannels.emplace(std::make_pair(i, std::make_shared(i, player->getName() + "'s Channel")));
if (ret.second) { // second is a bool that indicates that a new channel has been placed in the map
- auto &newChannel = (*ret.first).second;
- newChannel.setOwner(player->getGUID());
- return &newChannel;
+ const auto &newChannel = (*ret.first).second;
+ newChannel->setOwner(player->getGUID());
+ return newChannel;
}
}
break;
@@ -411,7 +411,7 @@ bool Chat::deleteChannel(const std::shared_ptr &player, uint16_t channel
return false;
}
- it->second.closeChannel();
+ it->second->closeChannel();
privateChannels.erase(it);
break;
@@ -420,8 +420,8 @@ bool Chat::deleteChannel(const std::shared_ptr &player, uint16_t channel
return true;
}
-ChatChannel* Chat::addUserToChannel(const std::shared_ptr &player, uint16_t channelId) {
- ChatChannel* channel = getChannel(player, channelId);
+std::shared_ptr Chat::addUserToChannel(const std::shared_ptr &player, uint16_t channelId) {
+ const auto &channel = getChannel(player, channelId);
if ((channel != nullptr) && channel->addUser(player)) {
return channel;
}
@@ -429,7 +429,7 @@ ChatChannel* Chat::addUserToChannel(const std::shared_ptr &player, uint1
}
bool Chat::removeUserFromChannel(const std::shared_ptr &player, uint16_t channelId) {
- ChatChannel* channel = getChannel(player, channelId);
+ const auto &channel = getChannel(player, channelId);
if ((channel == nullptr) || !channel->removeUser(player)) {
return false;
}
@@ -442,20 +442,20 @@ bool Chat::removeUserFromChannel(const std::shared_ptr &player, uint16_t
void Chat::removeUserFromAllChannels(const std::shared_ptr &player) {
for (auto &it : normalChannels) {
- it.second.removeUser(player);
+ it.second->removeUser(player);
}
for (auto &it : partyChannels) {
- it.second.removeUser(player);
+ it.second->removeUser(player);
}
for (auto &it : guildChannels) {
- it.second.removeUser(player);
+ it.second->removeUser(player);
}
auto it = privateChannels.begin();
while (it != privateChannels.end()) {
- PrivateChatChannel* channel = &it->second;
+ const auto &channel = it->second;
channel->removeInvite(player->getGUID());
channel->removeUser(player);
if (channel->getOwner() == player->getGUID()) {
@@ -468,7 +468,7 @@ void Chat::removeUserFromAllChannels(const std::shared_ptr &player) {
}
bool Chat::talkToChannel(const std::shared_ptr &player, SpeakClasses type, const std::string &text, uint16_t channelId) {
- ChatChannel* channel = getChannel(player, channelId);
+ const auto &channel = getChannel(player, channelId);
if (channel == nullptr) {
return false;
}
@@ -494,7 +494,7 @@ bool Chat::talkToChannel(const std::shared_ptr &player, SpeakClasses typ
ChannelList Chat::getChannelList(const std::shared_ptr &player) {
ChannelList list;
if (player->getGuild()) {
- ChatChannel* channel = getChannel(player, CHANNEL_GUILD);
+ auto channel = getChannel(player, CHANNEL_GUILD);
if (channel) {
list.push_back(channel);
} else {
@@ -506,7 +506,7 @@ ChannelList Chat::getChannelList(const std::shared_ptr &player) {
}
if (player->getParty()) {
- ChatChannel* channel = getChannel(player, CHANNEL_PARTY);
+ auto channel = getChannel(player, CHANNEL_PARTY);
if (channel) {
list.push_back(channel);
} else {
@@ -518,7 +518,7 @@ ChannelList Chat::getChannelList(const std::shared_ptr &player) {
}
for (const auto &it : normalChannels) {
- ChatChannel* channel = getChannel(player, it.first);
+ const auto &channel = getChannel(player, it.first);
if (channel) {
list.push_back(channel);
}
@@ -526,7 +526,7 @@ ChannelList Chat::getChannelList(const std::shared_ptr &player) {
bool hasPrivate = false;
for (auto &it : privateChannels) {
- if (PrivateChatChannel* channel = &it.second) {
+ if (const auto &channel = it.second) {
uint32_t guid = player->getGUID();
if (channel->isInvited(guid)) {
list.push_back(channel);
@@ -539,19 +539,19 @@ ChannelList Chat::getChannelList(const std::shared_ptr &player) {
}
if (!hasPrivate && player->isPremium()) {
- list.push_front(&dummyPrivate);
+ list.push_front(dummyPrivate);
}
return list;
}
-ChatChannel* Chat::getChannel(const std::shared_ptr &player, uint16_t channelId) {
+std::shared_ptr Chat::getChannel(const std::shared_ptr &player, uint16_t channelId) {
switch (channelId) {
case CHANNEL_GUILD: {
const auto guild = player->getGuild();
if (guild != nullptr) {
auto it = guildChannels.find(guild->getId());
if (it != guildChannels.end()) {
- return &it->second;
+ return it->second;
}
}
break;
@@ -562,7 +562,7 @@ ChatChannel* Chat::getChannel(const std::shared_ptr &player, uint16_t ch
if (party != nullptr) {
auto it = partyChannels.find(party);
if (it != partyChannels.end()) {
- return &it->second;
+ return it->second;
}
}
break;
@@ -571,16 +571,16 @@ ChatChannel* Chat::getChannel(const std::shared_ptr &player, uint16_t ch
default: {
auto it = normalChannels.find(channelId);
if (it != normalChannels.end()) {
- ChatChannel &channel = it->second;
- if (!channel.executeCanJoinEvent(player)) {
+ const auto &channel = it->second;
+ if (!channel->executeCanJoinEvent(player)) {
return nullptr;
}
- return &channel;
+ return channel;
}
auto it2 = privateChannels.find(channelId);
- if (it2 != privateChannels.end() && it2->second.isInvited(player->getGUID())) {
- return &it2->second;
+ if (it2 != privateChannels.end() && it2->second->isInvited(player->getGUID())) {
+ return it2->second;
}
break;
}
@@ -588,26 +588,26 @@ ChatChannel* Chat::getChannel(const std::shared_ptr &player, uint16_t ch
return nullptr;
}
-ChatChannel* Chat::getGuildChannelById(uint32_t guildId) {
+std::shared_ptr Chat::getGuildChannelById(uint32_t guildId) {
auto it = guildChannels.find(guildId);
if (it == guildChannels.end()) {
return nullptr;
}
- return &it->second;
+ return it->second;
}
-ChatChannel* Chat::getChannelById(uint16_t channelId) {
+std::shared_ptr Chat::getChannelById(uint16_t channelId) {
auto it = normalChannels.find(channelId);
if (it == normalChannels.end()) {
return nullptr;
}
- return &it->second;
+ return it->second;
}
-PrivateChatChannel* Chat::getPrivateChannel(const std::shared_ptr &player) {
+std::shared_ptr Chat::getPrivateChannel(const std::shared_ptr &player) {
for (auto &it : privateChannels) {
- if (it.second.getOwner() == player->getGUID()) {
- return &it.second;
+ if (it.second->getOwner() == player->getGUID()) {
+ return it.second;
}
}
return nullptr;
diff --git a/src/creatures/interactions/chat.hpp b/src/creatures/interactions/chat.hpp
index 31d2a612e1c..36768fa4f44 100644
--- a/src/creatures/interactions/chat.hpp
+++ b/src/creatures/interactions/chat.hpp
@@ -98,7 +98,7 @@ class PrivateChatChannel final : public ChatChannel {
void closeChannel() const;
- const InvitedMap* getInvitedUsers() const override {
+ [[nodiscard]] const InvitedMap* getInvitedUsers() const override {
return &invites;
}
@@ -107,7 +107,7 @@ class PrivateChatChannel final : public ChatChannel {
uint32_t owner = 0;
};
-using ChannelList = std::list;
+using ChannelList = std::list>;
class Chat {
public:
@@ -123,10 +123,10 @@ class Chat {
bool load();
- ChatChannel* createChannel(const std::shared_ptr &player, uint16_t channelId);
+ std::shared_ptr createChannel(const std::shared_ptr &player, uint16_t channelId);
bool deleteChannel(const std::shared_ptr &player, uint16_t channelId);
- ChatChannel* addUserToChannel(const std::shared_ptr &player, uint16_t channelId);
+ std::shared_ptr addUserToChannel(const std::shared_ptr &player, uint16_t channelId);
bool removeUserFromChannel(const std::shared_ptr &player, uint16_t channelId);
void removeUserFromAllChannels(const std::shared_ptr &player);
@@ -134,24 +134,24 @@ class Chat {
ChannelList getChannelList(const std::shared_ptr &player);
- ChatChannel* getChannel(const std::shared_ptr &player, uint16_t channelId);
- ChatChannel* getChannelById(uint16_t channelId);
- ChatChannel* getGuildChannelById(uint32_t guildId);
- PrivateChatChannel* getPrivateChannel(const std::shared_ptr &player);
+ std::shared_ptr getChannel(const std::shared_ptr &player, uint16_t channelId);
+ std::shared_ptr getChannelById(uint16_t channelId);
+ std::shared_ptr getGuildChannelById(uint32_t guildId);
+ std::shared_ptr getPrivateChannel(const std::shared_ptr &player);
LuaScriptInterface* getScriptInterface() {
return &scriptInterface;
}
private:
- std::map normalChannels;
- std::map privateChannels;
- std::map, ChatChannel> partyChannels;
- std::map guildChannels;
+ std::map> normalChannels;
+ std::map> privateChannels;
+ std::map, std::shared_ptr> partyChannels;
+ std::map> guildChannels;
LuaScriptInterface scriptInterface;
- PrivateChatChannel dummyPrivate;
+ std::shared_ptr dummyPrivate;
};
constexpr auto g_chat = Chat::getInstance;
diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp
index 615f9211f01..dab6acfe1ba 100644
--- a/src/creatures/monsters/monster.cpp
+++ b/src/creatures/monsters/monster.cpp
@@ -14,9 +14,9 @@
#include "creatures/players/wheel/player_wheel.hpp"
#include "game/game.hpp"
#include "game/scheduling/dispatcher.hpp"
-#include "lua/creature/events.hpp"
#include "lua/callbacks/event_callback.hpp"
#include "lua/callbacks/events_callbacks.hpp"
+#include "map/spectators.hpp"
int32_t Monster::despawnRange;
int32_t Monster::despawnRadius;
@@ -349,11 +349,8 @@ void Monster::updateTargetList() {
}
}
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, position, true);
- spectators.erase(this);
- for (auto spectator : spectators) {
- if (canSee(spectator->getPosition())) {
+ for (const auto &spectator : Spectators().find(position, true)) {
+ if (spectator.get() != this && canSee(spectator->getPosition())) {
onCreatureFound(spectator);
}
}
@@ -815,12 +812,6 @@ void Monster::doAttacking(uint32_t interval) {
bool resetTicks = interval != 0;
attackTicks += interval;
- float forgeAttackBonus = 0;
- if (monsterForgeClassification > ForgeClassifications_t::FORGE_NORMAL_MONSTER) {
- uint16_t damageBase = 3;
- forgeAttackBonus = static_cast(damageBase + 100) / 100.f;
- }
-
const Position &myPos = getPosition();
const Position &targetPos = attackedCreature->getPosition();
@@ -838,20 +829,8 @@ void Monster::doAttacking(uint32_t interval) {
updateLook = false;
}
- float multiplier;
- if (maxCombatValue > 0) { // Defense
- multiplier = getDefenseMultiplier();
- } else { // Attack
- multiplier = getAttackMultiplier();
- }
-
- minCombatValue = spellBlock.minCombatValue * multiplier;
- maxCombatValue = spellBlock.maxCombatValue * multiplier;
-
- if (maxCombatValue <= 0 && forgeAttackBonus > 0) {
- minCombatValue *= static_cast(forgeAttackBonus);
- maxCombatValue *= static_cast(forgeAttackBonus);
- }
+ minCombatValue = spellBlock.minCombatValue;
+ maxCombatValue = spellBlock.maxCombatValue;
if (spellBlock.spell == nullptr) {
continue;
@@ -1916,15 +1895,8 @@ bool Monster::getCombatValues(int32_t &min, int32_t &max) {
return false;
}
- float multiplier;
- if (maxCombatValue > 0) { // Defense
- multiplier = getDefenseMultiplier();
- } else { // Attack
- multiplier = getAttackMultiplier();
- }
-
- min = minCombatValue * multiplier;
- max = maxCombatValue * multiplier;
+ min = minCombatValue;
+ max = maxCombatValue;
return true;
}
diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp
index b168b18c3ea..9e3a9c52ddb 100644
--- a/src/creatures/monsters/monster.hpp
+++ b/src/creatures/monsters/monster.hpp
@@ -77,13 +77,13 @@ class Monster final : public Creature {
return mType->info.race;
}
float getMitigation() const override {
- return mType->info.mitigation;
+ return mType->info.mitigation * getDefenseMultiplier();
}
int32_t getArmor() const override {
- return mType->info.armor;
+ return mType->info.armor * getDefenseMultiplier();
}
int32_t getDefense() const override {
- return mType->info.defense;
+ return mType->info.defense * getDefenseMultiplier();
}
Faction_t getFaction() const override {
@@ -126,6 +126,9 @@ class Monster final : public Creature {
bool canSeeInvisibility() const override {
return isImmune(CONDITION_INVISIBLE);
}
+ uint16_t critChance() const {
+ return mType->info.critChance;
+ }
uint32_t getManaCost() const {
return mType->info.manaCost;
}
@@ -325,6 +328,16 @@ class Monster final : public Creature {
bool isImmune(ConditionType_t conditionType) const override;
bool isImmune(CombatType_t combatType) const override;
+ float getAttackMultiplier() const {
+ float multiplier = mType->getAttackMultiplier();
+ return multiplier * std::pow(1.03f, getForgeStack());
+ }
+
+ float getDefenseMultiplier() const {
+ float multiplier = mType->getDefenseMultiplier();
+ return multiplier * std::pow(1.01f, getForgeStack());
+ }
+
private:
CreatureWeakHashMap friendList;
CreatureIDList targetIDList;
@@ -436,14 +449,4 @@ class Monster final : public Creature {
void doRandomStep(Direction &nextDirection, bool &result);
void onConditionStatusChange(const ConditionType_t &type);
-
- float getAttackMultiplier() const {
- float multiplier = mType->getAttackMultiplier();
- return multiplier * std::pow(1.03f, getForgeStack());
- }
-
- float getDefenseMultiplier() const {
- float multiplier = mType->getAttackMultiplier();
- return multiplier * std::pow(1.01f, getForgeStack());
- }
};
diff --git a/src/creatures/monsters/monsters.hpp b/src/creatures/monsters/monsters.hpp
index a13792fd297..b214b478e3b 100644
--- a/src/creatures/monsters/monsters.hpp
+++ b/src/creatures/monsters/monsters.hpp
@@ -128,6 +128,7 @@ class MonsterType {
int32_t changeTargetChance = 0;
int32_t defense = 0;
int32_t armor = 0;
+ uint16_t critChance = 0;
int32_t strategiesTargetNearest = 0;
int32_t strategiesTargetHealth = 0;
int32_t strategiesTargetDamage = 0;
diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp
index 045038e6e45..414e3df1028 100644
--- a/src/creatures/monsters/spawns/spawn_monster.cpp
+++ b/src/creatures/monsters/spawns/spawn_monster.cpp
@@ -18,6 +18,7 @@
#include "lua/callbacks/event_callback.hpp"
#include "lua/callbacks/events_callbacks.hpp"
#include "utils/pugicast.hpp"
+#include "map/spectators.hpp"
static constexpr int32_t MONSTER_MINSPAWN_INTERVAL = 1000; // 1 second
static constexpr int32_t MONSTER_MAXSPAWN_INTERVAL = 86400000; // 1 day
@@ -155,9 +156,8 @@ SpawnMonster::~SpawnMonster() {
}
bool SpawnMonster::findPlayer(const Position &pos) {
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, pos, false, true);
- for (std::shared_ptr spectator : spectators) {
+ auto spectators = Spectators().find(pos);
+ for (const auto &spectator : spectators) {
if (!spectator->getPlayer()->hasFlag(PlayerFlags_t::IgnoredByMonsters)) {
return true;
}
diff --git a/src/creatures/npcs/npc.cpp b/src/creatures/npcs/npc.cpp
index 6b608add853..c31c13cc72d 100644
--- a/src/creatures/npcs/npc.cpp
+++ b/src/creatures/npcs/npc.cpp
@@ -16,6 +16,7 @@
#include "lua/callbacks/creaturecallback.hpp"
#include "game/scheduling/dispatcher.hpp"
#include "game/scheduling/scheduler.hpp"
+#include "map/spectators.hpp"
int32_t Npc::despawnRange;
int32_t Npc::despawnRadius;
@@ -151,7 +152,7 @@ void Npc::onPlayerAppear(std::shared_ptr player) {
if (player->hasFlag(PlayerFlags_t::IgnoredByNpcs) || playerSpectators.contains(player)) {
return;
}
- playerSpectators.insert(player);
+ playerSpectators.emplace_back(player);
manageIdle();
}
@@ -531,7 +532,7 @@ void Npc::onThinkWalk(uint32_t interval) {
void Npc::onCreatureWalk() {
Creature::onCreatureWalk();
- phmap::erase_if(playerSpectators, [this](const auto &creature) { return !this->canSee(creature->getPosition()); });
+ playerSpectators.erase_if([this](const auto &creature) { return !this->canSee(creature->getPosition()); });
}
void Npc::onPlacedCreature() {
@@ -539,11 +540,10 @@ void Npc::onPlacedCreature() {
}
void Npc::loadPlayerSpectators() {
- SpectatorHashSet spec;
- g_game().map.getSpectators(spec, position, true, true);
- for (auto creature : spec) {
- if (creature->getPlayer() || creature->getPlayer()->hasFlag(PlayerFlags_t::IgnoredByNpcs)) {
- playerSpectators.insert(creature);
+ auto spec = Spectators().find(position, true);
+ for (const auto &creature : spec) {
+ if (!creature->getPlayer()->hasFlag(PlayerFlags_t::IgnoredByNpcs)) {
+ playerSpectators.emplace_back(creature->getPlayer());
}
}
}
diff --git a/src/creatures/npcs/npc.hpp b/src/creatures/npcs/npc.hpp
index 61106c7b296..38cd9840a17 100644
--- a/src/creatures/npcs/npc.hpp
+++ b/src/creatures/npcs/npc.hpp
@@ -188,7 +188,7 @@ class Npc final : public Creature {
bool ignoreHeight;
- SpectatorHashSet playerSpectators;
+ stdext::vector_set> playerSpectators;
Position masterPos;
friend class LuaScriptInterface;
diff --git a/src/creatures/npcs/npcs.cpp b/src/creatures/npcs/npcs.cpp
index 432b0adbe15..c18897d1c4c 100644
--- a/src/creatures/npcs/npcs.cpp
+++ b/src/creatures/npcs/npcs.cpp
@@ -11,7 +11,6 @@
#include "declarations.hpp"
#include "creatures/combat/combat.hpp"
-#include "creatures/creature.hpp"
#include "lua/scripts/lua_environment.hpp"
#include "creatures/combat/spells.hpp"
#include "creatures/npcs/npcs.hpp"
diff --git a/src/creatures/npcs/spawns/spawn_npc.cpp b/src/creatures/npcs/spawns/spawn_npc.cpp
index c7605f36c06..878c1830b15 100644
--- a/src/creatures/npcs/spawns/spawn_npc.cpp
+++ b/src/creatures/npcs/spawns/spawn_npc.cpp
@@ -17,6 +17,7 @@
#include "lua/callbacks/event_callback.hpp"
#include "lua/callbacks/events_callbacks.hpp"
#include "utils/pugicast.hpp"
+#include "map/spectators.hpp"
static constexpr int32_t MINSPAWN_INTERVAL = 1000; // 1 second
static constexpr int32_t MAXSPAWN_INTERVAL = 86400000; // 1 day
@@ -141,9 +142,8 @@ SpawnNpc::~SpawnNpc() {
}
bool SpawnNpc::findPlayer(const Position &pos) {
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, pos, false, true);
- for (std::shared_ptr spectator : spectators) {
+ auto spectators = Spectators().find(pos);
+ for (const auto &spectator : spectators) {
if (!spectator->getPlayer()->hasFlag(PlayerFlags_t::IgnoredByNpcs)) {
return true;
}
diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp
index 4fffefdc6b6..fefcf83f8bb 100644
--- a/src/creatures/players/player.cpp
+++ b/src/creatures/players/player.cpp
@@ -30,6 +30,7 @@
#include "items/bed.hpp"
#include "items/weapons/weapons.hpp"
#include "core.hpp"
+#include "map/spectators.hpp"
MuteCountMap Player::muteCountMap;
@@ -861,12 +862,13 @@ void Player::addStorageValue(const uint32_t key, const int32_t value, const bool
}
if (value != -1) {
+ int32_t oldValue = getStorageValue(key);
storageMap[key] = value;
if (!isLogin) {
auto currentFrameTime = g_dispatcher().getDispatcherCycle();
- g_events().eventOnStorageUpdate(static_self_cast(), key, value, getStorageValue(key), currentFrameTime);
- g_callbacks().executeCallback(EventCallback_t::playerOnStorageUpdate, &EventCallback::playerOnStorageUpdate, getPlayer(), key, value, getStorageValue(key), currentFrameTime);
+ g_events().eventOnStorageUpdate(static_self_cast(), key, value, oldValue, currentFrameTime);
+ g_callbacks().executeCallback(EventCallback_t::playerOnStorageUpdate, &EventCallback::playerOnStorageUpdate, getPlayer(), key, value, oldValue, currentFrameTime);
}
} else {
storageMap.erase(key);
@@ -2243,8 +2245,7 @@ void Player::addExperience(std::shared_ptr target, uint64_t exp, bool
message.primary.color = TEXTCOLOR_WHITE_EXP;
sendTextMessage(message);
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, position, false, true);
+ auto spectators = Spectators().find(position);
spectators.erase(static_self_cast());
if (!spectators.empty()) {
message.type = MESSAGE_EXPERIENCE_OTHERS;
@@ -2337,8 +2338,7 @@ void Player::removeExperience(uint64_t exp, bool sendText /* = false*/) {
message.primary.color = TEXTCOLOR_RED;
sendTextMessage(message);
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, position, false, true);
+ auto spectators = Spectators().find(position);
spectators.erase(static_self_cast());
if (!spectators.empty()) {
message.type = MESSAGE_EXPERIENCE_OTHERS;
@@ -2769,14 +2769,9 @@ bool Player::spawn() {
return false;
}
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, position, true);
- for (std::shared_ptr spectator : spectators) {
- if (!spectator) {
- continue;
- }
-
- if (std::shared_ptr tmpPlayer = spectator->getPlayer()) {
+ auto spectators = Spectators().find(position, true);
+ for (const auto &spectator : spectators) {
+ if (const auto &tmpPlayer = spectator->getPlayer()) {
tmpPlayer->sendCreatureAppear(static_self_cast(), pos, true);
}
@@ -2812,18 +2807,13 @@ void Player::despawn() {
std::vector oldStackPosVector;
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, tile->getPosition(), true);
+ auto spectators = Spectators().find(tile->getPosition(), true);
size_t i = 0;
- for (std::shared_ptr spectator : spectators) {
- if (!spectator) {
- continue;
- }
-
- if (const auto player = spectator->getPlayer()) {
+ for (const auto &spectator : spectators) {
+ if (const auto &player = spectator->getPlayer()) {
oldStackPosVector.push_back(player->canSeeCreature(static_self_cast()) ? tile->getStackposOfCreature(player, getPlayer()) : -1);
}
- if (auto player = spectator->getPlayer()) {
+ if (const auto &player = spectator->getPlayer()) {
player->sendRemoveTileThing(tile->getPosition(), oldStackPosVector[i++]);
}
@@ -4232,7 +4222,7 @@ void Player::doAttacking(uint32_t) {
bool result = false;
std::shared_ptr
- tool = getWeapon();
- const Weapon* weapon = g_weapons().getWeapon(tool);
+ const WeaponShared_ptr weapon = g_weapons().getWeapon(tool);
uint32_t delay = getAttackSpeed();
bool classicSpeed = g_configManager().getBoolean(CLASSIC_ATTACK_SPEED);
@@ -6771,7 +6761,7 @@ bool Player::saySpell(
SpeakClasses type,
const std::string &text,
bool ghostMode,
- SpectatorHashSet* spectatorsPtr /* = nullptr*/,
+ Spectators* spectatorsPtr /* = nullptr*/,
const Position* pos /* = nullptr*/
) {
if (text.empty()) {
@@ -6783,17 +6773,17 @@ bool Player::saySpell(
pos = &getPosition();
}
- SpectatorHashSet spectators;
+ Spectators spectators;
if (!spectatorsPtr || spectatorsPtr->empty()) {
- // This somewhat complex construct ensures that the cached SpectatorHashSet
+ // This somewhat complex construct ensures that the cached Spectators
// is used if available and if it can be used, else a local vector is
// used (hopefully the compiler will optimize away the construction of
// the temporary when it's not used).
if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) {
- g_game().map.getSpectators(spectators, *pos, false, false, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_Y, MAP_MAX_CLIENT_VIEW_PORT_Y);
+ spectators.find(*pos, false, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_Y, MAP_MAX_CLIENT_VIEW_PORT_Y);
} else {
- g_game().map.getSpectators(spectators, *pos, true, false, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2);
+ spectators.find(*pos, true, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2);
}
} else {
spectators = (*spectatorsPtr);
diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp
index f348a207988..fba23cfd5ae 100644
--- a/src/creatures/players/player.hpp
+++ b/src/creatures/players/player.hpp
@@ -47,6 +47,7 @@ class PreySlot;
class TaskHuntingSlot;
class Spell;
class PlayerWheel;
+class Spectators;
enum class ForgeConversion_t : uint8_t {
FORGE_ACTION_FUSION = 0,
@@ -471,8 +472,8 @@ class Player final : public Creature, public Cylinder, public Bankable {
int32_t getStorageValueByName(const std::string &storageName) const;
void addStorageValueByName(const std::string &storageName, const int32_t value, const bool isLogin = false);
- std::shared_ptr kv() const {
- return g_kv().scoped("player")->scoped(getID());
+ std::shared_ptr kv() const {
+ return g_kv().scoped("player")->scoped(fmt::format("{}", getID()));
}
void genReservedStorageRange();
@@ -2285,7 +2286,7 @@ class Player final : public Creature, public Cylinder, public Bankable {
SpeakClasses type,
const std::string &text,
bool ghostMode,
- SpectatorHashSet* spectatorsPtr = nullptr,
+ Spectators* spectatorsPtr = nullptr,
const Position* pos = nullptr
);
diff --git a/src/game/game.cpp b/src/game/game.cpp
index 1d509f0e808..a0491f9f7c3 100644
--- a/src/game/game.cpp
+++ b/src/game/game.cpp
@@ -12,13 +12,11 @@
#include "lua/creature/actions.hpp"
#include "items/bed.hpp"
#include "creatures/creature.hpp"
-#include "lua/creature/creatureevent.hpp"
#include "database/databasetasks.hpp"
#include "lua/creature/events.hpp"
#include "lua/callbacks/event_callback.hpp"
#include "lua/callbacks/events_callbacks.hpp"
#include "game/game.hpp"
-#include "game/functions/game_reload.hpp"
#include "game/zones/zone.hpp"
#include "lua/global/globalevent.hpp"
#include "io/iologindata.hpp"
@@ -34,17 +32,14 @@
#include "creatures/combat/spells.hpp"
#include "lua/creature/talkaction.hpp"
#include "items/weapons/weapons.hpp"
-#include "lua/scripts/scripts.hpp"
-#include "lua/modules/modules.hpp"
#include "creatures/players/imbuements/imbuements.hpp"
-#include "account/account.hpp"
#include "creatures/players/wheel/player_wheel.hpp"
#include "creatures/npcs/npc.hpp"
-#include "creatures/npcs/npcs.hpp"
#include "server/network/webhook/webhook.hpp"
#include "protobuf/appearances.pb.h"
#include "server/network/protocol/protocollogin.hpp"
#include "server/network/protocol/protocolstatus.hpp"
+#include "map/spectators.hpp"
#include "kv/kv.hpp"
@@ -877,10 +872,8 @@ bool Game::placeCreature(std::shared_ptr creature, const Position &pos
}
bool hasPlayerSpectators = false;
- SpectatorHashSet spectators;
- map.getSpectators(spectators, creature->getPosition(), true);
- for (std::shared_ptr spectator : spectators) {
- if (std::shared_ptr tmpPlayer = spectator->getPlayer()) {
+ for (const auto &spectator : Spectators().find(creature->getPosition(), true)) {
+ if (const auto &tmpPlayer = spectator->getPlayer()) {
tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), true);
hasPlayerSpectators = true;
}
@@ -905,10 +898,11 @@ bool Game::removeCreature(std::shared_ptr creature, bool isLogout /* =
std::vector oldStackPosVector;
- SpectatorHashSet spectators;
- map.getSpectators(spectators, tile->getPosition(), true);
- for (auto spectator : spectators) {
- if (auto player = spectator->getPlayer()) {
+ auto spectators = Spectators().find(tile->getPosition(), true);
+ auto playersSpectators = spectators.filter();
+
+ for (const auto &spectator : playersSpectators) {
+ if (const auto &player = spectator->getPlayer()) {
oldStackPosVector.push_back(player->canSeeCreature(creature) ? tile->getStackposOfCreature(player, creature) : -1);
}
}
@@ -919,8 +913,8 @@ bool Game::removeCreature(std::shared_ptr creature, bool isLogout /* =
// Send to client
size_t i = 0;
- for (auto spectator : spectators) {
- if (auto player = spectator->getPlayer()) {
+ for (const auto &spectator : playersSpectators) {
+ if (const auto &player = spectator->getPlayer()) {
player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]);
}
}
@@ -2952,7 +2946,7 @@ void Game::playerCreatePrivateChannel(uint32_t playerId) {
return;
}
- ChatChannel* channel = g_chat().createChannel(player, CHANNEL_PRIVATE);
+ const auto &channel = g_chat().createChannel(player, CHANNEL_PRIVATE);
if (!channel || !channel->addUser(player)) {
return;
}
@@ -2966,7 +2960,7 @@ void Game::playerChannelInvite(uint32_t playerId, const std::string &name) {
return;
}
- PrivateChatChannel* channel = g_chat().getPrivateChannel(player);
+ const auto &channel = g_chat().getPrivateChannel(player);
if (!channel) {
return;
}
@@ -2989,7 +2983,7 @@ void Game::playerChannelExclude(uint32_t playerId, const std::string &name) {
return;
}
- PrivateChatChannel* channel = g_chat().getPrivateChannel(player);
+ const auto &channel = g_chat().getPrivateChannel(player);
if (!channel) {
return;
}
@@ -3021,7 +3015,7 @@ void Game::playerOpenChannel(uint32_t playerId, uint16_t channelId) {
return;
}
- const ChatChannel* channel = g_chat().addUserToChannel(player, channelId);
+ const auto &channel = g_chat().addUserToChannel(player, channelId);
if (!channel) {
return;
}
@@ -3066,17 +3060,13 @@ void Game::playerOpenPrivateChannel(uint32_t playerId, std::string &receiver) {
}
void Game::playerCloseNpcChannel(uint32_t playerId) {
- std::shared_ptr player = getPlayerByID(playerId);
+ const auto &player = getPlayerByID(playerId);
if (!player) {
return;
}
- SpectatorHashSet spectators;
- map.getSpectators(spectators, player->getPosition());
- for (std::shared_ptr spectator : spectators) {
- if (auto npc = spectator->getNpc()) {
- npc->onPlayerCloseChannel(player);
- }
+ for (const auto &spectator : Spectators().find(player->getPosition()).filter()) {
+ spectator->getNpc()->onPlayerCloseChannel(player);
}
}
@@ -3752,14 +3742,9 @@ void Game::playerSetShowOffSocket(uint32_t playerId, Outfit_t &outfit, const Pos
item->removeAttribute(ItemAttribute_t::NAME);
}
- SpectatorHashSet spectators;
- g_game().map.getSpectators(spectators, pos, true);
-
// Send to client
- for (auto spectator : spectators) {
- if (auto tmpPlayer = spectator->getPlayer()) {
- tmpPlayer->sendUpdateTileItem(tile, pos, item);
- }
+ for (const auto &spectator : Spectators().find(pos, true)) {
+ spectator->getPlayer()->sendUpdateTileItem(tile, pos, item);
}
}
@@ -5492,12 +5477,11 @@ bool Game::playerSaySpell(std::shared_ptr player, SpeakClasses type, con
}
void Game::playerWhisper(std::shared_ptr player, const std::string &text) {
- SpectatorHashSet spectators;
- map.getSpectators(spectators, player->getPosition(), false, false, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_Y, MAP_MAX_CLIENT_VIEW_PORT_Y);
+ auto spectators = Spectators().find(player->getPosition(), false, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_Y, MAP_MAX_CLIENT_VIEW_PORT_Y);
// Send to client
- for (auto spectator : spectators) {
- if (auto spectatorPlayer = spectator->getPlayer()) {
+ for (const auto &spectator : spectators) {
+ if (const auto &spectatorPlayer = spectator->getPlayer()) {
if (!Position::areInRange<1, 1>(player->getPosition(), spectatorPlayer->getPosition())) {
spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, "pspsps");
} else {
@@ -5507,7 +5491,7 @@ void Game::playerWhisper(std::shared_ptr player, const std::string &text
}
// event method
- for (auto spectator : spectators) {
+ for (const auto &spectator : spectators) {
spectator->onCreatureSay(player, TALKTYPE_WHISPER, text);
}
}
@@ -5570,12 +5554,8 @@ void Game::playerSpeakToNpc(std::shared_ptr player, const std::string &t
return;
}
- SpectatorHashSet spectators;
- map.getSpectators(spectators, player->getPosition());
- for (auto spectator : spectators) {
- if (spectator->getNpc()) {
- spectator->onCreatureSay(player, TALKTYPE_PRIVATE_PN, text);
- }
+ for (const auto &spectator : Spectators().find(player->getPosition()).filter()) {
+ spectator->getNpc()->onCreatureSay(player, TALKTYPE_PRIVATE_PN, text);
}
player->updateUIExhausted();
@@ -5599,25 +5579,18 @@ bool Game::internalCreatureTurn(std::shared_ptr creature, Direction di
return false;
}
- if (std::shared_ptr player = creature->getPlayer()) {
+ if (const auto &player = creature->getPlayer()) {
player->cancelPush();
}
creature->setDirection(dir);
- // Send to client
- SpectatorHashSet spectators;
- map.getSpectators(spectators, creature->getPosition(), true, true);
- for (auto spectator : spectators) {
- auto tmpPlayer = spectator->getPlayer();
- if (!tmpPlayer) {
- continue;
- }
- tmpPlayer->sendCreatureTurn(creature);
+ for (const auto &spectator : Spectators().find(creature->getPosition(), true)) {
+ spectator->getPlayer()->sendCreatureTurn(creature);
}
return true;
}
-bool Game::internalCreatureSay(std::shared_ptr creature, SpeakClasses type, const std::string &text, bool ghostMode, SpectatorHashSet* spectatorsPtr /* = nullptr*/, const Position* pos /* = nullptr*/) {
+bool Game::internalCreatureSay(std::shared_ptr creature, SpeakClasses type, const std::string &text, bool ghostMode, Spectators* spectatorsPtr /* = nullptr*/, const Position* pos /* = nullptr*/) {
if (text.empty()) {
return false;
}
@@ -5626,25 +5599,25 @@ bool Game::internalCreatureSay(std::shared_ptr creature, SpeakClasses
pos = &creature->getPosition();
}
- SpectatorHashSet spectators;
+ Spectators spectators;
if (!spectatorsPtr || spectatorsPtr->empty()) {
- // This somewhat complex construct ensures that the cached SpectatorHashSet
+ // This somewhat complex construct ensures that the cached Spectators
// is used if available and if it can be used, else a local vector is
// used (hopefully the compiler will optimize away the construction of
// the temporary when it's not used).
if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) {
- map.getSpectators(spectators, *pos, false, false, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_Y, MAP_MAX_CLIENT_VIEW_PORT_Y);
+ spectators.find(*pos, false, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_X, MAP_MAX_CLIENT_VIEW_PORT_Y, MAP_MAX_CLIENT_VIEW_PORT_Y);
} else {
- map.getSpectators(spectators, *pos, true, false, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2);
+ spectators.find(*pos, true, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_X + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2, (MAP_MAX_CLIENT_VIEW_PORT_Y + 1) * 2);
}
} else {
spectators = (*spectatorsPtr);
}
// Send to client
- for (auto spectator : spectators) {
- if (auto tmpPlayer = spectator->getPlayer()) {
+ for (const auto &spectator : spectators) {
+ if (const auto &tmpPlayer = spectator->getPlayer()) {
if (!ghostMode || tmpPlayer->canSeeCreature(creature)) {
tmpPlayer->sendCreatureSay(creature, type, text, pos);
}
@@ -5652,7 +5625,7 @@ bool Game::internalCreatureSay(std::shared_ptr creature, SpeakClasses
}
// event method
- for (auto spectator : spectators) {
+ for (const auto &spectator : spectators) {
spectator->onCreatureSay(creature, type, text);
if (creature != spectator) {
g_events().eventCreatureOnHear(spectator, creature, text, type);
@@ -5737,15 +5710,8 @@ void Game::changeSpeed(std::shared_ptr creature, int32_t varSpeedDelta
creature->setSpeed(varSpeed);
// Send to clients
- SpectatorHashSet spectators;
- map.getSpectators(spectators, creature->getPosition(), false, true);
- for (auto spectator : spectators) {
- auto player = spectator->getPlayer();
- if (!player) {
- continue;
- }
-
- player->sendChangeSpeed(creature, creature->getStepSpeed());
+ for (const auto &spectator : Spectators().find(creature->getPosition())) {
+ spectator->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed());
}
}
@@ -5753,15 +5719,8 @@ void Game::setCreatureSpeed(std::shared_ptr creature, int32_t speed) {
creature->setBaseSpeed(static_cast(speed));
// Send creature speed to client
- SpectatorHashSet spectators;
- map.getSpectators(spectators, creature->getPosition(), false, true);
- for (auto spectator : spectators) {
- auto player = spectator->getPlayer();
- if (!player) {
- continue;
- }
-
- player->sendChangeSpeed(creature, creature->getStepSpeed());
+ for (const auto &spectator : Spectators().find(creature->getPosition())) {
+ spectator->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed());
}
}
@@ -5772,21 +5731,8 @@ void Game::changePlayerSpeed(const std::shared_ptr &player, int32_t varS
player->setSpeed(varSpeed);
// Send new player speed to the spectators
- SpectatorHashSet spectators;
- map.getSpectators(spectators, player->getPosition(), false, true);
- for (std::shared_ptr creatureSpectator : spectators) {
- if (creatureSpectator == nullptr) {
- g_logger().error("[Game::changePlayerSpeed] - Creature spectator is nullptr");
- continue;
- }
-
- std::shared_ptr playerSpectator = creatureSpectator->getPlayer();
- if (playerSpectator == nullptr) {
- g_logger().error("[Game::changePlayerSpeed] - Player spectator is nullptr");
- continue;
- }
-
- playerSpectator->sendChangeSpeed(player, player->getStepSpeed());
+ for (const auto &creatureSpectator : Spectators().find(player->getPosition())) {
+ creatureSpectator->getPlayer()->sendChangeSpeed(player, player->getStepSpeed());
}
}
@@ -5806,57 +5752,29 @@ void Game::internalCreatureChangeOutfit(std::shared_ptr creature, cons
}
// Send to clients
- SpectatorHashSet spectators;
- map.getSpectators(spectators, creature->getPosition(), true, true);
- for (auto spectator : spectators) {
- auto player = spectator->getPlayer();
- if (!player) {
- continue;
- }
-
- player->sendCreatureChangeOutfit(creature, outfit);
+ for (const auto &spectator : Spectators().find(creature->getPosition(), true)) {
+ spectator->getPlayer()->sendCreatureChangeOutfit(creature, outfit);
}
}
void Game::internalCreatureChangeVisible(std::shared_ptr creature, bool visible) {
// Send to clients
- SpectatorHashSet spectators;
- map.getSpectators(spectators, creature->getPosition(), true, true);
- for (auto spectator : spectators) {
- auto player = spectator->getPlayer();
- if (!player) {
- continue;
- }
-
- player->sendCreatureChangeVisible(creature, visible);
+ for (const auto &spectator : Spectators().find(creature->getPosition(), true)) {
+ spectator->getPlayer()->sendCreatureChangeVisible(creature, visible);
}
}
void Game::changeLight(std::shared_ptr creature) {
// Send to clients
- SpectatorHashSet spectators;
- map.getSpectators(spectators, creature->getPosition(), true, true);
- for (auto spectator : spectators) {
- auto player = spectator->getPlayer();
- if (!player) {
- continue;
- }
-
- player->sendCreatureLight(creature);
+ for (const auto &spectator : Spectators().find(creature->getPosition(), true)) {
+ spectator->getPlayer()->sendCreatureLight(creature);
}
}
void Game::updateCreatureIcon(std::shared_ptr creature) {
// Send to clients
- SpectatorHashSet spectators;
- map.getSpectators(spectators, creature->getPosition(), true, true);
- for (auto spectator : spectators) {
- auto player = spectator->getPlayer();
- if (!player) {
- continue;
- }
-
- player->sendCreatureIcon(creature);
+ for (const auto &spectator : Spectators().find(creature->getPosition(), true)) {
+ spectator->getPlayer()->sendCreatureIcon(creature);
}
}
@@ -5866,14 +5784,8 @@ void Game::reloadCreature(std::shared_ptr creature) {
return;
}
- SpectatorHashSet spectators;
- map.getSpectators(spectators, creature->getPosition(), false, true);
- for (auto spectator : spectators) {
- auto tmpPlayer = spectator->getPlayer();
- if (!tmpPlayer) {
- continue;
- }
- tmpPlayer->reloadCreature(creature);
+ for (const auto &spectator : Spectators().find(creature->getPosition())) {
+ spectator->getPlayer()->reloadCreature(creature);
}
}
@@ -5882,21 +5794,18 @@ void Game::sendSingleSoundEffect(const Position &pos, SoundEffect_t soundId, std
return;
}
- SpectatorHashSet spectators;
- map.getSpectators(spectators, pos, false, true);
- for (auto spectator : spectators) {
- if (auto tmpPlayer = spectator->getPlayer()) {
- SourceEffect_t source = SourceEffect_t::CREATURES;
- if (!actor || actor->getNpc()) {
- source = SourceEffect_t::GLOBAL;
- } else if (actor == spectator) {
- source = SourceEffect_t::OWN;
- } else if (actor->getPlayer()) {
- source = SourceEffect_t::OTHERS;
- }
-
- tmpPlayer->sendSingleSoundEffect(pos, soundId, source);
+ using enum SourceEffect_t;
+ for (const auto &spectator : Spectators().find(pos)) {
+ SourceEffect_t source = CREATURES;
+ if (!actor || actor->getNpc()) {
+ source = GLOBAL;
+ } else if (actor == spectator) {
+ source = OWN;
+ } else if (actor->getPlayer()) {
+ source = OTHERS;
}
+
+ spectator->getPlayer()->sendSingleSoundEffect(pos, soundId, source);
}
}
@@ -5906,21 +5815,18 @@ void Game::sendDoubleSoundEffect(const Position &pos, SoundEffect_t mainSoundEff
return;
}
- SpectatorHashSet spectators;
- map.getSpectators(spectators, pos, false, true);
- for (auto spectator : spectators) {
- if (auto tmpPlayer = spectator->getPlayer()) {
- SourceEffect_t source = SourceEffect_t::CREATURES;
- if (!actor || actor->getNpc()) {
- source = SourceEffect_t::GLOBAL;
- } else if (actor == spectator) {
- source = SourceEffect_t::OWN;
- } else if (actor->getPlayer()) {
- source = SourceEffect_t::OTHERS;
- }
-
- tmpPlayer->sendDoubleSoundEffect(pos, mainSoundEffect, source, secondarySoundEffect, source);
+ using enum SourceEffect_t;
+ for (const auto &spectator : Spectators().find(pos)) {
+ SourceEffect_t source = CREATURES;
+ if (!actor || actor->getNpc()) {
+ source = GLOBAL;
+ } else if (actor == spectator) {
+ source = OWN;
+ } else if (actor->getPlayer()) {
+ source = OTHERS;
}
+
+ spectator->getPlayer()->sendDoubleSoundEffect(pos, mainSoundEffect, source, secondarySoundEffect, source);
}
}
@@ -6230,7 +6136,7 @@ void Game::handleHazardSystemAttack(CombatDamage &damage, std::shared_ptr attackerPlayer, std::shared_ptr targetMonster) {
+void Game::notifySpectators(const CreatureVector &spectators, const Position &targetPos, std::shared_ptr attackerPlayer, std::shared_ptr targetMonster) {
if (!spectators.empty()) {
for (auto spectator : spectators) {
if (!spectator) {
@@ -6400,10 +6306,8 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt
message.primary.value = realHealthChange;
message.primary.color = TEXTCOLOR_PASTELRED;
- SpectatorHashSet spectators;
- map.getSpectators(spectators, targetPos, false, true);
- for (auto spectator : spectators) {
- auto tmpPlayer = spectator->getPlayer();
+ for (const auto &spectator : Spectators().find(targetPos)) {
+ const auto &tmpPlayer = spectator->getPlayer();
if (!tmpPlayer) {
continue;
}
@@ -6455,14 +6359,9 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt
return true;
}
- std::shared_ptr attackerPlayer;
- if (attacker) {
- attackerPlayer = attacker->getPlayer();
- } else {
- attackerPlayer = nullptr;
- }
+ const auto &attackerPlayer = attacker ? attacker->getPlayer() : nullptr;
- auto targetPlayer = target->getPlayer();
+ const auto &targetPlayer = target->getPlayer();
if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) {
return false;
}
@@ -6539,8 +6438,7 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt
return true;
}
- SpectatorHashSet spectators;
- map.getSpectators(spectators, targetPos, true, true);
+ auto spectators = Spectators().find(targetPos, true);
if (targetPlayer && attackerMonster) {
handleHazardSystemAttack(damage, targetPlayer, attackerMonster, false);
@@ -6548,15 +6446,15 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt
handleHazardSystemAttack(damage, attackerPlayer, targetMonster, true);
if (damage.primary.value == 0 && damage.secondary.value == 0) {
- notifySpectators(spectators, targetPos, attackerPlayer, targetMonster);
+ notifySpectators(spectators.data(), targetPos, attackerPlayer, targetMonster);
return true;
}
}
if (damage.fatal) {
- addMagicEffect(spectators, targetPos, CONST_ME_FATAL);
+ addMagicEffect(spectators.data(), targetPos, CONST_ME_FATAL);
} else if (damage.critical) {
- addMagicEffect(spectators, targetPos, CONST_ME_CRITICAL_DAMAGE);
+ addMagicEffect(spectators.data(), targetPos, CONST_ME_CRITICAL_DAMAGE);
}
if (!damage.extension && attackerMonster && targetPlayer) {
@@ -6609,7 +6507,7 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt
target->removeCondition(CONDITION_MANASHIELD);
}
- addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY);
+ addMagicEffect(spectators.data(), targetPos, CONST_ME_LOSEENERGY);
std::string damageString = std::to_string(manaDamage);
@@ -6618,17 +6516,9 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt
message.primary.value = manaDamage;
message.primary.color = TEXTCOLOR_BLUE;
- for (std::shared_ptr spectator : spectators) {
- if (!spectator) {
- continue;
- }
-
- std::shared_ptr tmpPlayer = spectator->getPlayer();
- if (!tmpPlayer) {
- continue;
- }
-
- if (tmpPlayer->getPosition().z != targetPos.z) {
+ for (const auto &spectator : spectators) {
+ const auto &tmpPlayer = spectator->getPlayer();
+ if (!tmpPlayer || tmpPlayer->getPosition().z != targetPos.z) {
continue;
}
@@ -6730,10 +6620,10 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt
}
if (spectators.empty()) {
- map.getSpectators(spectators, targetPos, true, true);
+ spectators.find(targetPos, true);
}
- addCreatureHealth(spectators, target);
+ addCreatureHealth(spectators.data(), target);
sendDamageMessageAndEffects(
attacker,
@@ -6743,7 +6633,7 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt
attackerPlayer,
targetPlayer,
message,
- spectators,
+ spectators.data(),
realDamage
);
@@ -6778,7 +6668,7 @@ void Game::updatePlayerPartyHuntAnalyzer(const CombatDamage &damage, std::shared
void Game::sendDamageMessageAndEffects(
std::shared_ptr attacker, std::shared_ptr target, const CombatDamage &damage,
const Position &targetPos, std::shared_ptr attackerPlayer, std::shared_ptr targetPlayer,
- TextMessage &message, const SpectatorHashSet &spectators, int32_t realDamage
+ TextMessage &message, const CreatureVector &spectators, int32_t realDamage
) {
message.primary.value = damage.primary.value;
message.secondary.value = damage.secondary.value;
@@ -6797,7 +6687,7 @@ bool Game::shouldSendMessage(const TextMessage &message) const {
void Game::sendMessages(
std::shared_ptr attacker, std::shared_ptr target, const CombatDamage &damage,
const Position &targetPos, std::shared_ptr attackerPlayer, std::shared_ptr targetPlayer,
- TextMessage &message, const SpectatorHashSet &spectators, int32_t realDamage
+ TextMessage &message, const CreatureVector &spectators, int32_t realDamage
) const {
if (attackerPlayer) {
attackerPlayer->updateImpactTracker(damage.primary.type, damage.primary.value);
@@ -6912,7 +6802,7 @@ void Game::buildMessageAsAttacker(
void Game::sendEffects(
std::shared_ptr target, const CombatDamage &damage, const Position &targetPos, TextMessage &message,
- const SpectatorHashSet &spectators
+ const CreatureVector &spectators
) {
uint16_t hitEffect;
if (message.primary.value) {
@@ -7070,10 +6960,8 @@ bool Game::combatChangeMana(std::shared_ptr