diff --git a/.github/workflows/build-ubuntu-dummy.yml b/.github/workflows/build-ubuntu-dummy.yml
index 00c4efdef87..f4ebb00ff27 100644
--- a/.github/workflows/build-ubuntu-dummy.yml
+++ b/.github/workflows/build-ubuntu-dummy.yml
@@ -17,11 +17,9 @@ jobs:
strategy:
fail-fast: false
matrix:
- os: [ubuntu-20.04, ubuntu-22.04]
+ os: [ubuntu-22.04]
buildtype: [linux-release, linux-debug]
include:
- - os: ubuntu-20.04
- triplet: x64-linux
- os: ubuntu-22.04
triplet: x64-linux
diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml
index cdc54dc33f2..2b369e34875 100644
--- a/.github/workflows/build-ubuntu.yml
+++ b/.github/workflows/build-ubuntu.yml
@@ -36,11 +36,9 @@ jobs:
strategy:
fail-fast: false
matrix:
- os: [ubuntu-20.04, ubuntu-22.04]
+ os: [ubuntu-22.04]
buildtype: [linux-release, linux-debug]
include:
- - os: ubuntu-20.04
- triplet: x64-linux
- os: ubuntu-22.04
triplet: x64-linux
diff --git a/.gitignore b/.gitignore
index d233ae17409..a73a8c3022c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,9 @@ bld/
[Ll]og/
build/
vcproj/
+!vcproj/canary.sln
+!vcproj/canary.vcxproj
+!vcproj/settings.props
# Visual Studio 2015/2017 cache/options directory
.vs/
@@ -372,9 +375,7 @@ config.lua
config_canary.lua
client_assertions.txt
.env
-otservbr.otbm
-canary.otbm
-otservbr-custom.otbm
+data-otservbr-global/world/otservbr.otbm
# Extensions
*.ini
@@ -382,9 +383,6 @@ otservbr-custom.otbm
*.exe
*.manifest
*.rar
-*-house.xml
-*-monster.xml
-*-npc.xml
monster_count.txt
# SFTP for Sublime
@@ -398,4 +396,4 @@ canary.old
vcpkg_installed
# CLION
-cmake-build-debug*
+cmake-build-*
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e4093f4de2d..52c35a471f7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -86,10 +86,9 @@ endif()
# === IPO ===
-option(OPTIONS_ENABLE_IPO "Check and Enable interprocedural optimization (IPO/LTO)" ON)
if(OPTIONS_ENABLE_IPO)
- log_option_enabled("IPO/LTO")
if(MSVC)
+ log_option_enabled("IPO/LTO")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /GL")
set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
set(CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
@@ -97,6 +96,7 @@ if(OPTIONS_ENABLE_IPO)
set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
else()
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo" OR CMAKE_BUILD_TYPE STREQUAL "Release")
+ log_option_enabled("IPO/LTO")
include(CheckIPOSupported)
check_ipo_supported(RESULT result OUTPUT output)
if(result)
diff --git a/cmake/modules/BaseConfig.cmake b/cmake/modules/BaseConfig.cmake
index 7e3f6404b3f..a1980d0f604 100644
--- a/cmake/modules/BaseConfig.cmake
+++ b/cmake/modules/BaseConfig.cmake
@@ -121,7 +121,9 @@ if (MSVC)
endforeach(type)
add_compile_options(/MP /FS /Zf /EHsc)
-endif (MSVC)
+else()
+ add_compile_options(-Wno-unused-parameter -Wno-sign-compare -Wno-switch -Wno-implicit-fallthrough -Wno-extra)
+endif()
## Link compilation files to build/bin folder, else link to the main dir
function(set_output_directory target_name)
diff --git a/config.lua.dist b/config.lua.dist
index a5bb3d73ca5..447a90647c2 100644
--- a/config.lua.dist
+++ b/config.lua.dist
@@ -52,7 +52,8 @@ cleanProtectionZones = false
-- Connection Config
-- NOTE: allowOldProtocol can allow login on 10x protocol. (11.00)
-- NOTE: maxPlayers set to 0 means no limit
--- NOTE: MaxPacketsPerSeconds if you change you will be subject to bugs by WPE, keep the default value of 25
+-- NOTE: MaxPacketsPerSeconds if you change you will be subject to bugs by WPE, keep the default value of 25,
+-- It's recommended to use a range like min 50 in this function, otherwise you will be disconnected after equipping two-handed distance weapons.
ip = "127.0.0.1"
allowOldProtocol = false
bindOnlyGlobalAddress = false
@@ -80,6 +81,17 @@ freeDepotLimit = 2000
premiumDepotLimit = 10000
depotBoxes = 20
+-- Augments System (Get more info in: https://github.com/opentibiabr/canary/pull/2602)
+-- NOTE: the following values are for all weapons and equipments that have type of "increase damage", "powerful impact" and "strong impact".
+-- To customize the percentage of a particular item with these augment types, please add to the item "augments" section on items.xml as the example above.
+-- NOTE: The values represent percentage.
+-- NOTE: augmentIncreasedDamagePercent = value between 1 and 100 (damage percent to increase. ex: 5 = 5%, 50 = 50%)
+-- NOTE: augmentPowerfulImpactPercent = value between 1 and 100 (damage percent to increase. ex: 10 = 10%, 100 = 100%)
+-- NOTE: augmentStrongImpactPercent = value between 1 and 100 (damage percent to increase. ex: 7 = 7%, 70 = 70%)
+augmentIncreasedDamagePercent = 5
+augmentPowerfulImpactPercent = 7
+augmentStrongImpactPercent = 10
+
-- Prey system
-- NOTE: preyRerollPricePerLevel: Price multiplier in gold coin for rerolling prey list.
-- NOTE: preySelectListPrice: Price to manually select creature on list and to lock prey slot.
@@ -212,6 +224,8 @@ wheelAtelierRevealGreaterCost = 6000000
familiarTime = 30
partyAutoShareExperience = true
+-- partyShareRangeMultiplier: the range of the party share experience, default 3/2 (1.5)
+partyShareRangeMultiplier = 1.5
partyShareLootBoosts = false
partyShareLootBoostsDimishingFactor = 0.7
@@ -239,6 +253,7 @@ onlyPremiumAccount = false
-- NOTE: enablePlayerPutItemInAmmoSlot = true, will enable players to put any items on ammo slot, more used in custom shopping system
-- NOTE: startStreakLevel will make a reward streak level for new players who never logged in
-- NOTE: if showLootsInBestiary is true, will cause all loots to be shown in the bestiary even if the player has not reached the required number of kills
+-- NOTE: minTownIdToBankTransfer blocks towns less than defined from receiving money transfers
stashMoving = false
depotChest = 4
autoLoot = false
@@ -257,6 +272,7 @@ storeInboxMaxLimit = 2000
enablePlayerPutItemInAmmoSlot = false
startStreakLevel = 0
showLootsInBestiary = false
+minTownIdToBankTransfer = 3
-- Teleport summon
-- Set to true will never remove the summon
@@ -361,7 +377,10 @@ partyListMaxDistance = 30
toggleMapCustom = true
-- Market
+-- NOTE: marketRefreshPricesInterval (in minutes, minimum is 1 minute)
+-- NOTE: set it to 0 for disable, is the time in which the task will run updating the prices of the items that will be sent to the client
marketOfferDuration = 30 * 24 * 60 * 60
+marketRefreshPricesInterval = 30
premiumToCreateMarketOffer = true
checkExpiredMarketOffersEachMinutes = 60
maxMarketOffersAtATimePerPlayer = 100
@@ -393,7 +412,6 @@ resetSessionsOnStartup = false
-- Misc.
-- NOTE: experienceDisplayRates: set to false to ignore exp rate or true to include exp rate
-- NOTE: disableLegacyRaids: set to true to disable legacy XML raids
--- NOTE: combatChainDelay: set to minimum 50 miliseconds
allowChangeOutfit = true
toggleMountInProtectionZone = false
freePremium = false
@@ -426,7 +444,8 @@ maxElementalResistance = 200
maxDamageReflection = 200
-- Chain system
-toggleChainSystem = true
+-- NOTE: combatChainDelay: set to minimum 50 miliseconds
+toggleChainSystem = false
combatChainDelay = 50
combatChainTargets = 5
combatChainSkillFormulaAxe = 0.9
diff --git a/data-otservbr-global/lib/core/quests.lua b/data-otservbr-global/lib/core/quests.lua
index ef61ee2549b..a35dcb15dea 100644
--- a/data-otservbr-global/lib/core/quests.lua
+++ b/data-otservbr-global/lib/core/quests.lua
@@ -5592,7 +5592,7 @@ if not Quests then
},
[41] = {
name = "Adventurers Guild",
- startStorageId = Storage.AdventurersGuild.GreatDragonHunt.WarriorSkeleton,
+ startStorageId = Storage.AdventurersGuild.QuestLine,
startStorageValue = 1,
missions = {
[1] = {
@@ -5606,6 +5606,18 @@ if not Quests then
But the dragon hoards might justify the risks. You killed %d/50 dragons and dragon lords."):format(math.max(player:getStorageValue(Storage.AdventurersGuild.GreatDragonHunt.DragonCounter), 0))
end,
},
+ [2] = {
+ name = "The Lost Brother",
+ storageId = Storage.AdventurersGuild.TheLostBrother,
+ missionId = 11000,
+ startValue = 1,
+ endValue = 3,
+ states = {
+ [1] = "At the Kha'zeel Mountains you met the merchant Tarun. His brother has gone missing and was last seen following a beautiful woman into a palace. Tarun fears this woman might have been a demon.",
+ [2] = "You found the remains of Tarun's brother containing a message. Go back to Tarun and report his brother's last words.",
+ [3] = "You told Tarun about his brother's sad fate. He was very downhearted but gave you his sincere thanks. The beautiful asuri have taken a young man's life and the happiness of another one.",
+ },
+ },
},
},
[42] = {
diff --git a/data-otservbr-global/lib/core/storages.lua b/data-otservbr-global/lib/core/storages.lua
index 69f1d7f7948..fce8059f7d1 100644
--- a/data-otservbr-global/lib/core/storages.lua
+++ b/data-otservbr-global/lib/core/storages.lua
@@ -106,7 +106,6 @@ Storage = {
-- unused ExerciseDummyExhaust = 30029,
SamsOldBackpack = 30030,
SamsOldBackpackDoor = 30031,
- StrawberryCupcake = 30032,
ChayenneReward = 30033,
SwampDiggingTimeout = 30034,
HydraEggQuest = 30035,
@@ -125,8 +124,6 @@ Storage = {
Navigator = 30048,
DwarvenLegs = 30049,
PrinceDrazzakTime = 30050,
- LemonCupcake = 30052,
- BlueberryCupcake = 30053,
-- Reserved in Global.Storage.FamiliarSummonEvent10 = 30054
-- Reserved in Global.Storage.FamiliarSummonEvent60 = 30055
ChayenneKeyTime = 30056,
@@ -1573,6 +1570,8 @@ Storage = {
WarriorSkeleton = 52146,
DragonCounter = 52147,
},
+ QuestLine = 52148,
+ TheLostBrother = 52149,
},
DreamersChallenge = {
-- Reserved storage from 52160 - 52199
@@ -2537,7 +2536,6 @@ Storage = {
NightmareTeddy = {},
PoacherCavesMiniWorldChange = {},
TheGreatDragonHunt = {},
- TheLostBrother = {},
TheTaintedSouls = {},
},
U10_90 = { -- update 10.90 - Reserved Storages 45201 - 45350
diff --git a/data-otservbr-global/lib/quests/the_primal_ordeal.lua b/data-otservbr-global/lib/quests/the_primal_ordeal.lua
index de708ee4de2..24324e1147b 100644
--- a/data-otservbr-global/lib/quests/the_primal_ordeal.lua
+++ b/data-otservbr-global/lib/quests/the_primal_ordeal.lua
@@ -6,12 +6,10 @@ function RegisterPrimalPackBeast(template)
primalMonster.loot = {}
primalMonster.name = "Primal Pack Beast"
primalMonster.description = "a primal pack beast"
-
- primalMonster.health = primalMonster.health
- primalMonster.maxHealth = primalMonster.maxHealth
+ primalMonster.maxHealth = primalMonster.maxHealth * 0.7
+ primalMonster.health = primalMonster.maxHealth
primalMonster.raceId = nil
primalMonster.Bestiary = nil
primalMonster.corpse = 0
-
primal:register(primalMonster)
end
diff --git a/data-otservbr-global/migrations/43.lua b/data-otservbr-global/migrations/43.lua
index 1464703c96e..6d3492a815a 100644
--- a/data-otservbr-global/migrations/43.lua
+++ b/data-otservbr-global/migrations/43.lua
@@ -1,5 +1,5 @@
function onUpdateDatabase()
- logger.info("Updating database to version 43 (feat frags_limit, payment and duration_days in guild wars)")
+ logger.info("Updating database to version 44 (feat frags_limit, payment and duration_days in guild wars)")
db.query([[
ALTER TABLE `guild_wars`
diff --git a/data-otservbr-global/migrations/44.lua b/data-otservbr-global/migrations/44.lua
index 86a6d8ffec1..c551fc79aeb 100644
--- a/data-otservbr-global/migrations/44.lua
+++ b/data-otservbr-global/migrations/44.lua
@@ -1,3 +1,11 @@
function onUpdateDatabase()
- return false -- true = There are others migrations file | false = this is the last migration file
+ logger.info("Updating database to version 45 (fix: mana shield column size for more than 65k)")
+
+ db.query([[
+ ALTER TABLE `players`
+ MODIFY COLUMN `manashield` INT UNSIGNED NOT NULL DEFAULT '0',
+ MODIFY COLUMN `max_manashield` INT UNSIGNED NOT NULL DEFAULT '0';
+ ]])
+
+ return true
end
diff --git a/data-otservbr-global/migrations/45.lua b/data-otservbr-global/migrations/45.lua
new file mode 100644
index 00000000000..c606f18522e
--- /dev/null
+++ b/data-otservbr-global/migrations/45.lua
@@ -0,0 +1,56 @@
+function onUpdateDatabase()
+ logger.info("Updating database to version 46 (feat: vip groups)")
+
+ db.query([[
+ CREATE TABLE IF NOT EXISTS `account_vipgroups` (
+ `id` tinyint(3) UNSIGNED NOT NULL,
+ `account_id` int(11) UNSIGNED NOT NULL COMMENT 'id of account whose vip group entry it is',
+ `name` varchar(128) NOT NULL,
+ `customizable` BOOLEAN NOT NULL DEFAULT '1',
+ CONSTRAINT `account_vipgroups_pk` PRIMARY KEY (`id`, `account_id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ ]])
+
+ db.query([[
+ CREATE TRIGGER `oncreate_accounts` AFTER INSERT ON `accounts` FOR EACH ROW BEGIN
+ INSERT INTO `account_vipgroups` (`id`, `account_id`, `name`, `customizable`) VALUES (1, NEW.`id`, 'Enemies', 0);
+ INSERT INTO `account_vipgroups` (`id`, `account_id`, `name`, `customizable`) VALUES (2, NEW.`id`, 'Friends', 0);
+ INSERT INTO `account_vipgroups` (`id`, `account_id`, `name`, `customizable`) VALUES (3, NEW.`id`, 'Trading Partner', 0);
+ END;
+ ]])
+
+ db.query([[
+ CREATE TABLE IF NOT EXISTS `account_vipgrouplist` (
+ `account_id` int(11) UNSIGNED NOT NULL COMMENT 'id of account whose viplist entry it is',
+ `player_id` int(11) NOT NULL COMMENT 'id of target player of viplist entry',
+ `vipgroup_id` tinyint(3) UNSIGNED NOT NULL COMMENT 'id of vip group that player belongs',
+ INDEX `account_id` (`account_id`),
+ INDEX `player_id` (`player_id`),
+ INDEX `vipgroup_id` (`vipgroup_id`),
+ CONSTRAINT `account_vipgrouplist_unique` UNIQUE (`account_id`, `player_id`, `vipgroup_id`),
+ CONSTRAINT `account_vipgrouplist_player_fk`
+ FOREIGN KEY (`player_id`) REFERENCES `players` (`id`)
+ ON DELETE CASCADE,
+ CONSTRAINT `account_vipgrouplist_vipgroup_fk`
+ FOREIGN KEY (`vipgroup_id`, `account_id`) REFERENCES `account_vipgroups` (`id`, `account_id`)
+ ON DELETE CASCADE
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ ]])
+
+ db.query([[
+ INSERT INTO `account_vipgroups` (`id`, `account_id`, `name`, `customizable`)
+ SELECT 1, id, 'Friends', 0 FROM `accounts`;
+ ]])
+
+ db.query([[
+ INSERT INTO `account_vipgroups` (`id`, `account_id`, `name`, `customizable`)
+ SELECT 2, id, 'Enemies', 0 FROM `accounts`;
+ ]])
+
+ db.query([[
+ INSERT INTO `account_vipgroups` (`id`, `account_id`, `name`, `customizable`)
+ SELECT 3, id, 'Trading Partners', 0 FROM `accounts`;
+ ]])
+
+ return true
+end
diff --git a/data-otservbr-global/migrations/46.lua b/data-otservbr-global/migrations/46.lua
new file mode 100644
index 00000000000..86a6d8ffec1
--- /dev/null
+++ b/data-otservbr-global/migrations/46.lua
@@ -0,0 +1,3 @@
+function onUpdateDatabase()
+ return false -- true = There are others migrations file | false = this is the last migration file
+end
diff --git a/data-otservbr-global/monster/quests/primal_ordeal_quest/the_primal_menace.lua b/data-otservbr-global/monster/quests/primal_ordeal_quest/the_primal_menace.lua
index b0bc59364d6..7ad43b016c9 100644
--- a/data-otservbr-global/monster/quests/primal_ordeal_quest/the_primal_menace.lua
+++ b/data-otservbr-global/monster/quests/primal_ordeal_quest/the_primal_menace.lua
@@ -24,7 +24,6 @@ local thePrimalMenaceConfig = {
CountGrowthPerHazard = 1.05,
CountMax = 6,
- HpRateOnSpawn = 0.7,
MonsterPool = {
"Emerald Tortoise (Primal)",
"Gore Horn (Primal)",
@@ -291,8 +290,6 @@ local function spawnMonster(monsterId, spawnPosition)
MonsterId = primalMonster:getId(),
Created = os.time(),
}
- local monsterMaxHealth = primalMonster:getMaxHealth()
- primalMonster:setHealth(monsterMaxHealth * thePrimalMenaceConfig.MonsterConfig.HpRateOnSpawn)
local primalBeasts = monster:getStorageValue(thePrimalMenaceConfig.Storage.PrimalBeasts)
table.insert(primalBeasts, primalBeastEntry)
diff --git a/data-otservbr-global/npc/alaistar.lua b/data-otservbr-global/npc/alaistar.lua
index e893975b24b..68aaa75679a 100644
--- a/data-otservbr-global/npc/alaistar.lua
+++ b/data-otservbr-global/npc/alaistar.lua
@@ -36,13 +36,14 @@ local itemsTable = {
{ itemName = "strong health potion", clientId = 236, buy = 115 },
{ itemName = "strong mana potion", clientId = 237, buy = 93 },
{ itemName = "supreme health potion", clientId = 23375, buy = 625 },
- { itemName = "ultimate health potion", clientId = 7643, buy = 438 },
- { itemName = "ultimate mana potion", clientId = 23373, buy = 379 },
+ { itemName = "ultimate health potion", clientId = 7643, buy = 379 },
+ { itemName = "ultimate mana potion", clientId = 23373, buy = 438 },
{ itemName = "ultimate spirit potion", clientId = 23374, buy = 438 },
{ itemName = "vial", clientId = 2874, sell = 5 },
},
["creature products"] = {
{ itemName = "cowbell", clientId = 21204, sell = 210 },
+ { itemName = "execowtioner mask", clientId = 21201, sell = 240 },
{ itemName = "giant pacifier", clientId = 21199, sell = 170 },
{ itemName = "glob of glooth", clientId = 21182, sell = 125 },
{ itemName = "glooth injection tube", clientId = 21103, sell = 350 },
diff --git a/data-otservbr-global/npc/alexander.lua b/data-otservbr-global/npc/alexander.lua
index 78f51e486c8..fda2799bf4d 100644
--- a/data-otservbr-global/npc/alexander.lua
+++ b/data-otservbr-global/npc/alexander.lua
@@ -30,6 +30,28 @@ npcConfig.voices = {
}
local itemsTable = {
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["creature products"] = {
+ { itemName = "crystal ball", clientId = 3076, buy = 530, sell = 190 },
+ { itemName = "life crystal", clientId = 3061, sell = 83 },
+ { itemName = "mind stone", clientId = 3062, sell = 170 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook of enlightenment", clientId = 8072, sell = 4000 },
+ { itemName = "spellbook of warding", clientId = 8073, sell = 8000 },
+ { itemName = "spellbook of mind control", clientId = 8074, sell = 13000 },
+ { itemName = "spellbook of lost souls", clientId = 8075, sell = 19000 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
["runes"] = {
{ itemName = "animate dead rune", clientId = 3203, buy = 375 },
{ itemName = "blank rune", clientId = 3147, buy = 10 },
diff --git a/data-otservbr-global/npc/an_idol.lua b/data-otservbr-global/npc/an_idol.lua
new file mode 100644
index 00000000000..69977dc81ec
--- /dev/null
+++ b/data-otservbr-global/npc/an_idol.lua
@@ -0,0 +1,68 @@
+local internalNpcName = "An Idol"
+local npcType = Game.createNpcType(internalNpcName)
+local npcConfig = {}
+
+npcConfig.name = internalNpcName
+npcConfig.description = internalNpcName
+
+npcConfig.health = 100
+npcConfig.maxHealth = npcConfig.health
+npcConfig.walkInterval = 0
+npcConfig.walkRadius = 2
+
+npcConfig.outfit = {
+ lookTypeEx = 15894,
+}
+
+npcConfig.flags = {
+ floorchange = false,
+}
+
+local keywordHandler = KeywordHandler:new()
+local npcHandler = NpcHandler:new(keywordHandler)
+
+npcType.onThink = function(npc, interval)
+ npcHandler:onThink(npc, interval)
+end
+
+npcType.onAppear = function(npc, creature)
+ npcHandler:onAppear(npc, creature)
+end
+
+npcType.onDisappear = function(npc, creature)
+ npcHandler:onDisappear(npc, creature)
+end
+
+npcType.onMove = function(npc, creature, fromPosition, toPosition)
+ npcHandler:onMove(npc, creature, fromPosition, toPosition)
+end
+
+npcType.onSay = function(npc, creature, type, message)
+ npcHandler:onSay(npc, creature, type, message)
+end
+
+npcType.onCloseChannel = function(npc, creature)
+ npcHandler:onCloseChannel(npc, creature)
+end
+
+local function creatureSayCallback(npc, creature, type, message)
+ local player = Player(creature)
+
+ if not npcHandler:checkInteraction(npc, creature) then
+ return false
+ end
+
+ if MsgContains(message, "VBOX") then
+ npcHandler:say("J-T B^C J^BXT°", npc, creature)
+ player:teleportTo(Position(32366, 32531, 8), false)
+ player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
+ end
+
+ return true
+end
+
+npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
+npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, false)
+
+-- npcType registering the npcConfig table
+npcType:register(npcConfig)
diff --git a/data-otservbr-global/npc/asima.lua b/data-otservbr-global/npc/asima.lua
index 645689c42d4..c3aca3fcd5e 100644
--- a/data-otservbr-global/npc/asima.lua
+++ b/data-otservbr-global/npc/asima.lua
@@ -24,6 +24,14 @@ npcConfig.flags = {
}
local itemsTable = {
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
["potions"] = {
{ itemName = "empty potion flask", clientId = 283, sell = 5 },
{ itemName = "empty potion flask", clientId = 284, sell = 5 },
@@ -41,6 +49,12 @@ local itemsTable = {
{ itemName = "ultimate spirit potion", clientId = 23374, buy = 438 },
{ itemName = "vial", clientId = 2874, sell = 5 },
},
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
["runes"] = {
{ itemName = "avalanche rune", clientId = 3161, buy = 57 },
{ itemName = "blank rune", clientId = 3147, buy = 10 },
@@ -61,8 +75,27 @@ local itemsTable = {
{ itemName = "poison field rune", clientId = 3172, buy = 21 },
{ itemName = "poison wall rune", clientId = 3176, buy = 52 },
{ itemName = "sudden death rune", clientId = 3155, buy = 135 },
+ { itemName = "stalagmite rune", clientId = 3179, buy = 12 },
{ itemName = "ultimate healing rune", clientId = 3160, buy = 175 },
},
+ ["wands"] = {
+ { itemName = "hailstorm rod", clientId = 3067, buy = 15000 },
+ { itemName = "moonlight rod", clientId = 3070, buy = 1000 },
+ { itemName = "necrotic rod", clientId = 3069, buy = 5000 },
+ { itemName = "northwind rod", clientId = 8083, buy = 7500 },
+ { itemName = "snakebite rod", clientId = 3066, buy = 500 },
+ { itemName = "springsprout rod", clientId = 8084, buy = 18000 },
+ { itemName = "terra rod", clientId = 3065, buy = 10000 },
+ { itemName = "underworld rod", clientId = 8082, buy = 22000 },
+ { itemName = "wand of cosmic energy", clientId = 3073, buy = 10000 },
+ { itemName = "wand of decay", clientId = 3072, buy = 5000 },
+ { itemName = "wand of draconia", clientId = 8093, buy = 7500 },
+ { itemName = "wand of dragonbreath", clientId = 3075, buy = 1000 },
+ { itemName = "wand of inferno", clientId = 3071, buy = 15000 },
+ { itemName = "wand of starstorm", clientId = 8092, buy = 18000 },
+ { itemName = "wand of voodoo", clientId = 8094, buy = 22000 },
+ { itemName = "wand of vortex", clientId = 3074, buy = 500 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/asnarus.lua b/data-otservbr-global/npc/asnarus.lua
index a733d90834b..3992d513e9b 100644
--- a/data-otservbr-global/npc/asnarus.lua
+++ b/data-otservbr-global/npc/asnarus.lua
@@ -56,7 +56,7 @@ npcConfig.shop = {
{ itemName = "animate dead rune", clientId = 3203, buy = 375 },
{ itemName = "arrow", clientId = 3447, buy = 2 },
{ itemName = "blue quiver", clientId = 35848, buy = 400 },
- { itemName = "bolt", clientId = 3483, buy = 4 },
+ { itemName = "bolt", clientId = 3446, buy = 4 },
{ itemName = "bow", clientId = 3350, buy = 400, sell = 100 },
{ itemName = "bowl of terror sweat", clientId = 20204, sell = 500 },
{ itemName = "broken visor", clientId = 20184, sell = 1900 },
diff --git a/data-otservbr-global/npc/briasol.lua b/data-otservbr-global/npc/briasol.lua
index 6905011fee9..38dcd074159 100644
--- a/data-otservbr-global/npc/briasol.lua
+++ b/data-otservbr-global/npc/briasol.lua
@@ -113,7 +113,7 @@ npcConfig.shop = {
{ itemName = "cyan crystal fragment", clientId = 16125, sell = 800 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/chantalle.lua b/data-otservbr-global/npc/chantalle.lua
index 2615f6557da..3ed42984f5c 100644
--- a/data-otservbr-global/npc/chantalle.lua
+++ b/data-otservbr-global/npc/chantalle.lua
@@ -99,7 +99,7 @@ npcConfig.shop = {
{ itemName = "diamond", clientId = 32770, sell = 15000 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/chuckles.lua b/data-otservbr-global/npc/chuckles.lua
index 51fb3f2add6..4eb06b6bb3d 100644
--- a/data-otservbr-global/npc/chuckles.lua
+++ b/data-otservbr-global/npc/chuckles.lua
@@ -59,6 +59,38 @@ local itemsTable = {
{ itemName = "sudden death rune", clientId = 3155, buy = 135 },
{ itemName = "ultimate healing rune", clientId = 3160, buy = 175 },
},
+ ["wands"] = {
+ { itemName = "hailstorm rod", clientId = 3067, buy = 15000 },
+ { itemName = "moonlight rod", clientId = 3070, buy = 1000 },
+ { itemName = "necrotic rod", clientId = 3069, buy = 5000 },
+ { itemName = "northwind rod", clientId = 8083, buy = 7500 },
+ { itemName = "snakebite rod", clientId = 3066, buy = 500 },
+ { itemName = "springsprout rod", clientId = 8084, buy = 18000 },
+ { itemName = "terra rod", clientId = 3065, buy = 10000 },
+ { itemName = "underworld rod", clientId = 8082, buy = 22000 },
+ { itemName = "wand of cosmic energy", clientId = 3073, buy = 10000 },
+ { itemName = "wand of decay", clientId = 3072, buy = 5000 },
+ { itemName = "wand of draconia", clientId = 8093, buy = 7500 },
+ { itemName = "wand of dragonbreath", clientId = 3075, buy = 1000 },
+ { itemName = "wand of inferno", clientId = 3071, buy = 15000 },
+ { itemName = "wand of starstorm", clientId = 8092, buy = 18000 },
+ { itemName = "wand of voodoo", clientId = 8094, buy = 22000 },
+ { itemName = "wand of vortex", clientId = 3074, buy = 500 },
+ },
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/edmund.lua b/data-otservbr-global/npc/edmund.lua
index d8635297949..c61a95bcd72 100644
--- a/data-otservbr-global/npc/edmund.lua
+++ b/data-otservbr-global/npc/edmund.lua
@@ -68,7 +68,7 @@ npcConfig.shop = {
{ itemName = "cyan crystal fragment", clientId = 16125, sell = 800 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/fenech.lua b/data-otservbr-global/npc/fenech.lua
index 7152c0ef9c5..f69dcafaa89 100644
--- a/data-otservbr-global/npc/fenech.lua
+++ b/data-otservbr-global/npc/fenech.lua
@@ -71,6 +71,20 @@ local itemsTable = {
{ itemName = "sudden death rune", clientId = 3155, buy = 135 },
{ itemName = "ultimate healing rune", clientId = 3160, buy = 175 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/frans.lua b/data-otservbr-global/npc/frans.lua
index 8761a7d89d6..a1b695adf14 100644
--- a/data-otservbr-global/npc/frans.lua
+++ b/data-otservbr-global/npc/frans.lua
@@ -58,6 +58,20 @@ local itemsTable = {
{ itemName = "sudden death rune", clientId = 3155, buy = 135 },
{ itemName = "ultimate healing rune", clientId = 3160, buy = 175 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/frederik.lua b/data-otservbr-global/npc/frederik.lua
index 9b33ccf9684..81ff1ec58b6 100644
--- a/data-otservbr-global/npc/frederik.lua
+++ b/data-otservbr-global/npc/frederik.lua
@@ -82,6 +82,20 @@ local itemsTable = {
{ itemName = "ultimate spirit potion", clientId = 23374, buy = 438 },
{ itemName = "vial", clientId = 2874, sell = 5 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/gail.lua b/data-otservbr-global/npc/gail.lua
index 80a9b54b3e5..de2a52dc7f4 100644
--- a/data-otservbr-global/npc/gail.lua
+++ b/data-otservbr-global/npc/gail.lua
@@ -109,7 +109,7 @@ npcConfig.shop = {
{ itemName = "cyan crystal fragment", clientId = 16125, sell = 800 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/gnomegica.lua b/data-otservbr-global/npc/gnomegica.lua
index 0805ca33ace..b860caf3962 100644
--- a/data-otservbr-global/npc/gnomegica.lua
+++ b/data-otservbr-global/npc/gnomegica.lua
@@ -78,6 +78,20 @@ local itemsTable = {
{ itemName = "wand of dragonbreath", clientId = 3075, buy = 1000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/hanna.lua b/data-otservbr-global/npc/hanna.lua
index 7fca4c908aa..dfea1547135 100644
--- a/data-otservbr-global/npc/hanna.lua
+++ b/data-otservbr-global/npc/hanna.lua
@@ -145,7 +145,7 @@ npcConfig.shop = {
{ itemName = "cyan crystal fragment", clientId = 16125, sell = 800 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/hireling.lua b/data-otservbr-global/npc/hireling.lua
index 145d846bd7e..aad7785079d 100644
--- a/data-otservbr-global/npc/hireling.lua
+++ b/data-otservbr-global/npc/hireling.lua
@@ -228,7 +228,7 @@ function createHirelingType(HirelingName)
},
["distance"] = {
{ itemName = "arrow", clientId = 3447, buy = 2 },
- { itemName = "bolt", clientId = 3483, buy = 4 },
+ { itemName = "bolt", clientId = 3446, buy = 4 },
{ itemName = "bow", clientId = 3350, buy = 400, sell = 100 },
{ itemName = "crossbow", clientId = 3349, buy = 500, sell = 120 },
{ itemName = "crystalline arrow", clientId = 15793, buy = 450 },
diff --git a/data-otservbr-global/npc/ishina.lua b/data-otservbr-global/npc/ishina.lua
index 1fd61200c15..358ee2619a3 100644
--- a/data-otservbr-global/npc/ishina.lua
+++ b/data-otservbr-global/npc/ishina.lua
@@ -139,7 +139,7 @@ npcConfig.shop = {
{ itemName = "cyan crystal fragment", clientId = 16125, sell = 800 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/iwan.lua b/data-otservbr-global/npc/iwan.lua
index 2cc24843318..77689b12003 100644
--- a/data-otservbr-global/npc/iwan.lua
+++ b/data-otservbr-global/npc/iwan.lua
@@ -78,7 +78,7 @@ npcConfig.shop = {
{ itemName = "cyan crystal fragment", clientId = 16125, sell = 800 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/jessica.lua b/data-otservbr-global/npc/jessica.lua
index 37ac18ed54a..43b1839b3ee 100644
--- a/data-otservbr-global/npc/jessica.lua
+++ b/data-otservbr-global/npc/jessica.lua
@@ -98,7 +98,7 @@ npcConfig.shop = {
{ itemName = "diamond", clientId = 32770, sell = 15000 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/khanna.lua b/data-otservbr-global/npc/khanna.lua
index 02fcee7b6d5..76b5c1e70da 100644
--- a/data-otservbr-global/npc/khanna.lua
+++ b/data-otservbr-global/npc/khanna.lua
@@ -34,7 +34,7 @@ local itemsTable = {
["runes"] = {
{ itemName = "animate dead rune", clientId = 3203, buy = 375 },
{ itemName = "avalanche rune", clientId = 3161, buy = 57 },
- { itemName = "blank rune", clientId = 3147, buy = 10 },
+ { itemName = "blank rune", clientId = 3147, buy = 20 },
{ itemName = "chameleon rune", clientId = 3178, buy = 210 },
{ itemName = "convince creature rune", clientId = 3177, buy = 80 },
{ itemName = "cure poison rune", clientId = 3153, buy = 65 },
@@ -84,6 +84,46 @@ local itemsTable = {
{ itemName = "wand of voodoo", clientId = 8094, buy = 22000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["creature products"] = {
+ { itemName = "bashmu fang", clientId = 36820, sell = 600 },
+ { itemName = "bashmu feather", clientId = 36820, sell = 350 },
+ { itemName = "bashmu tongue", clientId = 36820, sell = 400 },
+ { itemName = "blue goanna scale", clientId = 31559, sell = 230 },
+ { itemName = "crystal ball", clientId = 3076, buy = 650 },
+ { itemName = "fafnar symbol", clientId = 31443, sell = 950 },
+ { itemName = "goanna claw", clientId = 31561, sell = 950 },
+ { itemName = "goanna meat", clientId = 31560, sell = 190 },
+ { itemName = "lamassu hoof", clientId = 31441, sell = 330 },
+ { itemName = "lamassu horn", clientId = 31442, sell = 240 },
+ { itemName = "life crystal", clientId = 3061, sell = 85 },
+ { itemName = "lizard heart", clientId = 31340, sell = 530 },
+ { itemName = "manticore ear", clientId = 31440, sell = 310 },
+ { itemName = "manticore tail", clientId = 31439, sell = 220 },
+ { itemName = "mind stone", clientId = 3062, sell = 170 },
+ { itemName = "old girtablilu carapace", clientId = 36972, sell = 570 },
+ { itemName = "red goanna scale", clientId = 31558, sell = 270 },
+ { itemName = "scorpion charm", clientId = 36822, sell = 620 },
+ { itemName = "sphinx feather", clientId = 31437, sell = 470 },
+ { itemName = "sphinx tiara", clientId = 31438, sell = 360 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ { itemName = "spellbook of enlightenment", clientId = 8072, sell = 4000 },
+ { itemName = "spellbook of lost souls", clientId = 8075, sell = 19000 },
+ { itemName = "spellbook of mind control", clientId = 8074, sell = 13000 },
+ { itemName = "spellbook of warding", clientId = 8073, sell = 8000 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/mordecai.lua b/data-otservbr-global/npc/mordecai.lua
index 60063a423ba..dc0e3c07a77 100644
--- a/data-otservbr-global/npc/mordecai.lua
+++ b/data-otservbr-global/npc/mordecai.lua
@@ -88,6 +88,17 @@ local itemsTable = {
{ itemName = "wand of voodoo", clientId = 8094, buy = 22000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/nelly.lua b/data-otservbr-global/npc/nelly.lua
index 8c3b123c47a..911464524c6 100644
--- a/data-otservbr-global/npc/nelly.lua
+++ b/data-otservbr-global/npc/nelly.lua
@@ -94,6 +94,20 @@ local itemsTable = {
{ itemName = "letter", clientId = 3505, buy = 8 },
{ itemName = "parcel", clientId = 3503, buy = 15 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/nipuna.lua b/data-otservbr-global/npc/nipuna.lua
index aa9d74cec52..ef3211bce42 100644
--- a/data-otservbr-global/npc/nipuna.lua
+++ b/data-otservbr-global/npc/nipuna.lua
@@ -101,6 +101,17 @@ local itemsTable = {
{ itemName = "wand of voodoo", clientId = 8094, buy = 22000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/odemara.lua b/data-otservbr-global/npc/odemara.lua
index bcfe94bf8eb..3e1986714c7 100644
--- a/data-otservbr-global/npc/odemara.lua
+++ b/data-otservbr-global/npc/odemara.lua
@@ -70,7 +70,7 @@ npcConfig.shop = {
{ itemName = "diamond", clientId = 32770, sell = 15000 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/oiriz.lua b/data-otservbr-global/npc/oiriz.lua
index 3a5d3a6b411..4b731c015a2 100644
--- a/data-otservbr-global/npc/oiriz.lua
+++ b/data-otservbr-global/npc/oiriz.lua
@@ -68,7 +68,7 @@ npcConfig.shop = {
{ itemName = "cyan crystal fragment", clientId = 16125, sell = 800 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/rabaz.lua b/data-otservbr-global/npc/rabaz.lua
index 3e47da6a5ca..e532e7def66 100644
--- a/data-otservbr-global/npc/rabaz.lua
+++ b/data-otservbr-global/npc/rabaz.lua
@@ -82,6 +82,20 @@ local itemsTable = {
{ itemName = "wand of voodoo", clientId = 8094, buy = 22000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/rachel.lua b/data-otservbr-global/npc/rachel.lua
index 3206c99864a..057787692c9 100644
--- a/data-otservbr-global/npc/rachel.lua
+++ b/data-otservbr-global/npc/rachel.lua
@@ -71,6 +71,23 @@ local itemsTable = {
{ itemName = "wand of dragonbreath", clientId = 3075, buy = 1000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["valuables"] = {
+ { itemName = "talon", clientId = 3034, sell = 320 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/romir.lua b/data-otservbr-global/npc/romir.lua
index 11ea038d266..09ab62ab1a4 100644
--- a/data-otservbr-global/npc/romir.lua
+++ b/data-otservbr-global/npc/romir.lua
@@ -82,6 +82,20 @@ local itemsTable = {
{ itemName = "wand of voodoo", clientId = 8094, buy = 22000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/shiriel.lua b/data-otservbr-global/npc/shiriel.lua
index 546bb26447b..fd982f345b9 100644
--- a/data-otservbr-global/npc/shiriel.lua
+++ b/data-otservbr-global/npc/shiriel.lua
@@ -70,6 +70,20 @@ local itemsTable = {
{ itemName = "wand of dragonbreath", clientId = 3075, buy = 1000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/sigurd.lua b/data-otservbr-global/npc/sigurd.lua
index cca63a33e3e..e4d1b585307 100644
--- a/data-otservbr-global/npc/sigurd.lua
+++ b/data-otservbr-global/npc/sigurd.lua
@@ -72,6 +72,20 @@ local itemsTable = {
{ itemName = "wand of dragonbreath", clientId = 3075, buy = 1000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/sundara.lua b/data-otservbr-global/npc/sundara.lua
index c1364240fa9..95ff206f2ad 100644
--- a/data-otservbr-global/npc/sundara.lua
+++ b/data-otservbr-global/npc/sundara.lua
@@ -101,6 +101,17 @@ local itemsTable = {
{ itemName = "wand of voodoo", clientId = 8094, buy = 22000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/tandros.lua b/data-otservbr-global/npc/tandros.lua
index e25ba65f2a3..ae647b26127 100644
--- a/data-otservbr-global/npc/tandros.lua
+++ b/data-otservbr-global/npc/tandros.lua
@@ -88,6 +88,20 @@ local itemsTable = {
{ itemName = "wand of voodoo", clientId = 8094, buy = 22000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/tarun.lua b/data-otservbr-global/npc/tarun.lua
index 31f56602f81..9f0b8be5883 100644
--- a/data-otservbr-global/npc/tarun.lua
+++ b/data-otservbr-global/npc/tarun.lua
@@ -51,6 +51,74 @@ npcType.onCloseChannel = function(npc, creature)
npcHandler:onCloseChannel(npc, creature)
end
+local function creatureSayCallback(npc, creature, type, message)
+ local player = Player(creature)
+ if not player then
+ return false
+ end
+ local playerId = player:getId()
+
+ if not npcHandler:checkInteraction(npc, creature) then
+ return false
+ end
+
+ local theLostBrotherStorage = player:getStorageValue(Storage.AdventurersGuild.TheLostBrother)
+ if MsgContains(message, "mission") then
+ if theLostBrotherStorage < 1 then
+ npcHandler:say({
+ "My brother is missing. I fear, he went to this evil palace north of here. A place of great beauty, certainly filled with riches and luxury. But in truth it is a threshold to hell and demonesses are after his blood. ...",
+ "He is my brother, and I am deeply ashamed to admit but I don't dare to go there. Perhaps your heart is more courageous than mine. Would you go to see this place and search for my brother?",
+ }, npc, creature)
+ npcHandler:setTopic(playerId, 1)
+ elseif theLostBrotherStorage == 1 then
+ npcHandler:say("I hope you will find my brother.", npc, creature)
+ npcHandler:setTopic(playerId, 0)
+ elseif theLostBrotherStorage == 2 then
+ npcHandler:say({
+ "So, he is dead as I feared. I warned him not to go with this woman, but he gave in to temptation. My heart darkens and moans. But you have my sincere thanks. ...",
+ "Without your help I would have stayed in the dark about his fate. Please, take this as a little recompense.",
+ }, npc, creature)
+ player:addItem(3039, 1)
+ player:addExperience(3000, true)
+ player:setStorageValue(Storage.AdventurersGuild.TheLostBrother, 3)
+ npcHandler:setTopic(playerId, 0)
+ end
+ elseif npcHandler:getTopic(playerId) == 1 then
+ if MsgContains(message, "yes") then
+ npcHandler:say("I thank you! This is more than I could hope!", npc, creature)
+ if theLostBrotherStorage < 1 then
+ player:setStorageValue(Storage.AdventurersGuild.QuestLine, 1)
+ end
+ player:setStorageValue(Storage.AdventurersGuild.TheLostBrother, 1)
+ elseif MsgContains(message, "no") then
+ npcHandler:say("As you wish.", npc, creature)
+ end
+ npcHandler:setTopic(playerId, 0)
+ end
+
+ return true
+end
+
+local function onTradeRequest(npc, creature)
+ local player = Player(creature)
+ if not player then
+ return false
+ end
+ local playerId = player:getId()
+
+ if player:getStorageValue(Storage.AdventurersGuild.TheLostBrother) ~= 3 then
+ return false
+ end
+
+ return true
+end
+
+npcHandler:setMessage(MESSAGE_GREET, "Greetings!")
+npcHandler:setMessage(MESSAGE_FAREWELL, "Farewell.")
+npcHandler:setMessage(MESSAGE_SENDTRADE, "Of course, just have a look.")
+npcHandler:setCallback(CALLBACK_ON_TRADE_REQUEST, onTradeRequest)
+npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
+npcHandler:setMessage(MESSAGE_WALKAWAY, "Farewell.")
npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true)
npcConfig.shop = {
diff --git a/data-otservbr-global/npc/tesha.lua b/data-otservbr-global/npc/tesha.lua
index 95e31f97cb7..6a3c4b9fadb 100644
--- a/data-otservbr-global/npc/tesha.lua
+++ b/data-otservbr-global/npc/tesha.lua
@@ -98,7 +98,7 @@ npcConfig.shop = {
{ itemName = "diamond", clientId = 32770, sell = 15000 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/tezila.lua b/data-otservbr-global/npc/tezila.lua
index dab92c807f5..fe667133ad7 100644
--- a/data-otservbr-global/npc/tezila.lua
+++ b/data-otservbr-global/npc/tezila.lua
@@ -67,7 +67,7 @@ npcConfig.shop = {
{ itemName = "cyan crystal fragment", clientId = 16125, sell = 800 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/topsy.lua b/data-otservbr-global/npc/topsy.lua
index 395fd23cbeb..66ea6f27e6a 100644
--- a/data-otservbr-global/npc/topsy.lua
+++ b/data-otservbr-global/npc/topsy.lua
@@ -78,6 +78,20 @@ local itemsTable = {
{ itemName = "wand of dragonbreath", clientId = 3075, buy = 1000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/valindara.lua b/data-otservbr-global/npc/valindara.lua
index 69655358dfe..cf9506fcdd5 100644
--- a/data-otservbr-global/npc/valindara.lua
+++ b/data-otservbr-global/npc/valindara.lua
@@ -111,7 +111,7 @@ npcConfig.shop = {
{ itemName = "fire wall rune", clientId = 3190, buy = 61 },
{ itemName = "fireball rune", clientId = 3189, buy = 30 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/npc/xodet.lua b/data-otservbr-global/npc/xodet.lua
index 2d8832bd964..f687b491e89 100644
--- a/data-otservbr-global/npc/xodet.lua
+++ b/data-otservbr-global/npc/xodet.lua
@@ -72,6 +72,20 @@ local itemsTable = {
{ itemName = "wand of dragonbreath", clientId = 3075, buy = 1000 },
{ itemName = "wand of vortex", clientId = 3074, buy = 500 },
},
+ ["exercise weapons"] = {
+ { itemName = "durable exercise rod", clientId = 35283, buy = 945000, count = 1800 },
+ { itemName = "durable exercise wand", clientId = 35284, buy = 945000, count = 1800 },
+ { itemName = "exercise rod", clientId = 28556, buy = 262500, count = 500 },
+ { itemName = "exercise wand", clientId = 28557, buy = 262500, count = 500 },
+ { itemName = "lasting exercise rod", clientId = 35289, buy = 7560000, count = 14400 },
+ { itemName = "lasting exercise wand", clientId = 35290, buy = 7560000, count = 14400 },
+ },
+ ["others"] = {
+ { itemName = "spellwand", clientId = 651, sell = 299 },
+ },
+ ["shields"] = {
+ { itemName = "spellbook", clientId = 3059, buy = 150 },
+ },
}
npcConfig.shop = {}
diff --git a/data-otservbr-global/npc/yasir.lua b/data-otservbr-global/npc/yasir.lua
index d576b611e53..9c5cf3dbf69 100644
--- a/data-otservbr-global/npc/yasir.lua
+++ b/data-otservbr-global/npc/yasir.lua
@@ -60,6 +60,7 @@ npcConfig.shop = {
{ itemName = "ape fur", clientId = 5883, sell = 120 },
{ itemName = "apron", clientId = 33933, sell = 1300 },
{ itemName = "badger fur", clientId = 903, sell = 15 },
+ { itemName = "bakragore's amalgamation", clientId = 43968, sell = 2000000 },
{ itemName = "bamboo stick", clientId = 11445, sell = 30 },
{ itemName = "banana sash", clientId = 11511, sell = 55 },
{ itemName = "basalt fetish", clientId = 17856, sell = 210 },
@@ -75,6 +76,7 @@ npcConfig.shop = {
{ itemName = "black hood", clientId = 9645, sell = 190 },
{ itemName = "black wool", clientId = 11448, sell = 300 },
{ itemName = "blazing bone", clientId = 16131, sell = 610 },
+ { itemName = "bloated maggot", clientId = 43856, sell = 5200 },
{ itemName = "blood preservation", clientId = 11449, sell = 320 },
{ itemName = "blood tincture in a vial", clientId = 18928, sell = 360 },
{ itemName = "bloody dwarven beard", clientId = 17827, sell = 110 },
@@ -173,7 +175,11 @@ npcConfig.shop = {
{ itemName = "dandelion seeds", clientId = 25695, sell = 200 },
{ itemName = "dangerous proto matter", clientId = 23515, sell = 300 },
{ itemName = "dark bell", clientId = 32596, sell = 310000 },
+ { itemName = "dark obsidian splinter", clientId = 43850, sell = 4400 },
{ itemName = "dark rosary", clientId = 10303, sell = 48 },
+ { itemName = "darklight core", clientId = 43853, sell = 4100 },
+ { itemName = "darklight figurine", clientId = 43961, sell = 3400000 },
+ { itemName = "darklight matter", clientId = 43851, sell = 5500 },
{ itemName = "dead weight", clientId = 20202, sell = 450 },
{ itemName = "deepling breaktime snack", clientId = 14011, sell = 90 },
{ itemName = "deepling claw", clientId = 14044, sell = 430 },
@@ -229,6 +235,7 @@ npcConfig.shop = {
{ itemName = "falcon crest", clientId = 28823, sell = 650 },
{ itemName = "fern", clientId = 3737, sell = 20 },
{ itemName = "fiery heart", clientId = 9636, sell = 375 },
+ { itemName = "fiery tear", clientId = 39040, sell = 1070000 },
{ itemName = "fig leaf", clientId = 25742, sell = 200 },
{ itemName = "figurine of cruelty", clientId = 34019, sell = 3100000 },
{ itemName = "figurine of greed", clientId = 34021, sell = 2900000 },
@@ -480,6 +487,8 @@ npcConfig.shop = {
{ itemName = "rorc feather", clientId = 18993, sell = 70 },
{ itemName = "rotten heart", clientId = 31589, sell = 74000 },
{ itemName = "rotten piece of cloth", clientId = 10291, sell = 30 },
+ { itemName = "rotten roots", clientId = 43849, sell = 3800 },
+ { itemName = "rotten vermin ichor", clientId = 43847, sell = 4500 },
{ itemName = "sabretooth", clientId = 10311, sell = 400 },
{ itemName = "sabretooth fur", clientId = 39378, sell = 2500 },
{ itemName = "safety pin", clientId = 11493, sell = 120 },
@@ -636,6 +645,7 @@ npcConfig.shop = {
{ itemName = "wolf paw", clientId = 5897, sell = 70 },
{ itemName = "wood", clientId = 5901, sell = 5 },
{ itemName = "wool", clientId = 10319, sell = 15 },
+ { itemName = "worm sponge", clientId = 43848, sell = 4200 },
{ itemName = "writhing brain", clientId = 32600, sell = 370000 },
{ itemName = "writhing heart", clientId = 32599, sell = 185000 },
{ itemName = "wyrm scale", clientId = 9665, sell = 400 },
diff --git a/data-otservbr-global/npc/yonan.lua b/data-otservbr-global/npc/yonan.lua
index 44dbb8dd83c..69bddee5cb3 100644
--- a/data-otservbr-global/npc/yonan.lua
+++ b/data-otservbr-global/npc/yonan.lua
@@ -39,7 +39,7 @@ npcConfig.shop = {
{ itemName = "cyan crystal fragment", clientId = 16125, sell = 800 },
{ itemName = "dragon figurine", clientId = 30053, sell = 45000 },
{ itemName = "gemmed figurine", clientId = 24392, sell = 3500 },
- { itemName = "giant amethyst", clientId = 30061, sell = 60000 },
+ { itemName = "giant amethyst", clientId = 32622, sell = 60000 },
{ itemName = "giant emerald", clientId = 30060, sell = 90000 },
{ itemName = "giant ruby", clientId = 30059, sell = 70000 },
{ itemName = "giant sapphire", clientId = 30061, sell = 50000 },
diff --git a/data-otservbr-global/scripts/actions/other/cup_cakes.lua b/data-otservbr-global/scripts/actions/other/cup_cakes.lua
deleted file mode 100644
index a9d94dbaacd..00000000000
--- a/data-otservbr-global/scripts/actions/other/cup_cakes.lua
+++ /dev/null
@@ -1,51 +0,0 @@
-local data = {
- [28484] = {
- Type = "mana",
- ExhaustStor = Storage.BlueberryCupcake,
- timestamp = 10,
- },
- [28485] = {
- Type = "health",
- ExhaustStor = Storage.StrawberryCupcake,
- timestamp = 10,
- },
- [28486] = {
- Type = "skill",
- ExhaustStor = Storage.LemonCupcake,
- timestamp = 10,
- },
-}
-
-local lemon = Condition(CONDITION_ATTRIBUTES)
-lemon:setParameter(CONDITION_PARAM_TICKS, 60 * 60 * 1000)
-lemon:setParameter(CONDITION_PARAM_SKILL_DISTANCE, 10)
-
-local cupCakes = Action()
-
-function cupCakes.onUse(player, item, fromPos, itemEx, toPos)
- local foundItem = data[item.itemid]
- if not foundItem then
- return
- end
- if (player:getStorageValue(foundItem.ExhaustStor)) < os.time() then
- if foundItem.Type == "mana" then
- player:addMana(player:getMaxMana())
- player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your mana has been refilled.")
- elseif foundItem.Type == "health" then
- player:addHealth(player:getMaxHealth())
- player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your health has been refilled.")
- elseif foundItem.Type == "skill" then
- player:addCondition(lemon)
- player:sendTextMessage(MESSAGE_FAILURE, "You feel more focused.")
- end
- player:say("Mmmm.", TALKTYPE_MONSTER_SAY)
- item:remove(1)
- player:setStorageValue(foundItem.ExhaustStor, os.time() + (foundItem.timestamp * 60))
- else
- player:sendTextMessage(MESSAGE_FAILURE, "You need to wait before using it again.")
- end
- return true
-end
-
-cupCakes:id(28484, 28485, 28486)
-cupCakes:register()
diff --git a/data-otservbr-global/scripts/actions/quests/adventurers_guild/warrior_skeleton.lua b/data-otservbr-global/scripts/actions/quests/adventurers_guild/warrior_skeleton.lua
index a1aec80aaee..73a5d3f8acb 100644
--- a/data-otservbr-global/scripts/actions/quests/adventurers_guild/warrior_skeleton.lua
+++ b/data-otservbr-global/scripts/actions/quests/adventurers_guild/warrior_skeleton.lua
@@ -3,6 +3,11 @@ function adventurersWarriorSkeleton.onUse(player, item, fromPosition, target, to
if player:getStorageValue(Storage.AdventurersGuild.GreatDragonHunt.WarriorSkeleton) < 1 then
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have discovered a deceased warrior's skeleton. It seems he tried to hunt the dragons around here - and failed.")
player:addItem(5882, 1) -- red dragon scale
+
+ if player:getStorageValue(Storage.AdventurersGuild.QuestLine) < 1 then
+ player:setStorageValue(Storage.AdventurersGuild.QuestLine, 1)
+ end
+
player:setStorageValue(Storage.AdventurersGuild.GreatDragonHunt.WarriorSkeleton, 1)
player:setStorageValue(Storage.AdventurersGuild.GreatDragonHunt.DragonCounter, 0)
else
diff --git a/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/galthens_tree.lua b/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/galthens_tree.lua
new file mode 100644
index 00000000000..441b91fa345
--- /dev/null
+++ b/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/galthens_tree.lua
@@ -0,0 +1,19 @@
+local galthensTree = Action()
+function galthensTree.onUse(player, item, fromPosition, target, toPosition, isHotkey)
+ local hasExhaustion, message = player:kv():get("galthens-satchel") or 0, "Empty."
+ if hasExhaustion < os.time() then
+ local container = player:addItem(36813)
+ container:addItem(36810, 1)
+ player:kv():set("galthens-satchel", os.time() + 30 * 24 * 60 * 60)
+ message = "You have found a galthens satchel."
+ end
+
+ player:teleportTo(Position(32396, 32520, 7))
+ player:getPosition():sendMagicEffect(CONST_ME_WATERSPLASH)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, message)
+
+ return true
+end
+
+galthensTree:position(Position(32366, 32542, 8))
+galthensTree:register()
diff --git a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua
index 725359eb64a..aebc0b3ca7a 100644
--- a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua
+++ b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua
@@ -103,26 +103,26 @@ function teleportBoss.onStepIn(creature, item, position, fromPosition)
end
local player = creature
if player:getLevel() < config.requiredLevel then
- player:teleportTo(fromPosition, true)
+ player:teleportTo(config.exitPosition, true)
player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to be level " .. config.requiredLevel .. " or higher.")
return true
end
if locked then
- player:teleportTo(fromPosition, true)
+ player:teleportTo(config.exitPosition, true)
player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "There's already someone fighting with " .. config.bossName .. ".")
return false
end
if zone:countPlayers(IgnoredByMonsters) >= 5 then
- player:teleportTo(fromPosition, true)
+ player:teleportTo(config.exitPosition, true)
player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The boss room is full.")
return false
end
local timeLeft = player:getBossCooldown(config.bossName) - os.time()
if timeLeft > 0 then
- player:teleportTo(fromPosition, true)
+ player:teleportTo(config.exitPosition, true)
player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait " .. getTimeInWords(timeLeft) .. " to face " .. config.bossName .. " again!")
player:getPosition():sendMagicEffect(CONST_ME_POFF)
diff --git a/data-otservbr-global/scripts/creaturescripts/customs/freequests.lua b/data-otservbr-global/scripts/creaturescripts/customs/freequests.lua
index ac8f4506c70..ad0e8d49b73 100644
--- a/data-otservbr-global/scripts/creaturescripts/customs/freequests.lua
+++ b/data-otservbr-global/scripts/creaturescripts/customs/freequests.lua
@@ -275,8 +275,10 @@ local questTable = {
{ storage = Storage.Quest.U11_40.ThreatenedDreams.QuestLine, storageValue = 1 },
{ storage = Storage.Quest.U11_40.ThreatenedDreams.Mission01[1], storageValue = 16 },
{ storage = Storage.Quest.U11_40.ThreatenedDreams.Mission02.KroazurAccess, storageValue = 1 },
+ { storage = Storage.AdventurersGuild.QuestLine, storageValue = 1 },
{ storage = Storage.AdventurersGuild.GreatDragonHunt.WarriorSkeleton, storageValue = 1 },
{ storage = Storage.AdventurersGuild.GreatDragonHunt.WarriorSkeleton, storageValue = 2 },
+ { storage = Storage.AdventurersGuild.TheLostBrother, storageValue = 3 },
{ storage = Storage.Quest.U10_55.Dawnport.Questline, storageValue = 1 },
{ storage = Storage.Quest.U10_55.Dawnport.GoMain, storageValue = 1 },
{ storage = Storage.ForgottenKnowledge.AccessDeath, storageValue = 1 },
diff --git a/data-otservbr-global/scripts/creaturescripts/quests/dark_trails/kill_the_ravager.lua b/data-otservbr-global/scripts/creaturescripts/quests/dark_trails/kill_the_ravager.lua
index 97798dadfeb..f11bfa41efe 100644
--- a/data-otservbr-global/scripts/creaturescripts/quests/dark_trails/kill_the_ravager.lua
+++ b/data-otservbr-global/scripts/creaturescripts/quests/dark_trails/kill_the_ravager.lua
@@ -7,7 +7,7 @@ local function removeTeleport(position)
end
local theRavager = CreatureEvent("TheRavagerDeath")
-function theRavager.onDeath(player, creature)
+function theRavager.onDeath(creature)
local position = creature:getPosition()
position:sendMagicEffect(CONST_ME_TELEPORT)
local item = Game.createItem(1949, 1, { x = 33496, y = 32070, z = 8 })
diff --git a/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua b/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua
index b2ba681d251..f8f7ddc5c6b 100644
--- a/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua
+++ b/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua
@@ -57,7 +57,7 @@ function deathEvent.onDeath(creature, _corpse, _lastHitKiller, mostDamageKiller)
end
end
-- Minotaurs
- killCheck(player, targetName, Storage.KillingInTheNameOf.BudrikMinos, 0, tasks.Budrik[1].creatures, nil, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.MinotaurCount)
+ killCheck(player, targetName, Storage.KillingInTheNameOf.BudrikMinos, 0, tasks.Budrik[1].creatures, Storage.Quest.U8_5.KillingInTheNameOf.AltKillCount.MinotaurCount, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.MinotaurCount)
-- Necromancers and Priestesses
killCheck(player, targetName, Storage.KillingInTheNameOf.LugriNecromancers, 0, tasks.Lugri[1].creatures, Storage.Quest.U8_5.KillingInTheNameOf.AltKillCount.NecromancerCount, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.NecromancerCount)
killCheck(player, targetName, Storage.KillingInTheNameOf.LugriNecromancers, 3, tasks.Lugri[1].creatures, Storage.Quest.U8_5.KillingInTheNameOf.AltKillCount.NecromancerCount, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.NecromancerCount)
diff --git a/data-otservbr-global/scripts/creaturescripts/quests/liquid_black/deepling_boss_kill.lua b/data-otservbr-global/scripts/creaturescripts/quests/liquid_black/deepling_boss_kill.lua
index 6916f3dfa5a..49fb3e17c11 100644
--- a/data-otservbr-global/scripts/creaturescripts/quests/liquid_black/deepling_boss_kill.lua
+++ b/data-otservbr-global/scripts/creaturescripts/quests/liquid_black/deepling_boss_kill.lua
@@ -5,12 +5,11 @@ local bosses = {
}
local deeplingBosses = CreatureEvent("DeeplingBossDeath")
-function deeplingBosses.onDeath(player, creature)
+function deeplingBosses.onDeath(creature)
local bossConfig = bosses[creature:getName():lower()]
if not bossConfig then
return true
end
-
onDeathForDamagingPlayers(creature, function(creature, player)
if player:getStorageValue(Storage.DeeplingBosses.DeeplingStatus) < bossConfig.status then
player:setStorageValue(Storage.DeeplingBosses.DeeplingStatus, bossConfig.status)
diff --git a/data-otservbr-global/scripts/game_migrations/20241715984279_move_wheel_scrolls_from_storagename_to_kv.lua b/data-otservbr-global/scripts/game_migrations/20241715984279_move_wheel_scrolls_from_storagename_to_kv.lua
new file mode 100644
index 00000000000..a5cc9a123f4
--- /dev/null
+++ b/data-otservbr-global/scripts/game_migrations/20241715984279_move_wheel_scrolls_from_storagename_to_kv.lua
@@ -0,0 +1,24 @@
+local promotionScrolls = {
+ { oldScroll = "wheel.scroll.abridged", newScroll = "abridged" },
+ { oldScroll = "wheel.scroll.basic", newScroll = "basic" },
+ { oldScroll = "wheel.scroll.revised", newScroll = "revised" },
+ { oldScroll = "wheel.scroll.extended", newScroll = "extended" },
+ { oldScroll = "wheel.scroll.advanced", newScroll = "advanced" },
+}
+
+local function migrate(player)
+ for _, scrollTable in ipairs(promotionScrolls) do
+ local oldStorage = player:getStorageValueByName(scrollTable.oldScroll)
+ if oldStorage > 0 then
+ player:kv():scoped("wheel-of-destiny"):scoped("scrolls"):set(scrollTable.newScroll, true)
+ end
+ end
+end
+
+local migration = Migration("20241715984279_move_wheel_scrolls_from_storagename_to_kv")
+
+function migration:onExecute()
+ self:forEachPlayer(migrate)
+end
+
+migration:register()
diff --git a/data-otservbr-global/scripts/quests/the_lost_brother/movement-find-remains.lua b/data-otservbr-global/scripts/quests/the_lost_brother/movement-find-remains.lua
new file mode 100644
index 00000000000..c2ddb8efcb7
--- /dev/null
+++ b/data-otservbr-global/scripts/quests/the_lost_brother/movement-find-remains.lua
@@ -0,0 +1,19 @@
+local findRemains = MoveEvent()
+
+function findRemains.onStepIn(creature, item, position, fromPosition)
+ local player = creature:getPlayer()
+ if not player then
+ return true
+ end
+
+ if player:getStorageValue(Storage.AdventurersGuild.TheLostBrother) == 1 then
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You stumble over some old bones. Something is carved into the stone wall here: 'Tarun, my brother, you were right. She's evil.'")
+ player:setStorageValue(Storage.AdventurersGuild.TheLostBrother, 2)
+ player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN)
+ end
+
+ return true
+end
+
+findRemains:position(Position(32959, 32674, 4))
+findRemains:register()
diff --git a/data-otservbr-global/scripts/spells/monster/doctor_marrow_explosion.lua b/data-otservbr-global/scripts/spells/monster/doctor_marrow_explosion.lua
index f616e8b8cee..491d9f2516d 100644
--- a/data-otservbr-global/scripts/spells/monster/doctor_marrow_explosion.lua
+++ b/data-otservbr-global/scripts/spells/monster/doctor_marrow_explosion.lua
@@ -21,7 +21,7 @@ end
local spell = Spell("instant")
function onTargetCreature(creature, target)
- if not targetPos then
+ if not target then
return true
end
local master = target:getMaster()
@@ -29,7 +29,7 @@ function onTargetCreature(creature, target)
return true
end
- local distance = math.floor(targetPos:getDistance(target:getPosition()))
+ local distance = math.floor(creature:getPosition():getDistance(target:getPosition()))
local actualDamage = damage / (2 ^ distance)
doTargetCombatHealth(0, target, COMBAT_EARTHDAMAGE, actualDamage, actualDamage, CONST_ME_NONE)
if crit then
@@ -63,21 +63,26 @@ function spell.onCastSpell(creature, var)
end, i * 100, targetPos)
end
- addEvent(function(cid)
+ addEvent(function(cid, pos)
local creature = Creature(cid)
- creature:getPosition():sendMagicEffect(CONST_ME_ORANGE_ENERGY_SPARK)
- targetPos:sendMagicEffect(CONST_ME_ORANGETELEPORT)
- end, 2000, creature:getId())
+ if creature then
+ creature:getPosition():sendMagicEffect(CONST_ME_ORANGE_ENERGY_SPARK)
+ pos:sendMagicEffect(CONST_ME_ORANGETELEPORT)
+ end
+ end, 2000, creature:getId(), targetPos)
addEvent(function(cid, pos)
- damage = -math.random(3500, 7000)
- if math.random(1, 100) <= 10 then
- crit = true
- damage = damage * 1.5
- else
- crit = false
+ local creature = Creature(cid)
+ if creature then
+ damage = -math.random(3500, 7000)
+ if math.random(1, 100) <= 10 then
+ crit = true
+ damage = damage * 1.5
+ else
+ crit = false
+ end
+ spellCombat:execute(creature, Variant(pos))
end
- spellCombat:execute(creature, Variant(pos))
end, totalDelay, creature:getId(), targetPos)
return true
end
diff --git a/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua b/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua
index 61984209287..36be8dc38b4 100644
--- a/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua
+++ b/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua
@@ -16,7 +16,7 @@ combat:setParameter(COMBAT_PARAM_BLOCKARMOR, true)
function onGetFormulaValues(player, skill, attack, factor)
local distanceSkill = player:getEffectiveSkillLevel(SKILL_DISTANCE)
local min = (player:getLevel() / 5)
- local max = (0.09 * factor) * distanceSkill * 37 + (player:getLevel() / 5)
+ local max = (0.09 * factor) * distanceSkill * attack + (player:getLevel() / 5)
return -min, -max
end
diff --git a/data-otservbr-global/world/otservbr-monster.xml b/data-otservbr-global/world/otservbr-monster.xml
index d3bbc69e858..974c2ea6809 100644
--- a/data-otservbr-global/world/otservbr-monster.xml
+++ b/data-otservbr-global/world/otservbr-monster.xml
@@ -4076,7 +4076,6 @@
-
@@ -4122,9 +4121,6 @@
-
-
-
@@ -6928,19 +6924,13 @@
-
-
-
-
-
-
@@ -6953,9 +6943,6 @@
-
-
-
@@ -6963,7 +6950,6 @@
-
@@ -6985,15 +6971,11 @@
-
-
-
-
@@ -7009,9 +6991,6 @@
-
-
-
@@ -63284,9 +63263,6 @@
-
-
-
@@ -63491,9 +63467,6 @@
-
-
-
@@ -96125,15 +96098,15 @@
+
+
+
-
-
-
@@ -96718,6 +96691,9 @@
+
+
+
@@ -96725,9 +96701,6 @@
-
-
-
@@ -118889,12 +118862,12 @@
-
-
-
+
+
+
diff --git a/data-otservbr-global/world/otservbr-npc.xml b/data-otservbr-global/world/otservbr-npc.xml
index 10772902341..f97bc118fdc 100644
--- a/data-otservbr-global/world/otservbr-npc.xml
+++ b/data-otservbr-global/world/otservbr-npc.xml
@@ -2994,5 +2994,8 @@
-
+
+
+
+
diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua
index 35e0bb17e7f..ec1a92f3528 100644
--- a/data/events/scripts/player.lua
+++ b/data/events/scripts/player.lua
@@ -140,8 +140,8 @@ local function useStaminaXpBoost(player)
return false
end
- local staminaMinutes = player:getExpBoostStamina() / 60
- if staminaMinutes == 0 then
+ local xpBoostMinutes = player:getXpBoostTime() / 60
+ if xpBoostMinutes == 0 then
return
end
@@ -156,18 +156,26 @@ local function useStaminaXpBoost(player)
return
end
+ local xpBoostLeftMinutesByDailyReward = player:kv():get("daily-reward-xp-boost") or 0
if timePassed > 60 then
- if staminaMinutes > 2 then
- staminaMinutes = staminaMinutes - 2
+ if xpBoostMinutes > 2 then
+ xpBoostMinutes = xpBoostMinutes - 2
+ if xpBoostLeftMinutesByDailyReward > 2 then
+ player:kv():set("daily-reward-xp-boost", xpBoostLeftMinutesByDailyReward - 2)
+ end
else
- staminaMinutes = 0
+ xpBoostMinutes = 0
+ player:kv():remove("daily-reward-xp-boost")
end
_G.NextUseXpStamina[playerId] = currentTime + 120
else
- staminaMinutes = staminaMinutes - 1
+ xpBoostMinutes = xpBoostMinutes - 1
+ if xpBoostLeftMinutesByDailyReward > 0 then
+ player:kv():set("daily-reward-xp-boost", xpBoostLeftMinutesByDailyReward - 1)
+ end
_G.NextUseXpStamina[playerId] = currentTime + 60
end
- player:setExpBoostStamina(staminaMinutes * 60)
+ player:setXpBoostTime(xpBoostMinutes * 60)
end
local function useConcoctionTime(player)
@@ -519,14 +527,14 @@ function Player:onGainExperience(target, exp, rawExp)
self:addCondition(soulCondition)
end
- -- Store Bonus
- useStaminaXpBoost(self) -- Use store boost stamina
+ -- XP Boost Bonus
+ useStaminaXpBoost(self) -- Use stamina XP boost (store or daily reward)
- local Boost = self:getExpBoostStamina()
- local stillHasBoost = Boost > 0
- local storeXpBoostAmount = stillHasBoost and self:getStoreXpBoost() or 0
+ local xpBoostTimeLeft = self:getXpBoostTime()
+ local stillHasXpBoost = xpBoostTimeLeft > 0
+ local xpBoostPercent = stillHasXpBoost and self:getXpBoostPercent() or 0
- self:setStoreXpBoost(storeXpBoostAmount)
+ self:setXpBoostPercent(xpBoostPercent)
-- Stamina Bonus
local staminaBonusXp = 1
@@ -564,7 +572,7 @@ function Player:onGainExperience(target, exp, rawExp)
local lowLevelBonuxExp = self:getFinalLowLevelBonus()
local baseRate = self:getFinalBaseRateExperience()
- return (exp + (exp * (storeXpBoostAmount / 100) + (exp * (lowLevelBonuxExp / 100)))) * staminaBonusXp * baseRate
+ return (exp + (exp * (xpBoostPercent / 100) + (exp * (lowLevelBonuxExp / 100)))) * staminaBonusXp * baseRate
end
function Player:onLoseExperience(exp)
diff --git a/data/items/items.xml b/data/items/items.xml
index 765c6e9db51..e7e73d9753b 100644
--- a/data/items/items.xml
+++ b/data/items/items.xml
@@ -53105,6 +53105,9 @@ hands of its owner. Granted by TibiaRoyal.com"/>
+
+
+
-
@@ -64463,8 +64466,8 @@ hands of its owner. Granted by TibiaRoyal.com"/>
+
-
@@ -74870,11 +74873,14 @@ Granted by TibiaGoals.com"/>
-
-
+
+
+
+
-
@@ -74991,6 +74997,15 @@ Granted by TibiaGoals.com"/>
+ -
+
+
+
+
+
+
+
+
-
@@ -75006,6 +75021,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75029,6 +75047,12 @@ Granted by TibiaGoals.com"/>
+
+
+
+
+
+
@@ -75052,6 +75076,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75075,6 +75102,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75098,6 +75128,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75121,6 +75154,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75146,6 +75182,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75170,6 +75209,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75194,6 +75236,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75218,6 +75263,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75242,6 +75290,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75266,6 +75317,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75282,6 +75336,14 @@ Granted by TibiaGoals.com"/>
+
+
+
+
+
+
+
+
@@ -75308,6 +75370,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75335,6 +75400,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75362,6 +75430,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75389,6 +75460,9 @@ Granted by TibiaGoals.com"/>
+
+
+
@@ -75404,6 +75478,14 @@ Granted by TibiaGoals.com"/>
+
+
+
+
+
+
+
+
@@ -75419,6 +75501,7 @@ Granted by TibiaGoals.com"/>
+
@@ -75426,8 +75509,12 @@ Granted by TibiaGoals.com"/>
+
+
+
+
-
+
@@ -75446,6 +75533,7 @@ Granted by TibiaGoals.com"/>
+
@@ -75454,8 +75542,12 @@ Granted by TibiaGoals.com"/>
+
+
+
+
-
+
@@ -75478,6 +75570,17 @@ Granted by TibiaGoals.com"/>
+
+
+
+
+
+
+
+
+
+
+
@@ -75489,6 +75592,7 @@ Granted by TibiaGoals.com"/>
+
@@ -75500,6 +75604,10 @@ Granted by TibiaGoals.com"/>
+
+
+
+
@@ -75519,6 +75627,7 @@ Granted by TibiaGoals.com"/>
+
@@ -75528,6 +75637,10 @@ Granted by TibiaGoals.com"/>
+
+
+
+
@@ -75552,6 +75665,17 @@ Granted by TibiaGoals.com"/>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/libs/functions/functions.lua b/data/libs/functions/functions.lua
index 9eb1b0b57b5..c2349ce7fd6 100644
--- a/data/libs/functions/functions.lua
+++ b/data/libs/functions/functions.lua
@@ -67,17 +67,28 @@ end
function getTimeInWords(secsParam)
local secs = tonumber(secsParam)
+ local days = math.floor(secs / (24 * 3600))
+ secs = secs - (days * 24 * 3600)
local hours, minutes, seconds = getHours(secs), getMinutes(secs), getSeconds(secs)
local timeStr = ""
+ if days > 0 then
+ timeStr = days .. (days > 1 and " days" or " day")
+ end
+
if hours > 0 then
- timeStr = hours .. (hours > 1 and " hours" or " hour")
+ if timeStr ~= "" then
+ timeStr = timeStr .. ", "
+ end
+
+ timeStr = timeStr .. hours .. (hours > 1 and " hours" or " hour")
end
if minutes > 0 then
if timeStr ~= "" then
timeStr = timeStr .. ", "
end
+
timeStr = timeStr .. minutes .. (minutes > 1 and " minutes" or " minute")
end
@@ -85,9 +96,9 @@ function getTimeInWords(secsParam)
if timeStr ~= "" then
timeStr = timeStr .. " and "
end
+
timeStr = timeStr .. seconds .. (seconds > 1 and " seconds" or " second")
end
-
return timeStr
end
diff --git a/data/modules/scripts/daily_reward/daily_reward.lua b/data/modules/scripts/daily_reward/daily_reward.lua
index 1f0e805704c..09b927cedda 100644
--- a/data/modules/scripts/daily_reward/daily_reward.lua
+++ b/data/modules/scripts/daily_reward/daily_reward.lua
@@ -476,8 +476,15 @@ function Player.selectDailyReward(self, msg)
end
dailyRewardMessage = "Picked items: " .. description
elseif dailyTable.type == DAILY_REWARD_TYPE_XP_BOOST then
- self:setExpBoostStamina(self:getExpBoostStamina() + (rewardCount * 60))
- self:setStoreXpBoost(50)
+ local rewardCountReviewed = rewardCount
+ local xpBoostLeftMinutes = self:kv():get("daily-reward-xp-boost") or 0
+ if xpBoostLeftMinutes > 0 then
+ rewardCountReviewed = rewardCountReviewed - xpBoostLeftMinutes
+ end
+
+ self:setXpBoostTime(self:getXpBoostTime() + (rewardCountReviewed * 60))
+ self:kv():set("daily-reward-xp-boost", rewardCount)
+ self:setXpBoostPercent(50)
dailyRewardMessage = "Picked reward: XP Bonus for " .. rewardCount .. " minutes."
elseif dailyTable.type == DAILY_REWARD_TYPE_PREY_REROLL then
self:addPreyCards(rewardCount)
diff --git a/data/modules/scripts/gamestore/gamestore.lua b/data/modules/scripts/gamestore/gamestore.lua
index 852bd4d4405..f000c4079af 100644
--- a/data/modules/scripts/gamestore/gamestore.lua
+++ b/data/modules/scripts/gamestore/gamestore.lua
@@ -6770,7 +6770,7 @@ for k, category in ipairs(GameStore.Categories) do
offer.type = GameStore.OfferTypes.OFFER_TYPE_NONE
end
if not offer.coinType then
- offer.coinType = GameStore.CoinType.Coin
+ offer.coinType = GameStore.CoinType.Transferable
end
end
end
diff --git a/data/modules/scripts/gamestore/init.lua b/data/modules/scripts/gamestore/init.lua
index a927715cd93..ba7398d9d3e 100644
--- a/data/modules/scripts/gamestore/init.lua
+++ b/data/modules/scripts/gamestore/init.lua
@@ -206,6 +206,13 @@ GameStore.DefaultDescriptions = {
TEMPLE = { "Need a quick way home? Buy this transportation service to get instantly teleported to your home temple. \n\nNote, you cannot use this service while having a battle sign or a protection zone block. Further, the service will not work in no-logout zones or close to your home temple." },
}
+GameStore.ItemLimit = {
+ PREY_WILDCARD = 50,
+ INSTANT_REWARD_ACCESS = 90,
+ EXPBOOST = 6,
+ HIRELING = 10,
+}
+
--==Parsing==--
GameStore.isItsPacket = function(byte)
for k, v in pairs(GameStore.RecivedPackets) do
@@ -402,7 +409,7 @@ function parseBuyStoreOffer(playerId, msg)
-- All guarding conditions under which the offer should not be processed must be included here
if
- (table.contains(GameStore.OfferTypes, offer.type) == false) -- we've got an invalid offer type
+ not table.contains(GameStore.OfferTypes, offer.type) -- we've got an invalid offer type
or not player
or (player:getVocation():getId() == 0) and (not GameStore.haveOfferRook(id)) -- we don't have such offer
or not offer
@@ -472,7 +479,7 @@ function parseBuyStoreOffer(playerId, msg)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE then
GameStore.processSexChangePurchase(player)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_EXPBOOST then
- GameStore.processExpBoostPuchase(player)
+ GameStore.processExpBoostPurchase(player)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HUNTINGSLOT then
GameStore.processTaskHuntingThirdSlot(player)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYSLOT then
@@ -507,8 +514,8 @@ function parseBuyStoreOffer(playerId, msg)
if not pcallOk then
local alertMessage = pcallError.code and pcallError.message or "Something went wrong. Your purchase has been cancelled."
- if not pcallError.code then -- unhandled error
- -- log some debugging info
+ -- unhandled error
+ if not pcallError.code then
logger.warn("[parseBuyStoreOffer] - Purchase failed due to an unhandled script error. Stacktrace: {}", pcallError)
end
@@ -618,9 +625,8 @@ function sendOfferDescription(player, offerId, description)
end
function Player.canBuyOffer(self, offer)
- local playerId = self:getId()
local disabled, disabledReason = 0, ""
- if offer.disabled == true or not offer.type then
+ if offer.disabled or not offer.type then
disabled = 1
end
@@ -692,18 +698,17 @@ function Player.canBuyOffer(self, offer)
disabledReason = "The offer is fake."
end
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_MOUNT then
- local hasMount = self:hasMount(offer.id)
- if hasMount == true then
+ if self:hasMount(offer.id) then
disabled = 1
disabledReason = "You already have this mount."
end
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_INSTANT_REWARD_ACCESS then
- if self:getCollectionTokens() >= 90 then
+ if self:getCollectionTokens() >= GameStore.ItemLimit.INSTANT_REWARD_ACCESS then
disabled = 1
disabledReason = "You already have maximum of reward tokens."
end
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYBONUS then
- if self:getPreyCards() >= 50 then
+ if self:getPreyCards() >= GameStore.ItemLimit.PREY_WILDCARD then
disabled = 1
disabledReason = "You already have maximum of prey wildcards."
end
@@ -723,17 +728,16 @@ function Player.canBuyOffer(self, offer)
disabledReason = "You already have 3 slots released."
end
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_EXPBOOST then
- local remainingBoost = self:getExpBoostStamina()
- if self:getStorageValue(GameStore.Storages.expBoostCount) == 6 then
+ if self:getStorageValue(GameStore.Storages.expBoostCount) == GameStore.ItemLimit.EXPBOOST then
disabled = 1
disabledReason = "You can't buy XP Boost for today."
end
- if remainingBoost > 0 then
+ if self:getXpBoostTime() > 0 then
disabled = 1
disabledReason = "You already have an active XP boost."
end
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HIRELING then
- if self:getHirelingsCount() >= 10 then
+ if self:getHirelingsCount() >= GameStore.ItemLimit.HIRELING then
disabled = 1
disabledReason = "You already have bought the maximum number of allowed hirelings."
end
@@ -786,9 +790,9 @@ function Player.canReceiveStoreItems(self, offerId, offerCount)
local inboxItems = inbox:getItems(true)
local slotsOccupied = #inboxItems
local maxCapacity = inbox:getMaxCapacity()
- local slotsAvailable = maxCapacity - slotsOccupied
if slotsOccupied + slotsNeeded > maxCapacity then
+ local slotsAvailable = maxCapacity - slotsOccupied
return false, string.format("Not enough free slots in your store inbox. You need %d more slot(s). Currently occupied: %d/%d", slotsNeeded - slotsAvailable, slotsOccupied, maxCapacity)
end
@@ -1104,14 +1108,16 @@ function sendStoreTransactionHistory(playerId, page, entriesPerPage)
if not player then
return false
end
- local oldProtocol = player:getClient().version < 1200
- local totalEntries = GameStore.retrieveHistoryTotalPages(player:getAccountId())
- local totalPages = math.ceil(totalEntries / entriesPerPage)
+
local entries = GameStore.retrieveHistoryEntries(player:getAccountId(), page, entriesPerPage) -- this makes everything easy!
if #entries == 0 then
return addPlayerEvent(sendStoreError, 250, playerId, GameStore.StoreErrors.STORE_ERROR_HISTORY, "You don't have any entries yet.")
end
+ local oldProtocol = player:getClient().version < 1200
+ local totalEntries = GameStore.retrieveHistoryTotalPages(player:getAccountId())
+ local totalPages = math.ceil(totalEntries / entriesPerPage)
+
local msg = NetworkMessage()
msg:addByte(GameStore.SendingPackets.S_OpenTransactionHistory)
msg:addU32(totalPages > 0 and page - 1 or 0x0) -- current page
@@ -1165,10 +1171,8 @@ function sendStoreError(playerId, errorType, message)
local msg = NetworkMessage()
msg:addByte(GameStore.SendingPackets.S_StoreError)
-
msg:addByte(errorType)
msg:addString(message, "sendStoreError - message")
-
msg:sendToPlayer(player)
end
@@ -1183,7 +1187,7 @@ function sendStoreBalanceUpdating(playerId, updating)
msg:addByte(0x00)
msg:sendToPlayer(player)
- if updating == true then
+ if updating then
sendUpdatedStoreBalances(playerId)
end
end
@@ -1194,7 +1198,6 @@ function sendUpdatedStoreBalances(playerId)
return false
end
- local oldProtocol = player:getClient().version < 1200
local msg = NetworkMessage()
msg:addByte(GameStore.SendingPackets.S_CoinBalanceUpdating)
msg:addByte(0x01)
@@ -1205,6 +1208,8 @@ function sendUpdatedStoreBalances(playerId)
-- Send total of coins (transferable and normal coin)
msg:addU32(player:getTibiaCoins())
msg:addU32(player:getTransferableCoins()) -- How many are Transferable
+
+ local oldProtocol = player:getClient().version < 1200
if not oldProtocol then
-- How many are reserved for a Character Auction
-- We currently do not have this system implemented, so we will send 0
@@ -1346,7 +1351,7 @@ end
GameStore.retrieveHistoryTotalPages = function(accountId)
local resultId = db.storeQuery("SELECT count(id) as total FROM store_history WHERE account_id = " .. accountId)
- if resultId == false then
+ if not resultId then
return 0
end
@@ -1360,7 +1365,7 @@ GameStore.retrieveHistoryEntries = function(accountId, currentPage, entriesPerPa
local offset = currentPage > 1 and entriesPerPage * (currentPage - 1) or 0
local resultId = db.storeQuery("SELECT * FROM `store_history` WHERE `account_id` = " .. accountId .. " ORDER BY `time` DESC LIMIT " .. offset .. ", " .. entriesPerPage .. ";")
- if resultId ~= false then
+ if resultId then
repeat
local entry = {
mode = Result.getNumber(resultId, "mode"),
@@ -1566,8 +1571,9 @@ function GameStore.processAllBlessingsPurchase(player, count)
end
function GameStore.processInstantRewardAccess(player, offerCount)
- if player:getCollectionTokens() + offerCount >= 91 then
- return error({ code = 1, message = "You cannot own more than 90 reward tokens." })
+ local limit = GameStore.ItemLimit.INSTANT_REWARD_ACCESS
+ if player:getCollectionTokens() + offerCount >= limit + 1 then
+ return error({ code = 1, message = "You cannot own more than " .. limit .. " reward tokens." })
end
player:setCollectionTokens(player:getCollectionTokens() + offerCount)
end
@@ -1602,7 +1608,7 @@ function GameStore.processStackablePurchase(player, offerId, offerCount, offerNa
local countToAdd = math.min(remainingCount, stackSize)
local inboxItem = inbox:addItem(offerId, countToAdd)
if inboxItem then
- if movable ~= true then
+ if not movable then
inboxItem:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime())
end
else
@@ -1631,22 +1637,32 @@ function GameStore.processHouseRelatedPurchase(player, offer)
local inbox = player:getStoreInbox()
if inbox then
for _, itemId in ipairs(itemIds) do
- for i = 1, offer.count do
+ if isCaskItem(itemId) then
local decoKit = inbox:addItem(ITEM_DECORATION_KIT, 1)
if decoKit then
decoKit:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, "You bought this item in the Store.\nUnwrap it in your own house to create a <" .. ItemType(itemId):getName() .. ">.")
decoKit:setCustomAttribute("unWrapId", itemId)
- if isCaskItem(itemId) then
- decoKit:setAttribute(ITEM_ATTRIBUTE_DATE, offer.count)
- end
+ decoKit:setAttribute(ITEM_ATTRIBUTE_DATE, offer.count)
- if offer.movable ~= true then
+ if not offer.movable then
decoKit:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime())
end
end
+ else
+ for i = 1, offer.count do
+ local decoKit = inbox:addItem(ITEM_DECORATION_KIT, 1)
+ if decoKit then
+ decoKit:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, "You bought this item in the Store.\nUnwrap it in your own house to create a <" .. ItemType(itemId):getName() .. ">.")
+ decoKit:setCustomAttribute("unWrapId", itemId)
+
+ if not offer.movable then
+ decoKit:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime())
+ end
+ end
+ end
end
- player:sendUpdateContainer(inbox)
end
+ player:sendUpdateContainer(inbox)
end
end
@@ -1667,11 +1683,7 @@ function GameStore.processOutfitPurchase(player, offerSexIdTable, addon)
elseif player:hasOutfit(looktype, _addon) then
return error({ code = 0, message = "You already own this outfit." })
else
- if
- not (player:addOutfitAddon(looktype, _addon)) -- TFS call failed
- or (not player:hasOutfit(looktype, _addon)) -- Additional check; if the looktype doesn't match player sex for example,
- -- then the TFS check will still pass... bug? (TODO)
- then
+ if not player:addOutfitAddon(looktype, _addon) or not player:hasOutfit(looktype, _addon) then
error({ code = 0, message = "There has been an issue with your outfit purchase. Your purchase has been cancelled." })
else
player:addOutfitAddon(offerSexIdTable.male, _addon)
@@ -1689,8 +1701,6 @@ function GameStore.processMountPurchase(player, offerId)
end
function GameStore.processNameChangePurchase(player, offer, productType, newName)
- local playerId = player:getId()
-
if productType == GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE then
local tile = Tile(player:getPosition())
if tile then
@@ -1720,11 +1730,11 @@ function GameStore.processNameChangePurchase(player, offer, productType, newName
else
message = "Your character has been renamed successfully."
end
- addPlayerEvent(sendStorePurchaseSuccessful, 500, playerId, message)
+ addPlayerEvent(sendStorePurchaseSuccessful, 500, player:getId(), message)
player:changeName(newName)
else
- return addPlayerEvent(sendRequestPurchaseData, 250, playerId, offer.id, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE)
+ return addPlayerEvent(sendRequestPurchaseData, 250, player:getId(), offer.id, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE)
end
end
@@ -1732,12 +1742,12 @@ function GameStore.processSexChangePurchase(player)
player:toggleSex()
end
-function GameStore.processExpBoostPuchase(player)
- local currentExpBoostTime = player:getExpBoostStamina()
+function GameStore.processExpBoostPurchase(player)
+ local currentXpBoostTime = player:getXpBoostTime()
local expBoostCount = player:getStorageValue(GameStore.Storages.expBoostCount)
- player:setStoreXpBoost(50)
- player:setExpBoostStamina(currentExpBoostTime + 3600)
+ player:setXpBoostPercent(50)
+ player:setXpBoostTime(currentXpBoostTime + 3600)
if expBoostCount == -1 or expBoostCount == 6 then
expBoostCount = 1
@@ -1761,8 +1771,9 @@ function GameStore.processTaskHuntingThirdSlot(player)
end
function GameStore.processPreyBonusReroll(player, offerCount)
- if player:getPreyCards() + offerCount >= 51 then
- return error({ code = 1, message = "You cannot own more than 50 prey wildcards." })
+ local limit = GameStore.ItemLimit.PREY_WILDCARD
+ if player:getPreyCards() + offerCount >= limit + 1 then
+ return error({ code = 1, message = "You cannot own more than " .. limit .. " prey wildcards." })
end
player:addPreyCards(offerCount)
end
@@ -1780,9 +1791,6 @@ function GameStore.processTempleTeleportPurchase(player)
end
function GameStore.processHirelingPurchase(player, offer, productType, hirelingName, chosenSex)
- local playerId = player:getId()
- local offerId = offer.id
-
if player:getClient().version < 1200 then
return error({ code = 1, message = "You cannot buy hirelings on client 10, please relog on client 12 and try again." })
end
@@ -1804,22 +1812,58 @@ function GameStore.processHirelingPurchase(player, offer, productType, hirelingN
player:makeCoinTransaction(offer, hirelingName)
local message = "You have successfully bought " .. hirelingName
- return addPlayerEvent(sendStorePurchaseSuccessful, 650, playerId, message)
+ return addPlayerEvent(sendStorePurchaseSuccessful, 650, player:getId(), message)
-- If not, we ask him to do!
else
- if player:getHirelingsCount() >= 10 then
- return error({ code = 1, message = "You cannot have more than 10 hirelings." })
+ if player:getHirelingsCount() >= GameStore.ItemLimit.HIRELING then
+ return error({ code = 1, message = "You cannot have more than " .. GameStore.ItemLimit.HIRELING .. " hirelings." })
end
-- TODO: Use the correct dialog (byte 0xDB) on client 1205+
-- for compatibility, request name using the change name dialog
- return addPlayerEvent(sendRequestPurchaseData, 250, playerId, offerId, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_HIRELING)
+ return addPlayerEvent(sendRequestPurchaseData, 250, player:getId(), offer.id, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_HIRELING)
end
end
-function GameStore.processHirelingChangeNamePurchase(player, offer, productType, newHirelingName)
- local playerId = player:getId()
- local offerId = offer.id
+-- Hireling Helpers
+local function HandleHirelingNameChange(playerId, offer, newHirelingName)
+ local player = Player(playerId)
+ if not player then
+ return
+ end
+
+ local functionCallback = function(playerIdInFunction, data, hireling)
+ local playerInFunction = Player(playerIdInFunction)
+ if not playerInFunction then
+ return
+ end
+ if not hireling then
+ return playerInFunction:showInfoModal("Error", "Your must select a hireling.")
+ end
+
+ if hireling.active > 0 then
+ return playerInFunction:showInfoModal("Error", "Your hireling must be inside his/her lamp.")
+ end
+
+ local oldName = hireling.name
+ hireling.name = data.newHirelingName
+
+ if not playerInFunction:makeCoinTransaction(data.offer, oldName .. " to " .. hireling.name) then
+ return playerInFunction:showInfoModal("Error", "Transaction error")
+ end
+
+ local lamp = playerInFunction:findHirelingLamp(hireling:getId())
+ if lamp then
+ lamp:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, "This mysterious lamp summons your very own personal hireling.\nThis item cannot be traded.\nThis magic lamp is the home of " .. hireling:getName() .. ".")
+ end
+ logger.debug("{} has been renamed to {}", oldName, hireling.name)
+ sendUpdatedStoreBalances(playerIdInFunction)
+ end
+
+ player:sendHirelingSelectionModal("Choose a Hireling", "Select a hireling below", functionCallback, { offer = offer, newHirelingName = newHirelingName })
+end
+
+function GameStore.processHirelingChangeNamePurchase(player, offer, productType, newHirelingName)
if player:getClient().version < 1200 then
return error({
code = 1,
@@ -1838,17 +1882,60 @@ function GameStore.processHirelingChangeNamePurchase(player, offer, productType,
end)
local message = "Close the store window to select which hireling should be renamed to " .. newHirelingName
+ local playerId = player:getId()
addPlayerEvent(sendStorePurchaseSuccessful, 200, playerId, message)
-
addPlayerEvent(HandleHirelingNameChange, 550, playerId, offer, newHirelingName)
else
- return addPlayerEvent(sendRequestPurchaseData, 250, playerId, offerId, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE)
+ return addPlayerEvent(sendRequestPurchaseData, 250, player:getId(), offer.id, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE)
end
end
-function GameStore.processHirelingChangeSexPurchase(player, offer)
- local playerId = player:getId()
+local function HandleHirelingSexChange(playerId, offer)
+ local player = Player(playerId)
+ if not player then
+ return
+ end
+
+ local functionCallback = function(playerIdInFunction, data, hireling)
+ local playerInFunction = Player(playerIdInFunction)
+ if not playerInFunction then
+ return
+ end
+
+ if not hireling then
+ return playerInFunction:showInfoModal("Error", "Your must select a hireling.")
+ end
+
+ if hireling.active > 0 then
+ return playerInFunction:showInfoModal("Error", "Your hireling must be inside his/her lamp.")
+ end
+
+ if not playerInFunction:makeCoinTransaction(data.offer, hireling:getName()) then
+ return playerInFunction:showInfoModal("Error", "Transaction error")
+ end
+
+ local changeTo, sexString, lookType
+ if hireling.sex == HIRELING_SEX.FEMALE then
+ changeTo = HIRELING_SEX.MALE
+ sexString = "male"
+ lookType = HIRELING_OUTFIT_DEFAULT.male
+ else
+ changeTo = HIRELING_SEX.FEMALE
+ sexString = "female"
+ lookType = HIRELING_OUTFIT_DEFAULT.female
+ end
+
+ hireling.sex = changeTo
+ hireling.looktype = lookType
+
+ logger.debug("{} sex was changed to {}", hireling:getName(), sexString)
+ sendUpdatedStoreBalances(playerIdInFunction)
+ end
+
+ player:sendHirelingSelectionModal("Choose a Hireling", "Select a hireling below", functionCallback, { offer = offer })
+end
+function GameStore.processHirelingChangeSexPurchase(player, offer)
if player:getClient().version < 1200 then
return error({
code = 1,
@@ -1857,8 +1944,8 @@ function GameStore.processHirelingChangeSexPurchase(player, offer)
end
local message = "Close the store window to select which hireling should have the sex changed."
+ local playerId = player:getId()
addPlayerEvent(sendStorePurchaseSuccessful, 200, playerId, message)
-
addPlayerEvent(HandleHirelingSexChange, 550, playerId, offer)
end
@@ -2135,7 +2222,8 @@ function sendHomePage(playerId)
msg:sendToPlayer(player)
end
-function Player:openStore(serviceName) --exporting the method so other scripts can use to open store
+--exporting the method so other scripts can use to open store
+function Player:openStore(serviceName)
local playerId = self:getId()
openStore(playerId)
@@ -2154,75 +2242,3 @@ function Player:openStore(serviceName) --exporting the method so other scripts c
addPlayerEvent(sendShowStoreOffers, 50, playerId, category)
end
end
-
--- Hireling Helpers
-function HandleHirelingNameChange(playerId, offer, newHirelingName)
- local player = Player(playerId)
-
- local cb = function(playerId, data, hireling)
- local offer = data.offer
- local newHirelingName = data.newHirelingName
- local player = Player(playerId)
- if not hireling then
- return player:showInfoModal("Error", "Your must select a hireling.")
- end
-
- if hireling.active > 0 then
- return player:showInfoModal("Error", "Your hireling must be inside his/her lamp.")
- end
-
- local oldName = hireling.name
- hireling.name = newHirelingName
-
- if not player:makeCoinTransaction(data.offer, oldName .. " to " .. newHirelingName) then
- return player:showInfoModal("Error", "Transaction error")
- end
-
- local lamp = player:findHirelingLamp(hireling:getId())
- if lamp then
- lamp:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, "This mysterious lamp summons your very own personal hireling.\nThis item cannot be traded.\nThis magic lamp is the home of " .. hireling:getName() .. ".")
- end
- logger.debug("{} has been renamed to {}", oldName, newHirelingName)
- sendUpdatedStoreBalances(playerId)
- end
-
- player:sendHirelingSelectionModal("Choose a Hireling", "Select a hireling below", cb, { offer = offer, newHirelingName = newHirelingName })
-end
-
-function HandleHirelingSexChange(playerId, offer)
- local player = Player(playerId)
-
- local cb = function(playerId, data, hireling)
- local player = Player(playerId)
- if not hireling then
- return player:showInfoModal("Error", "Your must select a hireling.")
- end
-
- if hireling.active > 0 then
- return player:showInfoModal("Error", "Your hireling must be inside his/her lamp.")
- end
-
- if not player:makeCoinTransaction(data.offer, hireling:getName()) then
- return player:showInfoModal("Error", "Transaction error")
- end
-
- local changeTo, sexString, lookType
- if hireling.sex == HIRELING_SEX.FEMALE then
- changeTo = HIRELING_SEX.MALE
- sexString = "male"
- lookType = HIRELING_OUTFIT_DEFAULT.male
- else
- changeTo = HIRELING_SEX.FEMALE
- sexString = "female"
- lookType = HIRELING_OUTFIT_DEFAULT.female
- end
-
- hireling.sex = changeTo
- hireling.looktype = lookType
-
- logger.debug("{} sex was changed to {}", hireling:getName(), sexString)
- sendUpdatedStoreBalances(playerId)
- end
-
- player:sendHirelingSelectionModal("Choose a Hireling", "Select a hireling below", cb, { offer = offer })
-end
diff --git a/data/scripts/actions/items/blueberry_cupcake.lua b/data/scripts/actions/items/blueberry_cupcake.lua
new file mode 100644
index 00000000000..f645e852101
--- /dev/null
+++ b/data/scripts/actions/items/blueberry_cupcake.lua
@@ -0,0 +1,18 @@
+local blueberryCupcake = Action()
+
+function blueberryCupcake.onUse(player, item, fromPosition, target, toPosition, isHotkey)
+ if player:hasExhaustion("blueberry-cupcake-cooldown") then
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to wait before using it again.")
+ return true
+ end
+
+ player:addMana(player:getMaxMana())
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your mana has been refilled.")
+ player:say("Mmmm.", TALKTYPE_MONSTER_SAY)
+ player:setExhaustion("blueberry-cupcake-cooldown", 10 * 60)
+ item:remove(1)
+ return true
+end
+
+blueberryCupcake:id(28484)
+blueberryCupcake:register()
diff --git a/data/scripts/actions/items/exercise_training_weapons.lua b/data/scripts/actions/items/exercise_training_weapons.lua
index 4a72f19e5c8..3c62d7c1183 100644
--- a/data/scripts/actions/items/exercise_training_weapons.lua
+++ b/data/scripts/actions/items/exercise_training_weapons.lua
@@ -133,7 +133,7 @@ end
local exerciseTraining = Action()
function exerciseTraining.onUse(player, item, fromPosition, target, toPosition, isHotkey)
- if not target or not target:getId() then
+ if not target or type(target) == "table" or not target:getId() then
return true
end
diff --git a/data/scripts/actions/items/lemon_cupcake.lua b/data/scripts/actions/items/lemon_cupcake.lua
new file mode 100644
index 00000000000..3cf085d8ad8
--- /dev/null
+++ b/data/scripts/actions/items/lemon_cupcake.lua
@@ -0,0 +1,24 @@
+local distanceCondition = Condition(CONDITION_ATTRIBUTES)
+distanceCondition:setParameter(CONDITION_PARAM_BUFF_SPELL, 1)
+distanceCondition:setParameter(CONDITION_PARAM_TICKS, 60 * 60 * 1000)
+distanceCondition:setParameter(CONDITION_PARAM_SKILL_DISTANCE, 10)
+distanceCondition:setParameter(CONDITION_PARAM_FORCEUPDATE, true)
+
+local lemonCupcake = Action()
+
+function lemonCupcake.onUse(player, item, fromPosition, target, toPosition, isHotkey)
+ if player:hasExhaustion("lemon-cupcake-cooldown") then
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to wait before using it again.")
+ return true
+ end
+
+ player:addCondition(distanceCondition)
+ player:sendTextMessage(MESSAGE_FAILURE, "You feel more focused.")
+ player:say("Mmmm.", TALKTYPE_MONSTER_SAY)
+ player:setExhaustion("lemon-cupcake-cooldown", 10 * 60)
+ item:remove(1)
+ return true
+end
+
+lemonCupcake:id(28486)
+lemonCupcake:register()
diff --git a/data/scripts/actions/items/potions.lua b/data/scripts/actions/items/potions.lua
index 02e92349363..473796d79df 100644
--- a/data/scripts/actions/items/potions.lua
+++ b/data/scripts/actions/items/potions.lua
@@ -89,10 +89,13 @@ function flaskPotion.onUse(player, item, fromPosition, target, toPosition, isHot
local deactivatedFlasks = player:kv():get("talkaction.potions.flask") or false
if not deactivatedFlasks then
local container = Container(item:getParent().uid)
- local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
-
- if fromPosition.x == CONTAINER_POSITION and container ~= inbox and container:getEmptySlots() ~= 0 then
- container:addItem(potion.flask, 1)
+ if container then
+ local storeInbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
+ if fromPosition.x == CONTAINER_POSITION and container ~= storeInbox and container:getEmptySlots() ~= 0 then
+ container:addItem(potion.flask, 1)
+ else
+ player:addItem(potion.flask, 1)
+ end
else
Game.createItem(potion.flask, 1, fromPosition)
end
diff --git a/data/scripts/actions/items/strawberry_cupcake.lua b/data/scripts/actions/items/strawberry_cupcake.lua
new file mode 100644
index 00000000000..7ee7ec75764
--- /dev/null
+++ b/data/scripts/actions/items/strawberry_cupcake.lua
@@ -0,0 +1,18 @@
+local strawberryCupcake = Action()
+
+function strawberryCupcake.onUse(player, item, fromPosition, target, toPosition, isHotkey)
+ if player:hasExhaustion("strawberry-cupcake-cooldown") then
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to wait before using it again.")
+ return true
+ end
+
+ player:addHealth(player:getMaxHealth())
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your health has been refilled.")
+ player:say("Mmmm.", TALKTYPE_MONSTER_SAY)
+ player:setExhaustion("strawberry-cupcake-cooldown", 10 * 60)
+ item:remove(1)
+ return true
+end
+
+strawberryCupcake:id(28485)
+strawberryCupcake:register()
diff --git a/data/scripts/actions/items/wheel_scrolls.lua b/data/scripts/actions/items/wheel_scrolls.lua
index b42339a706e..61aaa6fe40a 100644
--- a/data/scripts/actions/items/wheel_scrolls.lua
+++ b/data/scripts/actions/items/wheel_scrolls.lua
@@ -1,9 +1,9 @@
local promotionScrolls = {
- [43946] = { storageName = "wheel.scroll.abridged", points = 3, name = "abridged promotion scroll" },
- [43947] = { storageName = "wheel.scroll.basic", points = 5, name = "basic promotion scroll" },
- [43948] = { storageName = "wheel.scroll.revised", points = 9, name = "revised promotion scroll" },
- [43949] = { storageName = "wheel.scroll.extended", points = 13, name = "extended promotion scroll" },
- [43950] = { storageName = "wheel.scroll.advanced", points = 20, name = "advanced promotion scroll" },
+ [43946] = { name = "abridged", points = 3, itemName = "abridged promotion scroll" },
+ [43947] = { name = "basic", points = 5, itemName = "basic promotion scroll" },
+ [43948] = { name = "revised", points = 9, itemName = "revised promotion scroll" },
+ [43949] = { name = "extended", points = 13, itemName = "extended promotion scroll" },
+ [43950] = { name = "advanced", points = 20, itemName = "advanced promotion scroll" },
}
local scroll = Action()
@@ -15,13 +15,14 @@ function scroll.onUse(player, item, fromPosition, target, toPosition, isHotkey)
end
local scrollData = promotionScrolls[item:getId()]
- if player:getStorageValueByName(scrollData.storageName) == 1 then
+ local scrollKV = player:kv():scoped("wheel-of-destiny"):scoped("scrolls")
+ if scrollKV:get(scrollData.name) then
player:sendTextMessage(MESSAGE_LOOK, "You have already deciphered this scroll.")
return true
end
- player:setStorageValueByName(scrollData.storageName, 1)
- player:sendTextMessage(MESSAGE_LOOK, "You have gained " .. scrollData.points .. " promotion points for the Wheel of Destiny by deciphering the " .. scrollData.name .. ".")
+ scrollKV:set(scrollData.name, true)
+ player:sendTextMessage(MESSAGE_LOOK, "You have gained " .. scrollData.points .. " promotion points for the Wheel of Destiny by deciphering the " .. scrollData.itemName .. ".")
item:remove(1)
return true
end
diff --git a/data/scripts/creaturescripts/player/offline_training.lua b/data/scripts/creaturescripts/player/offline_training.lua
index abfb0b94b3d..4466c6ee0e3 100644
--- a/data/scripts/creaturescripts/player/offline_training.lua
+++ b/data/scripts/creaturescripts/player/offline_training.lua
@@ -12,7 +12,7 @@ function offlineTraining.onLogin(player)
player:setOfflineTrainingSkill(SKILL_NONE)
if offlineTime < 600 then
- player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You must be logged out for more than 10 minutes to start offline training.")
+ player:sendTextMessage(MESSAGE_OFFLINE_TRAINING, "You must be logged out for more than 10 minutes to start offline training.")
return true
end
@@ -50,15 +50,15 @@ function offlineTraining.onLogin(player)
end
text = string.format("%s.", text)
- player:sendTextMessage(MESSAGE_EVENT_ADVANCE, text)
+ player:sendTextMessage(MESSAGE_OFFLINE_TRAINING, text)
local vocation = player:getVocation()
local promotion = vocation:getPromotion()
local topVocation = not promotion and vocation or promotion
-
local updateSkills = false
+
if table.contains({ SKILL_CLUB, SKILL_SWORD, SKILL_AXE, SKILL_DISTANCE }, offlineTrainingSkill) then
- local modifier = topVocation:getBaseAttackSpeed() / 1000
+ local modifier = topVocation:getBaseAttackSpeed() / 1000 / configManager.getFloat(configKeys.RATE_OFFLINE_TRAINING_SPEED)
updateSkills = player:addOfflineTrainingTries(offlineTrainingSkill, (trainingTime / modifier) / (offlineTrainingSkill == SKILL_DISTANCE and 4 or 2))
elseif offlineTrainingSkill == SKILL_MAGLEVEL then
local gainTicks = topVocation:getManaGainTicks() * 2
diff --git a/data/scripts/eventcallbacks/README.md b/data/scripts/eventcallbacks/README.md
index bdafcb41b33..601653574bd 100644
--- a/data/scripts/eventcallbacks/README.md
+++ b/data/scripts/eventcallbacks/README.md
@@ -47,6 +47,7 @@ Event callbacks are available for several categories of game entities, such as `
- `(void)` `playerOnCombat`
- `(void)` `playerOnInventoryUpdate`
- `(bool)` `playerOnRotateItem`
+- `(void)` `playerOnWalk`
- `(void)` `monsterOnDropLoot`
- `(void)` `monsterPostDropLoot`
- `(void)` `monsterOnSpawn`
diff --git a/data/scripts/lib/register_achievements.lua b/data/scripts/lib/register_achievements.lua
index 18a2d6e41ef..23d4bbd93ca 100644
--- a/data/scripts/lib/register_achievements.lua
+++ b/data/scripts/lib/register_achievements.lua
@@ -58,7 +58,7 @@ ACHIEVEMENTS = {
[57] = { name = "Annihilator", grade = 2, points = 5, description = "You've daringly jumped into the infamous Annihilator and survived - taking home fame, glory and your reward." },
[58] = { name = "Master of the Nexus", grade = 2, points = 6, description = "You were able to fight your way through the countless hordes in the Demon Forge. Once more you proved that nothing is impossible." },
[59] = { name = "Talented Dancer", grade = 1, points = 1, description = "You're a lord or lady of the dance - and not afraid to use your skills to impress tribal gods. One step to the left, one jump to the right, twist and shout!" },
- [60] = { name = "Ministrel", grade = 1, points = 2, secret = true, description = "You can handle any music instrument you're given - and actually manage to produce a pleasant sound with it. You're a welcome guest and entertainer in most taverns." },
+ [60] = { name = "Allow Cookies?", grade = 1, points = 2, description = "With a perfectly harmless smile, you tricked all the funny guys into eating your exploding cookies. Next time you pull this prank, consider wearing a Boy Scout outfit to make it even better." },
[61] = { name = "Ruthless", grade = 2, points = 5, description = "You've touched all thrones of the Ruthless Seven and absorbed some of their evil spirit. It may have changed you forever." },
[62] = { name = "Champion of Chazorai", grade = 2, points = 4, description = "You won the merciless 2 vs. 2 team tournament on the Isle of Strife and wiped out wave after wave of fearsome opponents. Death or victory - you certainly chose the latter." },
[63] = { name = "Wayfarer", grade = 1, points = 3, secret = true, description = "Dragon dreams are golden." },
@@ -193,7 +193,7 @@ ACHIEVEMENTS = {
[192] = { name = "I Like it Fancy", grade = 1, points = 1, secret = true, description = "You definitely know how to bring out the best in your furniture and decoration pieces. Beautiful." },
[193] = { name = "Skin-Deep", grade = 2, points = 4, secret = true, description = "You always carry your obsidian knife with you and won't hesitate to use it. You've skinned countless little - and bigger - critters and yeah: they usually don't get any more beautiful on the inside. It's rather blood and gore and all that..." },
[194] = { name = "Ashes to Dust", grade = 2, points = 4, secret = true, description = "Staking vampires and demons has almost turned into your profession. You make sure to gather even the tiniest amount of evil dust particles. Beware of silicosis." },
- [195] = { name = "Silent Pet", grade = 1, points = 1, secret = true, description = "Awww. Your very own little goldfish friend - he's cute, he's shiny and he can't complain should you forget to feed him. He'll definitely brighten up your day!" },
+ -- [195] = Unknown/non-existent
[196] = { name = "Safely Stored Away", grade = 1, points = 2, secret = true, description = "Don't worry, no one will be able to take it from you. Probably." },
[197] = { name = "Something's in There", grade = 1, points = 1, secret = true, description = "By the gods! What was that?" },
[198] = { name = "Silent Pet", grade = 1, points = 1, secret = true, description = "Awww. Your very own little goldfish friend - he's cute, he's shiny and he can't complain should you forget to feed him. He'll definitely brighten up your day!" },
@@ -218,7 +218,7 @@ ACHIEVEMENTS = {
[217] = { name = "Doctor! Doctor!", grade = 1, points = 2, secret = true, description = "Did someone call a doctor? You delivered 100 medicine bags to Ottokar of the Venore poor house in times of dire need, well done!" },
[218] = { name = "Beak Doctor", grade = 2, points = 4, description = "You significantly helped the afflicted citizens of Venore in times of dire need. Somehow you still feel close to the victims of the fever outbreak. Your clothes make you one of them, one poor soul amongst the countless afflicted." },
[219] = { name = "Mystic Fabric Magic", grade = 2, points = 4, description = "You vanquished the mad mage, you subdued the raging mage - no spellweaving self-exposer can stand in your way. Yet you are quite absorbed in magical studies yourself. This very fabric reflects this personal approval of the magic arts." },
- [220] = { name = "Breaking the Ice", grade = 1, points = 1, description = "You almost made friends with Shardhead... before he died. Poor guy only seems to attract violence with his frosty attitude." },
+ -- [220] = Unknown/non-existent
[221] = { name = "Arachnoise", grade = 1, points = 1, description = "You've shattered each of Bloodweb's eight frozen legs. As they say: break a leg, and then some more." },
[222] = { name = "Rootless Behaviour", grade = 1, points = 1, description = "You've descended into the swampy depths of Deathbine's lair and made quick work of it." },
[223] = { name = "Twisted Mutation", grade = 1, points = 1, description = "You've slain Esmeralda, the most hideous and aggressive of the mutated rats. No one will know that you almost lost a finger in the process." },
@@ -248,7 +248,7 @@ ACHIEVEMENTS = {
[247] = { name = "Torn Treasures", grade = 1, points = 1, secret = true, description = "Wyda seems to be really, really bored. You also found out that she doesn't really need all those blood herbs that adventurers brought her. Still, she was nice enough to take one from you and gave you something quite cool in exchange." },
[248] = { name = "Loyal Subject", grade = 1, points = 1, secret = true, description = "You joined the Kingsday festivities and payed King Tibianus your respects. Now, off to party!" },
[249] = { name = "Desert Fisher", grade = 1, points = 1, description = "You managed to catch a fish in a surrounding that usually doesn't even carry water. Everything is subject to change, probably..." },
- [250] = { name = "Gem Cutter", grade = 1, points = 1, secret = true, description = 'You cut your first gem - and it bears your own name! Now that would be a nice gift! This does not make it a "true" Heart of the Sea, however...' },
+ -- [250] = Unknown/non-existent
[251] = { name = "Dog Sitter", grade = 1, points = 1, description = "You showed Noodles the way home. How long will it take this time until he's on the loose again? That dog must be really bored in the throne room by now." },
[252] = { name = "Ice Harvester", grade = 1, points = 1, description = "You witnessed the thawing of Svargrond and harvested rare seeds from some strange icy plants. They must be good for something." },
[253] = { name = "Preservationist", grade = 1, points = 1, secret = true, description = "You are a pretty smart thinker and managed to create everlasting flowers. They might become a big hit with all the people who aren't blessed with a green thumb or just forgetful." },
@@ -273,7 +273,7 @@ ACHIEVEMENTS = {
[272] = { name = "Headache", grade = 1, points = 2, description = "Even in the deepest structures of the hive, you began to strike against the mighty foe. Your actions probably already gave the hive a headache." },
[273] = { name = "Confusion", grade = 1, points = 3, description = "The destruction you have caused by now can be felt throughout the whole hive. The mayhem that follows your step caused significant confusion in the consciousness of the hive." },
[274] = { name = "Manic", grade = 2, points = 4, description = "You have destroyed a significant amount of the hive's vital nerve centers and caused massive destruction to the hive's awareness. You are probably causing the hive horrible nightmares." },
- [275] = { name = "Suppressor", grade = 2, points = 4, description = "A war is won by those who have the best supply of troops. The hive's troops have been dealt a significant blow by your actions. You interrupted the hive's replenishment of troops lastingly and severely." },
+ -- [275] = Unknown/non-existent
[276] = { name = "Navigational Error", grade = 2, points = 5, secret = true, description = "You confronted the Navigator." },
[277] = { name = "Si, Ariki!", grade = 1, points = 1, description = "You've found the oriental traveller Yasir and were able to trade with him - even if you didn't really understand his language." },
[278] = { name = "Guardian Downfall", grade = 2, points = 4, description = "You ended the life of over three hundred Deepling Guards. Not quite the guardian of the Deeplings, are you?" },
@@ -282,7 +282,7 @@ ACHIEVEMENTS = {
[281] = { name = "Gem Cutter", grade = 1, points = 1, secret = true, description = 'You cut your first gem - and it bears your own name! Now that would be a nice gift! This does not make it a "true" Heart of the Sea, however...' },
[282] = { name = "Spolium Profundis", grade = 2, points = 4, description = "You travelled the depths of this very world. You entered the blackness of the deep sea to conquer the realm of the Deeplings. May this suit remind you of the strange beauty below." },
[283] = { name = "Bane of the Hive", grade = 1, points = 2, description = "Countless fights and never tiring effort in the war against the hive grant you the experience to finish your outfit with the last remaining part. Your chitin outfit is a testament of your skills and dedication for the cause." },
- [284] = { name = "King of the Ring", grade = 1, points = 2, description = "Bretzecutioner's body just got slammed away. You are a true king of the ring!" },
+ -- [284] = Unknown/non-existent
[285] = { name = "Hive War Veteran", grade = 1, points = 1, description = "Your invaluable experience in fighting the hive allows you to add another piece of armor to your chitin outfit to prove your dedication for the cause." },
[286] = { name = "Hive Fighter", grade = 1, points = 1, description = "You have participated that much in the hive war, that you are able to create some makeshift armor from the remains of dead hive born that can be found in the major hive, to show of your skill." },
[287] = { name = "Howly Silence", grade = 1, points = 1, description = "You muted the everlasting howling of Hemming." },
@@ -294,7 +294,7 @@ ACHIEVEMENTS = {
[293] = { name = "King of the Ring", grade = 1, points = 2, description = "Bretzecutioner's body just got slammed away. You are a true king of the ring!" },
[294] = { name = "Back from the Dead", grade = 1, points = 2, description = "You overcame the undead Zanakeph and sent him back into the darkness that spawned him." },
[295] = { name = "Pwned All Fur", grade = 3, points = 8, secret = true, description = "You've faced and defeated each of the mighty bosses the Paw and Fur society sent you out to kill. All by yourself. What a hunt!" },
- [296] = { name = "Diplomatic Immunity", grade = 2, points = 4, secret = true, description = "You killed the ambassador of the abyss that often that they might consider sending another one. Perhaps that will one day stop further intrusions." },
+ -- [296] = Unknown/non-existent
[297] = { name = "Bibby's Bloodbath", grade = 1, points = 1, secret = true, description = "You lend a helping hand in defeating invading Orcs by destroying their warcamp along with their leader. Bibby's personal bloodbath..." },
[298] = { name = "Nestling", grade = 1, points = 1, description = "You cleansed the land from an eight legged nuisance by defeating Mamma Longlegs three times. She won't be back soon... or will she?" },
[299] = { name = "Becoming a Bigfoot", grade = 1, points = 1, description = "You did it! You convinced the reclusive gnomes to accept you as one of their Bigfoots. Now you are ready to help them. With big feet big missions seen to come." },
@@ -321,14 +321,14 @@ ACHIEVEMENTS = {
[320] = { name = "Funghitastic", grade = 1, points = 3, description = "Finally your dream to become a walking mushroom has come true ... No, wait a minute!" },
[321] = { name = "Crystal Clear", grade = 1, points = 3, description = "If the gnomes had told you that crystal armor is see-through you had probably changed your underwear in time." },
[322] = { name = "Gnomish Art Of War", grade = 1, points = 3, description = "You have unleashed your inner gnome and slain some of the most fearsome threats that gnomekind has ever faced. Now you can come and go to the warzones as it pleases you. The enemies of gnomekind will never be safe again." },
- [323] = { name = "Never Surrender", grade = 1, points = 3, description = "You did not show any signs of surrender to any sight of... you get the picture. Even a hundred of them did not pose a threat to you." },
+ -- [323] = Unknown/non-existent
[324] = { name = "True Dedication", grade = 2, points = 5, secret = true, description = "You conquered the demon challenge and prevailed... now show off your success in style!" },
[325] = { name = "Task Manager", grade = 1, points = 2, secret = true, description = "Helping a poor, stupid goblin to feed his starving children and wifes feels good ... if you'd only get rid of the strange feeling that you're missing something." },
[326] = { name = "Gravedigger", grade = 1, points = 3, description = "Assisting Omrabas' sick plan to resurrect made you dig your way through the blood-soaked halls of Drefia. Maybe better he failed!" },
[327] = { name = "Repenter", grade = 1, points = 1, secret = true, description = "You cleansed your soul in serving the Repenter enclave and purified thine self in completing all tasks in a single day of labour." },
[328] = { name = "Umbral Swordsman", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your blade into a master state and have proven yourself worthy in a nightmarish world." },
- [329] = { name = "Umbral Berserker", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your hammer into a master state and have proven yourself worthy in a nightmarish world." },
- [330] = { name = "Umbral Bladelord", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your slayer into a master state and have proven yourself worthy in a nightmarish world." },
+ -- [329] = Unknown/non-existent
+ -- [330] = Unknown/non-existent
[331] = { name = "Cave Completionist", grade = 1, points = 2, description = "You have helped the gnomes of the spike in securing the caves and explored enough of the lightles depths to earn you a complete cave explorers outfit. Well done!" },
[332] = { name = "Umbral Bladelord", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your slayer into a master state and have proven yourself worthy in a nightmarish world." },
[333] = { name = "Umbral Headsman", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your axe into a master state and have proven yourself worthy in a nightmarish world." },
@@ -367,7 +367,7 @@ ACHIEVEMENTS = {
[366] = { name = "Publicity", grade = 1, points = 1, description = "You are a man of the public. Or of good publicity at least. Through your efforts in advertising the airtight cloth, Zeronex might yet be redeemed - and Rathleton might yet see its first working Gloud Ship." },
[367] = { name = "Snake Charmer", grade = 1, points = 1, description = "By restoring the Everhungry Altar, you charmed the Fire-Feathered Sea Serpent back into its fitful sleep, twenty miles beneath the sea." },
[368] = { name = "Hoard of the Dragon", grade = 1, points = 1, secret = true, description = "Your adventurous way through countless dragon lairs earned you a pretty treasure - and surely the enmity of many a dragon." },
- [369] = { name = "Icy Glare", grade = 1, points = 1, description = "Here's looking at you, kid. This ancient creature seems to size you up with its brilliant eyes and barely tolerates you riding it. Maybe it thinks you're the defrosted snack, after all?" },
+ -- [369] = Unknown/non-existent
[370] = { name = "Little Ball of Wool", grade = 1, points = 1, description = "You found a lost sheep and thus a steady source of black wool. But careful: don't get entangled." },
[371] = { name = "Luminous Kitty", grade = 1, points = 3, description = "You made some efforts to bring a little more light into the world. And what a nice present you got in return!" },
[372] = { name = "The Right Tone", grade = 1, points = 1, description = "By setting the right tone you convinced a crystal wolf to accompany you. Remember it is made of crystal, though, so be careful in a banshee's presence." },
@@ -404,10 +404,10 @@ ACHIEVEMENTS = {
[403] = { name = "Icy Glare", grade = 1, points = 1, description = "Here's looking at you, kid. This ancient creature seems to size you up with its brilliant eyes and barely tolerates you riding it. Maybe it thinks you're the defrosted snack, after all?" },
[404] = { name = "Cartography 101", grade = 1, points = 2, description = "You succeeded in finding and charting several previously unexplored landmarks and locations for the Adventurer's Guild, you probably never need to ask anyone for the way - do you?" },
[405] = { name = "Lost Palace Raider", grade = 1, points = 2, secret = true, description = "Lifting the secrets of a fabulous palace and defeating a beautiful demon princess was a thrilling experience indeed. This site's marvels nearly matched its terrors. Nearly." },
- [406] = { name = "The More the Merrier", grade = 0, points = 0, secret = true, description = "It's dangerous to go alone... Take ten friends." },
- [407] = { name = "Contender", grade = 1, points = 3, description = "You have fully unlocked 10 medium monsters in the cyclopedia." },
+ [406] = { name = "The More the Merrier", grade = 1, points = 0, secret = true, description = "It's dangerous to go alone... Take ten friends." },
+ -- [407] = Unknown/non-existent
[408] = { name = "Rift Warrior", grade = 1, points = 3, description = "You went through hell. Seven times. You defeated the demons. Countless times. You put an end to Ferumbras claims to ascendancy. Once and for all." },
- [409] = { name = "Duked It Out", grade = 1, points = 1, description = "You defeated the Duke of the Depths and destroyed his lava pump!" },
+ -- [409] = Unknown/non-existent
[410] = { name = "Hat Hunter", grade = 2, points = 5, description = "You sucessfully fought against all odds to protect your world from an ascending god! – You weren't there for the hat only after all?" },
[411] = { name = "Ogre Chef", grade = 1, points = 1, description = "You didn't manage to become an ogre chief. But at least you are, beyond doubt, a worthy ogre chef." },
[412] = { name = "The Call of the Wild", grade = 1, points = 2, description = "You opposed man-eating ogres and clumsy clomps. You grappled with hungry chieftains, desperate goblins and angry spirits. So you truly overcame the wild vastness of Krailos." },
@@ -421,13 +421,13 @@ ACHIEVEMENTS = {
[420] = { name = "Toothfairy Assistant", grade = 1, points = 1, description = "You assisted a very prominent fae and you fought tooth and nail to earn this title." },
[421] = { name = "Fairy Teasing", grade = 1, points = 1, secret = true, description = "Teasing fairies is fun. They leave behind such pretty clouds of glittering dust when chased. Just hope they don't get you back for it." },
[422] = { name = "Corruption Contained", grade = 2, points = 5, description = "You have managed to stall the worst incursion of corruption. Still this is just one battle won in an all out war for your world." },
- [423] = { name = "Daraman's Footsteps", grade = 1, points = 1, description = "You journeyed through Darashia and the sea of sand around it, while fighting the perils of the desert." },
- [424] = { name = "Dwarven Mines", grade = 1, points = 1, description = "Vast mines, an orc fortress and the magnificence of Kazordoon - you really know every corner of North-Eastern Mainland now." },
- [425] = { name = "Elven Woods", grade = 1, points = 1, description = "Tall trees, deep forests and and the beauty of Ab'Dendriel - you really know every corner of the elven lands now." },
- [426] = { name = "Glooth Punk", grade = 1, points = 1, description = "Glooth is the substance that powers a whole continent and all its weird inhabitants, workshops and factories. You travelled this strange smorgasbord of curiosities in its entirety - just in time for tea." },
- [427] = { name = "High and Dry", grade = 1, points = 2, description = "You asked Captain Charles to take a shortcut quite a few times. Now you are all too familiar with desert islands all over Tibia." },
- [428] = { name = "Jewel in the Swamp", grade = 1, points = 1, description = "Damp swamps, a dry desert and the opulence of Venore - you really know every corner of Eastern Mainland now." },
- [429] = { name = "King of the Jungle", grade = 1, points = 1, description = "You have searched Port Hope and the jungle that thoroughly, that you are up to adoption by a friendly ape family." },
+ -- [423] = Unknown/non-existent
+ -- [424] = Unknown/non-existent
+ -- [425] = Unknown/non-existent
+ -- [426] = Unknown/non-existent
+ -- [427] = Unknown/non-existent
+ -- [428] = Unknown/non-existent
+ -- [429] = Unknown/non-existent
[430] = { name = "Little Adventure", grade = 1, points = 1, description = "You have fully unlocked 10 easy monsters in the cyclopedia." },
[431] = { name = "Little Big Adventure", grade = 1, points = 2, secret = true, description = "You have fully unlocked 100 easy monsters in the cyclopedia." },
[432] = { name = "Contender", grade = 1, points = 3, description = "You have fully unlocked 10 medium monsters in the cyclopedia." },
@@ -470,7 +470,7 @@ ACHIEVEMENTS = {
[469] = { name = "Battle Mage", grade = 2, points = 6, description = "Wielding dangerous knowledge as well as the sword is your expertise. You have proven yourself versatile in all manner of situations." },
[470] = { name = "Widely Travelled", grade = 3, points = 7, description = "As a true globetrotter you can now show your colours proudly with this extraordinary outfit." },
[471] = { name = "Running the Rift", grade = 1, points = 3, description = "You don't just have a permission to ride a rift runner, you literally went through hell and earned it!" },
- [472] = { name = "Nothing but Hot Air", grade = 1, points = 2, description = "You have tamed the ghostly mists to do your bidding. For now ..." },
+ -- [472] = Unknown/non-existent
[473] = { name = "Exalted Battle Mage", grade = 1, points = 2, description = "Not only did you master the battlefield as a mage, you were also induced to the most inner secrets of the art of magical warfare and prevailed." },
[474] = { name = "Areas of Effect", grade = 1, points = 3, secret = true, description = "Wisely contributing your resources to areas, you pushed creatures to maximum effect, allowing improved respawn for everyone! Well done!" },
[475] = { name = "Tied the Knot", grade = 1, points = 1, secret = true, description = "You figured out the right order of spells in the buried cathedral, how enchanting!" },
@@ -483,7 +483,7 @@ ACHIEVEMENTS = {
[482] = { name = "Dream Catcher", grade = 1, points = 3, description = "You are the slayer of the ancient nightmare beast and prevented the nightmare to spread its madness." },
[483] = { name = "Champion of Summer", grade = 1, points = 2, secret = true, description = "You have vanquished numerous arena champions in the name of the Summer Court." },
[484] = { name = "Champion of Winter", grade = 1, points = 2, secret = true, description = "You have vanquished numerous arena champions in the name of the Winter Court." },
- [485] = { name = "Allow Cookies?", grade = 2, points = 6, description = "With a perfectly harmless smile, you tricked all the funny guys into eating your exploding cookies. Next time you pull this prank, consider wearing a Boy Scout outfit to make it even better." },
+ -- [485] = Unknown/non-existent
[486] = { name = "Bewitcher", grade = 2, points = 5, secret = true, description = "You literally put everything in that cauldron except lilac and gooseberries." },
[487] = { name = "Gryphon Rider", grade = 1, points = 3, description = "Unmasking spies, killing demons, discovering omens, solving puzzles and fighting ogres, manticores and feral sphinxes. - Nobody said it was easy to become a gryphon rider." },
[488] = { name = "Sculptor Apprentice", grade = 1, points = 2, secret = true, description = "Granted, you didn't carve those lifelike animal figurines yourself. But helping a medusa to find proper objects and even watching her using her petrifying gaze is almost as rewarding." },
@@ -496,6 +496,7 @@ ACHIEVEMENTS = {
[495] = { name = "Inquisition's Arm", grade = 1, points = 2, description = "Your special garb, solely awarded to a dedicated Hand of the Inquisition, is now complete." },
[496] = { name = "Traditionalist", grade = 2, points = 6, description = "You proudly wear the traditional Orcsoberfest garb, same as it ever was and as it always will be." },
[497] = { name = "Do a Barrel Roll!", grade = 1, points = 3, description = "Riding a traditional beer barrel from the Orcsoberfest is a once-in-a-lifetime experience. Beer sold separately." },
+ -- [498] = Unknown/non-existent
[499] = { name = "Orcsoberfest Welcome", grade = 1, points = 3, secret = true, description = 'The Orcsoberfest is not only known for its traditional food, beer and customs but also fun events and excitement! You took part in all of that and can now truly say: "I survived!"' },
[500] = { name = "Prospectre", grade = 1, points = 1, secret = true, description = "You made acquaintance with the Thaian. A strange contemporary with a dark history. No man but a derivate of greed and obsession." },
[501] = { name = "Nothing but Hot Air", grade = 1, points = 3, description = "You have tamed the ghostly mists to do your bidding. For now ..." },
@@ -506,19 +507,24 @@ ACHIEVEMENTS = {
[506] = { name = "Falconer", grade = 1, points = 2, description = "A true beastmaster learns the language of his animal companions. Now you as well can bolster your unique bond with nature and help preserve the balance of life as a proud falconer." },
[507] = { name = "Steppe Elegance", grade = 1, points = 3, description = "Champion of the wildlands, a swift strider among the creatures of the wild. The elegant nature of the gallop, this envoy of speed has mastered, indicates the precise understanding of its terrain and environment." },
[508] = { name = "Beyonder", grade = 1, points = 3, description = "Adventurous beyond death, you travelled the Netherworld. Although you had just the ghost of a chance you survived and even came back from the realm of the dead." },
+ -- [509] = Unknown/non-existent
[510] = { name = "Drama in Darama", grade = 1, points = 3, description = "If a pride of lions and a pack of hyaenas feud, it is not called a catfight but a ... whatsoever. For sure, it caused a lot of drama in the Darama Desert." },
[511] = { name = "Malefitz", grade = 1, points = 1, secret = true, description = "Made acquaintance with three brothers Fitz." },
[512] = { name = "Lionheart", grade = 1, points = 3, description = "You bested the maleficent duo Drume and Fugue and restored order to the besieged town of Bounac. You conquered the exotic stronghold of the Order of the Cobra and bested the undead knights of the Order of the Falcon. A true knight in heart and mind." },
+ [513] = { name = "Soul Mender", grade = 4, points = 10, description = "Brought back to the realm of the living this magnificent creature will carry you through death and everything that lays beyond." },
[514] = { name = "You Got Horse Power", grade = 3, points = 8, description = "Brought back to the realm of the living this magnificent creature will carry you through death and everything that lays beyond." },
[515] = { name = "Unleash the Beast", grade = 3, points = 8, description = "You defeated the manifestation of Goshnar's evil traits by fighting your way through beasts you didn't even want to imagine. It transformed you and now you can also look the part." },
[516] = { name = "Well Roared, Lion!", grade = 1, points = 1, description = "You helped Domizian and thus proved yourself worthy to enter the werelion sanctum underneath Lion's Rock. You faced the mighty werelions there and one of the rare white lions even chose to accompany you." },
+ -- [517] = Unknown/non-existent
[518] = { name = "Honorary Rascoohan", grade = 1, points = 2, description = "When in Rascacoon, do as the Rascoohans do!" },
[519] = { name = "Release the Kraken", grade = 1, points = 3, description = "Riding around on this squishy companion gives you the feeling of flying through the air... uhm... swimming through the seven seas!" },
+ -- [520] = Unknown/non-existent
[521] = { name = "Pied Piper", grade = 1, points = 3, secret = true, description = "You are not exactly the Pied Piper of Hamelin but at least you managed to fend off a decent amount of pirats and helped to keep them out of the cities." },
[522] = { name = "Woodcarver", grade = 1, points = 3, secret = true, description = "You defeated Megasylvan Yselda in the wake of the sleeping carnisylvan menace deep under Bounac." },
[523] = { name = "Bounacean Chivalry", grade = 1, points = 2, secret = true, description = "Yselda forever stands watch against the carnisylvan menace. Ever awake, waiting in the dark, her heart longs to be united with her king once again. Deep empathy let a hero to bring her Kesar's tulip as a token of his love. That hero was you." },
[524] = { name = "Knowledge Raider", grade = 1, points = 3, description = "Your thirst for knowledge is insatiable. In the task of helping your gnomish friends, flawless execution is just the icing on the cake." },
[525] = { name = "Citizen of Issavi", grade = 1, points = 2, description = "It was not the first time that you helped the Sapphire Blade or the Midnight Flame with a difficult task. You may now wear the Kilmareshian robes as well as the tagralt blade and the eye-embroidered veil of the seers as a sign of Issavi's gratitude." },
+ [526] = { name = "King's Council", grade = 1, points = 0, description = "Your continued efforts in keeping Bounac and the people of Kesar the Younger safe, earned you a permanent place at the royal court as an advisor to the king." },
[527] = { name = "Hot on the Trail", grade = 1, points = 3, description = "Since it is fireproof, this flaming creature feels right at home in raging infernos. But remember: just because it doesn't burn, you still do!" },
[528] = { name = "Shell We Take a Ride", grade = 1, points = 3, description = "Equipped with the shell of a tortoise and claws of a lobster this insect like companion will help you through every hardship." },
[529] = { name = "Phantastic!", grade = 1, points = 3, description = "This mighty pachyderm will march into battle as if just taking its Sunday stroll. The cost of friendship was only a few drome points!" },
@@ -526,7 +532,22 @@ ACHIEVEMENTS = {
[531] = { name = "First Achievement", grade = 1, points = 1, secret = true, description = "Congratulations to your very first achievement! ... Well, not really. But imagine, it is. Because at this point during your journey into Tibia's past, achievements have been introduced." },
[532] = { name = "Sharp Dressed", grade = 1, points = 2, description = "Just everyone will be crazy about you if you are wearing this formal dress. They will come running, promise!" },
[533] = { name = "Engine Driver", grade = 1, points = 3, description = "This glooth-driven locomotive will bring you to any party in the blink of an eye." },
+ [534] = { name = "Friendly Fire", grade = 1, points = 2, description = "You mastered the fire and tamed a supervulcano!" },
+ [535] = { name = "Wedding Planner", grade = 1, points = 3, description = "Alas! What could be more beautiful and satisfying than bringing two loving hearts together? So romantic!" },
+ [536] = { name = "Beaver Away", grade = 1, points = 1, description = "You really were as busy as a beaver in order to help the nagas. Enjoy some eager company!" },
+ [537] = { name = "Snake Pit", grade = 1, points = 1, description = "Mysterious nagas, a vibrant jungle and a sinking island - you really know every corner of Marapur now." },
+ [538] = { name = "Royalty of Hazard", grade = 1, points = 1, description = "For some it can't be hazardous enough." },
+ [539] = { name = "Measuring the World", grade = 1, points = 2, description = "Step by step you discovered many of the secrets hidden in the world, thus gaining the right to wear the Discoverer outfit and hat. Made-to-measure for a brave traveller of the Tibian wilds." },
[540] = { name = "Ripp-Ripp Hooray!", grade = 1, points = 3, description = "Don't get carried away by your success. Get carried away by your Ripptor." },
+ [541] = { name = "Warrior of the Iks", grade = 1, points = 2, description = "Combining unabating courage in combat and respect for the traditions and culture of the ancient Iks earned you the honours of true Aucar." },
+ [542] = { name = "Mutagenius", grade = 1, points = 2, description = "You accomplished the impossible and created 16 mutagens of corresponding colours." },
+ [543] = { name = "Strangest Thing", grade = 1, points = 3, description = "Only its rider can love this abomination of a mount." },
+ [544] = { name = "Fully Decayed", grade = 1, points = 2, description = "You defeated the embodiments of decay and live to tell the tale, wear the rotting attire of the unfaltering defender proudly." },
+ [545] = { name = "Like Fox and Mouse", grade = 1, points = 3, description = "Sly as a fox, quiet as a mouse - the perfect mount for a stealthy foray." },
+ [546] = { name = "The Spirit of Purity", grade = 1, points = 3, description = "Withstanding both filth and desolation of the rotten darkness that corrupted the very core of this world, you embodied the weapon of purity and light to defy all that was tainted. This spirit will continue guide you on all future paths." },
+ [547] = { name = "Museum Goer", grade = 1, points = 2, description = "You unveiled the secret plot of the Mitmah who stole away an entire civilisation for their own entertainment. Let the death of their outpost vanguard be an eternal lesson to them." },
+ [548] = { name = "Mystic Predator", grade = 1, points = 3, description = "Proving your true worth to a mystic creature like the jaguar, king of the hunt, granted you not only respect but also its heart." },
+ [549] = { name = "The Rule of Raccool", grade = 1, points = 2, description = "You almost feel as cool as a raccoon. Now, where's the trash?" },
}
--[[
diff --git a/data/scripts/lib/register_migrations.lua b/data/scripts/lib/register_migrations.lua
index 5a2734dfc51..26b9a7b1a94 100644
--- a/data/scripts/lib/register_migrations.lua
+++ b/data/scripts/lib/register_migrations.lua
@@ -45,7 +45,7 @@ function Migration:register()
return
end
if not self:_validateName() then
- error("Invalid migration name: " .. self.name .. ". Migration names must be in the format: _. Example: 20231128213149_add_new_monsters")
+ logger.error("Invalid migration name: " .. self.name .. ". Migration names must be in the format: _. Example: 20231128213149_add_new_monsters")
end
table.insert(Migration.registry, self)
diff --git a/data/scripts/runes/destroy_field_rune.lua b/data/scripts/runes/destroy_field_rune.lua
index 024dbc1bcd7..8752be7eb51 100644
--- a/data/scripts/runes/destroy_field_rune.lua
+++ b/data/scripts/runes/destroy_field_rune.lua
@@ -4,6 +4,13 @@ local fields = { 105, 2118, 2119, 2120, 2121, 2122, 2123, 2124, 2125, 2126, 2132
local rune = Spell("rune")
function rune.onCastSpell(creature, variant, isHotkey)
+ local inPz = creature:getTile():hasFlag(TILESTATE_PROTECTIONZONE)
+ if inPz then
+ creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
+ creature:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+
local position = Variant.getPosition(variant)
local tile = Tile(position)
local field = tile and tile:getItemByType(ITEM_TYPE_MAGICFIELD)
diff --git a/data/scripts/spells/conjuring/conjure_diamond_arrow.lua b/data/scripts/spells/conjuring/conjure_diamond_arrow.lua
deleted file mode 100644
index 2ab4bbe0647..00000000000
--- a/data/scripts/spells/conjuring/conjure_diamond_arrow.lua
+++ /dev/null
@@ -1,21 +0,0 @@
-local spell = Spell("instant")
-
-function spell.onCastSpell(creature, variant)
- return creature:conjureItem(0, 25757, 100, CONST_ME_MAGIC_BLUE)
-end
-
-spell:group("support")
-spell:id(192)
-spell:name("Conjure Diamond Arrow")
-spell:words("exevo gran con hur")
-spell:cooldown(2 * 1000)
-spell:groupCooldown(2 * 1000)
-spell:level(150)
-spell:mana(1000)
-spell:soul(0)
-spell:isPremium(true)
-spell:isSelfTarget(true)
-spell:isAggressive(false)
-spell:vocation("paladin;true", "royal paladin;true")
-spell:needLearn(false)
-spell:register()
diff --git a/data/scripts/spells/conjuring/conjure_spectral_bolt.lua b/data/scripts/spells/conjuring/conjure_spectral_bolt.lua
deleted file mode 100644
index 336eb423139..00000000000
--- a/data/scripts/spells/conjuring/conjure_spectral_bolt.lua
+++ /dev/null
@@ -1,21 +0,0 @@
-local spell = Spell("instant")
-
-function spell.onCastSpell(creature, variant)
- return creature:conjureItem(0, 35902, 100, CONST_ME_MAGIC_BLUE)
-end
-
-spell:group("support")
-spell:id(193)
-spell:name("Conjure Spectral Bolt")
-spell:words("exevo gran con vis")
-spell:cooldown(2 * 1000)
-spell:groupCooldown(2 * 1000)
-spell:level(150)
-spell:mana(1000)
-spell:soul(0)
-spell:isPremium(true)
-spell:isSelfTarget(true)
-spell:isAggressive(false)
-spell:vocation("paladin;true", "royal paladin;true")
-spell:needLearn(false)
-spell:register()
diff --git a/data/scripts/spells/house/kick.lua b/data/scripts/spells/house/kick.lua
index b4b583c1007..265ac48f796 100644
--- a/data/scripts/spells/house/kick.lua
+++ b/data/scripts/spells/house/kick.lua
@@ -4,6 +4,7 @@ function spell.onCastSpell(player, variant)
local targetPlayer = Player(variant:getString()) or player
local guest = targetPlayer:getTile():getHouse()
local owner = player:getTile():getHouse()
+
-- Owner kick yourself from house
if targetPlayer == player then
player:getPosition():sendMagicEffect(CONST_ME_POFF)
@@ -11,6 +12,13 @@ function spell.onCastSpell(player, variant)
player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
return true
end
+
+ if not owner:canEditAccessList(GUEST_LIST, player) then
+ player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
+ player:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+
if not owner or not guest or not guest:kickPlayer(player, targetPlayer) then
player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
player:getPosition():sendMagicEffect(CONST_ME_POFF)
diff --git a/data/scripts/spells/support/avatar_of_light.lua b/data/scripts/spells/support/avatar_of_light.lua
index edc25c4f66e..a84fd3b8c8e 100644
--- a/data/scripts/spells/support/avatar_of_light.lua
+++ b/data/scripts/spells/support/avatar_of_light.lua
@@ -15,20 +15,9 @@ function spell.onCastSpell(creature, variant)
return false
end
- local cooldown = 0
- if grade >= 3 then
- cooldown = 60
- elseif grade >= 2 then
- cooldown = 90
- elseif grade >= 1 then
- cooldown = 120
- end
local duration = 15000
condition:setTicks(duration)
- local conditionCooldown = Condition(CONDITION_SPELLCOOLDOWN, CONDITIONID_DEFAULT, 265)
- conditionCooldown:setTicks((cooldown * 1000 * 60) / configManager.getFloat(configKeys.RATE_SPELL_COOLDOWN))
- -- creature:getPosition():sendMagicEffect(CONST_ME_AVATAR_APPEAR)
- creature:addCondition(conditionCooldown)
+ creature:getPosition():sendMagicEffect(CONST_ME_AVATAR_APPEAR)
creature:addCondition(condition)
creature:avatarTimer((os.time() * 1000) + duration)
creature:reloadData()
@@ -43,7 +32,7 @@ spell:words("uteta res sac")
spell:level(300)
spell:mana(1500)
spell:isPremium(true)
-spell:cooldown(1000) -- Cooldown is calculated on the casting
+spell:cooldown(2 * 60 * 60 * 1000) -- Default cooldown = 2 hours
spell:groupCooldown(2 * 1000)
spell:vocation("paladin;true", "royal paladin;true")
spell:hasParams(true)
diff --git a/data/scripts/spells/support/avatar_of_nature.lua b/data/scripts/spells/support/avatar_of_nature.lua
index 0bd1473c3ca..fa32dd6b821 100644
--- a/data/scripts/spells/support/avatar_of_nature.lua
+++ b/data/scripts/spells/support/avatar_of_nature.lua
@@ -15,20 +15,9 @@ function spell.onCastSpell(creature, variant)
return false
end
- local cooldown = 0
- if grade >= 3 then
- cooldown = 60
- elseif grade >= 2 then
- cooldown = 90
- elseif grade >= 1 then
- cooldown = 120
- end
local duration = 15000
condition:setTicks(duration)
- local conditionCooldown = Condition(CONDITION_SPELLCOOLDOWN, CONDITIONID_DEFAULT, 267)
- conditionCooldown:setTicks((cooldown * 1000 * 60) / configManager.getFloat(configKeys.RATE_SPELL_COOLDOWN))
- -- creature:getPosition():sendMagicEffect(CONST_ME_AVATAR_APPEAR)
- creature:addCondition(conditionCooldown)
+ creature:getPosition():sendMagicEffect(CONST_ME_AVATAR_APPEAR)
creature:addCondition(condition)
creature:avatarTimer((os.time() * 1000) + duration)
creature:reloadData()
@@ -43,7 +32,7 @@ spell:words("uteta res dru")
spell:level(300)
spell:mana(2200)
spell:isPremium(true)
-spell:cooldown(1000) -- Cooldown is calculated on the casting
+spell:cooldown(2 * 60 * 60 * 1000) -- Default cooldown = 2 hours
spell:groupCooldown(2 * 1000)
spell:vocation("druid;true", "elder druid;true")
spell:hasParams(true)
diff --git a/data/scripts/spells/support/avatar_of_steel.lua b/data/scripts/spells/support/avatar_of_steel.lua
index 8380a524c6d..da71ff14f96 100644
--- a/data/scripts/spells/support/avatar_of_steel.lua
+++ b/data/scripts/spells/support/avatar_of_steel.lua
@@ -15,20 +15,9 @@ function spell.onCastSpell(creature, variant)
return false
end
- local cooldown = 0
- if grade >= 3 then
- cooldown = 60
- elseif grade >= 2 then
- cooldown = 90
- elseif grade >= 1 then
- cooldown = 120
- end
local duration = 15000
condition:setTicks(duration)
- local conditionCooldown = Condition(CONDITION_SPELLCOOLDOWN, CONDITIONID_DEFAULT, 264)
- conditionCooldown:setTicks((cooldown * 1000 * 60) / configManager.getFloat(configKeys.RATE_SPELL_COOLDOWN))
- -- creature:getPosition():sendMagicEffect(CONST_ME_AVATAR_APPEAR)
- creature:addCondition(conditionCooldown)
+ creature:getPosition():sendMagicEffect(CONST_ME_AVATAR_APPEAR)
creature:addCondition(condition)
creature:avatarTimer((os.time() * 1000) + duration)
creature:reloadData()
@@ -43,7 +32,7 @@ spell:words("uteta res eq")
spell:level(300)
spell:mana(800)
spell:isPremium(true)
-spell:cooldown(1000) -- Cooldown is calculated on the casting
+spell:cooldown(2 * 60 * 60 * 1000) -- Default cooldown = 2 hours
spell:groupCooldown(2 * 1000)
spell:vocation("knight;true", "elite knight;true")
spell:hasParams(true)
diff --git a/data/scripts/spells/support/avatar_of_storm.lua b/data/scripts/spells/support/avatar_of_storm.lua
index 79bb4e3b37e..ad644de01ba 100644
--- a/data/scripts/spells/support/avatar_of_storm.lua
+++ b/data/scripts/spells/support/avatar_of_storm.lua
@@ -15,20 +15,9 @@ function spell.onCastSpell(creature, variant)
return false
end
- local cooldown = 0
- if grade >= 3 then
- cooldown = 60
- elseif grade >= 2 then
- cooldown = 90
- elseif grade >= 1 then
- cooldown = 120
- end
local duration = 15000
condition:setTicks(duration)
- local conditionCooldown = Condition(CONDITION_SPELLCOOLDOWN, CONDITIONID_DEFAULT, 266)
- conditionCooldown:setTicks((cooldown * 1000 * 60) / configManager.getFloat(configKeys.RATE_SPELL_COOLDOWN))
- -- creature:getPosition():sendMagicEffect(CONST_ME_AVATAR_APPEAR)
- creature:addCondition(conditionCooldown)
+ creature:getPosition():sendMagicEffect(CONST_ME_AVATAR_APPEAR)
creature:addCondition(condition)
creature:avatarTimer((os.time() * 1000) + duration)
creature:reloadData()
@@ -43,7 +32,7 @@ spell:words("uteta res ven")
spell:level(300)
spell:mana(2200)
spell:isPremium(true)
-spell:cooldown(1000) -- Cooldown is calculated on the casting
+spell:cooldown(2 * 60 * 60 * 1000) -- Default cooldown = 2 hours
spell:groupCooldown(2 * 1000)
spell:vocation("sorcerer;true", "master sorcerer;true")
spell:hasParams(true)
diff --git a/data/scripts/talkactions/gm/afk.lua b/data/scripts/talkactions/gm/afk.lua
new file mode 100644
index 00000000000..6167a2b6068
--- /dev/null
+++ b/data/scripts/talkactions/gm/afk.lua
@@ -0,0 +1,94 @@
+local afk = TalkAction("/afk")
+
+playersAFKs = {}
+
+local function checkIsAFK(id)
+ for index, item in pairs(playersAFKs) do
+ if id == item.id then
+ return { afk = true, index = index }
+ end
+ end
+ return { afk = false }
+end
+
+local function showAfkMessage(playerPosition)
+ local spectators = Game.getSpectators(playerPosition, false, true, 8, 8, 8, 8)
+ if #spectators > 0 then
+ for _, spectator in ipairs(spectators) do
+ spectator:say("AFK !", TALKTYPE_MONSTER_SAY, false, spectator, playerPosition)
+ end
+ end
+end
+
+function afk.onSay(player, words, param)
+ if param == "" then
+ player:sendCancelMessage("You need to specify on/off param.")
+ return true
+ end
+
+ local id, playerPosition = player:getId(), player:getPosition()
+ local isAfk = checkIsAFK(id)
+ if param == "on" then
+ if isAfk.afk then
+ player:sendCancelMessage("You are already AFK!")
+ return true
+ end
+
+ table.insert(playersAFKs, { id = id, position = playerPosition })
+ if player:isInGhostMode() then
+ player:setGhostMode(false)
+ end
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are now AFK!")
+ playerPosition:sendMagicEffect(CONST_ME_REDSMOKE)
+ showAfkMessage(playerPosition)
+ elseif param == "off" then
+ if isAfk.afk then
+ table.remove(playersAFKs, isAfk.index)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are no longer AFK!")
+ playerPosition:sendMagicEffect(CONST_ME_REDSMOKE)
+ end
+ end
+
+ return true
+end
+
+afk:separator(" ")
+afk:groupType("gamemaster")
+afk:register()
+
+------------------ AFK Effect Message ------------------
+local afkEffect = GlobalEvent("GodAfkEffect")
+function afkEffect.onThink(interval)
+ for _, player in ipairs(playersAFKs) do
+ showAfkMessage(player.position)
+ end
+ return true
+end
+
+afkEffect:interval(5000)
+afkEffect:register()
+
+------------------ Stop AFK Message when moves ------------------
+local callback = EventCallback()
+function callback.playerOnWalk(player, creature, creaturePos, toPos)
+ local isAfk = checkIsAFK(player:getId())
+ if isAfk.afk then
+ table.remove(playersAFKs, isAfk.index)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are no longer AFK!")
+ end
+ return true
+end
+
+callback:register()
+
+------------------ Player Logout ------------------
+local godAfkLogout = CreatureEvent("GodAfkLogout")
+function godAfkLogout.onLogout(player)
+ local isAfk = checkIsAFK(player:getId())
+ if isAfk.afk then
+ table.remove(playersAFKs, isAfk.index)
+ end
+ return true
+end
+
+godAfkLogout:register()
diff --git a/data/scripts/talkactions/god/add_skill.lua b/data/scripts/talkactions/god/add_skill.lua
index f1c6244fba6..20644582325 100644
--- a/data/scripts/talkactions/god/add_skill.lua
+++ b/data/scripts/talkactions/god/add_skill.lua
@@ -44,8 +44,7 @@ function addSkill.onSay(player, words, param)
return true
end
- -- Trim left
- split[2] = split[2]:gsub("^%s*(.-)$", "%1")
+ split[2] = split[2]:trimSpace()
local count = 1
if split[3] then
diff --git a/data/scripts/talkactions/god/charms.lua b/data/scripts/talkactions/god/charms.lua
index 14dde0a58d7..f06c17ac7e7 100644
--- a/data/scripts/talkactions/god/charms.lua
+++ b/data/scripts/talkactions/god/charms.lua
@@ -19,8 +19,8 @@ function addCharm.onSay(player, words, param)
player:sendCancelMessage("A player with that name is not online.")
return true
end
- --trim left
- split[2] = split[2]:gsub("^%s*(.-)$", "%1")
+
+ split[2] = split[2]:trimSpace()
player:sendCancelMessage("Added " .. split[2] .. " charm points to character '" .. target:getName() .. "'.")
target:sendCancelMessage("Received " .. split[2] .. " charm points!")
@@ -133,8 +133,8 @@ function setBestiary.onSay(player, words, param)
return true
end
- split[2] = split[2]:gsub("^%s*(.-)$", "%1") --Trim left
- split[3] = split[3]:gsub("^%s*(.-)$", "%1") --Trim left
+ split[2] = split[2]:trimSpace()
+ split[3] = split[3]:trimSpace()
local monsterName = split[2]
local mType = MonsterType(monsterName)
diff --git a/data/scripts/talkactions/god/create_monster.lua b/data/scripts/talkactions/god/create_monster.lua
index 924a0d0906d..ce5869a2eee 100644
--- a/data/scripts/talkactions/god/create_monster.lua
+++ b/data/scripts/talkactions/god/create_monster.lua
@@ -73,20 +73,20 @@ function createMonster.onSay(player, words, param)
local monsterName = split[1]
local monsterCount = 0
if split[2] then
- split[2] = split[2]:gsub("^%s*(.-)$", "%1") --Trim left
+ split[2] = split[2]:trimSpace()
monsterCount = tonumber(split[2])
end
local monsterForge = nil
if split[3] then
- split[3] = split[3]:gsub("^%s*(.-)$", "%1") --Trim left
+ split[3] = split[3]:trimSpace()
monsterForge = split[3]
end
if monsterCount > 1 then
local spawnRadius = 5
if split[4] then
- split[4] = split[4]:gsub("^%s*(.-)$", "%1") --Trim left
+ split[4] = split[4]:trimSpace()
spawnRadius = split[4]
print(spawnRadius)
end
diff --git a/data/scripts/talkactions/god/manage_badge.lua b/data/scripts/talkactions/god/manage_badge.lua
new file mode 100644
index 00000000000..310cce247b0
--- /dev/null
+++ b/data/scripts/talkactions/god/manage_badge.lua
@@ -0,0 +1,35 @@
+local addBadge = TalkAction("/addbadge")
+
+function addBadge.onSay(player, words, param)
+ -- create log
+ logCommand(player, words, param)
+
+ if param == "" then
+ player:sendCancelMessage("Command param required.")
+ return true
+ end
+
+ local split = param:split(",")
+ if not split[2] then
+ player:sendCancelMessage("Insufficient parameters. Usage: /addbadge playerName, badgeID")
+ return true
+ end
+
+ local target = Player(split[1])
+ if not target then
+ player:sendCancelMessage("A player with that name is not online.")
+ return true
+ end
+
+ split[2] = split[2]:trimSpace()
+ local id = tonumber(split[2])
+ if target:addBadge(id) then
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format('You added a badge with ID "%i" to player "%s".', id, target:getName()))
+ target:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("%s added a badge to you.", player:getName()))
+ end
+ return true
+end
+
+addBadge:separator(" ")
+addBadge:groupType("god")
+addBadge:register()
diff --git a/data/scripts/talkactions/god/manage_storage.lua b/data/scripts/talkactions/god/manage_storage.lua
index 929ef7b4417..e1973ebde00 100644
--- a/data/scripts/talkactions/god/manage_storage.lua
+++ b/data/scripts/talkactions/god/manage_storage.lua
@@ -18,8 +18,7 @@ function Player.getStorageValueTalkaction(self, param)
return true
end
- -- Trim left
- split[2] = split[2]:gsub("^%s*(.-)$", "%1")
+ split[2] = split[2]:trimSpace()
-- Try to convert the second parameter to a number. If it's not a number, treat it as a storage name
local storageKey = tonumber(split[2])
diff --git a/data/scripts/talkactions/god/manage_title.lua b/data/scripts/talkactions/god/manage_title.lua
new file mode 100644
index 00000000000..d99adbe5dc0
--- /dev/null
+++ b/data/scripts/talkactions/god/manage_title.lua
@@ -0,0 +1,70 @@
+local addTitle = TalkAction("/addtitle")
+
+function addTitle.onSay(player, words, param)
+ -- create log
+ logCommand(player, words, param)
+
+ if param == "" then
+ player:sendCancelMessage("Command param required.")
+ return true
+ end
+
+ local split = param:split(",")
+ if not split[2] then
+ player:sendCancelMessage("Insufficient parameters. Usage: /addtitle playerName, badgeID")
+ return true
+ end
+
+ local target = Player(split[1])
+ if not target then
+ player:sendCancelMessage("A player with that name is not online.")
+ return true
+ end
+
+ split[2] = split[2]:trimSpace()
+ local id = tonumber(split[2])
+ if target:addTitle(id) then
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format('You added a title with ID "%i" to player "%s".', id, target:getName()))
+ target:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("%s added a title to you.", player:getName()))
+ end
+
+ return true
+end
+
+addTitle:separator(" ")
+addTitle:groupType("god")
+addTitle:register()
+
+-----------------------------------------
+local setTitle = TalkAction("/settitle")
+
+function setTitle.onSay(player, words, param)
+ -- create log
+ logCommand(player, words, param)
+
+ if param == "" then
+ player:sendCancelMessage("Command param required.")
+ return true
+ end
+
+ local split = param:split(",")
+ if not split[2] then
+ player:sendCancelMessage("Insufficient parameters. Usage: /settitle playerName, badgeID")
+ return true
+ end
+
+ local target = Player(split[1])
+ if not target then
+ player:sendCancelMessage("A player with that name is not online.")
+ return true
+ end
+
+ split[2] = split[2]:trimSpace()
+ local id = tonumber(split[2])
+ target:setCurrentTitle(id)
+ return true
+end
+
+setTitle:separator(" ")
+setTitle:groupType("god")
+setTitle:register()
diff --git a/data/scripts/talkactions/god/vip_manager.lua b/data/scripts/talkactions/god/manage_vip.lua
similarity index 100%
rename from data/scripts/talkactions/god/vip_manager.lua
rename to data/scripts/talkactions/god/manage_vip.lua
diff --git a/data/scripts/talkactions/god/reload.lua b/data/scripts/talkactions/god/reload.lua
index 5bf72868320..20b9431dbba 100644
--- a/data/scripts/talkactions/god/reload.lua
+++ b/data/scripts/talkactions/god/reload.lua
@@ -30,6 +30,7 @@ local reloadTypes = {
["scripts"] = RELOAD_TYPE_SCRIPTS,
["stage"] = RELOAD_TYPE_CORE,
["stages"] = RELOAD_TYPE_CORE,
+ ["vocations"] = RELOAD_TYPE_VOCATIONS,
}
local reload = TalkAction("/reload")
diff --git a/schema.sql b/schema.sql
index 58e6ea216ce..7bbf6c86ac5 100644
--- a/schema.sql
+++ b/schema.sql
@@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS `server_config` (
CONSTRAINT `server_config_pk` PRIMARY KEY (`config`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '44'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');
+INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '46'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');
-- Table structure `accounts`
CREATE TABLE IF NOT EXISTS `accounts` (
@@ -127,8 +127,8 @@ CREATE TABLE IF NOT EXISTS `players` (
`skill_lifeleech_amount` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
`skill_manaleech_chance` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
`skill_manaleech_amount` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
- `manashield` SMALLINT UNSIGNED NOT NULL DEFAULT '0',
- `max_manashield` SMALLINT UNSIGNED NOT NULL DEFAULT '0',
+ `manashield` INT UNSIGNED NOT NULL DEFAULT '0',
+ `max_manashield` INT UNSIGNED NOT NULL DEFAULT '0',
`xpboost_stamina` smallint(5) UNSIGNED DEFAULT NULL,
`xpboost_value` tinyint(4) UNSIGNED DEFAULT NULL,
`marriage_status` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
@@ -215,6 +215,44 @@ CREATE TABLE IF NOT EXISTS `account_viplist` (
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- Table structure `account_vipgroup`
+CREATE TABLE IF NOT EXISTS `account_vipgroups` (
+ `id` tinyint(3) UNSIGNED NOT NULL,
+ `account_id` int(11) UNSIGNED NOT NULL COMMENT 'id of account whose vip group entry it is',
+ `name` varchar(128) NOT NULL,
+ `customizable` BOOLEAN NOT NULL DEFAULT '1',
+ CONSTRAINT `account_vipgroups_pk` PRIMARY KEY (`id`, `account_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Trigger
+--
+DELIMITER //
+CREATE TRIGGER `oncreate_accounts` AFTER INSERT ON `accounts` FOR EACH ROW BEGIN
+ INSERT INTO `account_vipgroups` (`id`, `account_id`, `name`, `customizable`) VALUES (1, NEW.`id`, 'Enemies', 0);
+ INSERT INTO `account_vipgroups` (`id`, `account_id`, `name`, `customizable`) VALUES (2, NEW.`id`, 'Friends', 0);
+ INSERT INTO `account_vipgroups` (`id`, `account_id`, `name`, `customizable`) VALUES (3, NEW.`id`, 'Trading Partner', 0);
+END
+//
+DELIMITER ;
+
+-- Table structure `account_vipgrouplist`
+CREATE TABLE IF NOT EXISTS `account_vipgrouplist` (
+ `account_id` int(11) UNSIGNED NOT NULL COMMENT 'id of account whose viplist entry it is',
+ `player_id` int(11) NOT NULL COMMENT 'id of target player of viplist entry',
+ `vipgroup_id` tinyint(3) UNSIGNED NOT NULL COMMENT 'id of vip group that player belongs',
+ INDEX `account_id` (`account_id`),
+ INDEX `player_id` (`player_id`),
+ INDEX `vipgroup_id` (`vipgroup_id`),
+ CONSTRAINT `account_vipgrouplist_unique` UNIQUE (`account_id`, `player_id`, `vipgroup_id`),
+ CONSTRAINT `account_vipgrouplist_player_fk`
+ FOREIGN KEY (`player_id`) REFERENCES `players` (`id`)
+ ON DELETE CASCADE,
+ CONSTRAINT `account_vipgrouplist_vipgroup_fk`
+ FOREIGN KEY (`vipgroup_id`, `account_id`) REFERENCES `account_vipgroups` (`id`, `account_id`)
+ ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
-- Table structure `boosted_boss`
CREATE TABLE IF NOT EXISTS `boosted_boss` (
`boostname` TEXT,
@@ -372,9 +410,9 @@ CREATE TABLE IF NOT EXISTS `guild_ranks` (
--
DELIMITER //
CREATE TRIGGER `oncreate_guilds` AFTER INSERT ON `guilds` FOR EACH ROW BEGIN
- INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('The Leader', 3, NEW.`id`);
- INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('Vice-Leader', 2, NEW.`id`);
- INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('Member', 1, NEW.`id`);
+ INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('The Leader', 3, NEW.`id`);
+ INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('Vice-Leader', 2, NEW.`id`);
+ INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('Member', 1, NEW.`id`);
END
//
DELIMITER ;
@@ -428,9 +466,8 @@ CREATE TABLE IF NOT EXISTS `houses` (
-- trigger
--
DELIMITER //
-CREATE TRIGGER `ondelete_players` BEFORE DELETE ON `players`
- FOR EACH ROW BEGIN
- UPDATE `houses` SET `owner` = 0 WHERE `owner` = OLD.`id`;
+CREATE TRIGGER `ondelete_players` BEFORE DELETE ON `players` FOR EACH ROW BEGIN
+ UPDATE `houses` SET `owner` = 0 WHERE `owner` = OLD.`id`;
END
//
DELIMITER ;
@@ -815,3 +852,9 @@ INSERT INTO `players`
(4, 'Paladin Sample', 1, 1, 8, 3, 185, 185, 4200, 113, 115, 95, 39, 129, 0, 90, 90, 0, 8, '', 470, 1, 10, 0, 10, 0, 10, 0, 10, 0),
(5, 'Knight Sample', 1, 1, 8, 4, 185, 185, 4200, 113, 115, 95, 39, 129, 0, 90, 90, 0, 8, '', 470, 1, 10, 0, 10, 0, 10, 0, 10, 0),
(6, 'GOD', 6, 1, 2, 0, 155, 155, 100, 113, 115, 95, 39, 75, 0, 60, 60, 0, 8, '', 410, 1, 10, 0, 10, 0, 10, 0, 10, 0);
+
+-- Create vip groups for GOD account
+INSERT INTO `account_vipgroups` (`id`, `name`, `account_id`. `customizable`) VALUES
+(1, 'Friends', 1, 0),
+(2, 'Enemies', 1, 0),
+(3, 'Trading Partners', 1, 0);
diff --git a/src/canary_server.cpp b/src/canary_server.cpp
index 0961bc93fde..17a36ff3d23 100644
--- a/src/canary_server.cpp
+++ b/src/canary_server.cpp
@@ -327,7 +327,7 @@ void CanaryServer::loadModules() {
// If "USE_ANY_DATAPACK_FOLDER" is set to true then you can choose any datapack folder for your server
const auto useAnyDatapack = g_configManager().getBoolean(USE_ANY_DATAPACK_FOLDER, __FUNCTION__);
auto datapackName = g_configManager().getString(DATA_DIRECTORY, __FUNCTION__);
- if (!useAnyDatapack && (datapackName != "data-canary" && datapackName != "data-otservbr-global" || datapackName != "data-otservbr-global" && datapackName != "data-canary")) {
+ if (!useAnyDatapack && datapackName != "data-canary" && datapackName != "data-otservbr-global") {
throw FailedToInitializeCanary(fmt::format(
"The datapack folder name '{}' is wrong, please select valid "
"datapack name 'data-canary' or 'data-otservbr-global "
@@ -376,6 +376,7 @@ void CanaryServer::loadModules() {
g_game().loadBoostedCreature();
g_ioBosstiary().loadBoostedBoss();
g_ioprey().initializeTaskHuntOptions();
+ g_game().logCyclopediaStats();
}
void CanaryServer::modulesLoadHelper(bool loaded, std::string moduleName) {
@@ -389,4 +390,5 @@ void CanaryServer::shutdown() {
g_dispatcher().shutdown();
g_metrics().shutdown();
inject().shutdown();
+ std::exit(0);
}
diff --git a/src/canary_server.hpp b/src/canary_server.hpp
index bb22232b8e4..57c931cd133 100644
--- a/src/canary_server.hpp
+++ b/src/canary_server.hpp
@@ -46,8 +46,8 @@ class CanaryServer {
FAILED
};
- RSA &rsa;
Logger &logger;
+ RSA &rsa;
ServiceManager &serviceManager;
std::atomic loaderStatus = LoaderStatus::LOADING;
diff --git a/src/config/config_enums.hpp b/src/config/config_enums.hpp
index b9b857f435c..22043f42525 100644
--- a/src/config/config_enums.hpp
+++ b/src/config/config_enums.hpp
@@ -17,6 +17,9 @@ enum ConfigKey_t : uint16_t {
ALLOW_BLOCK_SPAWN,
ALLOW_CHANGEOUTFIT,
ALLOW_RELOAD,
+ AUGMENT_INCREASED_DAMAGE_PERCENT,
+ AUGMENT_POWERFUL_IMPACT_PERCENT,
+ AUGMENT_STRONG_IMPACT_PERCENT,
AUTH_TYPE,
AUTOBANK,
AUTOLOOT,
@@ -36,10 +39,10 @@ enum ConfigKey_t : uint16_t {
CLASSIC_ATTACK_SPEED,
CLEAN_PROTECTION_ZONES,
COMBAT_CHAIN_DELAY,
- COMBAT_CHAIN_TARGETS,
COMBAT_CHAIN_SKILL_FORMULA_AXE,
COMBAT_CHAIN_SKILL_FORMULA_CLUB,
COMBAT_CHAIN_SKILL_FORMULA_SWORD,
+ COMBAT_CHAIN_TARGETS,
COMPRESSION_LEVEL,
CONVERT_UNSAFE_SCRIPTS,
CORE_DIRECTORY,
@@ -136,6 +139,7 @@ enum ConfigKey_t : uint16_t {
MAP_DOWNLOAD_URL,
MAP_NAME,
MARKET_OFFER_DURATION,
+ MARKET_REFRESH_PRICES,
MARKET_PREMIUM,
MAX_ALLOWED_ON_A_DUMMY,
MAX_CONTAINER_ITEM,
@@ -155,6 +159,7 @@ enum ConfigKey_t : uint16_t {
METRICS_PROMETHEUS_ADDRESS,
MIN_DELAY_BETWEEN_CONDITIONS,
MIN_ELEMENTAL_RESISTANCE,
+ MIN_TOWN_ID_TO_BANK_TRANSFER,
MOMENTUM_CHANCE_FORMULA_A,
MOMENTUM_CHANCE_FORMULA_B,
MOMENTUM_CHANCE_FORMULA_C,
@@ -178,6 +183,7 @@ enum ConfigKey_t : uint16_t {
OWNER_NAME,
PARALLELISM,
PARTY_AUTO_SHARE_EXPERIENCE,
+ PARTY_SHARE_RANGE_MULTIPLIER,
PARTY_LIST_MAX_DISTANCE,
PARTY_SHARE_LOOT_BOOSTS_DIMINISHING_FACTOR,
PARTY_SHARE_LOOT_BOOSTS,
@@ -322,5 +328,5 @@ enum ConfigKey_t : uint16_t {
WHEEL_POINTS_PER_LEVEL,
WHITE_SKULL_TIME,
WORLD_TYPE,
- XP_DISPLAY_MODE,
+ XP_DISPLAY_MODE
};
diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp
index c1702672e71..e1345bbac4b 100644
--- a/src/config/configmanager.cpp
+++ b/src/config/configmanager.cpp
@@ -11,7 +11,6 @@
#include "config/configmanager.hpp"
#include "lib/di/container.hpp"
-#include "declarations.hpp"
#include "game/game.hpp"
#include "server/network/webhook/webhook.hpp"
@@ -62,6 +61,7 @@ bool ConfigManager::load() {
loadIntConfig(L, GAME_PORT, "gameProtocolPort", 7172);
loadIntConfig(L, LOGIN_PORT, "loginProtocolPort", 7171);
loadIntConfig(L, MARKET_OFFER_DURATION, "marketOfferDuration", 30 * 24 * 60 * 60);
+ loadIntConfig(L, MARKET_REFRESH_PRICES, "marketRefreshPricesInterval", 30);
loadIntConfig(L, PREMIUM_DEPOT_LIMIT, "premiumDepotLimit", 8000);
loadIntConfig(L, SQL_PORT, "mysqlPort", 3306);
loadIntConfig(L, STASH_ITEMS, "stashItemCount", 5000);
@@ -166,6 +166,9 @@ bool ConfigManager::load() {
loadBoolConfig(L, XP_DISPLAY_MODE, "experienceDisplayRates", true);
loadFloatConfig(L, BESTIARY_RATE_CHARM_SHOP_PRICE, "bestiaryRateCharmShopPrice", 1.0);
+ loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_AXE, "combatChainSkillFormulaAxe", 0.9);
+ loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_CLUB, "combatChainSkillFormulaClub", 0.7);
+ loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_SWORD, "combatChainSkillFormulaSword", 1.1);
loadFloatConfig(L, FORGE_AMOUNT_MULTIPLIER, "forgeAmountMultiplier", 3.0);
loadFloatConfig(L, HAZARD_EXP_BONUS_MULTIPLIER, "hazardExpBonusMultiplier", 2.0);
loadFloatConfig(L, LOYALTY_BONUS_PERCENTAGE_MULTIPLIER, "loyaltyBonusPercentageMultiplier", 1.0);
@@ -218,9 +221,6 @@ bool ConfigManager::load() {
loadIntConfig(L, CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES, "checkExpiredMarketOffersEachMinutes", 60);
loadIntConfig(L, COMBAT_CHAIN_DELAY, "combatChainDelay", 50);
loadIntConfig(L, COMBAT_CHAIN_TARGETS, "combatChainTargets", 5);
- loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_AXE, "combatChainSkillFormulaAxe", 0.9);
- loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_CLUB, "combatChainSkillFormulaClub", 0.7);
- loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_SWORD, "combatChainSkillFormulaSword", 1.1);
loadIntConfig(L, COMPRESSION_LEVEL, "packetCompressionLevel", 6);
loadIntConfig(L, CRITICALCHANCE, "criticalChance", 10);
loadIntConfig(L, DAY_KILLS_TO_RED, "dayKillsToRedSkull", 3);
@@ -287,6 +287,7 @@ bool ConfigManager::load() {
loadIntConfig(L, METRICS_OSTREAM_INTERVAL, "metricsOstreamInterval", 1000);
loadIntConfig(L, MIN_DELAY_BETWEEN_CONDITIONS, "minDelayBetweenConditions", 0);
loadIntConfig(L, MIN_ELEMENTAL_RESISTANCE, "minElementalResistance", -200);
+ loadIntConfig(L, MIN_TOWN_ID_TO_BANK_TRANSFER, "minTownIdToBankTransfer", 3);
loadIntConfig(L, MONTH_KILLS_TO_RED, "monthKillsToRedSkull", 10);
loadIntConfig(L, MULTIPLIER_ATTACKONFIST, "multiplierSpeedOnFist", 5);
loadIntConfig(L, ORANGE_SKULL_DURATION, "orangeSkullDuration", 7);
@@ -317,6 +318,7 @@ bool ConfigManager::load() {
loadIntConfig(L, STAMINA_PZ_GAIN, "staminaPzGain", 1);
loadIntConfig(L, STAMINA_TRAINER_DELAY, "staminaTrainerDelay", 5);
loadIntConfig(L, STAMINA_TRAINER_GAIN, "staminaTrainerGain", 1);
+ loadFloatConfig(L, PARTY_SHARE_RANGE_MULTIPLIER, "partyShareRangeMultiplier", 1.5f);
loadIntConfig(L, START_STREAK_LEVEL, "startStreakLevel", 0);
loadIntConfig(L, STATUSQUERY_TIMEOUT, "statusTimeout", 5000);
loadIntConfig(L, STORE_COIN_PACKET, "coinPacketSize", 25);
@@ -343,6 +345,9 @@ bool ConfigManager::load() {
loadIntConfig(L, WHEEL_ATELIER_ROTATE_REGULAR_COST, "wheelAtelierRotateRegularCost", 250000);
loadIntConfig(L, WHEEL_POINTS_PER_LEVEL, "wheelPointsPerLevel", 1);
loadIntConfig(L, WHITE_SKULL_TIME, "whiteSkullTime", 15 * 60 * 1000);
+ loadIntConfig(L, AUGMENT_INCREASED_DAMAGE_PERCENT, "augmentIncreasedDamagePercent", 5);
+ loadIntConfig(L, AUGMENT_POWERFUL_IMPACT_PERCENT, "augmentPowerfulImpactPercent", 10);
+ loadIntConfig(L, AUGMENT_STRONG_IMPACT_PERCENT, "augmentStrongImpactPercent", 7);
loadStringConfig(L, CORE_DIRECTORY, "coreDirectory", "data");
loadStringConfig(L, DATA_DIRECTORY, "dataPackDirectory", "data-otservbr-global");
diff --git a/src/creatures/CMakeLists.txt b/src/creatures/CMakeLists.txt
index f3dedf3a8f3..af6ba129eac 100644
--- a/src/creatures/CMakeLists.txt
+++ b/src/creatures/CMakeLists.txt
@@ -22,7 +22,10 @@ target_sources(${PROJECT_NAME}_lib PRIVATE
players/storages/storages.cpp
players/player.cpp
players/achievement/player_achievement.cpp
+ players/cyclopedia/player_badge.cpp
+ players/cyclopedia/player_title.cpp
players/wheel/player_wheel.cpp
players/wheel/wheel_gems.cpp
players/vocations/vocation.cpp
+ players/vip/player_vip.cpp
)
diff --git a/src/creatures/appearance/mounts/mounts.cpp b/src/creatures/appearance/mounts/mounts.cpp
index dff0f02a451..7051d04ddb8 100644
--- a/src/creatures/appearance/mounts/mounts.cpp
+++ b/src/creatures/appearance/mounts/mounts.cpp
@@ -35,7 +35,7 @@ bool Mounts::loadFromXml() {
continue;
}
- mounts.emplace_back(std::make_shared(
+ mounts.emplace(std::make_shared(
static_cast(pugi::cast(mountNode.attribute("id").value())),
lookType,
mountNode.attribute("name").as_string(),
@@ -44,12 +44,11 @@ bool Mounts::loadFromXml() {
mountNode.attribute("type").as_string()
));
}
- mounts.shrink_to_fit();
return true;
}
std::shared_ptr Mounts::getMountByID(uint8_t id) {
- auto it = std::find_if(mounts.begin(), mounts.end(), [id](const std::shared_ptr mount) {
+ auto it = std::find_if(mounts.begin(), mounts.end(), [id](const std::shared_ptr &mount) {
return mount->id == id; // Note the use of -> operator to access the members of the Mount object
});
@@ -58,7 +57,7 @@ std::shared_ptr Mounts::getMountByID(uint8_t id) {
std::shared_ptr Mounts::getMountByName(const std::string &name) {
auto mountName = name.c_str();
- auto it = std::find_if(mounts.begin(), mounts.end(), [mountName](const std::shared_ptr mount) {
+ auto it = std::find_if(mounts.begin(), mounts.end(), [mountName](const std::shared_ptr &mount) {
return strcasecmp(mountName, mount->name.c_str()) == 0;
});
@@ -66,7 +65,7 @@ std::shared_ptr Mounts::getMountByName(const std::string &name) {
}
std::shared_ptr Mounts::getMountByClientID(uint16_t clientId) {
- auto it = std::find_if(mounts.begin(), mounts.end(), [clientId](const std::shared_ptr mount) {
+ auto it = std::find_if(mounts.begin(), mounts.end(), [clientId](const std::shared_ptr &mount) {
return mount->clientId == clientId; // Note the use of -> operator to access the members of the Mount object
});
diff --git a/src/creatures/appearance/mounts/mounts.hpp b/src/creatures/appearance/mounts/mounts.hpp
index 05bd330a66b..ca4f969842a 100644
--- a/src/creatures/appearance/mounts/mounts.hpp
+++ b/src/creatures/appearance/mounts/mounts.hpp
@@ -11,8 +11,8 @@
struct Mount {
Mount(uint8_t initId, uint16_t initClientId, std::string initName, int32_t initSpeed, bool initPremium, std::string initType) :
- name(initName), speed(initSpeed), clientId(initClientId), id(initId), premium(initPremium),
- type(initType) { }
+ name(std::move(initName)), speed(initSpeed), clientId(initClientId), id(initId), premium(initPremium),
+ type(std::move(initType)) { }
std::string name;
int32_t speed;
@@ -30,10 +30,10 @@ class Mounts {
std::shared_ptr getMountByName(const std::string &name);
std::shared_ptr getMountByClientID(uint16_t clientId);
- [[nodiscard]] const std::vector> &getMounts() const {
+ [[nodiscard]] const phmap::parallel_flat_hash_set> &getMounts() const {
return mounts;
}
private:
- std::vector> mounts;
+ phmap::parallel_flat_hash_set> mounts;
};
diff --git a/src/creatures/appearance/outfit/outfit.cpp b/src/creatures/appearance/outfit/outfit.cpp
index 6f0490339de..9e96c60e756 100644
--- a/src/creatures/appearance/outfit/outfit.cpp
+++ b/src/creatures/appearance/outfit/outfit.cpp
@@ -14,6 +14,13 @@
#include "utils/tools.hpp"
#include "game/game.hpp"
+bool Outfits::reload() {
+ for (auto &outfitsVector : outfits) {
+ outfitsVector.clear();
+ }
+ return loadFromXml();
+}
+
bool Outfits::loadFromXml() {
pugi::xml_document doc;
auto folder = g_configManager().getString(CORE_DIRECTORY, __FUNCTION__) + "/XML/outfits.xml";
@@ -68,10 +75,12 @@ bool Outfits::loadFromXml() {
}
std::shared_ptr Outfits::getOutfitByLookType(PlayerSex_t sex, uint16_t lookType) const {
- for (const auto &outfit : outfits[sex]) {
- if (outfit->lookType == lookType) {
- return outfit;
- }
+ auto it = std::ranges::find_if(outfits[sex], [&lookType](const auto &outfit) {
+ return outfit->lookType == lookType;
+ });
+
+ if (it != outfits[sex].end()) {
+ return *it;
}
return nullptr;
}
@@ -83,17 +92,7 @@ std::shared_ptr Outfits::getOutfitByLookType(PlayerSex_t sex, uint16_t l
* @return const pointer to the outfit or nullptr if it could not be found.
*/
-std::shared_ptr Outfits::getOpositeSexOutfitByLookType(PlayerSex_t sex, uint16_t lookType) {
+std::shared_ptr Outfits::getOpositeSexOutfitByLookType(PlayerSex_t sex, uint16_t lookType) const {
PlayerSex_t searchSex = (sex == PLAYERSEX_MALE) ? PLAYERSEX_FEMALE : PLAYERSEX_MALE;
-
- for (uint16_t i = 0; i < outfits[sex].size(); i++) {
- if (outfits[sex].at(i)->lookType == lookType) {
- if (outfits[searchSex].size() > i) {
- return outfits[searchSex].at(i);
- } else { // looktype found but the oposite sex array doesn't have this index.
- return nullptr;
- }
- }
- }
- return nullptr;
+ return getOutfitByLookType(searchSex, lookType);
}
diff --git a/src/creatures/appearance/outfit/outfit.hpp b/src/creatures/appearance/outfit/outfit.hpp
index e1a5143c673..30b84d1aca2 100644
--- a/src/creatures/appearance/outfit/outfit.hpp
+++ b/src/creatures/appearance/outfit/outfit.hpp
@@ -12,9 +12,17 @@
#include "declarations.hpp"
#include "lib/di/container.hpp"
+struct OutfitEntry {
+ constexpr OutfitEntry(uint16_t initLookType, uint8_t initAddons) :
+ lookType(initLookType), addons(initAddons) { }
+
+ uint16_t lookType;
+ uint8_t addons;
+};
+
struct Outfit {
Outfit(std::string initName, uint16_t initLookType, bool initPremium, bool initUnlocked, std::string initFrom) :
- name(initName), lookType(initLookType), premium(initPremium), unlocked(initUnlocked), from(initFrom) { }
+ name(std::move(initName)), lookType(initLookType), premium(initPremium), unlocked(initUnlocked), from(std::move(initFrom)) { }
std::string name;
uint16_t lookType;
@@ -38,9 +46,10 @@ class Outfits {
return inject();
}
- std::shared_ptr getOpositeSexOutfitByLookType(PlayerSex_t sex, uint16_t lookType);
+ std::shared_ptr getOpositeSexOutfitByLookType(PlayerSex_t sex, uint16_t lookType) const;
bool loadFromXml();
+ bool reload();
[[nodiscard]] std::shared_ptr getOutfitByLookType(PlayerSex_t sex, uint16_t lookType) const;
[[nodiscard]] const std::vector> &getOutfits(PlayerSex_t sex) const {
diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp
index cf98fcb6a7b..2aaadac4d61 100644
--- a/src/creatures/combat/combat.cpp
+++ b/src/creatures/combat/combat.cpp
@@ -98,7 +98,11 @@ CombatDamage Combat::getCombatDamage(std::shared_ptr creature, std::sh
}
}
}
+ if (attackerPlayer && wheelSpell && wheelSpell->isInstant()) {
+ wheelSpell->getCombatDataAugment(attackerPlayer, damage);
+ }
}
+
return damage;
}
@@ -283,7 +287,7 @@ bool Combat::isProtected(std::shared_ptr attacker, std::shared_ptrgetVocation()->canCombat() || !target->getVocation()->canCombat() && (attacker->getVocationId() == VOCATION_NONE || target->getVocationId() == VOCATION_NONE)) {
+ if ((!attacker->getVocation()->canCombat() || !target->getVocation()->canCombat()) && (attacker->getVocationId() == VOCATION_NONE || target->getVocationId() == VOCATION_NONE)) {
return true;
}
@@ -649,6 +653,10 @@ CombatDamage Combat::applyImbuementElementalDamage(std::shared_ptr attac
continue;
}
+ if (damage.primary.type != COMBAT_PHYSICALDAMAGE) {
+ break;
+ }
+
float damagePercent = imbuementInfo.imbuement->elementDamage / 100.0;
damage.secondary.type = imbuementInfo.imbuement->combatType;
@@ -699,7 +707,7 @@ bool Combat::checkFearConditionAffected(std::shared_ptr player) {
auto affectedCount = (party->getMemberCount() + 5) / 5;
g_logger().debug("[{}] Player is member of a party, {} members can be feared", __FUNCTION__, affectedCount);
- for (const auto member : party->getMembers()) {
+ for (const auto &member : party->getMembers()) {
if (member->hasCondition(CONDITION_FEARED)) {
affectedCount -= 1;
}
@@ -751,7 +759,7 @@ void Combat::CombatConditionFunc(std::shared_ptr caster, std::shared_p
}
}
- if (caster == target || target && !target->isImmune(condition->getType())) {
+ if (caster == target || (target && !target->isImmune(condition->getType()))) {
auto conditionCopy = condition->clone();
if (caster) {
conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID());
@@ -1022,10 +1030,10 @@ bool Combat::doCombatChain(std::shared_ptr caster, std::shared_ptrgetChainValues(caster, maxTargets, chainDistance, backtracking);
- auto targets = pickChainTargets(caster, params, chainDistance, maxTargets, backtracking, aggressive, target);
+ auto targets = pickChainTargets(caster, params, chainDistance, maxTargets, aggressive, backtracking, std::move(target));
g_logger().debug("[{}] Chain targets: {}", __FUNCTION__, targets.size());
- if (targets.empty() || targets.size() == 1 && targets.begin()->second.empty()) {
+ if (targets.empty() || (targets.size() == 1 && targets.begin()->second.empty())) {
return false;
}
@@ -1114,7 +1122,7 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin
uint32_t maxY = 0;
// calculate the max viewable range
- for (std::shared_ptr tile : tileList) {
+ for (const std::shared_ptr &tile : tileList) {
const Position &tilePos = tile->getPosition();
uint32_t diff = Position::getDistanceX(tilePos, pos);
@@ -1132,7 +1140,7 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin
const int32_t rangeY = maxY + MAP_MAX_VIEW_PORT_Y;
int affected = 0;
- for (std::shared_ptr tile : tileList) {
+ for (const std::shared_ptr &tile : tileList) {
if (canDoCombat(caster, tile, params.aggressive) != RETURNVALUE_NOERROR) {
continue;
}
@@ -1187,7 +1195,7 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin
uint8_t beamAffectedCurrent = 0;
tmpDamage.affected = affected;
- for (std::shared_ptr tile : tileList) {
+ for (const std::shared_ptr &tile : tileList) {
if (canDoCombat(caster, tile, params.aggressive) != RETURNVALUE_NOERROR) {
continue;
}
@@ -1233,7 +1241,7 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin
}
void Combat::doCombatHealth(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms) {
- doCombatHealth(caster, target, caster ? caster->getPosition() : Position(), damage, params);
+ doCombatHealth(caster, std::move(target), caster ? caster->getPosition() : Position(), damage, params);
}
void Combat::doCombatHealth(std::shared_ptr caster, std::shared_ptr target, const Position &origin, CombatDamage &damage, const CombatParams ¶ms) {
@@ -1374,7 +1382,7 @@ void Combat::doCombatDispel(std::shared_ptr caster, std::shared_ptr caster, std::shared_ptr target, const CombatParams ¶ms) {
+[[maybe_unused]] void Combat::doCombatDefault(std::shared_ptr caster, std::shared_ptr target, const CombatParams ¶ms) {
doCombatDefault(caster, target, caster ? caster->getPosition() : Position(), params);
}
@@ -2068,7 +2076,7 @@ void AreaCombat::setupExtArea(const std::list &list, uint32_t rows) {
void MagicField::onStepInField(const std::shared_ptr &creature) {
// remove magic walls/wild growth
- if (!isBlocking() && g_game().getWorldType() == WORLD_TYPE_NO_PVP && id == ITEM_MAGICWALL_SAFE || id == ITEM_WILDGROWTH_SAFE) {
+ if ((!isBlocking() && g_game().getWorldType() == WORLD_TYPE_NO_PVP && id == ITEM_MAGICWALL_SAFE) || id == ITEM_WILDGROWTH_SAFE) {
if (!creature->isInGhostMode()) {
g_game().internalRemoveItem(static_self_cast
- (), 1);
}
@@ -2083,7 +2091,7 @@ void MagicField::onStepInField(const std::shared_ptr &creature) {
if (ownerId) {
bool harmfulField = true;
auto itemTile = getTile();
- if (g_game().getWorldType() == WORLD_TYPE_NO_PVP || itemTile && itemTile->hasFlag(TILESTATE_NOPVPZONE)) {
+ if (g_game().getWorldType() == WORLD_TYPE_NO_PVP || (itemTile && itemTile->hasFlag(TILESTATE_NOPVPZONE))) {
auto ownerPlayer = g_game().getPlayerByGUID(ownerId);
if (ownerPlayer) {
harmfulField = false;
diff --git a/src/creatures/combat/combat.hpp b/src/creatures/combat/combat.hpp
index 6a79455c7c4..93b02502f25 100644
--- a/src/creatures/combat/combat.hpp
+++ b/src/creatures/combat/combat.hpp
@@ -72,8 +72,8 @@ class ChainCallback final : public CallBack {
private:
void onChainCombat(std::shared_ptr creature, uint8_t &chainTargets, uint8_t &chainDistance, bool &backtracking);
- uint8_t m_chainTargets = 0;
uint8_t m_chainDistance = 0;
+ uint8_t m_chainTargets = 0;
bool m_backtracking = false;
bool m_fromLua = false;
};
@@ -125,7 +125,7 @@ class MatrixArea {
data_[row] = new bool[cols];
for (uint32_t col = 0; col < cols; ++col) {
- data_[row][col] = 0;
+ data_[row][col] = false;
}
}
}
@@ -324,7 +324,7 @@ class Combat {
}
void setPlayerCombatValues(formulaType_t formulaType, double mina, double minb, double maxa, double maxb);
void postCombatEffects(std::shared_ptr caster, const Position &origin, const Position &pos) const {
- postCombatEffects(caster, origin, pos, params);
+ postCombatEffects(std::move(caster), origin, pos, params);
}
void setOrigin(CombatOrigin origin) {
@@ -393,7 +393,7 @@ class Combat {
* @param damage The combat damage.
* @return The calculated level formula.
*/
- int32_t getLevelFormula(std::shared_ptr player, const std::shared_ptr wheelSpell, const CombatDamage &damage) const;
+ int32_t getLevelFormula(std::shared_ptr player, std::shared_ptr wheelSpell, const CombatDamage &damage) const;
CombatDamage getCombatDamage(std::shared_ptr creature, std::shared_ptr target) const;
// configureable
diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp
index 9d0f6962884..7f477f1d859 100644
--- a/src/creatures/combat/condition.cpp
+++ b/src/creatures/combat/condition.cpp
@@ -1308,7 +1308,7 @@ void ConditionManaShield::addCondition(std::shared_ptr creature, const
bool ConditionManaShield::unserializeProp(ConditionAttr_t attr, PropStream &propStream) {
if (attr == CONDITIONATTR_MANASHIELD) {
- return propStream.read(manaShield);
+ return propStream.read(manaShield);
}
return Condition::unserializeProp(attr, propStream);
}
@@ -1317,7 +1317,7 @@ void ConditionManaShield::serialize(PropWriteStream &propWriteStream) {
Condition::serialize(propWriteStream);
propWriteStream.write(CONDITIONATTR_MANASHIELD);
- propWriteStream.write(manaShield);
+ propWriteStream.write(manaShield);
}
bool ConditionManaShield::setParam(ConditionParam_t param, int32_t value) {
@@ -1472,7 +1472,7 @@ bool ConditionDamage::unserializeProp(ConditionAttr_t attr, PropStream &propStre
} else if (attr == CONDITIONATTR_OWNER) {
return propStream.skip(4);
} else if (attr == CONDITIONATTR_INTERVALDATA) {
- IntervalInfo damageInfo;
+ IntervalInfo damageInfo {};
if (!propStream.read(damageInfo)) {
return false;
}
@@ -1530,7 +1530,7 @@ bool ConditionDamage::addDamage(int32_t rounds, int32_t time, int32_t value) {
// rounds, time, damage
for (int32_t i = 0; i < rounds; ++i) {
- IntervalInfo damageInfo;
+ IntervalInfo damageInfo {};
damageInfo.interval = time;
damageInfo.timeLeft = time;
damageInfo.value = value;
@@ -1803,13 +1803,9 @@ void ConditionDamage::generateDamageList(int32_t amount, int32_t start, std::lis
* ConditionFeared
*/
bool ConditionFeared::isStuck(std::shared_ptr creature, Position pos) const {
- for (Direction dir : m_directionsVector) {
- if (canWalkTo(creature, pos, dir)) {
- return false;
- }
- }
-
- return true;
+ return std::ranges::all_of(m_directionsVector, [&](Direction dir) {
+ return !canWalkTo(creature, pos, dir);
+ });
}
bool ConditionFeared::getRandomDirection(std::shared_ptr creature, Position pos) {
@@ -1995,6 +1991,8 @@ bool ConditionFeared::getFleePath(std::shared_ptr creature, const Posi
futurePos.y -= wsize;
g_logger().debug("[{}] Trying to flee to NORTHWEST to {} [{}]", __FUNCTION__, futurePos.toString(), wsize);
break;
+ case DIRECTION_NONE:
+ break;
}
found = creature->getPathTo(futurePos, dirList, 0, 30);
diff --git a/src/creatures/combat/condition.hpp b/src/creatures/combat/condition.hpp
index 1cffba99b50..07b31b42d15 100644
--- a/src/creatures/combat/condition.hpp
+++ b/src/creatures/combat/condition.hpp
@@ -27,7 +27,7 @@ class Condition : public SharedObject {
virtual bool startCondition(std::shared_ptr creature);
virtual bool executeCondition(std::shared_ptr creature, int32_t interval);
virtual void endCondition(std::shared_ptr creature) = 0;
- virtual void addCondition(std::shared_ptr creature, const std::shared_ptr condition) = 0;
+ virtual void addCondition(std::shared_ptr creature, std::shared_ptr condition) = 0;
virtual uint32_t getIcons() const;
ConditionId_t getId() const {
return id;
@@ -65,14 +65,14 @@ class Condition : public SharedObject {
protected:
uint8_t drainBodyStage = 0;
- int64_t endTime;
- uint32_t subId;
- int32_t ticks;
- ConditionType_t conditionType;
- ConditionId_t id;
- bool isBuff;
+ int64_t endTime {};
+ uint32_t subId {};
+ int32_t ticks {};
+ ConditionType_t conditionType {};
+ ConditionId_t id {};
+ bool isBuff {};
- virtual bool updateCondition(const std::shared_ptr addCondition);
+ virtual bool updateCondition(std::shared_ptr addCondition);
private:
SoundEffect_t tickSound = SoundEffect_t::SILENCE;
@@ -90,7 +90,7 @@ class ConditionGeneric : public Condition {
bool startCondition(std::shared_ptr creature) override;
bool executeCondition(std::shared_ptr creature, int32_t interval) override;
void endCondition(std::shared_ptr creature) override;
- void addCondition(std::shared_ptr creature, const std::shared_ptr condition) override;
+ void addCondition(std::shared_ptr creature, std::shared_ptr condition) override;
uint32_t getIcons() const override;
std::shared_ptr clone() const override {
@@ -106,7 +106,7 @@ class ConditionAttributes final : public ConditionGeneric {
bool startCondition(std::shared_ptr creature) final;
bool executeCondition(std::shared_ptr creature, int32_t interval) final;
void endCondition(std::shared_ptr creature) final;
- void addCondition(std::shared_ptr creature, const std::shared_ptr condition) final;
+ void addCondition(std::shared_ptr creature, std::shared_ptr condition) final;
bool setParam(ConditionParam_t param, int32_t value) final;
@@ -172,7 +172,7 @@ class ConditionRegeneration final : public ConditionGeneric {
bool startCondition(std::shared_ptr creature) override;
void endCondition(std::shared_ptr creature) override;
- void addCondition(std::shared_ptr creature, const std::shared_ptr addCondition) override;
+ void addCondition(std::shared_ptr creature, std::shared_ptr addCondition) override;
bool executeCondition(std::shared_ptr creature, int32_t interval) override;
bool setParam(ConditionParam_t param, int32_t value) override;
@@ -205,7 +205,7 @@ class ConditionManaShield final : public Condition {
bool startCondition(std::shared_ptr creature) override;
void endCondition(std::shared_ptr creature) override;
- void addCondition(std::shared_ptr creature, const std::shared_ptr addCondition) override;
+ void addCondition(std::shared_ptr creature, std::shared_ptr addCondition) override;
uint32_t getIcons() const override;
bool setParam(ConditionParam_t param, int32_t value) override;
@@ -219,7 +219,7 @@ class ConditionManaShield final : public Condition {
bool unserializeProp(ConditionAttr_t attr, PropStream &propStream) override;
private:
- uint16_t manaShield = 0;
+ uint32_t manaShield = 0;
};
class ConditionSoul final : public ConditionGeneric {
@@ -227,7 +227,7 @@ class ConditionSoul final : public ConditionGeneric {
ConditionSoul(ConditionId_t initId, ConditionType_t initType, int32_t iniTicks, bool initBuff = false, uint32_t initSubId = 0) :
ConditionGeneric(initId, initType, iniTicks, initBuff, initSubId) { }
- void addCondition(std::shared_ptr creature, const std::shared_ptr addCondition) override;
+ void addCondition(std::shared_ptr creature, std::shared_ptr addCondition) override;
bool executeCondition(std::shared_ptr creature, int32_t interval) override;
bool setParam(ConditionParam_t param, int32_t value) override;
@@ -270,7 +270,7 @@ class ConditionDamage final : public Condition {
bool startCondition(std::shared_ptr creature) override;
bool executeCondition(std::shared_ptr creature, int32_t interval) override;
void endCondition(std::shared_ptr creature) override;
- void addCondition(std::shared_ptr creature, const std::shared_ptr condition) override;
+ void addCondition(std::shared_ptr creature, std::shared_ptr condition) override;
uint32_t getIcons() const override;
std::shared_ptr clone() const override {
@@ -309,7 +309,7 @@ class ConditionDamage final : public Condition {
bool getNextDamage(int32_t &damage);
bool doDamage(std::shared_ptr creature, int32_t healthChange);
- bool updateCondition(const std::shared_ptr addCondition) override;
+ bool updateCondition(std::shared_ptr addCondition) override;
};
class ConditionFeared final : public Condition {
@@ -321,7 +321,7 @@ class ConditionFeared final : public Condition {
bool startCondition(std::shared_ptr creature) override;
bool executeCondition(std::shared_ptr creature, int32_t interval) override;
void endCondition(std::shared_ptr creature) override;
- void addCondition(std::shared_ptr creature, const std::shared_ptr condition) override;
+ void addCondition(std::shared_ptr creature, std::shared_ptr condition) override;
uint32_t getIcons() const override;
std::shared_ptr clone() const override {
@@ -360,7 +360,7 @@ class ConditionSpeed final : public Condition {
bool startCondition(std::shared_ptr creature) override;
bool executeCondition(std::shared_ptr creature, int32_t interval) override;
void endCondition(std::shared_ptr creature) override;
- void addCondition(std::shared_ptr creature, const std::shared_ptr condition) override;
+ void addCondition(std::shared_ptr creature, std::shared_ptr condition) override;
uint32_t getIcons() const override;
std::shared_ptr clone() const override {
@@ -395,7 +395,7 @@ class ConditionOutfit final : public Condition {
bool startCondition(std::shared_ptr creature) override;
bool executeCondition(std::shared_ptr creature, int32_t interval) override;
void endCondition(std::shared_ptr creature) override;
- void addCondition(std::shared_ptr creature, const std::shared_ptr condition) override;
+ void addCondition(std::shared_ptr creature, std::shared_ptr condition) override;
std::shared_ptr clone() const override {
return std::make_shared(*this);
@@ -421,7 +421,7 @@ class ConditionLight final : public Condition {
bool startCondition(std::shared_ptr creature) override;
bool executeCondition(std::shared_ptr creature, int32_t interval) override;
void endCondition(std::shared_ptr creature) override;
- void addCondition(std::shared_ptr creature, const std::shared_ptr addCondition) override;
+ void addCondition(std::shared_ptr creature, std::shared_ptr addCondition) override;
std::shared_ptr clone() const override {
return std::make_shared(*this);
@@ -445,7 +445,7 @@ class ConditionSpellCooldown final : public ConditionGeneric {
ConditionGeneric(initId, initType, initTicks, initBuff, initSubId) { }
bool startCondition(std::shared_ptr creature) override;
- void addCondition(std::shared_ptr creature, const std::shared_ptr condition) override;
+ void addCondition(std::shared_ptr creature, std::shared_ptr condition) override;
std::shared_ptr clone() const override {
return std::make_shared(*this);
@@ -458,7 +458,7 @@ class ConditionSpellGroupCooldown final : public ConditionGeneric {
ConditionGeneric(initId, initType, initTicks, initBuff, initSubId) { }
bool startCondition(std::shared_ptr creature) override;
- void addCondition(std::shared_ptr creature, const std::shared_ptr condition) override;
+ void addCondition(std::shared_ptr creature, std::shared_ptr condition) override;
std::shared_ptr clone() const override {
return std::make_shared(*this);
diff --git a/src/creatures/combat/spells.cpp b/src/creatures/combat/spells.cpp
index 60cc5e7d2a9..dd4305de14e 100644
--- a/src/creatures/combat/spells.cpp
+++ b/src/creatures/combat/spells.cpp
@@ -630,6 +630,43 @@ void Spell::setWheelOfDestinyBoost(WheelSpellBoost_t boost, WheelSpellGrade_t gr
}
}
+void Spell::getCombatDataAugment(std::shared_ptr player, CombatDamage &damage) {
+ if (!(damage.instantSpellName).empty()) {
+ const auto equippedAugmentItems = player->getEquippedAugmentItems();
+ for (const auto &item : equippedAugmentItems) {
+ const auto augments = item->getAugmentsBySpellName(damage.instantSpellName);
+ for (auto &augment : augments) {
+ if (augment->value == 0) {
+ continue;
+ }
+ if (augment->type == Augment_t::IncreasedDamage || augment->type == Augment_t::PowerfulImpact || augment->type == Augment_t::StrongImpact) {
+ const float augmentPercent = augment->value / 100.0;
+ damage.primary.value += static_cast(damage.primary.value * augmentPercent);
+ damage.secondary.value += static_cast(damage.secondary.value * augmentPercent);
+ } else if (augment->type != Augment_t::Cooldown) {
+ const int32_t augmentValue = augment->value * 100;
+ damage.lifeLeech += augment->type == Augment_t::LifeLeech ? augmentValue : 0;
+ damage.manaLeech += augment->type == Augment_t::ManaLeech ? augmentValue : 0;
+ damage.criticalDamage += augment->type == Augment_t::CriticalExtraDamage ? augmentValue : 0;
+ }
+ }
+ }
+ }
+};
+
+int32_t Spell::calculateAugmentSpellCooldownReduction(std::shared_ptr player) const {
+ int32_t spellCooldown = 0;
+ const auto equippedAugmentItems = player->getEquippedAugmentItemsByType(Augment_t::Cooldown);
+ for (const auto &item : equippedAugmentItems) {
+ const auto augments = item->getAugmentsBySpellNameAndType(getName(), Augment_t::Cooldown);
+ for (auto &augment : augments) {
+ spellCooldown += augment->value;
+ }
+ }
+
+ return spellCooldown;
+}
+
void Spell::applyCooldownConditions(std::shared_ptr player) const {
WheelSpellGrade_t spellGrade = player->wheel()->getSpellUpgrade(getName());
bool isUpgraded = getWheelOfDestinyUpgraded() && static_cast(spellGrade) > 0;
@@ -644,8 +681,10 @@ void Spell::applyCooldownConditions(std::shared_ptr player) const {
if (isUpgraded) {
spellCooldown -= getWheelOfDestinyBoost(WheelSpellBoost_t::COOLDOWN, spellGrade);
}
- g_logger().debug("[{}] spell name: {}, spellCooldown: {}, bonus: {}", __FUNCTION__, name, spellCooldown, player->wheel()->getSpellBonus(name, WheelSpellBoost_t::COOLDOWN));
+ int32_t augmentCooldownReduction = calculateAugmentSpellCooldownReduction(player);
+ g_logger().debug("[{}] spell name: {}, spellCooldown: {}, bonus: {}, augment {}", __FUNCTION__, name, spellCooldown, player->wheel()->getSpellBonus(name, WheelSpellBoost_t::COOLDOWN), augmentCooldownReduction);
spellCooldown -= player->wheel()->getSpellBonus(name, WheelSpellBoost_t::COOLDOWN);
+ spellCooldown -= augmentCooldownReduction;
if (spellCooldown > 0) {
std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, spellCooldown / rateCooldown, 0, false, m_spellId);
player->addCondition(condition);
@@ -727,7 +766,6 @@ uint32_t Spell::getManaCost(std::shared_ptr player) const {
if (manaPercent != 0) {
uint32_t maxMana = player->getMaxMana();
uint32_t manaCost = (maxMana * manaPercent) / 100;
- WheelSpellGrade_t spellGrade = player->wheel()->getSpellUpgrade(getName());
if (manaRedution > manaCost) {
return 0;
}
@@ -750,7 +788,7 @@ bool InstantSpell::playerCastInstant(std::shared_ptr player, std::string
var.type = VARIANT_NUMBER;
var.number = player->getID();
} else if (needTarget || casterTargetOrDirection) {
- std::shared_ptr target = nullptr;
+ std::shared_ptr target;
bool useDirection = false;
if (hasParam) {
@@ -1046,7 +1084,7 @@ bool RuneSpell::castSpell(std::shared_ptr creature, std::shared_ptr creature, const LuaVariant &var, bool isHotkey) {
bool result;
if (isLoadedCallback()) {
- result = executeCastSpell(creature, var, isHotkey);
+ result = executeCastSpell(std::move(creature), var, isHotkey);
} else {
result = false;
}
diff --git a/src/creatures/combat/spells.hpp b/src/creatures/combat/spells.hpp
index cd1a7aaa623..fc23bbf9030 100644
--- a/src/creatures/combat/spells.hpp
+++ b/src/creatures/combat/spells.hpp
@@ -63,8 +63,8 @@ class Spells final : public Scripts {
}
void clear();
- bool registerInstantLuaEvent(const std::shared_ptr instant);
- bool registerRuneLuaEvent(const std::shared_ptr rune);
+ bool registerInstantLuaEvent(std::shared_ptr instant);
+ bool registerRuneLuaEvent(std::shared_ptr rune);
private:
std::map> runes;
@@ -92,7 +92,7 @@ class BaseSpell {
class CombatSpell final : public Script, public BaseSpell, public std::enable_shared_from_this {
public:
// Constructor
- CombatSpell(const std::shared_ptr newCombat, bool newNeedTarget, bool newNeedDirection);
+ CombatSpell(std::shared_ptr newCombat, bool newNeedTarget, bool newNeedDirection);
// The copy constructor and the assignment operator have been deleted to prevent accidental copying.
CombatSpell(const CombatSpell &) = delete;
@@ -345,6 +345,9 @@ class Spell : public BaseSpell {
m_separator = newSeparator.data();
}
+ void getCombatDataAugment(std::shared_ptr player, CombatDamage &damage);
+ int32_t calculateAugmentSpellCooldownReduction(std::shared_ptr player) const;
+
protected:
void applyCooldownConditions(std::shared_ptr player) const;
bool playerSpellCheck(std::shared_ptr player) const;
diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp
index 6101ff0473d..7796863f066 100644
--- a/src/creatures/creature.cpp
+++ b/src/creatures/creature.cpp
@@ -125,7 +125,7 @@ void Creature::onThink(uint32_t interval) {
auto onThink = [self = getCreature(), interval] {
// scripting event - onThink
const auto &thinkEvents = self->getCreatureEvents(CREATURE_EVENT_THINK);
- for (const auto creatureEventPtr : thinkEvents) {
+ for (const auto &creatureEventPtr : thinkEvents) {
creatureEventPtr->executeOnThink(self->static_self_cast(), interval);
}
};
@@ -299,7 +299,7 @@ void Creature::updateTileCache(std::shared_ptr upTile, const Position &pos
if (pos.z == myPos.z) {
int32_t dx = Position::getOffsetX(pos, myPos);
int32_t dy = Position::getOffsetY(pos, myPos);
- updateTileCache(upTile, dx, dy);
+ updateTileCache(std::move(upTile), dx, dy);
}
}
@@ -332,7 +332,7 @@ int32_t Creature::getWalkCache(const Position &pos) {
void Creature::onAddTileItem(std::shared_ptr tileItem, const Position &pos) {
if (isMapLoaded && pos.z == getPosition().z) {
- updateTileCache(tileItem, pos);
+ updateTileCache(std::move(tileItem), pos);
}
}
@@ -343,7 +343,7 @@ void Creature::onUpdateTileItem(std::shared_ptr updateTile, const Position
if (oldType.blockSolid || oldType.blockPathFind || newType.blockPathFind || newType.blockSolid) {
if (pos.z == getPosition().z) {
- updateTileCache(updateTile, pos);
+ updateTileCache(std::move(updateTile), pos);
}
}
}
@@ -355,7 +355,7 @@ void Creature::onRemoveTileItem(std::shared_ptr updateTile, const Position
if (iType.blockSolid || iType.blockPathFind || iType.isGroundTile()) {
if (pos.z == getPosition().z) {
- updateTileCache(updateTile, pos);
+ updateTileCache(std::move(updateTile), pos);
}
}
}
@@ -439,7 +439,7 @@ void Creature::checkSummonMove(const Position &newPos, bool teleportSummon) {
// Check if any of our summons is out of range (+/- 2 floors or 30 tiles away)
bool checkRemoveDist = Position::getDistanceZ(newPos, pos) > 2 || (std::max(Position::getDistanceX(newPos, pos), Position::getDistanceY(newPos, pos)) > 30);
- if (monster && monster->isFamiliar() && checkSummonDist || teleportSummon && !protectionZoneCheck && checkSummonDist) {
+ if ((monster && monster->isFamiliar() && checkSummonDist) || (teleportSummon && !protectionZoneCheck && checkSummonDist)) {
const auto &creatureMaster = summon->getMaster();
if (!creatureMaster) {
continue;
@@ -709,7 +709,7 @@ void Creature::onDeath() {
}
}
- bool killedByPlayer = mostDamageCreature && mostDamageCreature->getPlayer() || mostDamageCreatureMaster && mostDamageCreatureMaster->getPlayer();
+ bool killedByPlayer = (mostDamageCreature && mostDamageCreature->getPlayer()) || (mostDamageCreatureMaster && mostDamageCreatureMaster->getPlayer());
if (getPlayer()) {
g_metrics().addCounter(
"player_death",
@@ -761,7 +761,7 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared
if (getMaster()) {
// Scripting event onDeath
const CreatureEventList &deathEvents = getCreatureEvents(CREATURE_EVENT_DEATH);
- for (const auto deathEventPtr : deathEvents) {
+ for (const auto &deathEventPtr : deathEvents) {
deathEventPtr->executeOnDeath(static_self_cast(), nullptr, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified);
}
}
@@ -805,7 +805,8 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared
auto monster = getMonster();
if (monster && !monster->isRewardBoss()) {
std::ostringstream lootMessage;
- lootMessage << "Loot of " << getNameDescription() << ": " << corpseContainer->getContentDescription(player->getProtocolVersion() < 1200) << ".";
+ auto collorMessage = player->getProtocolVersion() > 1200 && player->getOperatingSystem() < CLIENTOS_OTCLIENT_LINUX;
+ lootMessage << "Loot of " << getNameDescription() << ": " << corpseContainer->getContentDescription(collorMessage) << ".";
auto suffix = corpseContainer->getAttribute(ItemAttribute_t::LOOTMESSAGE_SUFFIX);
if (!suffix.empty()) {
lootMessage << suffix;
@@ -823,7 +824,7 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared
auto isReachable = g_game().map.getPathMatching(player->getPosition(), dirList, FrozenPathingConditionCall(corpse->getPosition()), fpp);
- if (player->checkAutoLoot(monster->isRewardBoss()) && corpseContainer && mostDamageCreature->getPlayer() && isReachable) {
+ if (player->checkAutoLoot(monster->isRewardBoss()) && isReachable) {
g_dispatcher().addEvent([player, corpseContainer, corpsePosition = corpse->getPosition()] {
g_game().playerQuickLootCorpse(player, corpseContainer, corpsePosition);
},
@@ -833,7 +834,7 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared
}
// Scripting event onDeath
- for (const auto deathEventPtr : getCreatureEvents(CREATURE_EVENT_DEATH)) {
+ for (const auto &deathEventPtr : getCreatureEvents(CREATURE_EVENT_DEATH)) {
if (deathEventPtr) {
deathEventPtr->executeOnDeath(static_self_cast(), corpse, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified);
}
@@ -1168,7 +1169,7 @@ double Creature::getDamageRatio(std::shared_ptr attacker) const {
}
uint64_t Creature::getGainedExperience(std::shared_ptr attacker) const {
- return std::floor(getDamageRatio(attacker) * getLostExperience());
+ return std::floor(getDamageRatio(std::move(attacker)) * getLostExperience());
}
void Creature::addDamagePoints(std::shared_ptr attacker, int32_t damagePoints) {
@@ -1180,7 +1181,7 @@ void Creature::addDamagePoints(std::shared_ptr attacker, int32_t damag
auto it = damageMap.find(attackerId);
if (it == damageMap.end()) {
- CountBlock_t cb;
+ CountBlock_t cb {};
cb.ticks = OTSYS_TIME();
cb.total = damagePoints;
damageMap[attackerId] = cb;
@@ -1246,7 +1247,7 @@ void Creature::onTickCondition(ConditionType_t type, bool &bRemove) {
}
void Creature::onCombatRemoveCondition(std::shared_ptr condition) {
- removeCondition(condition);
+ removeCondition(std::move(condition));
}
void Creature::onAttacked() {
@@ -1274,7 +1275,7 @@ bool Creature::deprecatedOnKilledCreature(std::shared_ptr target, bool
// scripting event - onKill
const CreatureEventList &killEvents = getCreatureEvents(CREATURE_EVENT_KILL);
- for (const auto killEventPtr : killEvents) {
+ for (const auto &killEventPtr : killEvents) {
killEventPtr->executeOnKill(static_self_cast(), target, lastHit);
}
return false;
@@ -1292,7 +1293,7 @@ void Creature::onGainExperience(uint64_t gainExp, std::shared_ptr targ
gainExp /= 2;
}
- master->onGainExperience(gainExp, target);
+ master->onGainExperience(gainExp, std::move(target));
if (!m->isFamiliar()) {
auto spectators = Spectators().find(position);
@@ -1573,7 +1574,7 @@ void Creature::setSpeed(int32_t varSpeedDelta) {
}
void Creature::setCreatureLight(LightInfo lightInfo) {
- internalLight = std::move(lightInfo);
+ internalLight = lightInfo;
}
void Creature::setNormalCreatureLight() {
@@ -1588,7 +1589,7 @@ bool Creature::registerCreatureEvent(const std::string &name) {
CreatureEventType_t type = event->getEventType();
if (hasEventRegistered(type)) {
- for (const auto creatureEventPtr : eventsList) {
+ for (const auto &creatureEventPtr : eventsList) {
if (creatureEventPtr == event) {
return false;
}
@@ -1641,7 +1642,7 @@ CreatureEventList Creature::getCreatureEvents(CreatureEventType_t type) {
return tmpEventList;
}
- for (const auto creatureEventPtr : eventsList) {
+ for (const auto &creatureEventPtr : eventsList) {
if (creatureEventPtr->getEventType() == type) {
tmpEventList.push_back(creatureEventPtr);
}
@@ -1726,7 +1727,7 @@ bool FrozenPathingConditionCall::operator()(const Position &startPos, const Posi
}
bool Creature::isInvisible() const {
- return std::find_if(conditions.begin(), conditions.end(), [](const std::shared_ptr condition) {
+ return std::find_if(conditions.begin(), conditions.end(), [](const std::shared_ptr &condition) {
return condition->getType() == CONDITION_INVISIBLE;
})
!= conditions.end();
diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp
index 4acbacb570a..670d5f3d688 100644
--- a/src/creatures/creature.hpp
+++ b/src/creatures/creature.hpp
@@ -209,19 +209,19 @@ class Creature : virtual public Thing, public SharedObject {
return mana;
}
- uint16_t getManaShield() const {
+ uint32_t getManaShield() const {
return manaShield;
}
- void setManaShield(uint16_t value) {
+ void setManaShield(uint32_t value) {
manaShield = value;
}
- uint16_t getMaxManaShield() const {
+ uint32_t getMaxManaShield() const {
return maxManaShield;
}
- void setMaxManaShield(uint16_t value) {
+ void setMaxManaShield(uint32_t value) {
maxManaShield = value;
}
@@ -400,13 +400,13 @@ class Creature : virtual public Thing, public SharedObject {
void executeConditions(uint32_t interval);
bool hasCondition(ConditionType_t type, uint32_t subId = 0) const;
- virtual bool isImmune(CombatType_t type) const {
+ virtual bool isImmune([[maybe_unused]] CombatType_t type) const {
return false;
}
- virtual bool isImmune(ConditionType_t type) const {
+ virtual bool isImmune([[maybe_unused]] ConditionType_t type) const {
return false;
}
- virtual bool isSuppress(ConditionType_t type, bool attackerPlayer) const {
+ virtual bool isSuppress([[maybe_unused]] ConditionType_t type, [[maybe_unused]] bool attackerPlayer) const {
return false;
};
@@ -424,7 +424,7 @@ class Creature : virtual public Thing, public SharedObject {
virtual void drainHealth(std::shared_ptr attacker, int32_t damage);
virtual void drainMana(std::shared_ptr attacker, int32_t manaLoss);
- virtual bool challengeCreature(std::shared_ptr, int targetChangeCooldown) {
+ virtual bool challengeCreature(std::shared_ptr, [[maybe_unused]] int targetChangeCooldown) {
return false;
}
@@ -448,10 +448,10 @@ class Creature : virtual public Thing, public SharedObject {
* @deprecated -- This is here to trigger the deprecated onKill events in lua
*/
bool deprecatedOnKilledCreature(std::shared_ptr target, bool lastHit);
- virtual bool onKilledPlayer(const std::shared_ptr &target, bool lastHit) {
+ virtual bool onKilledPlayer([[maybe_unused]] const std::shared_ptr &target, [[maybe_unused]] bool lastHit) {
return false;
};
- virtual bool onKilledMonster(const std::shared_ptr &target) {
+ virtual bool onKilledMonster([[maybe_unused]] const std::shared_ptr &target) {
return false;
};
virtual void onGainExperience(uint64_t gainExp, std::shared_ptr