From 3fe2043a0cd3eedc1084b04d21a67bbccae86a23 Mon Sep 17 00:00:00 2001 From: Sarah Wesker Date: Sat, 27 Apr 2024 23:33:32 -0400 Subject: [PATCH] update data --- .lua-format | 2 +- data/creaturescripts/scripts/login.lua | 5 +- data/creaturescripts/scripts/logout.lua | 2 +- data/creaturescripts/scripts/playerdeath.lua | 114 ++++++++++-------- data/events/events.xml | 16 +-- data/global.lua | 111 ++++++++++------- data/lib/compat/compat.lua | 24 ++++ data/lib/core/actionids.lua | 17 +++ data/lib/core/container.lua | 36 +++--- data/lib/core/game.lua | 6 - data/lib/core/item.lua | 113 +++++++++++++++-- data/lib/core/player.lua | 51 +++++++- data/lib/core/position.lua | 82 +++++++++---- data/lib/core/storages.lua | 34 ++++++ .../player/default_onGainExperience.lua | 53 +++----- .../eventcallbacks/player/default_onLook.lua | 85 +++++++------ data/talkactions/scripts/attributes.lua | 65 ++++++++-- data/talkactions/scripts/serverinfo.lua | 27 +++-- 18 files changed, 582 insertions(+), 261 deletions(-) diff --git a/.lua-format b/.lua-format index df17791..8ca0b8c 100644 --- a/.lua-format +++ b/.lua-format @@ -3,4 +3,4 @@ continuation_indent_width: 1 use_tab: true tab_width: 4 chop_down_table: true -column_limit: 100 \ No newline at end of file +column_limit: 90 \ No newline at end of file diff --git a/data/creaturescripts/scripts/login.lua b/data/creaturescripts/scripts/login.lua index ce79844..7fc7e53 100644 --- a/data/creaturescripts/scripts/login.lua +++ b/data/creaturescripts/scripts/login.lua @@ -15,7 +15,7 @@ function onLogin(player) local promotion = vocation:getPromotion() if player:isPremium() then local value = player:getStorageValue(PlayerStorageKeys.promotion) - if value == 1 then player:setVocation(promotion) end + if value and value == 1 then player:setVocation(promotion) end elseif not promotion then player:setVocation(vocation:getDemotion()) end @@ -23,5 +23,8 @@ function onLogin(player) -- Events player:registerEvent("PlayerDeath") player:registerEvent("DropLoot") + + -- Update Experience Rate Stamina + player:updateStamina() return true end diff --git a/data/creaturescripts/scripts/logout.lua b/data/creaturescripts/scripts/logout.lua index 80c52b3..6438881 100644 --- a/data/creaturescripts/scripts/logout.lua +++ b/data/creaturescripts/scripts/logout.lua @@ -1,5 +1,5 @@ function onLogout(player) local playerId = player:getId() - if nextUseStaminaTime[playerId] then nextUseStaminaTime[playerId] = nil end + nextUseStaminaTime[playerId] = nil return true end diff --git a/data/creaturescripts/scripts/playerdeath.lua b/data/creaturescripts/scripts/playerdeath.lua index a58dcfd..23f2da6 100644 --- a/data/creaturescripts/scripts/playerdeath.lua +++ b/data/creaturescripts/scripts/playerdeath.lua @@ -1,85 +1,97 @@ local deathListEnabled = true local maxDeathRecords = 5 +local playerDeathQuery = + "INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) VALUES (%d, %d, %d, %s, %d, %s, %d, %d, %d)" +local format = string.format + +---@param killer Creature +---@return boolean, string local function getKiller(killer) if not killer then return false, "field item" end if killer:isPlayer() then return true, killer:getName() end local master = killer:getMaster() - if master and master ~= killer and master:isPlayer() then - return true, master:getName() - end + if master and master ~= killer and master:isPlayer() then return true, master:getName() end return false, killer:getName() end -function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, - mostDamageUnjustified) - local playerId = player:getId() - if nextUseStaminaTime[playerId] then nextUseStaminaTime[playerId] = nil end - - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are dead.") - if not deathListEnabled then return end - - local byPlayer, killerName = getKiller(killer) - local byPlayerMostDamage, killerNameMostDamage = getKiller(mostDamageKiller) - - local playerGuid = player:getGuid() - db.query( - "INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) VALUES (" .. - playerGuid .. ", " .. os.time() .. ", " .. player:getLevel() .. ", " .. - db.escapeString(killerName) .. ", " .. (byPlayer and 1 or 0) .. ", " .. - db.escapeString(killerNameMostDamage) .. ", " .. - (byPlayerMostDamage and 1 or 0) .. ", " .. (lastHitUnjustified and 1 or 0) .. - ", " .. (mostDamageUnjustified and 1 or 0) .. ")") - local resultId = db.storeQuery( - "SELECT `player_id` FROM `player_deaths` WHERE `player_id` = " .. - playerGuid) +---@param playerId integer +---@param playerName string +---@param killerId integer +---@param playerGuid integer +---@param byPlayer boolean +---@param killerName string +---@param playerGuildId integer +---@param killerGuildId integer +---@param timeNow integer +---@return nil +local function playerDeathSuccess(playerId, playerName, killerId, playerGuid, byPlayer, killerName, playerGuildId, killerGuildId, + timeNow) + local resultId = db.storeQuery("SELECT `player_id` FROM `player_deaths` WHERE `player_id` = " .. playerGuid) + if not resultId then return end local deathRecords = 0 - local tmpResultId = resultId - while tmpResultId ~= false do + local tmpResultId = true + while tmpResultId do tmpResultId = result.next(resultId) deathRecords = deathRecords + 1 end - if resultId ~= false then result.free(resultId) end + result.free(resultId) local limit = deathRecords - maxDeathRecords if limit > 0 then - db.asyncQuery( - "DELETE FROM `player_deaths` WHERE `player_id` = " .. playerGuid .. - " ORDER BY `time` LIMIT " .. limit) + db.asyncQuery(format("DELETE FROM `player_deaths` WHERE `player_id` = %d ORDER BY `time` LIMIT %d", playerGuid, limit)) end if byPlayer then - local targetGuild = player:getGuild() - targetGuild = targetGuild and targetGuild:getId() or 0 - if targetGuild ~= 0 then - local killerGuild = killer:getGuild() - killerGuild = killerGuild and killerGuild:getId() or 0 - if killerGuild ~= 0 and targetGuild ~= killerGuild and - isInWar(playerId, killer:getId()) then - local warId = false - resultId = db.storeQuery( - "SELECT `id` FROM `guild_wars` WHERE `status` = 1 AND ((`guild1` = " .. - killerGuild .. " AND `guild2` = " .. targetGuild .. - ") OR (`guild1` = " .. targetGuild .. " AND `guild2` = " .. - killerGuild .. "))") - if resultId ~= false then + if playerGuildId ~= 0 then + if killerGuildId ~= 0 and playerGuildId ~= killerGuildId and isInWar(playerId, killerId) then + resultId = db.storeQuery(format( + "SELECT `id` FROM `guild_wars` WHERE `status` = 1 AND ((`guild1` = %d AND `guild2` = %d) OR (`guild1` = %d AND `guild2` = %d))", + killerGuildId, playerGuildId, playerGuildId, killerGuildId)) + + local warId = nil + if resultId then warId = result.getNumber(resultId, "id") result.free(resultId) end - if warId ~= false then - db.asyncQuery( - "INSERT INTO `guildwar_kills` (`killer`, `target`, `killerguild`, `targetguild`, `time`, `warid`) VALUES (" .. - db.escapeString(killerName) .. ", " .. db.escapeString(player:getName()) .. - ", " .. killerGuild .. ", " .. targetGuild .. ", " .. os.time() .. ", " .. - warId .. ")") + if warId then + db.asyncQuery(format( + "INSERT INTO `guildwar_kills` (`killer`, `target`, `killerguild`, `targetguild`, `time`, `warid`) VALUES (%s, %s, %d, %d, %d, %d)", + db.escapeString(killerName), db.escapeString(playerName), killerGuildId, playerGuildId, timeNow, warId)) end end end end end + +function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) + local playerId = player:getId() + nextUseStaminaTime[playerId] = nil + + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are dead.") + if not deathListEnabled then return end + + local timeNow = os.time() + local byPlayer, killerName = getKiller(killer) + local byPlayerMostDamage, killerNameMostDamage = getKiller(mostDamageKiller) + local playerGuid = player:getGuid() + local playerName = player:getName() + local playerGuild = player:getGuild() + local playerGuildId = playerGuild and playerGuild:getId() or 0 + local killerGuild = byPlayer and killer:getGuild() or nil + local killerGuildId = killerGuild and killerGuild:getId() or 0 + local killerId = byPlayer and killer:getId() or 0 + db.asyncQuery(format(playerDeathQuery, playerGuid, timeNow, player:getLevel(), db.escapeString(killerName), byPlayer and 1 or 0, + db.escapeString(killerNameMostDamage), byPlayerMostDamage and 1 or 0, lastHitUnjustified and 1 or 0, + mostDamageUnjustified and 1 or 0), function(success) + if success then + playerDeathSuccess(playerId, playerName, killerId, playerGuid, byPlayer, killerName, playerGuildId, killerGuildId, timeNow) + end + end) +end diff --git a/data/events/events.xml b/data/events/events.xml index 6bd4c24..77982c9 100644 --- a/data/events/events.xml +++ b/data/events/events.xml @@ -2,8 +2,8 @@ - - + + @@ -23,14 +23,14 @@ - - + + - - - + + + @@ -42,5 +42,5 @@ - + diff --git a/data/global.lua b/data/global.lua index 3246b48..b65182b 100644 --- a/data/global.lua +++ b/data/global.lua @@ -1,70 +1,84 @@ math.randomseed(os.time()) dofile('data/lib/lib.lua') -ropeSpots = {384, 418, 8278, 8592} +-- LuaFormatter off +ropeSpots = { + 384, 418, 8278, 8592, 13189, 14435, 14436, 14857, 15635, 19518, 24621, 24622, 24623, 24624, 26019 +} -keys = {2086, 2087, 2088, 2089, 2090, 2091, 2092, 10032} +keys = { + 2086, 2087, 2088, 2089, 2090, 2091, 2092, 10032 +} openDoors = { - 1211, 1214, 1233, 1236, 1251, 1254, 3546, 3537, 4915, 4918, 5100, 5109, 5118, - 5127, 5136, 5139, 5142, 5145, 5280, 5283, 5734, 5737, 6194, 6197, 6251, 6254, - 6893, 6902, 7035, 7044, 8543, 8546, 9167, 9170, 9269, 9272, 10270, 10273, - 10470, 10479, 10777, 10786, 12094, 12101, 12190, 12199 + 1211, 1214, 1233, 1236, 1251, 1254, 3537, 3546, 4915, 4918, 5100, 5109, 5118, 5127, 5136, 5139, 5142, + 5145, 5280, 5283, 5734, 5737, 6194, 6197, 6251, 6254, 6893, 6902, 7035, 7044, 8543, 8546, 9167, 9170, + 9269, 9272, 10270, 10273, 10470, 10479, 10777, 10786, 12094, 12101, 12190, 12199, 12695, 12703, 14635, + 17435, 19842, 19851, 19982, 19991, 20275, 20284, 22816, 22825, 25285, 25292, 26533, 26534, 31176, 31024, + 31025, 32691, 32692, 32695, 32696, 33432, 33433, 33493, 33494, 36292, 36293, 36869, 36872, 37293, 37296, + 37299, 37302, 37596, 37597, 39119, 39120 } closedDoors = { - 1210, 1213, 1232, 1235, 1250, 1253, 3536, 3545, 4914, 4917, 5099, 5108, 5117, - 5126, 5135, 5138, 5141, 5144, 5279, 5282, 5733, 5736, 6193, 6196, 6250, 6253, - 6892, 6901, 7034, 7043, 8542, 8545, 9166, 9169, 9268, 9271, 10269, 10272, - 10766, 10785, 10469, 10478, 12093, 12100, 12189, 12198 + 1210, 1213, 1232, 1235, 1250, 1253, 3536, 3545, 4914, 4917, 5099, 5108, 5117, 5126, 5135, 5138, 5141, + 5144, 5279, 5282, 5733, 5736, 6193, 6196, 6250, 6253, 6892, 6901, 7034, 7043, 8542, 8545, 9166, 9169, + 9268, 9271, 10269, 10272, 10469, 10478, 10776, 10785, 12093, 12100, 12189, 12198, 12692, 12701, 14633, + 14640, 19841, 19850, 19981, 19990, 20274, 20283, 22815, 22824, 25284, 25291, 26529, 26531, 27559, 31020, + 31022, 32689, 32690, 32693, 32694, 33428, 33430, 33489, 33491, 36288, 36290, 36868, 36871, 37292, 37295, + 37298, 37301, 37592, 37594, 39115, 39117 } lockedDoors = { - 1209, 1212, 1231, 1234, 1249, 1252, 3535, 3544, 4913, 4916, 5098, 5107, 5116, - 5125, 5134, 5137, 5140, 5143, 5278, 5281, 5732, 5735, 6192, 6195, 6249, 6252, - 6891, 6900, 7033, 7042, 8541, 8544, 9165, 9168, 9267, 9270, 10268, 10271, - 10468, 10477, 10775, 10784, 12092, 12099, 12188, 12197 + 1209, 1212, 1231, 1234, 1249, 1252, 3535, 3544, 4913, 4916, 5098, 5107, 5116, 5125, 5134, 5137, 5140, + 5143, 5278, 5281, 5732, 5735, 6192, 6195, 6249, 6252, 6891, 6900, 7033, 7042, 8541, 8544, 9165, 9168, + 9267, 9270, 10268, 10271, 10468, 10477, 10775, 10784, 12092, 12099, 12188, 12197, 13236, 13237, 14634, + 14641, 19840, 19849, 19980, 19989, 20273, 20282, 22814, 22823, 25283, 25290, 26530, 26532, 31175, 31021, + 31023, 32705, 32706, 32707, 32708, 33429, 33431, 33490, 33492, 36289, 36291, 36867, 36870, 37291, 37294, + 37297, 37300, 37593, 37595, 39116, 39118 } -openExtraDoors = {1540, 1542, 6796, 6798, 6800, 6802, 7055, 7057} -closedExtraDoors = {1539, 1541, 6795, 6797, 6799, 6801, 7054, 7056} +openExtraDoors = { + 1540, 1542, 6796, 6798, 6800, 6802, 6960, 6962, 7055, 7057, 25159, 25161, 27198, 27200, 27243, 27245, + 31541, 31542, 34152, 34153, 36878, 36880, 39204 +} +closedExtraDoors = { + 1539, 1541, 6795, 6797, 6799, 6801, 6959, 6961, 7054, 7056, 25158, 25160, 27197, 27199, 27242, 27244, + 31314, 31315, 34150, 34151, 36877, 36879, 39203 +} openHouseDoors = { - 1220, 1222, 1238, 1240, 3539, 3548, 5083, 5085, 5102, 5111, 5120, 5129, 5285, - 5287, 5516, 5518, 6199, 6201, 6256, 6258, 6895, 6904, 7037, 7046, 8548, 8550, - 9172, 9174, 9274, 9276, 10275, 10277, 10472, 10481 + 1220, 1222, 1238, 1240, 3539, 3548, 5083, 5085, 5102, 5111, 5120, 5129, 5285, 5287, 5516, 5518, 6199, + 6201, 6256, 6258, 6895, 6904, 7037, 7046, 8548, 8550, 9172, 9174, 9274, 9276, 10275, 10277, 10472, 10481, + 13021, 13023, 17236, 17238, 18209, 19844, 19853, 19984, 19993, 20277, 20286, 22818, 22827, 35928, 35930 } closedHouseDoors = { - 1219, 1221, 1237, 1239, 3538, 3547, 5082, 5084, 5101, 5110, 5119, 5128, 5284, - 5286, 5515, 5517, 6198, 6200, 6255, 6257, 6894, 6903, 7036, 7045, 8547, 8549, - 9171, 9173, 9273, 9275, 10274, 10276, 10471, 10480 + 1219, 1221, 1237, 1239, 3538, 3547, 5082, 5084, 5101, 5110, 5119, 5128, 5284, 5286, 5515, 5517, 6198, + 6200, 6255, 6257, 6894, 6903, 7036, 7045, 8547, 8549, 9171, 9173, 9273, 9275, 10274, 10276, 10471, 10480, + 13020, 13022, 17235, 17237, 18208, 19843, 19852, 19983, 19992, 20276, 20285, 22817, 22826, 35927, 35929 } ---[[ (Not currently used, but probably useful to keep up to date) openQuestDoors = { 1224, 1226, 1242, 1244, 1256, 1258, 3543, 3552, 5106, 5115, 5124, 5133, 5289, 5291, 5746, 5749, 6203, 6205, 6260, 6262, 6899, 6908, 7041, 7050, 8552, 8554, 9176, 9178, 9278, 9280, 10279, 10281, 10476, 10485, - 10783, 10792, 12098, 12105, 12194, 12203 + 10783, 10792, 12098, 12105, 12196, 12205, 14639, 14646, 19848, 19857, 19988, 19997, 20281, 20290, 22822, + 22831, 25163, 25165, 25289, 25296, 32698, 32700, 32702, 32704, 34320, 34322, 34225, 34227 } -]] -- closedQuestDoors = { - 1223, 1225, 1241, 1243, 1255, 1257, 3542, 3551, 5105, 5114, 5123, 5132, 5288, - 5290, 5745, 5748, 6202, 6204, 6259, 6261, 6898, 6907, 7040, 7049, 8551, 8553, - 9175, 9177, 9277, 9279, 10278, 10280, 10475, 10484, 10782, 10791, 12097, 12104, - 12193, 12202 + 1223, 1225, 1241, 1243, 1255, 1257, 3542, 3551, 5105, 5114, 5123, 5132, 5288, 5290, 5745, 5748, 6202, + 6204, 6259, 6261, 6898, 6907, 7040, 7049, 8551, 8553, 9175, 9177, 9277, 9279, 10278, 10280, 10475, 10484, + 10782, 10791, 12097, 12104, 12195, 12204, 14638, 14645, 19847, 19856, 19987, 19996, 20280, 20289, 22821, + 22830, 25162, 25164, 25288, 25295, 32697, 32699, 32701, 32703, 34319, 34321, 34224, 34226 } ---[[ (Not currently used, but probably useful to keep up to date) openLevelDoors = { 1228, 1230, 1246, 1248, 1260, 1262, 3541, 3550, 5104, 5113, 5122, 5131, 5293, 5295, 6207, 6209, 6264, 6266, 6897, 6906, 7039, 7048, 8556, 8558, 9180, 9182, 9282, 9284, 10283, 10285, 10474, 10483, 10781, - 10790, 12096, 12103, 12196, 12205 + 10790, 12096, 12103, 12194, 12203, 19846, 19855, 19986, 19995, 20279, 20288, 22820, 22829, 25287, 25294 } -]] -- closedLevelDoors = { - 1227, 1229, 1245, 1247, 1259, 1261, 3540, 3549, 5103, 5112, 5121, 5130, 5292, - 5294, 6206, 6208, 6263, 6265, 6896, 6905, 7038, 7047, 8555, 8557, 9179, 9181, - 9281, 9283, 10282, 10284, 10473, 10482, 10780, 10789, 12095, 12102, 12195, - 12204 + 1227, 1229, 1245, 1247, 1259, 1261, 3540, 3549, 5103, 5112, 5121, 5130, 5292, 5294, 6206, 6208, 6263, + 6265, 6896, 6905, 7038, 7047, 8555, 8557, 9179, 9181, 9281, 9283, 10282, 10284, 10473, 10482, 10780, + 10789, 12095, 12102, 12193, 12202, 19845, 19854, 19985, 19994, 20278, 20287, 22819, 22828, 25286, 25293 } +-- LuaFormatter on function getDistanceBetween(firstPosition, secondPosition) local xDif = math.abs(firstPosition.x - secondPosition.x) @@ -84,32 +98,39 @@ function getFormattedWorldTime() end function getLootRandom() - return math.random(0, MAX_LOOTCHANCE) / - configManager.getNumber(configKeys.RATE_LOOT) + return math.random(0, MAX_LOOTCHANCE) / configManager.getNumber(configKeys.RATE_LOOT) end +---@generic T: table, K, V +---@param array T The table to search in +---@param value K The value to search for +---@return boolean found Whether the value was found table.contains = function(array, value) - for _, targetColumn in pairs(array) do - if targetColumn == value then return true end - end + for _, targetColumn in pairs(array) do if targetColumn == value then return true end end return false end +---@param str string +---@param sep string +---@return table string.split = function(str, sep) local res = {} for v in str:gmatch("([^" .. sep .. "]+)") do res[#res + 1] = v end return res end +---@param str string +---@param sep string +---@return table string.splitTrimmed = function(str, sep) local res = {} for v in str:gmatch("([^" .. sep .. "]+)") do res[#res + 1] = v:trim() end return res end -string.trim = function(str) - return str:match '^()%s*$' and '' or str:match '^%s*(.*%S)' -end +---@param str string +---@return string +string.trim = function(str) return str:match '^()%s*$' and '' or str:match '^%s*(.*%S)' end do local function tchelper(first, rest) return first:upper() .. rest:lower() end @@ -122,8 +143,6 @@ do string.titleCase = function(str) return str:gsub("(%a)([%w_']*)", tchelper) end end -if not nextUseStaminaTime then nextUseStaminaTime = {} end - function getPlayerDatabaseInfo(name_or_guid) local sql_where = "" diff --git a/data/lib/compat/compat.lua b/data/lib/compat/compat.lua index 6afaa90..79d2946 100644 --- a/data/lib/compat/compat.lua +++ b/data/lib/compat/compat.lua @@ -1839,6 +1839,17 @@ do function getStatName(stat) return stats[stat] or "unknown" end end +do + local rates = { + [ExperienceRateType.BASE] = "base", + [ExperienceRateType.LOW_LEVEL] = "low level", + [ExperienceRateType.BONUS] = "bonus", + [ExperienceRateType.STAMINA] = "stamina" + } + + function getExperienceRateName(rate) return rates[rate] or "unknown" end +end + function indexToCombatType(idx) return 1 << idx end function showpos(v) return v > 0 and "+" or "-" end @@ -1873,4 +1884,17 @@ do ---@param obj any ---@param class table function isClass(obj, class) return getmetatable(obj) == class end + + ---@param cylinder Thing + ---@return Player? + function getPlayerFromCylinder(cylinder) + if isClass(cylinder, Player) then + ---@cast cylinder Player + return cylinder + elseif isClass(cylinder, Item) or isClass(cylinder, Container) then + ---@cast cylinder Item + local topParent = cylinder:getTopParent() + if topParent then return topParent:getPlayer() end + end + end end diff --git a/data/lib/core/actionids.lua b/data/lib/core/actionids.lua index 2d5b292..0cd0e8e 100644 --- a/data/lib/core/actionids.lua +++ b/data/lib/core/actionids.lua @@ -5,3 +5,20 @@ actionIds = { citizenship = 30020, -- citizenship teleport citizenshipLast = 30050 -- citizenship teleport last } + +-- Check duplicates actionIds +do + local duplicates = {} + for name, id in pairs(actionIds) do + if duplicates[id] then error("Duplicate actionId: " .. id) end + duplicates[id] = name + end + + local __index = function(self, key) + local aid = actionIds[key] + if not aid then debugPrint("Invalid actionId: " .. key) end + return aid + end + + setmetatable(actionIds, {__index = __index}) +end diff --git a/data/lib/core/container.lua b/data/lib/core/container.lua index d1e02dd..ac2df79 100644 --- a/data/lib/core/container.lua +++ b/data/lib/core/container.lua @@ -1,15 +1,18 @@ -function Container.isContainer(self) return true end +function Container:isContainer() return true end -function Container.createLootItem(self, item) +function Container:createLootItem(lootItem) if self:getEmptySlots() == 0 then return true end local itemCount = 0 local randvalue = getLootRandom() - local itemType = ItemType(item.itemId) + local itemType = ItemType(lootItem.itemId) - if randvalue < item.chance then + if randvalue < lootItem.chance then if itemType:isStackable() then - itemCount = math.floor(randvalue % item.maxCount) + 1 + local max = math.floor(randvalue % lootItem.maxCount) + 1 + local min = lootItem.minCount ~= 0 and math.floor(randvalue % lootItem.minCount) + 1 or max + if min > max then min, max = max, min end + itemCount = math.random(min, max) else itemCount = 1 end @@ -19,33 +22,31 @@ function Container.createLootItem(self, item) local count = math.min(itemType:getStackSize(), itemCount) local subType = count - if itemType:isFluidContainer() then subType = math.max(0, item.subType) end + if itemType:isFluidContainer() then subType = math.max(0, lootItem.subType) end - local tmpItem = Game.createItem(item.itemId, subType) + local tmpItem = Game.createItem(lootItem.itemId, subType) if not tmpItem then return false end local tmpContainer = tmpItem:getContainer() if tmpContainer then - for i = 1, #item.childLoot do - if not tmpContainer:createLootItem(item.childLoot[i]) then + for i = 1, #lootItem.childLoot do + if not tmpContainer:createLootItem(lootItem.childLoot[i]) then tmpContainer:remove() return false end end - if #item.childLoot > 0 and tmpContainer:getSize() == 0 then + if #lootItem.childLoot > 0 and tmpContainer:getSize() == 0 then tmpItem:remove() return true end end - if item.subType ~= -1 then - tmpItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, item.subType) - end + if lootItem.subType ~= -1 then tmpItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, lootItem.subType) end - if item.actionId ~= -1 then tmpItem:setActionId(item.actionId) end + if lootItem.actionId ~= -1 then tmpItem:setActionId(lootItem.actionId) end - if item.text and item.text ~= "" then tmpItem:setText(item.text) end + if lootItem.text and lootItem.text ~= "" then tmpItem:setText(lootItem.text) end local ret = self:addItemEx(tmpItem) if ret ~= RETURNVALUE_NOERROR then tmpItem:remove() end @@ -59,9 +60,8 @@ function Container:getContentDescription() local items = self:getItems() if items and #items > 0 then local loot = {} - for _, item in ipairs(items) do - loot[#loot + 1] = string.format("%s", item:getNameDescription( - item:getSubType(), true)) + for _, lootItem in ipairs(items) do + loot[#loot + 1] = lootItem:getNameDescription(lootItem:getSubType(), true) end return table.concat(loot, ", ") diff --git a/data/lib/core/game.lua b/data/lib/core/game.lua index 5ab4304..41661b7 100644 --- a/data/lib/core/game.lua +++ b/data/lib/core/game.lua @@ -44,12 +44,6 @@ function Game.getSkillType(weaponType) return SKILL_FIST end -if not globalStorageTable then globalStorageTable = {} end - -function Game.getStorageValue(key) return globalStorageTable[key] or -1 end - -function Game.setStorageValue(key, value) globalStorageTable[key] = value end - do local cdShort = {"d", "h", "m", "s"} local cdLong = {" day", " hour", " minute", " second"} diff --git a/data/lib/core/item.lua b/data/lib/core/item.lua index 4bd11f8..5c4f745 100644 --- a/data/lib/core/item.lua +++ b/data/lib/core/item.lua @@ -138,6 +138,10 @@ do function StringStream() return setmetatable({}, StreamMeta) end + ---@param it ItemType + ---@param item Item + ---@param subType integer + ---@param addArticle boolean local function internalItemGetNameDescription(it, item, subType, addArticle) subType = subType or (item and item:getSubType() or -1) local ss = StringStream() @@ -278,6 +282,8 @@ do -- melee weapons and missiles -- atk x physical +y% element + elseif itemType:isWand() then + descriptions[#descriptions + 1] = fmt("Magic Atk:%d", attack) elseif table.contains(showAtkWeaponTypes, weaponType) then local atkString = fmt("Atk:%d", attack) local elementDmg = itemType:getElementDamage() @@ -349,15 +355,16 @@ do -- display the buffs for _, statData in pairs(stats) do local displayValues = {} - if statData.flat then displayValues[#displayValues + 1] = statData.flat end + if statData.flat then displayValues[#displayValues + 1] = fmt("%+d", statData.flat) end - if statData.percent then displayValues[#displayValues + 1] = statData.percent end + if statData.percent then + displayValues[#displayValues + 1] = fmt("%+d%%", statData.percent - 100) + end -- desired format examples: -- +5% -- +20 and 5% - if #displayValues > 0 then - displayValues[1] = fmt("%+d", displayValues[1]) + if #displayValues ~= 0 then descriptions[#descriptions + 1] = fmt("%s %s", statData.name, concat(displayValues, " and ")) end end @@ -376,7 +383,17 @@ do do for element, value in pairs(abilities.specialMagicLevel) do if value ~= 0 then - descriptions[#descriptions + 1] = fmt("%s magic level %+d", getCombatName(2^(element-1)), value) + descriptions[#descriptions + 1] = fmt("%s magic level %+d", getCombatName(2 ^ (element - 1)), + value) + end + end + end + + -- experience rates + do + for type, rate in ipairs(abilities.experienceRate) do + if rate ~= 0 then + descriptions[#descriptions + 1] = fmt("xp rate %s %+d%%", getExperienceRateName(type), rate) end end end @@ -401,20 +418,63 @@ do -- protections do - local protections = {} - for element, value in pairs(abilities.absorbPercent) do - if value ~= 0 then - protections[#protections + 1] = fmt("%s %+d%%", getCombatName(2 ^ (element - 1)), value) + local absorbPercent = abilities.absorbPercent + local protectionPhysical = absorbPercent[1] --[[@as integer?]] + for elem = 2, #absorbPercent do + local val = absorbPercent[elem] + if protectionPhysical ~= val then + protectionPhysical = nil + break end end - if #protections > 0 then - descriptions[#descriptions + 1] = fmt("protection %s", concat(protections, ", ")) + if protectionPhysical and protectionPhysical ~= 0 then + descriptions[#descriptions + 1] = fmt("protection all %+d%%", protectionPhysical) + else + local protections = {} + for element, value in pairs(abilities.absorbPercent) do + if value ~= 0 then + protections[#protections + 1] = fmt("%s %+d%%", getCombatName(2 ^ (element - 1)), value) + end + end + + if #protections > 0 then + descriptions[#descriptions + 1] = fmt("protection %s", concat(protections, ", ")) + end end end -- damage reflection -- to do + do + local reflectPercent = abilities.reflectPercent + local reflectChance = abilities.reflectChance + end + + -- boost percent + do + local all = abilities.boostPercent[1] --[[@as integer?]] + for elem = 2, #abilities.boostPercent do + local val = abilities.boostPercent[elem] + if all ~= val then + all = nil + break + end + end + + if all and all ~= 0 then + descriptions[#descriptions + 1] = fmt("boost all %+d%%", all) + else + local boosts = {} + for element, value in pairs(abilities.boostPercent) do + if value ~= 0 then + boosts[#boosts + 1] = fmt("%s %+d%%", getCombatName(2 ^ (element - 1)), value) + end + end + + if #boosts > 0 then descriptions[#descriptions + 1] = fmt("boost %s", concat(boosts, ", ")) end + end + end -- magic shield (classic) if abilities.manaShield then descriptions[#descriptions + 1] = "magic shield" end @@ -423,8 +483,35 @@ do -- to do -- regeneration - if abilities.manaGain > 0 or abilities.healthGain > 0 or abilities.regeneration then - descriptions[#descriptions + 1] = "faster regeneration" + if abilities.regeneration then + local displayHealth = {} + local displayMana = {} + + if abilities.healthGain ~= 0 then + displayHealth[#displayHealth + 1] = fmt("%+d", abilities.healthGain) + end + + if abilities.healthGainPercent ~= 0 then + displayHealth[#displayHealth + 1] = fmt("%+d%%", abilities.healthGainPercent - 100) + end + + if abilities.manaGain ~= 0 then displayMana[#displayMana + 1] = fmt("%+d", abilities.manaGain) end + + if abilities.manaGainPercent ~= 0 then + displayMana[#displayMana + 1] = fmt("%+d%%", abilities.manaGainPercent - 100) + end + + local displayValues = {} + if #displayHealth ~= 0 then + displayValues[#displayValues + 1] = fmt("hp %s", concat(displayHealth, ", ")) + end + if #displayMana ~= 0 then + displayValues[#displayValues + 1] = fmt("mp %s", concat(displayMana, ", ")) + end + + if #displayValues ~= 0 then + descriptions[#descriptions + 1] = fmt("regeneration %s", concat(displayValues, " and ")) + end end -- invisibility diff --git a/data/lib/core/player.lua b/data/lib/core/player.lua index 07fda9c..0ffa120 100644 --- a/data/lib/core/player.lua +++ b/data/lib/core/player.lua @@ -78,7 +78,7 @@ end function Player.isUsingOtClient(self) return self:getClient().os >= CLIENTOS_OTCLIENT_LINUX end function Player.sendExtendedOpcode(self, opcode, buffer) - if not self:isUsingOtClient() then return false end + if not self:isUsingOtcV8() then return false end local networkMessage = NetworkMessage() networkMessage:addByte(0x32) @@ -296,3 +296,52 @@ function Player.getExhaustion(self, key) end function Player.hasExhaustion(self, key) return self:getExhaustion(key) > 0 end + +---@param type ExperienceRateType +---@param value integer +function Player:addExperienceRate(type, value) + return self:setExperienceRate(type, self:getExperienceRate(type) + value) +end + +do + if not nextUseStaminaTime then nextUseStaminaTime = {} end + + local function useStamina(player) + local staminaMinutes = player:getStamina() + if staminaMinutes == 0 then return end + + local playerId = player:getId() + if not nextUseStaminaTime[playerId] then nextUseStaminaTime[playerId] = 0 end + + local currentTime = os.time() + local timePassed = currentTime - nextUseStaminaTime[playerId] + if timePassed <= 0 then return end + + if timePassed > 60 then + if staminaMinutes > 2 then + staminaMinutes = staminaMinutes - 2 + else + staminaMinutes = 0 + end + nextUseStaminaTime[playerId] = currentTime + 120 + else + staminaMinutes = staminaMinutes - 1 + nextUseStaminaTime[playerId] = currentTime + 60 + end + player:setStamina(math.floor(staminaMinutes)) + end + + function Player:updateStamina() + if not configManager.getBoolean(configKeys.STAMINA_SYSTEM) then return false end + + useStamina(self) + + local staminaMinutes = self:getStamina() + if staminaMinutes > 2400 and self:isPremium() then + self:addExperienceRate(ExperienceRateType.STAMINA, 50) + elseif staminaMinutes <= 840 then + self:addExperienceRate(ExperienceRateType.STAMINA, -50) + end + return true + end +end diff --git a/data/lib/core/position.lua b/data/lib/core/position.lua index 9f34ae4..2562240 100644 --- a/data/lib/core/position.lua +++ b/data/lib/core/position.lua @@ -1,3 +1,34 @@ +local mt = rawgetmetatable("Position") + +---@param lhs Position +---@param rhs Position +function mt.__add(lhs, rhs) + local stackpos = lhs.stackpos or rhs.stackpos + return Position(lhs.x + (rhs.x or 0), lhs.y + (rhs.y or 0), lhs.z + (rhs.z or 0), + stackpos) +end + +---@param lhs Position +---@param rhs Position +function mt.__sub(lhs, rhs) + local stackpos = lhs.stackpos or rhs.stackpos + return Position(lhs.x - (rhs.x or 0), lhs.y - (rhs.y or 0), lhs.z - (rhs.z or 0), + stackpos) +end + +---@param lhs Position +---@param rhs Position +function mt.__concat(lhs, rhs) return tostring(lhs) .. tostring(rhs) end + +---@param lhs Position +---@param rhs Position +function mt.__eq(lhs, rhs) return lhs.x == rhs.x and lhs.y == rhs.y and lhs.z == rhs.z end + +---@param self Position +function mt.__tostring(self) + return string.format("Position(%d, %d, %d)", self.x, self.y, self.z) +end + Position.directionOffset = { [DIRECTION_NORTH] = {x = 0, y = -1}, [DIRECTION_EAST] = {x = 1, y = 0}, @@ -9,8 +40,17 @@ Position.directionOffset = { [DIRECTION_NORTHEAST] = {x = 1, y = -1} } +local abs, max = math.abs, math.max +function Position:getDistance(positionEx) + local dx = abs(self.x - positionEx.x) + local dy = abs(self.y - positionEx.y) + local dz = abs(self.z - positionEx.z) + return max(dx, dy, dz) +end + function Position:getTile() return Tile(self) end +--- This method modifies the position and returns self function Position:getNextPosition(direction, steps) local offset = Position.directionOffset[direction] if offset then @@ -18,33 +58,36 @@ function Position:getNextPosition(direction, steps) self.x = self.x + offset.x * steps self.y = self.y + offset.y * steps end + return self end -function Position:moveUpstairs() +do local swap = function(lhs, rhs) lhs.x, rhs.x = rhs.x, lhs.x lhs.y, rhs.y = rhs.y, lhs.y lhs.z, rhs.z = rhs.z, lhs.z end - self.z = self.z - 1 + function Position:moveUpstairs() + self.z = self.z - 1 - local defaultPosition = self + Position.directionOffset[DIRECTION_SOUTH] - local toTile = Tile(defaultPosition) - if not toTile or not toTile:isWalkable() then - for direction = DIRECTION_NORTH, DIRECTION_NORTHEAST do - if direction == DIRECTION_SOUTH then direction = DIRECTION_WEST end + local defaultPosition = self + Position.directionOffset[DIRECTION_SOUTH] + local toTile = Tile(defaultPosition) + if not toTile or not toTile:isWalkable() then + for direction = DIRECTION_NORTH, DIRECTION_NORTHEAST do + if direction == DIRECTION_SOUTH then direction = DIRECTION_WEST end - local position = self + Position.directionOffset[direction] - toTile = Tile(position) - if toTile and toTile:isWalkable() then - swap(self, position) - return self + local position = self + Position.directionOffset[direction] + toTile = Tile(position) + if toTile and toTile:isWalkable() then + swap(self, position) + return self + end end end + swap(self, defaultPosition) + return self end - swap(self, defaultPosition) - return self end function Position:isInRange(from, to) @@ -63,18 +106,15 @@ function Position:isInRange(from, to) } } - if self.x >= zone.nW.x and self.x <= zone.sE.x and self.y >= zone.nW.y and - self.y <= zone.sE.y and self.z >= zone.nW.z and self.z <= zone.sE.z then - return true - end + if self.x >= zone.nW.x and self.x <= zone.sE.x and self.y >= zone.nW.y and self.y <= + zone.sE.y and self.z >= zone.nW.z and self.z <= zone.sE.z then return true end return false end function Position:notifySummonAppear(summon) local spectators = Game.getSpectators(self) for _, spectator in ipairs(spectators) do - if spectator:isMonster() and spectator ~= summon then - spectator:addTarget(summon) - end + local monster = spectator:getMonster() + if monster and monster ~= summon then monster:addTarget(summon) end end end diff --git a/data/lib/core/storages.lua b/data/lib/core/storages.lua index bda11ba..1555b17 100644 --- a/data/lib/core/storages.lua +++ b/data/lib/core/storages.lua @@ -17,3 +17,37 @@ PlayerStorageKeys = { } GlobalStorageKeys = {} + +-- Check duplicates player storage keys +do + local duplicates = {} + for name, id in pairs(PlayerStorageKeys) do + if duplicates[id] then error("Duplicate keyStorage: " .. id) end + duplicates[id] = name + end + + local __index = function(self, key) + local keyStorage = PlayerStorageKeys[key] + if not keyStorage then debugPrint("Invalid keyStorage: " .. key) end + return keyStorage + end + + setmetatable(PlayerStorageKeys, {__index = __index}) +end + +-- Check duplicates global storage keys +do + local duplicates = {} + for name, id in pairs(GlobalStorageKeys) do + if duplicates[id] then error("Duplicate keyStorage: " .. id) end + duplicates[id] = name + end + + local __index = function(self, key) + local keyStorage = GlobalStorageKeys[key] + if not keyStorage then debugPrint("Invalid keyStorage: " .. key) end + return keyStorage + end + + setmetatable(GlobalStorageKeys, {__index = __index}) +end diff --git a/data/scripts/eventcallbacks/player/default_onGainExperience.lua b/data/scripts/eventcallbacks/player/default_onGainExperience.lua index 6de9e1b..dae1acb 100644 --- a/data/scripts/eventcallbacks/player/default_onGainExperience.lua +++ b/data/scripts/eventcallbacks/player/default_onGainExperience.lua @@ -2,31 +2,6 @@ local soulCondition = Condition(CONDITION_SOUL, CONDITIONID_DEFAULT) soulCondition:setTicks(4 * 60 * 1000) soulCondition:setParameter(CONDITION_PARAM_SOULGAIN, 1) -local function useStamina(player) - local staminaMinutes = player:getStamina() - if staminaMinutes == 0 then return end - - local playerId = player:getId() - if not nextUseStaminaTime[playerId] then nextUseStaminaTime[playerId] = 0 end - - local currentTime = os.time() - local timePassed = currentTime - nextUseStaminaTime[playerId] - if timePassed <= 0 then return end - - if timePassed > 60 then - if staminaMinutes > 2 then - staminaMinutes = staminaMinutes - 2 - else - staminaMinutes = 0 - end - nextUseStaminaTime[playerId] = currentTime + 120 - else - staminaMinutes = staminaMinutes - 1 - nextUseStaminaTime[playerId] = currentTime + 60 - end - player:setStamina(staminaMinutes) -end - local event = Event() function event.onGainExperience(player, source, exp, rawExp) @@ -35,8 +10,7 @@ function event.onGainExperience(player, source, exp, rawExp) -- Soul regeneration local vocation = player:getVocation() if player:getSoul() < vocation:getMaxSoul() and exp >= player:getLevel() then - soulCondition:setParameter(CONDITION_PARAM_SOULTICKS, - vocation:getSoulGainTicks() * 1000) + soulCondition:setParameter(CONDITION_PARAM_SOULTICKS, vocation:getSoulGainTicks() * 1000) player:addCondition(soulCondition) end @@ -44,16 +18,21 @@ function event.onGainExperience(player, source, exp, rawExp) exp = exp * Game.getExperienceStage(player:getLevel()) -- Stamina modifier - if configManager.getBoolean(configKeys.STAMINA_SYSTEM) then - useStamina(player) - - local staminaMinutes = player:getStamina() - if staminaMinutes > 2400 and player:isPremium() then - exp = exp * 1.5 - elseif staminaMinutes <= 840 then - exp = exp * 0.5 - end - end + player:updateStamina() + + -- Experience Rates + local staminaRate = player:getExperienceRate(ExperienceRateType.STAMINA) + if staminaRate ~= 100 then exp = exp * staminaRate / 100 end + + local baseRate = player:getExperienceRate(ExperienceRateType.BASE) + if baseRate ~= 100 then exp = exp * baseRate / 100 end + + local lowLevelRate = player:getExperienceRate(ExperienceRateType.LOW_LEVEL) + if lowLevelRate ~= 100 then exp = exp * lowLevelRate / 100 end + + local bonusRate = player:getExperienceRate(ExperienceRateType.BONUS) + if bonusRate ~= 100 then exp = exp * bonusRate / 100 end + return exp end diff --git a/data/scripts/eventcallbacks/player/default_onLook.lua b/data/scripts/eventcallbacks/player/default_onLook.lua index 5c6f50b..99c0a30 100644 --- a/data/scripts/eventcallbacks/player/default_onLook.lua +++ b/data/scripts/eventcallbacks/player/default_onLook.lua @@ -1,58 +1,69 @@ +local fmt = string.format + local event = Event() -event.onLook = function(self, thing, position, distance, description) - local description = "You see " .. thing:getDescription(distance) - if self:getGroup():getAccess() then - if thing:isItem() then - description = string.format("%s\nItem ID: %d", description, thing:getId()) +event.onLook = function(player, thing, position, distance, description) + description = "You see " .. thing:getDescription(distance) + if player:getGroup():getAccess() then + local item = thing:getItem() + if item then + description = fmt("%s\nItem ID: %d", description, item:getId()) - local actionId = thing:getActionId() - if actionId ~= 0 then - description = string.format("%s, Action ID: %d", description, actionId) - end + local actionId = item:getActionId() + if actionId ~= 0 then description = fmt("%s, Action ID: %d", description, actionId) end - local uniqueId = thing:getAttribute(ITEM_ATTRIBUTE_UNIQUEID) + local uniqueId = item:getAttribute(ITEM_ATTRIBUTE_UNIQUEID) if uniqueId > 0 and uniqueId < 65536 then - description = string.format("%s, Unique ID: %d", description, uniqueId) + description = fmt("%s, Unique ID: %d", description, uniqueId) end - local itemType = thing:getType() + local itemType = item:getType() local transformEquipId = itemType:getTransformEquipId() local transformDeEquipId = itemType:getTransformDeEquipId() if transformEquipId ~= 0 then - description = string.format("%s\nTransforms to: %d (onEquip)", description, - transformEquipId) + description = fmt("%s\nTransforms to: %d (onEquip)", description, transformEquipId) elseif transformDeEquipId ~= 0 then - description = string.format("%s\nTransforms to: %d (onDeEquip)", - description, transformDeEquipId) + description = + fmt("%s\nTransforms to: %d (onDeEquip)", description, transformDeEquipId) end local decayId = itemType:getDecayId() - if decayId ~= -1 then - description = string.format("%s\nDecays to: %d", description, decayId) - end - elseif thing:isCreature() then - local str = "%s\nHealth: %d / %d" - if thing:isPlayer() and thing:getMaxMana() > 0 then - str = string.format("%s, Mana: %d / %d", str, thing:getMana(), - thing:getMaxMana()) - elseif thing:isMonster() then - local raceId = thing:getType():raceId() - if raceId ~= 0 then str = string.format("%s\nRaceId: %d", str, raceId) end + if decayId ~= -1 then description = fmt("%s\nDecays to: %d", description, decayId) end + else + local thingCreature = thing:getCreature() + local thingPlayer = thing:getPlayer() + local thingMonster = thing:getMonster() + if thingCreature then + local str = "%s\nHealth: %d / %d" + + if thingPlayer then + local thinPlayerMana = thingPlayer:getMana() + if thinPlayerMana > 0 then + str = fmt("%s, Mana: %d / %d", str, thinPlayerMana, thingPlayer:getMaxMana()) + end + elseif thingMonster then + local raceId = thingMonster:getType():raceId() + if raceId ~= 0 then str = fmt("%s\nRaceId: %d", str, raceId) end + end + + description = fmt(str, description, thingCreature:getHealth(), + thingCreature:getMaxHealth()) .. "." end - description = string.format(str, description, thing:getHealth(), - thing:getMaxHealth()) .. "." - end - local position = thing:getPosition() - description = string.format("%s\nPosition: %d, %d, %d", description, - position.x, position.y, position.z) + local thingPosition = thing:getPosition() + if thingPosition then + description = fmt("%s\nPosition: %d, %d, %d", description, thingPosition.x, + thingPosition.y, thingPosition.z) + end - if thing:isCreature() then - if thing:isPlayer() then - description = string.format("%s\nIP: %s.", description, - Game.convertIpToString(thing:getIp())) + if thingPlayer then + description = fmt("%s\nIP: %s.", description, + Game.convertIpToString(thingPlayer:getIp())) + if thingPlayer:getGroup():getAccess() then + description = fmt("%s\nVocation: %s.", description, + thingPlayer:getVocation():getName()) + end end end end diff --git a/data/talkactions/scripts/attributes.lua b/data/talkactions/scripts/attributes.lua index 842fec3..307e228 100644 --- a/data/talkactions/scripts/attributes.lua +++ b/data/talkactions/scripts/attributes.lua @@ -1,34 +1,59 @@ +local function message(player, msg, ...) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format(msg, ...)) +end + local function setAttribute(player, thing, attribute, value) local attributeId = Game.getItemAttributeByName(attribute) if attributeId == ITEM_ATTRIBUTE_NONE then return "Invalid attribute name." end if not thing:setAttribute(attribute, value) then return "Could not set attribute." end - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, - string.format("Attribute %s set to: %s", attribute, thing:getAttribute(attributeId))) + message(player, "Attribute %s set to: %s", attribute, thing:getAttribute(attributeId)) thing:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) return true end +---@type {[string]: fun(player: Player, creature: Creature, value: string): boolean} +local creatureAttrs = { + health = function(player, creature, value) + creature:setHealth(math.floor(tonumber(value) or 0), player) + return true + end, + + addSummon = function(player, creature, value) + local summon = Game.createMonster(value, creature:getPosition()) + if not summon then return false end + + creature:addSummon(summon) + return true + end, + + event = function (player, creature, value) + creature:registerEvent(value) + return true + end +} + function onSay(player, words, param) local position = player:getPosition() position:getNextPosition(player:getDirection()) local tile = Tile(position) if not tile then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "There is no tile in front of you.") + message(player, "There is no tile in front of you.") return false end local thing = tile:getTopVisibleThing(player) if not thing then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "There is an empty tile in front of you.") + message(player, "There is an empty tile in front of you.") return false end + ---@type string, string, string local attribute, value, extra = unpack(param:splitTrimmed(",")) if attribute == "" then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format("Usage: %s attribute, value.", words)) + message(player, "Usage: %s attribute, value.", words) return false end @@ -44,18 +69,36 @@ function onSay(player, words, param) end if not item:setCustomAttribute(value, extra) then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Could not set custom attribute.") + message(player, "Could not set custom attribute.") return false end - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, - string.format("Custom attribute %s set to: %s", value, item:getCustomAttribute(value))) + message(player, "Custom attribute %s set to: %s", value, item:getCustomAttribute(value)) position:sendMagicEffect(CONST_ME_MAGIC_GREEN) else - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, response) + message(player, response) end - else - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Thing in front of you is not supported.") + return false end + + local creature = thing:getCreature() + if creature then + local creatureAttr = creatureAttrs[attribute] + if not creatureAttr then + message(player, "Invalid attribute name.") + return false + end + + local response = creatureAttr(player, creature, value) + if not response then + message(player, "Could not set attribute.") + return false + end + + position:sendMagicEffect(CONST_ME_MAGIC_GREEN) + return true + end + + player:sendCancelMessage("You can only use this command on items or creatures.") return false end diff --git a/data/talkactions/scripts/serverinfo.lua b/data/talkactions/scripts/serverinfo.lua index 41a85bc..a87ad2e 100644 --- a/data/talkactions/scripts/serverinfo.lua +++ b/data/talkactions/scripts/serverinfo.lua @@ -1,12 +1,21 @@ +local fmt = string.format + function onSay(player, words, param) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, - "Server Info:" .. "\nExp rate: " .. - Game.getExperienceStage(player:getLevel()) .. - "\nSkill rate: " .. - configManager.getNumber(configKeys.RATE_SKILL) .. - "\nMagic rate: " .. - configManager.getNumber(configKeys.RATE_MAGIC) .. - "\nLoot rate: " .. - configManager.getNumber(configKeys.RATE_LOOT)) + + local desc = {"Server Info:\n"} + + -- Global Rates + desc[#desc + 1] = fmt("Exp rate: %s", Game.getExperienceStage(player:getLevel())) + desc[#desc + 1] = fmt("Skill rate: %s", configManager.getNumber(configKeys.RATE_SKILL)) + desc[#desc + 1] = fmt("Magic rate: %s", configManager.getNumber(configKeys.RATE_MAGIC)) + desc[#desc + 1] = fmt("Loot rate: %s", configManager.getNumber(configKeys.RATE_LOOT)) + + -- Player Rates + desc[#desc + 1] = fmt("XP rate base: %+d%%", player:getExperienceRate(ExperienceRateType.BASE) - 100) + desc[#desc + 1] = fmt("XP rate low level: %+d%%", player:getExperienceRate(ExperienceRateType.LOW_LEVEL) - 100) + desc[#desc + 1] = fmt("XP rate bonus: %+d%%", player:getExperienceRate(ExperienceRateType.BONUS) - 100) + desc[#desc + 1] = fmt("XP rate stamina: %+d%%", player:getExperienceRate(ExperienceRateType.STAMINA) - 100) + + player:popupFYI(table.concat(desc, "\n")) return false end