diff --git a/config.lua.dist b/config.lua.dist index a0e0a96f..d739a078 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -426,7 +426,6 @@ allowChangeOutfit = true freePremium = false kickIdlePlayerAfterMinutes = 15 maxMessageBuffer = 4 -emoteSpells = false allowWalkthrough = true coinPacketSize = 25 coinImagesURL = "http://127.0.0.1/images/store/" @@ -452,8 +451,10 @@ maxDamageReflection = 200 -- Chain system -- NOTE: combatChainDelay: set to minimum 50 miliseconds +-- NOTE: chainSystemModifyMagic will increase power of wands and rods based on player magic level toggleChainSystem = false chainSystemVipOnly = true +chainSystemModifyMagic = false combatChainDelay = 50 combatChainTargets = 5 combatChainSkillFormulaAxe = 0.9 @@ -463,6 +464,10 @@ combatChainSkillFormulaDistance = 0.9 combatChainSkillFormulaMissile = 0.9 combatChainSkillFormulaWandsAndRods = 1.0 +-- Spells +emoteSpells = false +spellNameInsteadOfWords = false + -- Global server Save -- NOTE: globalServerSaveNotifyDuration in minutes globalServerSaveNotifyMessage = true diff --git a/data-global/scripts/lib/register_actions.lua b/data-global/scripts/lib/register_actions.lua index 12ddb3e7..70a841ea 100644 --- a/data-global/scripts/lib/register_actions.lua +++ b/data-global/scripts/lib/register_actions.lua @@ -704,7 +704,10 @@ function onUsePick(player, item, fromPosition, target, toPosition, isHotkey) -- The Pits of Inferno Quest if toPosition == Position(32808, 32334, 11) then for i = 1, #lava do - Game.createItem(5815, 1, lava[i]) + local lavaTile = Tile(lava[i]):getItemById(21477) + if lavaTile then + lavaTile:transform(5815) + end end target:transform(3141) toPosition:sendMagicEffect(CONST_ME_SMOKE) diff --git a/data-global/scripts/quests/ferumbras_ascension/actions_flower_puzzle_lever.lua b/data-global/scripts/quests/ferumbras_ascension/actions_flower_puzzle_lever.lua index d09178c1..92584b80 100644 --- a/data-global/scripts/quests/ferumbras_ascension/actions_flower_puzzle_lever.lua +++ b/data-global/scripts/quests/ferumbras_ascension/actions_flower_puzzle_lever.lua @@ -1,13 +1,13 @@ local flowerPositions = { - [1] = { itemid = 3676, position = Position(33455, 32707, 14) }, - [2] = { itemid = 3676, position = Position(33460, 32707, 14) }, - [3] = { itemid = 3678, position = Position(33455, 32708, 14) }, - [4] = { itemid = 3677, position = Position(33457, 32707, 14) }, - [5] = { itemid = 3678, position = Position(33457, 32708, 14) }, - [6] = { itemid = 3677, position = Position(33456, 32708, 14) }, - [7] = { itemid = 3676, position = Position(33458, 32709, 14) }, - [8] = { itemid = 3677, position = Position(33459, 32708, 14) }, - [9] = { itemid = 3678, position = Position(33460, 32709, 14) }, + [1] = { itemid = 3676, position = Position(33455, 32708, 14) }, + [2] = { itemid = 3676, position = Position(33459, 32707, 14) }, + [3] = { itemid = 3676, position = Position(33460, 32708, 14) }, + [4] = { itemid = 3678, position = Position(33456, 32692, 14) }, + [5] = { itemid = 3678, position = Position(33458, 32692, 14) }, + [6] = { itemid = 3678, position = Position(33460, 32693, 14) }, + [7] = { itemid = 3677, position = Position(33457, 32686, 14) }, + [8] = { itemid = 3677, position = Position(33459, 32686, 14) }, + [9] = { itemid = 3677, position = Position(33456, 32687, 14) }, } local gates = { diff --git a/data-global/scripts/quests/ferumbras_ascension/movements_flower_puzzle.lua b/data-global/scripts/quests/ferumbras_ascension/movements_flower_puzzle.lua index de00f104..28df4983 100644 --- a/data-global/scripts/quests/ferumbras_ascension/movements_flower_puzzle.lua +++ b/data-global/scripts/quests/ferumbras_ascension/movements_flower_puzzle.lua @@ -2,188 +2,158 @@ local positions = { [1] = { squarePos = Position(33452, 32721, 14), flowerOne = Position(33455, 32685, 14), - flowerTwo = Position(33455, 32691, 14), - flowerThree = Position(33455, 32707, 14), + flowerTwo = Position(33455, 32686, 14), + flowerThree = Position(33455, 32687, 14), }, [2] = { squarePos = Position(33455, 32721, 14), flowerOne = Position(33456, 32685, 14), - flowerTwo = Position(33456, 32691, 14), - flowerThree = Position(33456, 32707, 14), + flowerTwo = Position(33456, 32686, 14), + flowerThree = Position(33456, 32687, 14), }, [3] = { squarePos = Position(33458, 32721, 14), flowerOne = Position(33457, 32685, 14), - flowerTwo = Position(33457, 32691, 14), - flowerThree = Position(33457, 32707, 14), + flowerTwo = Position(33457, 32686, 14), + flowerThree = Position(33457, 32687, 14), }, [4] = { squarePos = Position(33461, 32721, 14), flowerOne = Position(33458, 32685, 14), - flowerTwo = Position(33458, 32691, 14), - flowerThree = Position(33458, 32707, 14), + flowerTwo = Position(33458, 32686, 14), + flowerThree = Position(33458, 32687, 14), }, [5] = { squarePos = Position(33464, 32721, 14), flowerOne = Position(33459, 32685, 14), - flowerTwo = Position(33459, 32691, 14), - flowerThree = Position(33459, 32707, 14), + flowerTwo = Position(33459, 32686, 14), + flowerThree = Position(33459, 32687, 14), }, [6] = { squarePos = Position(33467, 32721, 14), flowerOne = Position(33460, 32685, 14), - flowerTwo = Position(33460, 32691, 14), - flowerThree = Position(33460, 32707, 14), + flowerTwo = Position(33460, 32686, 14), + flowerThree = Position(33460, 32687, 14), }, -- done [7] = { squarePos = Position(33452, 32724, 14), - flowerOne = Position(33455, 32686, 14), + flowerOne = Position(33455, 32691, 14), flowerTwo = Position(33455, 32692, 14), - flowerThree = Position(33455, 32708, 14), + flowerThree = Position(33455, 32693, 14), }, [8] = { squarePos = Position(33455, 32724, 14), - flowerOne = Position(33456, 32686, 14), + flowerOne = Position(33456, 32691, 14), flowerTwo = Position(33456, 32692, 14), - flowerThree = Position(33456, 32708, 14), + flowerThree = Position(33456, 32693, 14), }, [9] = { squarePos = Position(33458, 32724, 14), - flowerOne = Position(33457, 32686, 14), + flowerOne = Position(33457, 32691, 14), flowerTwo = Position(33457, 32692, 14), - flowerThree = Position(33457, 32708, 14), + flowerThree = Position(33457, 32693, 14), }, [10] = { squarePos = Position(33461, 32724, 14), - flowerOne = Position(33458, 32686, 14), + flowerOne = Position(33458, 32691, 14), flowerTwo = Position(33458, 32692, 14), - flowerThree = Position(33458, 32708, 14), + flowerThree = Position(33458, 32693, 14), }, [11] = { squarePos = Position(33464, 32724, 14), - flowerOne = Position(33459, 32686, 14), + flowerOne = Position(33459, 32691, 14), flowerTwo = Position(33459, 32692, 14), - flowerThree = Position(33459, 32708, 14), + flowerThree = Position(33459, 32693, 14), }, [12] = { squarePos = Position(33467, 32724, 14), - flowerOne = Position(33460, 32686, 14), + flowerOne = Position(33460, 32691, 14), flowerTwo = Position(33460, 32692, 14), - flowerThree = Position(33460, 32708, 14), + flowerThree = Position(33460, 32693, 14), }, -- done [13] = { squarePos = Position(33452, 32727, 14), - flowerOne = Position(33455, 32687, 14), - flowerTwo = Position(33455, 32693, 14), + flowerOne = Position(33455, 32707, 14), + flowerTwo = Position(33455, 32708, 14), flowerThree = Position(33455, 32709, 14), }, [14] = { squarePos = Position(33455, 32727, 14), - flowerOne = Position(33456, 32687, 14), - flowerTwo = Position(33456, 32693, 14), + flowerOne = Position(33456, 32707, 14), + flowerTwo = Position(33456, 32708, 14), flowerThree = Position(33456, 32709, 14), }, [15] = { squarePos = Position(33458, 32727, 14), - flowerOne = Position(33457, 32687, 14), - flowerTwo = Position(33457, 32693, 14), + flowerOne = Position(33457, 32707, 14), + flowerTwo = Position(33457, 32708, 14), flowerThree = Position(33457, 32709, 14), }, [16] = { squarePos = Position(33461, 32727, 14), - flowerOne = Position(33458, 32687, 14), - flowerTwo = Position(33458, 32693, 14), + flowerOne = Position(33458, 32707, 14), + flowerTwo = Position(33458, 32708, 14), flowerThree = Position(33458, 32709, 14), }, [17] = { squarePos = Position(33464, 32727, 14), - flowerOne = Position(33459, 32687, 14), - flowerTwo = Position(33459, 32693, 14), + flowerOne = Position(33459, 32707, 14), + flowerTwo = Position(33459, 32708, 14), flowerThree = Position(33459, 32709, 14), }, [18] = { squarePos = Position(33467, 32727, 14), - flowerOne = Position(33460, 32687, 14), - flowerTwo = Position(33460, 32693, 14), + flowerOne = Position(33460, 32707, 14), + flowerTwo = Position(33460, 32708, 14), flowerThree = Position(33460, 32709, 14), }, } local flowerPuzzle = MoveEvent() +function clearFlowers(tiles) + for _, tile in ipairs(tiles) do + local items = tile:getItems() + for i = 1, #items do + if items[i]:getId() == 3676 or items[i]:getId() == 3677 or items[i]:getId() == 3678 then + items[i]:remove() + end + end + end +end + function flowerPuzzle.onStepIn(creature, item, position, fromPosition) - for i = 1, #positions do - local itempos = positions[i] + for i, itempos in ipairs(positions) do if position == itempos.squarePos then - if item.itemid == 22538 then - item:transform(22539) - local flowerOne = Tile(itempos.flowerOne):getItemById(3676) or Tile(itempos.flowerOne):getItemById(3678) or Tile(itempos.flowerOne):getItemById(3677) - local flowerTwo = Tile(itempos.flowerTwo):getItemById(3676) or Tile(itempos.flowerTwo):getItemById(3678) or Tile(itempos.flowerTwo):getItemById(3677) - local flowerThree = Tile(itempos.flowerThree):getItemById(3676) or Tile(itempos.flowerThree):getItemById(3678) or Tile(itempos.flowerThree):getItemById(3677) - if not flowerOne then - flowerOne = Game.createItem(3677, 1, itempos.flowerOne) - end - if not flowerTwo then - flowerTwo = Game.createItem(3677, 1, itempos.flowerTwo) - end - if not flowerThree then - flowerThree = Game.createItem(3677, 1, itempos.flowerThree) - end - flowerOne:transform(3677) - flowerTwo:transform(3677) - flowerThree:transform(3677) - elseif item.itemid == 22539 then - item:transform(22540) - local flowerOne = Tile(itempos.flowerOne):getItemById(3676) or Tile(itempos.flowerOne):getItemById(3678) or Tile(itempos.flowerOne):getItemById(3677) - local flowerTwo = Tile(itempos.flowerTwo):getItemById(3676) or Tile(itempos.flowerTwo):getItemById(3678) or Tile(itempos.flowerTwo):getItemById(3677) - local flowerThree = Tile(itempos.flowerThree):getItemById(3676) or Tile(itempos.flowerThree):getItemById(3678) or Tile(itempos.flowerThree):getItemById(3677) - if not flowerOne then - flowerOne = Game.createItem(3678, 1, itempos.flowerOne) - end - if not flowerTwo then - flowerTwo = Game.createItem(3678, 1, itempos.flowerTwo) - end - if not flowerThree then - flowerThree = Game.createItem(3678, 1, itempos.flowerThree) - end - flowerOne:transform(3678) - flowerTwo:transform(3678) - flowerThree:transform(3678) - elseif item.itemid == 22540 then - item:transform(22541) - local flowerOne = Tile(itempos.flowerOne):getItemById(3676) or Tile(itempos.flowerOne):getItemById(3678) or Tile(itempos.flowerOne):getItemById(3677) - local flowerTwo = Tile(itempos.flowerTwo):getItemById(3676) or Tile(itempos.flowerTwo):getItemById(3678) or Tile(itempos.flowerTwo):getItemById(3677) - local flowerThree = Tile(itempos.flowerThree):getItemById(3676) or Tile(itempos.flowerThree):getItemById(3678) or Tile(itempos.flowerThree):getItemById(3677) - if not flowerOne then - flowerOne = Game.createItem(3676, 1, itempos.flowerOne) - end - if not flowerTwo then - flowerTwo = Game.createItem(3676, 1, itempos.flowerTwo) - end - if not flowerThree then - flowerThree = Game.createItem(3676, 1, itempos.flowerThree) - end - flowerOne:transform(3676) - flowerTwo:transform(3676) - flowerThree:transform(3676) - elseif item.itemid == 22541 then + local flowerOneTile = Tile(itempos.flowerOne) + local flowerTwoTile = Tile(itempos.flowerTwo) + local flowerThreeTile = Tile(itempos.flowerThree) + local flowerTiles = { flowerOneTile, flowerTwoTile, flowerThreeTile } + + local itemId + if i <= 6 then + itemId = 3677 + elseif i <= 12 then + itemId = 3678 + else + itemId = 3676 + end + + local step = item.itemid - 22538 + 1 + + if step == 4 then + clearFlowers(flowerTiles) item:transform(22538) - local flowerOne = Tile(itempos.flowerOne):getItemById(3676) or Tile(itempos.flowerOne):getItemById(3678) or Tile(itempos.flowerOne):getItemById(3677) - local flowerTwo = Tile(itempos.flowerTwo):getItemById(3676) or Tile(itempos.flowerTwo):getItemById(3678) or Tile(itempos.flowerTwo):getItemById(3677) - local flowerThree = Tile(itempos.flowerThree):getItemById(3676) or Tile(itempos.flowerThree):getItemById(3678) or Tile(itempos.flowerThree):getItemById(3677) - if flowerOne then - flowerOne:remove() - end - if flowerTwo then - flowerTwo:remove() - end - if flowerThree then - flowerThree:remove() - end + else + clearFlowers(flowerTiles) + Game.createItem(itemId, 1, flowerTiles[step]:getPosition()) + item:transform(item.itemid + 1) end + + return true end end - return true + return false end flowerPuzzle:type("stepin") diff --git a/data-global/startup/others/functions.lua b/data-global/startup/others/functions.lua index 362e2a38..b6aad1fc 100644 --- a/data-global/startup/others/functions.lua +++ b/data-global/startup/others/functions.lua @@ -3,11 +3,14 @@ function CreateMapItem(tablename) for index, value in pairs(tablename) do for i = 1, #value.itemPos do + local item local tile = Tile(value.itemPos[i]) -- Checks if the position is valid if tile then - if tile:getItemCountById(index) == 0 then + item = tile:getItemById(index) + if item then Game.createItem(index, 1, value.itemPos[i]) + item:setLoadedFromMap(true) end end end @@ -102,6 +105,7 @@ function loadLuaMapSign(tablename) -- If he found the item, add the text if item then item:setAttribute(ITEM_ATTRIBUTE_TEXT, value.text) + item:setLoadedFromMap(true) end end ::continue:: @@ -138,6 +142,7 @@ function loadLuaMapBookDocument(tablename) -- If the item exists, add the text if item then item:setAttribute(ITEM_ATTRIBUTE_TEXT, value.text) + item:setLoadedFromMap(true) totals[2] = totals[2] + 1 else logger.warn("[loadLuaMapBookDocument] - Item not found! Index: {}, itemId: {}", index, value.itemId) diff --git a/data/libs/functions/boss_lever.lua b/data/libs/functions/boss_lever.lua index 32e8cec7..33b1b3e7 100644 --- a/data/libs/functions/boss_lever.lua +++ b/data/libs/functions/boss_lever.lua @@ -186,7 +186,7 @@ function BossLever:onUse(player) end local infoPositions = lever:getInfoPositions() - if creature:getGroup():getId() < GROUP_TYPE_GOD and isAccountNormal and self:lastEncounterTime(creature) > os.time() then + if creature:getGroup():getId() < GROUP_TYPE_GOD and checkAccountType and self:lastEncounterTime(creature) > os.time() then for _, posInfo in pairs(infoPositions) do local currentPlayer = posInfo.creature if currentPlayer then diff --git a/data/libs/systems/features.lua b/data/libs/systems/features.lua index b75f7740..b6e25b24 100644 --- a/data/libs/systems/features.lua +++ b/data/libs/systems/features.lua @@ -1,6 +1,8 @@ Features = { AutoLoot = "autoloot", ChainSystem = "chainSystem", + EmoteSpells = "emoteSpells", + SpellNameInsteadOfWords = "spellNameInsteadOfWords", } local function validateFeature(feature) diff --git a/data/scripts/creaturescripts/player/swimming.lua b/data/scripts/creaturescripts/player/swimming.lua new file mode 100644 index 00000000..c90e8a59 --- /dev/null +++ b/data/scripts/creaturescripts/player/swimming.lua @@ -0,0 +1,12 @@ +local swimmingLogout = CreatureEvent("SwimmingLogout") + +function swimmingLogout.onLogout(player) + if player:getOutfit().lookType == 267 then + player:sendCancelMessage("You cannot logout while in the pool.") + return false + end + + return true +end + +swimmingLogout:register() diff --git a/data/scripts/movements/swimming.lua b/data/scripts/movements/swimming.lua index d34d5036..3f0e485e 100644 --- a/data/scripts/movements/swimming.lua +++ b/data/scripts/movements/swimming.lua @@ -23,6 +23,12 @@ function swimming.onStepIn(creature, item, position, fromPosition) return false end + if player:isInGhostMode() then + player:sendCancelMessage("You cannot enter the water while in ghost mode.") + player:teleportTo(fromPosition, true) + return false + end + for i = 1, #conditions do player:removeCondition(conditions[i]) end diff --git a/data/scripts/talkactions/god/create_item.lua b/data/scripts/talkactions/god/create_item.lua index 4fa3be3d..b8fcd331 100644 --- a/data/scripts/talkactions/god/create_item.lua +++ b/data/scripts/talkactions/god/create_item.lua @@ -48,7 +48,9 @@ function createItem.onSay(player, words, param) end end - player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + if not player:isInGhostMode() then + player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + end return true elseif not itemType:isFluidContainer() then local min = 100 @@ -95,7 +97,10 @@ function createItem.onSay(player, words, param) result:decay() end end - player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + + if not player:isInGhostMode() then + player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + end end return true end diff --git a/data/scripts/talkactions/god/create_npc.lua b/data/scripts/talkactions/god/create_npc.lua index b6d0412d..c8e98e36 100644 --- a/data/scripts/talkactions/god/create_npc.lua +++ b/data/scripts/talkactions/god/create_npc.lua @@ -12,6 +12,11 @@ function createNpc.onSay(player, words, param) return true end + if player:getOutfit().lookType == 267 then + player:sendCancelMessage("You cannot execte this command while in the pool.") + return true + end + local split = param:split(",") local name = split[1] local permanentStr = split[2] diff --git a/data/scripts/talkactions/god/create_summon.lua b/data/scripts/talkactions/god/create_summon.lua index 0aec53d1..b1396559 100644 --- a/data/scripts/talkactions/god/create_summon.lua +++ b/data/scripts/talkactions/god/create_summon.lua @@ -9,6 +9,11 @@ function createSummon.onSay(player, words, param) return true end + if player:getOutfit().lookType == 267 then + player:sendCancelMessage("You cannot execte this command while in the pool.") + return true + end + local position = player:getPosition() local summon = Game.createMonster(param, position, true, false, player) if not summon then diff --git a/data/scripts/talkactions/god/manage_monster.lua b/data/scripts/talkactions/god/manage_monster.lua index 106acefa..8bb898a0 100644 --- a/data/scripts/talkactions/god/manage_monster.lua +++ b/data/scripts/talkactions/god/manage_monster.lua @@ -58,6 +58,11 @@ function createMonster.onSay(player, words, param) return true end + if player:getOutfit().lookType == 267 then + player:sendCancelMessage("You cannot execte this command while in the pool.") + return true + end + local playerPosition = player:getPosition() local splitParams = param:split(",") local monsterName = splitParams[1] diff --git a/data/scripts/talkactions/player/chain_system.lua b/data/scripts/talkactions/player/chain_system.lua index 7a128e8d..a2eee986 100644 --- a/data/scripts/talkactions/player/chain_system.lua +++ b/data/scripts/talkactions/player/chain_system.lua @@ -7,6 +7,7 @@ local validValues = { function feature.onSay(player, words, param) if not configManager.getBoolean(configKeys.TOGGLE_CHAIN_SYSTEM) then + player:sendTextMessage(MESSAGE_FAILURE, "Chain system have been disabled by the administrator.") return true end diff --git a/data/scripts/talkactions/player/emote_spell.lua b/data/scripts/talkactions/player/emote_spell.lua index 23162ee8..ed97a101 100644 --- a/data/scripts/talkactions/player/emote_spell.lua +++ b/data/scripts/talkactions/player/emote_spell.lua @@ -1,22 +1,27 @@ --- Usage talkaction: "!emote on" or "!emote off" local emoteSpell = TalkAction("!emote") +local validValues = { + "on", + "off", +} + function emoteSpell.onSay(player, words, param) - if configManager.getBoolean(configKeys.EMOTE_SPELLS) == false then - player:sendTextMessage(MESSAGE_LOOK, "Emote spells have been disabled by the administrator.") + if not configManager.getBoolean(configKeys.EMOTE_SPELLS) then + player:sendTextMessage(MESSAGE_FAILURE, "Emote spells have been disabled by the administrator.") return true end - if param == "" then - player:sendCancelMessage("Please specify the parameter: 'on' to activate or 'off' to deactivate.") + if not table.contains(validValues, param) then + local validValuesStr = table.concat(validValues, "/") + player:sendTextMessage(MESSAGE_FAILURE, "Invalid param specified. Usage: !emote [" .. validValuesStr .. "]") return true end if param == "on" then - player:setStorageValue(STORAGEVALUE_EMOTE, 1) + player:setFeature(Features.EmoteSpells, 1) player:sendTextMessage(MESSAGE_LOOK, "You have activated emote spells.") elseif param == "off" then - player:setStorageValue(STORAGEVALUE_EMOTE, 0) + player:setFeature(Features.EmoteSpells, 0) player:sendTextMessage(MESSAGE_LOOK, "You have deactivated emote spells.") end return true diff --git a/data/scripts/talkactions/player/randomoutfit.lua b/data/scripts/talkactions/player/randomoutfit.lua new file mode 100644 index 00000000..a3479020 --- /dev/null +++ b/data/scripts/talkactions/player/randomoutfit.lua @@ -0,0 +1,63 @@ +local randomOutfit = TalkAction("!randomoutfit") + +local config = { + changeInterval = 100, + showEffect = false, +} + +local validValues = { + "on", + "off", +} + +local activePlayers = {} + +local function generateRandomColors(outfit) + outfit.lookHead = math.random(0, 132) + outfit.lookBody = math.random(0, 132) + outfit.lookLegs = math.random(0, 132) + outfit.lookFeet = math.random(0, 132) + return outfit +end + +local function updateColors(playerId) + local player = Player(playerId) + if player and activePlayers[playerId] then + local newOutfit = generateRandomColors(player:getOutfit()) + player:setOutfit(newOutfit) + if config.showEffect then + player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + end + addEvent(updateColors, config.changeInterval, playerId) + end +end + +function randomOutfit.onSay(player, words, param) + if not table.contains(validValues, param) then + local validValuesStr = table.concat(validValues, "/") + player:sendTextMessage(MESSAGE_FAILURE, "Invalid param specified. Usage: !randomoutfit [" .. validValuesStr .. "]") + return true + end + + local playerId = player:getId() + + if param == "on" then + if activePlayers[playerId] then + player:sendTextMessage(MESSAGE_FAILURE, "Random outfit is already active.") + return true + end + + activePlayers[playerId] = true + updateColors(playerId) + player:sendTextMessage(MESSAGE_LOOK, "Random outfit is now enabled.") + else + activePlayers[playerId] = nil + player:sendTextMessage(MESSAGE_LOOK, "Random outfit is now disabled.") + end + + return true +end + +randomOutfit:separator(" ") +randomOutfit:groupType("normal") +randomOutfit:register() diff --git a/data/scripts/talkactions/player/spell_name_instead_words.lua b/data/scripts/talkactions/player/spell_name_instead_words.lua new file mode 100644 index 00000000..810f0089 --- /dev/null +++ b/data/scripts/talkactions/player/spell_name_instead_words.lua @@ -0,0 +1,32 @@ +local featrue = TalkAction("!spellwords") + +local validValues = { + "on", + "off", +} + +function featrue.onSay(player, words, param) + if not configManager.getBoolean(configKeys.EMOTE_SPELLS) then + player:sendTextMessage(MESSAGE_FAILURE, "Spell name instead of words have been disabled by the administrator.") + return true + end + + if not table.contains(validValues, param) then + local validValuesStr = table.concat(validValues, "/") + player:sendTextMessage(MESSAGE_FAILURE, "Invalid param specified. Usage: !spellwords [" .. validValuesStr .. "]") + return true + end + + if param == "on" then + player:setFeature(Features.SpellNameInsteadOfWords, 1) + player:sendTextMessage(MESSAGE_LOOK, "You have activated spell name instead of words.") + elseif param == "off" then + player:setFeature(Features.SpellNameInsteadOfWords, 0) + player:sendTextMessage(MESSAGE_LOOK, "You have deactivated spell name instead of words.") + end + return true +end + +featrue:separator(" ") +featrue:groupType("normal") +featrue:register() diff --git a/markdowns/CHANGELOG.md b/markdowns/CHANGELOG.md index d50c1eb1..32532dbc 100644 --- a/markdowns/CHANGELOG.md +++ b/markdowns/CHANGELOG.md @@ -7,36 +7,57 @@ - Protocol 14.05 support. ([Tryller](https://github.com/jprzimba)) - New protocol 14.05 assets. ([Tryller](https://github.com/jprzimba)) -- Fix gotoHouse talkaction. ([Tryller](https://github.com/jprzimba)) - Optimized the `onPlayerSellAllLoot` code to prevent prolonged freezes. ([Tryller](https://github.com/jprzimba)) -- Add new configurable featurees in `config.lua`: `chainSystemVipOnly`, `fieldOwnershipDuration`, `bedsOnlyPremium`, `loginProtectionPeriod`. ([Tryller](https://github.com/jprzimba)) +- Add new configurable featurees in `config.lua`: `chainSystemVipOnly`, `fieldOwnershipDuration`, `bedsOnlyPremium`, `loginProtectionPeriod`, `chainSystemModifyMagic`. ([Tryller](https://github.com/jprzimba)) +- Added a new commands for players: `!randomoutfit`, `!spellwords`. ([Tryller](https://github.com/jprzimba)) +- Moved emote spells to `kv` instead of `storage`. ([Tryller](https://github.com/jprzimba)) ## Added files - Add all files in data/migrations +- data/scripts/talkactions/player/randomoutfit.lua +- data/scripts/talkactions/player/spell_name_instead_words.lua +- data/scripts/creaturescripts/player/swimming.lua +- data-global/scripts/creaturescripts/customs/water_houses.lua ## Modified files - crystalserver.exe - config.lua -- data-global/world/world.otbm (7z file) -- data-global/world/world-house.xml - data/items/assets.dat - data/items/items.xml +- data/libs/systems/features.lua - data/scripts/creaturescripts/player/login.lua - data/scripts/movements/special_tiles.lua +- data/scripts/movements/swimming.lua - data/scripts/talkactions/god/icons_functions.lua - data/scripts/talkactions/god/goto_house.lua - -## Added files - -- data-global/scripts/creaturescripts/customs/water_houses.lua +- data/scripts/talkactions/god/manage_monster.lua +- data/scripts/talkactions/god/create_item.lua +- data/scripts/talkactions/god/create_summon.lua +- data/scripts/talkactions/god/create_npc.lua +- data/scripts/talkactions/player/chain_system.lua +- data/scripts/talkactions/player/emote_spell.lua +- data-global/world/world.otbm (7z file) +- data-global/world/world-house.xml +- data-global/scripts/quests/ferumbras_ascension/actions_flower_puzzle_lever.lua +- data-global/scripts/quests/ferumbras_ascension/movements_flower_puzzle.lua +- data-global/scripts/lib/register_actions.lua ### Bug Fixes - Fixed influenced creatures not spawning. ([Tryller](https://github.com/jprzimba)) - Fixed houses with pool bug. ([Tryller](https://github.com/jprzimba)) - Fixed some V.I.P list issues. ([Tryller](https://github.com/jprzimba)) +- Fixed gotoHouse talkaction. ([Tryller](https://github.com/jprzimba)) +- Fixed the `/i` command displaying effects while in ghost mode. ([Tryller](https://github.com/jprzimba)) +- Fixed Ferumbras' Ascendant Garden Flower Puzzle. ([Mckay666](https://github.com/Mckay666)) +- Fixed lava tiles in entrance of The Pits of Inferno Quest. ([jeansouzak](https://github.com/jeansouzak)) +- Fixed Items created using CreateMapItem are cleanable by /clean. ([#7](https://github.com/jprzimba/crystalserver/issues/7)) ([Tryller](https://github.com/jprzimba)) +- Prevent players from entering the pool while in ghost mode. ([Tryller](https://github.com/jprzimba)) +- Prevent players from logging out while in the pool. +- Prevent the use of `/n`, `/m`, and `/s` commands while in the pool. ([Tryller](https://github.com/jprzimba)) + ## Version 4.1 diff --git a/src/config/config_enums.hpp b/src/config/config_enums.hpp index 6d5b70f0..e860bc4a 100644 --- a/src/config/config_enums.hpp +++ b/src/config/config_enums.hpp @@ -289,6 +289,7 @@ enum ConfigKey_t : uint16_t { TIBIADROME_CONCOCTION_TICK_TYPE, TOGGLE_ATTACK_SPEED_ONFIST, TOGGLE_CHAIN_SYSTEM, + CHAIN_SYSTEM_MODIFY_MAGIC, TOGGLE_FREE_QUEST, TOGGLE_GOLD_POUCH_ALLOW_ANYTHING, TOGGLE_GOLD_POUCH_QUICKLOOT_ONLY, @@ -349,4 +350,5 @@ enum ConfigKey_t : uint16_t { FIELD_OWNERSHIP, BEDS_ONLY_PREMIUM, LOGIN_PROTECTION, + SPELL_NAME_INSTEAD_WORDS, }; diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index de72d9f5..a5e34c99 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -141,6 +141,7 @@ bool ConfigManager::load() { loadBoolConfig(L, TELEPORT_SUMMONS, "teleportSummons", false); loadBoolConfig(L, TOGGLE_ATTACK_SPEED_ONFIST, "toggleAttackSpeedOnFist", false); loadBoolConfig(L, TOGGLE_CHAIN_SYSTEM, "toggleChainSystem", true); + loadBoolConfig(L, CHAIN_SYSTEM_MODIFY_MAGIC, "chainSystemModifyMagic", false); loadBoolConfig(L, TOGGLE_FREE_QUEST, "toggleFreeQuest", true); loadBoolConfig(L, TOGGLE_GOLD_POUCH_ALLOW_ANYTHING, "toggleGoldPouchAllowAnything", false); loadBoolConfig(L, TOGGLE_GOLD_POUCH_QUICKLOOT_ONLY, "toggleGoldPouchQuickLootOnly", false); @@ -170,6 +171,7 @@ bool ConfigManager::load() { loadBoolConfig(L, HALF_LOSS_MAGIC, "halfLossMagicLevel", true); loadBoolConfig(L, CHAIN_SYSTEM_VIP_ONLY, "chainSystemVipOnly", false); loadBoolConfig(L, BEDS_ONLY_PREMIUM, "bedsOnlyPremium", true); + loadBoolConfig(L, SPELL_NAME_INSTEAD_WORDS, "spellNameInsteadOfWords", false); loadFloatConfig(L, BESTIARY_RATE_CHARM_SHOP_PRICE, "bestiaryRateCharmShopPrice", 1.0); loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_AXE, "combatChainSkillFormulaAxe", 0.9); diff --git a/src/creatures/combat/spells.cpp b/src/creatures/combat/spells.cpp index ad238092..07c15fbb 100644 --- a/src/creatures/combat/spells.cpp +++ b/src/creatures/combat/spells.cpp @@ -99,7 +99,12 @@ TalkActionResult_t Spells::playerSaySpell(const std::shared_ptr &player, } if (instantSpell->playerCastInstant(player, param)) { - words = instantSpell->getWords(); + if (!player->checkSpellNameInsteadOfWords()) { + words = instantSpell->getWords(); + } + else { + words = instantSpell->getName(); + } if (instantSpell->getHasParam() && !param.empty()) { words += " \"" + param + "\""; diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index ed69faa5..f21f720f 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -2796,13 +2796,19 @@ int32_t Player::getIdleTime() const { } void Player::setTraining(bool value) { + if (isExerciseTraining() == value) { + return; + } + + exerciseTraining = value; + VipStatus_t newStatus = exerciseTraining ? VipStatus_t::TRAINING : VipStatus_t::ONLINE; for (const auto &[key, player] : g_game().getPlayers()) { if (!this->isInGhostMode() || player->isAccessPlayer()) { - player->vip()->notifyStatusChange(static_self_cast(), value ? VipStatus_t::TRAINING : VipStatus_t::ONLINE, false); + player->vip()->notifyStatusChange(static_self_cast(), newStatus, false); } } - vip()->setStatus(VipStatus_t::TRAINING); - setExerciseTraining(value); + + setExerciseTraining(exerciseTraining); } void Player::addItemImbuementStats(const Imbuement* imbuement) { @@ -3518,11 +3524,13 @@ void Player::death(const std::shared_ptr &lastHitCreature) { } } + double magicLossPercent = deathLossPercent / 100.; double magicLossPercent = deathLossPercent / 100.; if (g_configManager().getBoolean(HALF_LOSS_MAGIC) && magicLossPercent > 0) { magicLossPercent /= 2; } + lostMana = static_cast(sumMana * magicLossPercent); while (lostMana > manaSpent && magLevel > 0) { @@ -3876,7 +3884,8 @@ void Player::removeList() { void Player::addList() { for (const auto &[key, player] : g_game().getPlayers()) { - player->vip()->notifyStatusChange(static_self_cast(), vip()->getStatus()); + VipStatus_t status = player->isExerciseTraining() ? VipStatus_t::TRAINING : VipStatus_t::ONLINE; + player->vip()->notifyStatusChange(static_self_cast(), status); } g_game().addPlayer(static_self_cast()); @@ -4995,6 +5004,42 @@ bool Player::checkChainSystem() const { return false; } +bool Player::checkEmoteSpells() const { + if (!g_configManager().getBoolean(EMOTE_SPELLS)) { + return false; + } + + auto featureKV = kv()->scoped("features")->get("emoteSpells"); + if (featureKV.has_value()) { + auto value = featureKV->getNumber(); + if (value == 1) { + return true; + } else if (value == 0) { + return false; + } + } + + return false; +} + +bool Player::checkSpellNameInsteadOfWords() const { + if (!g_configManager().getBoolean(SPELL_NAME_INSTEAD_WORDS)) { + return false; + } + + auto featureKV = kv()->scoped("features")->get("spellNameInsteadOfWords"); + if (featureKV.has_value()) { + auto value = featureKV->getNumber(); + if (value == 1) { + return true; + } else if (value == 0) { + return false; + } + } + + return false; +} + QuickLootFilter_t Player::getQuickLootFilter() const { return quickLootFilter; } @@ -5131,8 +5176,7 @@ ItemsTierCountList Player::getDepotInboxItemsId() const { } } - return itemMap; - ; + return itemMap;; } std::vector> Player::getAllInventoryItems(bool ignoreEquiped /*= false*/, bool ignoreItemWithTier /* false*/) const { @@ -8112,25 +8156,15 @@ bool Player::isGuildMate(const std::shared_ptr &player) const { } bool Player::addItemFromStash(uint16_t itemId, uint32_t itemCount) { - const ItemType &itemType = Item::items[itemId]; - if (itemType.stackable) { - while (itemCount > 0) { - const auto addValue = itemCount > 100 ? 100 : itemCount; - itemCount -= addValue; - const auto &newItem = Item::CreateItem(itemId, addValue); + const uint32_t stackCount = 100u; - if (!g_game().tryRetrieveStashItems(static_self_cast(), newItem)) { - g_game().internalPlayerAddItem(static_self_cast(), newItem, true); - } - } - } else { - while (itemCount > 0) { - --itemCount; - const auto &newItem = Item::CreateItem(itemId); + while (itemCount > 0) { + const auto addValue = itemCount > stackCount ? stackCount : itemCount; + itemCount -= addValue; + const auto &newItem = Item::CreateItem(itemId, addValue); - if (!g_game().tryRetrieveStashItems(static_self_cast(), newItem)) { - g_game().internalPlayerAddItem(static_self_cast(), newItem, true); - } + if (!g_game().tryRetrieveStashItems(static_self_cast(), newItem)) { + g_game().internalPlayerAddItem(static_self_cast(), newItem, true); } } @@ -8142,20 +8176,20 @@ bool Player::addItemFromStash(uint16_t itemId, uint32_t itemCount) { return true; } -void processContainerItems(const std::shared_ptr &item, const std::shared_ptr &container, StashContainerList &itemDict) { - if (!container) { - return; +void sendStowItems(const std::shared_ptr &item, const std::shared_ptr &stowItem, StashContainerList &itemDict) { + if (stowItem->getID() == item->getID()) { + itemDict.emplace_back(stowItem, stowItem->getItemCount()); } - if (const auto &containerItems = container->getContainer()) { - for (const auto &subItem : containerItems->getItems(true)) { - itemDict.emplace_back(subItem, subItem->getItemCount()); - } + if (const auto &container = stowItem->getContainer()) { + std::ranges::copy_if(container->getStowableItems(), std::back_inserter(itemDict), [&item](const auto &stowable_it) { + return stowable_it.first->getID() == item->getID(); + }); } } void Player::stowItem(const std::shared_ptr &item, uint32_t count, bool allItems) { - if (!item) { + if (!item || (!item->isItemStorable() && item->getID() != ITEM_GOLD_POUCH)) { sendCancelMessage("This item cannot be stowed here."); return; } @@ -8165,35 +8199,46 @@ void Player::stowItem(const std::shared_ptr &item, uint32_t count, bool al if (!item->isInsideDepot(true)) { // Stow "all items" from player backpack if (const auto &backpack = getInventoryItem(CONST_SLOT_BACKPACK)) { - processContainerItems(item, backpack, itemDict); + sendStowItems(item, backpack, itemDict); } // Stow "all items" from loot pouch const auto &itemParent = item->getParent(); - const auto &lootPouch = itemParent ? itemParent->getItem() : nullptr; - if (lootPouch && lootPouch->getID() == ITEM_GOLD_POUCH) { - processContainerItems(item, lootPouch, itemDict); + const auto &lootPouch = itemParent->getItem(); + if (itemParent && lootPouch && lootPouch->getID() == ITEM_GOLD_POUCH) { + sendStowItems(item, lootPouch, itemDict); } } // Stow locker items const auto &depotLocker = getDepotLocker(getLastDepotId()); - for (const auto &lockerItem : depotLocker->getItems(true)) { - processContainerItems(item, lockerItem, itemDict); + const auto &[itemVector, itemMap] = requestLockerItems(depotLocker); + for (const auto &lockerItem : itemVector) { + if (lockerItem == nullptr) { + break; + } + + if (item->isInsideDepot(true)) { + sendStowItems(item, lockerItem, itemDict); + } } - } else { - // Process case for items inside containers - if (item->getContainer()) { - for (const auto &containerItem : item->getContainer()->getItems(true)) { - itemDict.emplace_back(containerItem, containerItem->getItemCount()); + } else if (item->getContainer()) { + itemDict = item->getContainer()->getStowableItems(); + for (const std::shared_ptr &containerItem : item->getContainer()->getItems(true)) { + uint32_t depotChest = g_configManager().getNumber(DEPOTCHEST); + bool validDepot = depotChest > 0 && depotChest < 21; + if (g_configManager().getBoolean(STASH_MOVING) && containerItem && !containerItem->isStackable() && validDepot) { + g_game().internalMoveItem(containerItem->getParent(), getDepotChest(depotChest, true), INDEX_WHEREEVER, containerItem, containerItem->getItemCount(), nullptr); + movedItems++; + moved = true; } - } else { - itemDict.emplace_back(item, count); } + } else { + itemDict.emplace_back(item, count); } if (itemDict.empty()) { - sendCancelMessage("There are no items to be stowed in this container."); + sendCancelMessage("There is no stowable items on this container."); return; } @@ -8814,15 +8859,11 @@ bool Player::saySpell(SpeakClasses type, const std::string &text, bool isGhostMo spectators = (*spectatorsPtr); } - int32_t valueEmote = 0; // Send to client for (const auto &spectator : spectators) { if (const auto &tmpPlayer = spectator->getPlayer()) { - if (g_configManager().getBoolean(EMOTE_SPELLS)) { - valueEmote = tmpPlayer->getStorageValue(STORAGEVALUE_EMOTE); - } if (!isGhostMode || tmpPlayer->canSeeCreature(static_self_cast())) { - if (valueEmote == 1) { + if (checkEmoteSpells()) { tmpPlayer->sendCreatureSay(static_self_cast(), TALKTYPE_MONSTER_SAY, text, pos); } else { tmpPlayer->sendCreatureSay(static_self_cast(), TALKTYPE_SPELL_USE, text, pos); @@ -8952,70 +8993,70 @@ void Player::forgeFuseItems(ForgeAction_t actionType, uint16_t firstItemId, uint return; } + uint32_t maxContainer = static_cast(g_configManager().getNumber(MAX_CONTAINER)); + auto backpack = getInventoryItem(CONST_SLOT_BACKPACK); + auto mainBackpack = backpack ? backpack->getContainer() : nullptr; + + if (mainBackpack && mainBackpack->getContainerHoldingCount() >= maxContainer) { + sendCancelMessage(RETURNVALUE_CONTAINERISFULL); + return; + } + ForgeHistory history; history.actionType = actionType; history.tier = tier; history.success = success; history.tierLoss = reduceTierLoss; - const auto &exaltationChest = Item::CreateItem(ITEM_EXALTATION_CHEST, 1); - if (!exaltationChest) { - g_logger().error("Failed to create exaltation chest"); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; - } - const auto &exaltationContainer = exaltationChest->getContainer(); - if (!exaltationContainer) { - g_logger().error("Failed to create exaltation container"); + const auto &firstForgingItem = getForgeItemFromId(firstItemId, tier); + if (!firstForgingItem) { + g_logger().error("[Log 1] Player with name {} failed to fuse item with id {}", getName(), firstItemId); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } - - auto returnValue = queryAdd(CONST_SLOT_BACKPACK, exaltationContainer, 1, 0); + auto returnValue = g_game().internalRemoveItem(firstForgingItem, 1); if (returnValue != RETURNVALUE_NOERROR) { - g_logger().error("[Log 1] Failed to add forge item {} from player with name {}", firstItemId, getName()); + g_logger().error("[Log 1] Failed to remove forge item {} from player with name {}", firstItemId, getName()); sendCancelMessage(getReturnMessage(returnValue)); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } - - const auto &firstForgedItem = Item::CreateItem(firstItemId, 1); - if (!firstForgedItem) { - g_logger().error("[Log 3] Player with name {} failed to fuse item with id {}", getName(), firstItemId); + const auto &secondForgingItem = getForgeItemFromId(secondItemId, tier); + if (!secondForgingItem) { + g_logger().error("[Log 2] Player with name {} failed to fuse item with id {}", getName(), secondItemId); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } - - returnValue = g_game().internalAddItem(exaltationContainer, firstForgedItem, INDEX_WHEREEVER); - if (returnValue != RETURNVALUE_NOERROR) { - g_logger().error("[Log 1] Failed to add forge item {} from player with name {}", firstItemId, getName()); + if (returnValue = g_game().internalRemoveItem(secondForgingItem, 1); + returnValue != RETURNVALUE_NOERROR) { + g_logger().error("[Log 2] Failed to remove forge item {} from player with name {}", secondItemId, getName()); sendCancelMessage(getReturnMessage(returnValue)); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } - const auto &firstForgingItem = getForgeItemFromId(firstItemId, tier); - if (!firstForgingItem) { - g_logger().error("[Log 1] Player with name {} failed to fuse item with id {}", getName(), firstItemId); + const auto &exaltationChest = Item::CreateItem(ITEM_EXALTATION_CHEST, 1); + if (!exaltationChest) { + g_logger().error("Failed to create exaltation chest"); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } - returnValue = g_game().internalRemoveItem(firstForgingItem, 1); - if (returnValue != RETURNVALUE_NOERROR) { - g_logger().error("[Log 1] Failed to remove forge item {} from player with name {}", firstItemId, getName()); - sendCancelMessage(getReturnMessage(returnValue)); + const auto &exaltationContainer = exaltationChest->getContainer(); + if (!exaltationContainer) { + g_logger().error("Failed to create exaltation container"); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } - const auto &secondForgingItem = getForgeItemFromId(secondItemId, tier); - if (!secondForgingItem) { - g_logger().error("[Log 2] Player with name {} failed to fuse item with id {}", getName(), secondItemId); + + const auto &firstForgedItem = Item::CreateItem(firstItemId, 1); + if (!firstForgedItem) { + g_logger().error("[Log 3] Player with name {} failed to fuse item with id {}", getName(), firstItemId); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } - if (returnValue = g_game().internalRemoveItem(secondForgingItem, 1); - returnValue != RETURNVALUE_NOERROR) { - g_logger().error("[Log 2] Failed to remove forge item {} from player with name {}", secondItemId, getName()); + returnValue = g_game().internalAddItem(exaltationContainer, firstForgedItem, INDEX_WHEREEVER); + if (returnValue != RETURNVALUE_NOERROR) { + g_logger().error("[Log 1] Failed to add forge item {} from player with name {}", firstItemId, getName()); sendCancelMessage(getReturnMessage(returnValue)); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; @@ -9182,7 +9223,7 @@ void Player::forgeFuseItems(ForgeAction_t actionType, uint16_t firstItemId, uint returnValue = g_game().internalAddItem(static_self_cast(), exaltationContainer, INDEX_WHEREEVER); if (returnValue != RETURNVALUE_NOERROR) { - g_logger().error("Failed to add exaltation chest to player with name {}", fmt::underlying(ITEM_EXALTATION_CHEST), getName()); + g_logger().error("Failed to add exaltation chest to player with name {}", getName()); sendCancelMessage(getReturnMessage(returnValue)); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; @@ -9209,33 +9250,13 @@ void Player::forgeTransferItemTier(ForgeAction_t actionType, uint16_t donorItemI history.tier = tier; history.success = true; - const auto &exaltationChest = Item::CreateItem(ITEM_EXALTATION_CHEST, 1); - if (!exaltationChest) { - g_logger().error("Exaltation chest is nullptr"); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; - } - const auto &exaltationContainer = exaltationChest->getContainer(); - if (!exaltationContainer) { - g_logger().error("Exaltation container is nullptr"); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; - } - - auto returnValue = queryAdd(CONST_SLOT_BACKPACK, exaltationContainer, 1, 0); - if (returnValue != RETURNVALUE_NOERROR) { - sendCancelMessage(getReturnMessage(returnValue)); - sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); - return; - } - const auto &donorItem = getForgeItemFromId(donorItemId, tier); if (!donorItem) { g_logger().error("[Log 1] Player with name {} failed to transfer item with id {}", getName(), donorItemId); sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } - returnValue = g_game().internalRemoveItem(donorItem, 1); + auto returnValue = g_game().internalRemoveItem(donorItem, 1); if (returnValue != RETURNVALUE_NOERROR) { g_logger().error("[Log 1] Failed to remove transfer item {} from player with name {}", donorItemId, getName()); sendCancelMessage(getReturnMessage(returnValue)); @@ -9257,6 +9278,19 @@ void Player::forgeTransferItemTier(ForgeAction_t actionType, uint16_t donorItemI return; } + const auto &exaltationChest = Item::CreateItem(ITEM_EXALTATION_CHEST, 1); + if (!exaltationChest) { + g_logger().error("Exaltation chest is nullptr"); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); + return; + } + const auto &exaltationContainer = exaltationChest->getContainer(); + if (!exaltationContainer) { + g_logger().error("Exaltation container is nullptr"); + sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); + return; + } + const auto &newReceiveItem = Item::CreateItem(receiveItemId, 1); if (!newReceiveItem) { g_logger().error("[Log 6] Player with name {} failed to fuse item with id {}", getName(), receiveItemId); diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 69a92ad4..6f355847 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -1211,6 +1211,8 @@ class Player final : public Creature, public Cylinder, public Bankable { bool checkAutoLoot(bool isBoss) const; bool checkChainSystem() const; + bool checkEmoteSpells() const; + bool checkSpellNameInsteadOfWords() const; QuickLootFilter_t getQuickLootFilter() const; diff --git a/src/creatures/players/vip/player_vip.hpp b/src/creatures/players/vip/player_vip.hpp index 3ac761fa..6fddd832 100644 --- a/src/creatures/players/vip/player_vip.hpp +++ b/src/creatures/players/vip/player_vip.hpp @@ -43,10 +43,6 @@ class PlayerVIP { size_t getMaxEntries() const; uint8_t getMaxGroupEntries() const; - VipStatus_t getStatus() const { - return status; - } - void setStatus(VipStatus_t newStatus) { status = newStatus; } diff --git a/src/game/game.cpp b/src/game/game.cpp index 9b0cc448..d22e8ecd 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -5990,7 +5990,9 @@ void Game::playerRequestAddVip(uint32_t playerId, const std::string &name) { } if (!vipPlayer->isInGhostMode() || player->isAccessPlayer()) { - player->vip()->add(vipPlayer->getGUID(), vipPlayer->getName(), vipPlayer->vip()->getStatus()); + player->vip()->add(vipPlayer->getGUID(), vipPlayer->getName(), VipStatus_t::ONLINE); + } else if (vipPlayer->isExerciseTraining()) { + player->vip()->add(vipPlayer->getGUID(), vipPlayer->getName(), VipStatus_t::TRAINING); } else { player->vip()->add(vipPlayer->getGUID(), vipPlayer->getName(), VipStatus_t::OFFLINE); } diff --git a/src/items/item.cpp b/src/items/item.cpp index ba45c226..32607546 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -1213,8 +1213,8 @@ bool Item::canBeMoved() const { void Item::checkDecayMapItemOnMove() { if (getDuration() > 0 && isDecayDisabled() && canBeMoved()) { - decayDisabled = false; - loadedFromMap = false; + setDecayDisabled(false); + setLoadedFromMap(false); startDecaying(); } } diff --git a/src/items/item.hpp b/src/items/item.hpp index 061e592b..b33cfc87 100644 --- a/src/items/item.hpp +++ b/src/items/item.hpp @@ -542,10 +542,6 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { return isDummy() || items[id].m_canBeUsedByGuests; } - bool isDecayDisabled() const { - return decayDisabled; - } - const std::string &getName() const { if (hasAttribute(ItemAttribute_t::NAME)) { return getString(ItemAttribute_t::NAME); @@ -632,6 +628,18 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { return loadedFromMap; } + void setLoadedFromMap(bool value) { + loadedFromMap = value; + } + + bool isDecayDisabled() const { + return decayDisabled; + } + + void setDecayDisabled(bool value) { + decayDisabled = value; + } + bool isCleanable() const { return !loadedFromMap && canRemove() && isPickupable() && !hasAttribute(ItemAttribute_t::UNIQUEID) && !hasAttribute(ItemAttribute_t::ACTIONID); } diff --git a/src/items/weapons/weapons.cpp b/src/items/weapons/weapons.cpp index b55e8f00..17f19281 100644 --- a/src/items/weapons/weapons.cpp +++ b/src/items/weapons/weapons.cpp @@ -955,6 +955,10 @@ int32_t WeaponWand::getWeaponDamage(const std::shared_ptr &player, const return maxDamage ? -maxChange : -normal_random(minChange, maxChange); } + if (!g_configManager().getBoolean(CHAIN_SYSTEM_MODIFY_MAGIC)) { + return maxDamage ? -maxChange : -normal_random(minChange, maxChange); + } + // If chain system is enabled, calculates magic-based damage int32_t attackSkill = 0; int32_t attackValue = 0; diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp index 8aa8a4b1..7895742a 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -159,7 +159,6 @@ void LuaEnums::initOthersEnums(lua_State* L) { registerEnum(L, LIGHT_STATE_NIGHT); registerEnum(L, LIGHT_STATE_SUNSET); registerEnum(L, LIGHT_STATE_SUNRISE); - registerEnum(L, STORAGEVALUE_EMOTE); registerEnum(L, IMMOVABLE_ACTION_ID); diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index ad577d70..ac7475f4 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -3447,7 +3447,8 @@ int PlayerFunctions::luaPlayerSetGhostMode(lua_State* L) { } else { for (const auto &it : g_game().getPlayers()) { if (!it.second->isAccessPlayer()) { - it.second->vip()->notifyStatusChange(player, player->vip()->getStatus()); + VipStatus_t status = it.second->isExerciseTraining() ? VipStatus_t::TRAINING : VipStatus_t::ONLINE; + it.second->vip()->notifyStatusChange(player, status); } } } diff --git a/src/lua/functions/items/item_functions.cpp b/src/lua/functions/items/item_functions.cpp index 143162ec..a3971510 100644 --- a/src/lua/functions/items/item_functions.cpp +++ b/src/lua/functions/items/item_functions.cpp @@ -45,6 +45,7 @@ void ItemFunctions::init(lua_State* L) { Lua::registerMethod(L, "Item", "getUniqueId", ItemFunctions::luaItemGetUniqueId); Lua::registerMethod(L, "Item", "getActionId", ItemFunctions::luaItemGetActionId); Lua::registerMethod(L, "Item", "setActionId", ItemFunctions::luaItemSetActionId); + Lua::registerMethod(L, "Item", "setLoadedFromMap", ItemFunctions::luaItemSetLoadedFromMap); Lua::registerMethod(L, "Item", "getCount", ItemFunctions::luaItemGetCount); Lua::registerMethod(L, "Item", "getCharges", ItemFunctions::luaItemGetCharges); @@ -314,6 +315,19 @@ int ItemFunctions::luaItemSetActionId(lua_State* L) { return 1; } +int ItemFunctions::luaItemSetLoadedFromMap(lua_State* L) { + // item:setLoadedFromMap(value) + const auto &item = Lua::getUserdataShared(L, 1); + if (item) { + const bool boolValue = Lua::getBoolean(L, 2); + item->setLoadedFromMap(boolValue); + Lua::pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int ItemFunctions::luaItemGetCount(lua_State* L) { // item:getCount() const auto &item = Lua::getUserdataShared(L, 1); diff --git a/src/lua/functions/items/item_functions.hpp b/src/lua/functions/items/item_functions.hpp index c9ce77d1..16a11fe2 100644 --- a/src/lua/functions/items/item_functions.hpp +++ b/src/lua/functions/items/item_functions.hpp @@ -43,6 +43,7 @@ class ItemFunctions { static int luaItemGetUniqueId(lua_State* L); static int luaItemGetActionId(lua_State* L); static int luaItemSetActionId(lua_State* L); + static int luaItemSetLoadedFromMap(lua_State* L); static int luaItemGetCount(lua_State* L); static int luaItemGetCharges(lua_State* L); diff --git a/src/map/mapcache.cpp b/src/map/mapcache.cpp index 14f2d22f..a5cf00ca 100644 --- a/src/map/mapcache.cpp +++ b/src/map/mapcache.cpp @@ -101,9 +101,9 @@ std::shared_ptr MapCache::createItem(const std::shared_ptr &Bas if (item->canDecay()) { item->startDecaying(); } - item->loadedFromMap = true; - item->decayDisabled = Item::items[item->getID()].decayTo != -1; + item->setLoadedFromMap(true); + item->setDecayDisabled(Item::items[item->getID()].decayTo != -1); return item; } diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index a9de7dbf..c385a2c4 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -6890,26 +6890,26 @@ void ProtocolGame::sendAddCreature(const std::shared_ptr &creature, co if (player->isAccessPlayer()) { for (const VIPEntry &entry : vipEntries) { - VipStatus_t vipStatus; + VipStatus_t vipStatus = VipStatus_t::ONLINE; std::shared_ptr vipPlayer = g_game().getPlayerByGUID(entry.guid); if (!vipPlayer) { vipStatus = VipStatus_t::OFFLINE; - } else { - vipStatus = vipPlayer->vip()->getStatus(); + } else if (vipPlayer->isExerciseTraining()) { + vipStatus = VipStatus_t::TRAINING; } sendVIP(entry.guid, entry.name, entry.description, entry.icon, entry.notify, vipStatus); } } else { for (const VIPEntry &entry : vipEntries) { - VipStatus_t vipStatus; + VipStatus_t vipStatus = VipStatus_t::ONLINE; std::shared_ptr vipPlayer = g_game().getPlayerByGUID(entry.guid); if (!vipPlayer || vipPlayer->isInGhostMode()) { vipStatus = VipStatus_t::OFFLINE; - } else { - vipStatus = vipPlayer->vip()->getStatus(); + } else if (vipPlayer->isExerciseTraining()) { + vipStatus = VipStatus_t::TRAINING; } sendVIP(entry.guid, entry.name, entry.description, entry.icon, entry.notify, vipStatus); diff --git a/src/utils/const.hpp b/src/utils/const.hpp index 7626ef6c..5ec75158 100644 --- a/src/utils/const.hpp +++ b/src/utils/const.hpp @@ -33,7 +33,6 @@ static constexpr int32_t CHANNEL_PRIVATE = 0xFFFF; static constexpr int32_t EVENT_IMBUEMENT_INTERVAL = 1000; static constexpr uint8_t IMBUEMENT_MAX_TIER = 3; -static constexpr int32_t STORAGEVALUE_EMOTE = 30008; static constexpr int32_t STORAGEVALUE_PODIUM = 30020; static constexpr int32_t STORAGEVALUE_BESTIARYKILLCOUNT = 61305000; // Can get up to 2000 storages!