diff --git a/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua b/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua index b2ba681d251..f8f7ddc5c6b 100644 --- a/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua +++ b/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua @@ -57,7 +57,7 @@ function deathEvent.onDeath(creature, _corpse, _lastHitKiller, mostDamageKiller) end end -- Minotaurs - killCheck(player, targetName, Storage.KillingInTheNameOf.BudrikMinos, 0, tasks.Budrik[1].creatures, nil, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.MinotaurCount) + killCheck(player, targetName, Storage.KillingInTheNameOf.BudrikMinos, 0, tasks.Budrik[1].creatures, Storage.Quest.U8_5.KillingInTheNameOf.AltKillCount.MinotaurCount, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.MinotaurCount) -- Necromancers and Priestesses killCheck(player, targetName, Storage.KillingInTheNameOf.LugriNecromancers, 0, tasks.Lugri[1].creatures, Storage.Quest.U8_5.KillingInTheNameOf.AltKillCount.NecromancerCount, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.NecromancerCount) killCheck(player, targetName, Storage.KillingInTheNameOf.LugriNecromancers, 3, tasks.Lugri[1].creatures, Storage.Quest.U8_5.KillingInTheNameOf.AltKillCount.NecromancerCount, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.NecromancerCount) diff --git a/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua b/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua index 61984209287..36be8dc38b4 100644 --- a/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua +++ b/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua @@ -16,7 +16,7 @@ combat:setParameter(COMBAT_PARAM_BLOCKARMOR, true) function onGetFormulaValues(player, skill, attack, factor) local distanceSkill = player:getEffectiveSkillLevel(SKILL_DISTANCE) local min = (player:getLevel() / 5) - local max = (0.09 * factor) * distanceSkill * 37 + (player:getLevel() / 5) + local max = (0.09 * factor) * distanceSkill * attack + (player:getLevel() / 5) return -min, -max end diff --git a/data/items/items.xml b/data/items/items.xml index 0bc1c782452..421e981ad02 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -53105,6 +53105,9 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + diff --git a/data/libs/functions/functions.lua b/data/libs/functions/functions.lua index 9eb1b0b57b5..c2349ce7fd6 100644 --- a/data/libs/functions/functions.lua +++ b/data/libs/functions/functions.lua @@ -67,17 +67,28 @@ end function getTimeInWords(secsParam) local secs = tonumber(secsParam) + local days = math.floor(secs / (24 * 3600)) + secs = secs - (days * 24 * 3600) local hours, minutes, seconds = getHours(secs), getMinutes(secs), getSeconds(secs) local timeStr = "" + if days > 0 then + timeStr = days .. (days > 1 and " days" or " day") + end + if hours > 0 then - timeStr = hours .. (hours > 1 and " hours" or " hour") + if timeStr ~= "" then + timeStr = timeStr .. ", " + end + + timeStr = timeStr .. hours .. (hours > 1 and " hours" or " hour") end if minutes > 0 then if timeStr ~= "" then timeStr = timeStr .. ", " end + timeStr = timeStr .. minutes .. (minutes > 1 and " minutes" or " minute") end @@ -85,9 +96,9 @@ function getTimeInWords(secsParam) if timeStr ~= "" then timeStr = timeStr .. " and " end + timeStr = timeStr .. seconds .. (seconds > 1 and " seconds" or " second") end - return timeStr end diff --git a/data/scripts/eventcallbacks/README.md b/data/scripts/eventcallbacks/README.md index bdafcb41b33..601653574bd 100644 --- a/data/scripts/eventcallbacks/README.md +++ b/data/scripts/eventcallbacks/README.md @@ -47,6 +47,7 @@ Event callbacks are available for several categories of game entities, such as ` - `(void)` `playerOnCombat` - `(void)` `playerOnInventoryUpdate` - `(bool)` `playerOnRotateItem` +- `(void)` `playerOnWalk` - `(void)` `monsterOnDropLoot` - `(void)` `monsterPostDropLoot` - `(void)` `monsterOnSpawn` diff --git a/data/scripts/runes/destroy_field_rune.lua b/data/scripts/runes/destroy_field_rune.lua index 024dbc1bcd7..8752be7eb51 100644 --- a/data/scripts/runes/destroy_field_rune.lua +++ b/data/scripts/runes/destroy_field_rune.lua @@ -4,6 +4,13 @@ local fields = { 105, 2118, 2119, 2120, 2121, 2122, 2123, 2124, 2125, 2126, 2132 local rune = Spell("rune") function rune.onCastSpell(creature, variant, isHotkey) + local inPz = creature:getTile():hasFlag(TILESTATE_PROTECTIONZONE) + if inPz then + creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + creature:getPosition():sendMagicEffect(CONST_ME_POFF) + return false + end + local position = Variant.getPosition(variant) local tile = Tile(position) local field = tile and tile:getItemByType(ITEM_TYPE_MAGICFIELD) diff --git a/data/scripts/spells/house/kick.lua b/data/scripts/spells/house/kick.lua index b4b583c1007..265ac48f796 100644 --- a/data/scripts/spells/house/kick.lua +++ b/data/scripts/spells/house/kick.lua @@ -4,6 +4,7 @@ function spell.onCastSpell(player, variant) local targetPlayer = Player(variant:getString()) or player local guest = targetPlayer:getTile():getHouse() local owner = player:getTile():getHouse() + -- Owner kick yourself from house if targetPlayer == player then player:getPosition():sendMagicEffect(CONST_ME_POFF) @@ -11,6 +12,13 @@ function spell.onCastSpell(player, variant) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) return true end + + if not owner:canEditAccessList(GUEST_LIST, player) then + player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + player:getPosition():sendMagicEffect(CONST_ME_POFF) + return false + end + if not owner or not guest or not guest:kickPlayer(player, targetPlayer) then player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) player:getPosition():sendMagicEffect(CONST_ME_POFF) diff --git a/data/scripts/talkactions/gm/afk.lua b/data/scripts/talkactions/gm/afk.lua new file mode 100644 index 00000000000..6167a2b6068 --- /dev/null +++ b/data/scripts/talkactions/gm/afk.lua @@ -0,0 +1,94 @@ +local afk = TalkAction("/afk") + +playersAFKs = {} + +local function checkIsAFK(id) + for index, item in pairs(playersAFKs) do + if id == item.id then + return { afk = true, index = index } + end + end + return { afk = false } +end + +local function showAfkMessage(playerPosition) + local spectators = Game.getSpectators(playerPosition, false, true, 8, 8, 8, 8) + if #spectators > 0 then + for _, spectator in ipairs(spectators) do + spectator:say("AFK !", TALKTYPE_MONSTER_SAY, false, spectator, playerPosition) + end + end +end + +function afk.onSay(player, words, param) + if param == "" then + player:sendCancelMessage("You need to specify on/off param.") + return true + end + + local id, playerPosition = player:getId(), player:getPosition() + local isAfk = checkIsAFK(id) + if param == "on" then + if isAfk.afk then + player:sendCancelMessage("You are already AFK!") + return true + end + + table.insert(playersAFKs, { id = id, position = playerPosition }) + if player:isInGhostMode() then + player:setGhostMode(false) + end + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are now AFK!") + playerPosition:sendMagicEffect(CONST_ME_REDSMOKE) + showAfkMessage(playerPosition) + elseif param == "off" then + if isAfk.afk then + table.remove(playersAFKs, isAfk.index) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are no longer AFK!") + playerPosition:sendMagicEffect(CONST_ME_REDSMOKE) + end + end + + return true +end + +afk:separator(" ") +afk:groupType("gamemaster") +afk:register() + +------------------ AFK Effect Message ------------------ +local afkEffect = GlobalEvent("GodAfkEffect") +function afkEffect.onThink(interval) + for _, player in ipairs(playersAFKs) do + showAfkMessage(player.position) + end + return true +end + +afkEffect:interval(5000) +afkEffect:register() + +------------------ Stop AFK Message when moves ------------------ +local callback = EventCallback() +function callback.playerOnWalk(player, creature, creaturePos, toPos) + local isAfk = checkIsAFK(player:getId()) + if isAfk.afk then + table.remove(playersAFKs, isAfk.index) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are no longer AFK!") + end + return true +end + +callback:register() + +------------------ Player Logout ------------------ +local godAfkLogout = CreatureEvent("GodAfkLogout") +function godAfkLogout.onLogout(player) + local isAfk = checkIsAFK(player:getId()) + if isAfk.afk then + table.remove(playersAFKs, isAfk.index) + end + return true +end + +godAfkLogout:register() diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 4f6b5d29294..73dba80b929 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -1926,6 +1926,8 @@ void Player::onWalk(Direction &dir) { Creature::onWalk(dir); setNextActionTask(nullptr); setNextAction(OTSYS_TIME() + getStepDuration(dir)); + + g_callbacks().executeCallback(EventCallback_t::playerOnWalk, &EventCallback::playerOnWalk, getPlayer(), dir); } void Player::onCreatureMove(const std::shared_ptr &creature, const std::shared_ptr &newTile, const Position &newPos, const std::shared_ptr &oldTile, const Position &oldPos, bool teleport) { @@ -2918,6 +2920,7 @@ bool Player::spawn() { getParent()->postAddNotification(static_self_cast(), nullptr, 0); g_game().addCreatureCheck(static_self_cast()); g_game().addPlayer(static_self_cast()); + static_self_cast()->onChangeZone(static_self_cast()->getZoneType()); return true; } @@ -4673,6 +4676,8 @@ void Player::onPlacedCreature() { removePlayer(true); } + this->onChangeZone(this->getZoneType()); + sendUnjustifiedPoints(); } @@ -6692,7 +6697,7 @@ void Player::triggerTranscendance() { outfit.lookType = getVocation()->getAvatarLookType(); outfitCondition->setOutfit(outfit); addCondition(outfitCondition); - wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR, OTSYS_TIME() + duration); + wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR_FORGE, OTSYS_TIME() + duration); g_game().addMagicEffect(getPosition(), CONST_ME_AVATAR_APPEAR); sendTextMessage(MESSAGE_ATTENTION, "Transcendance was triggered."); sendSkills(); diff --git a/src/creatures/players/wheel/player_wheel.cpp b/src/creatures/players/wheel/player_wheel.cpp index 77a15f7cbf7..20e8f683bf9 100644 --- a/src/creatures/players/wheel/player_wheel.cpp +++ b/src/creatures/players/wheel/player_wheel.cpp @@ -2410,21 +2410,25 @@ int32_t PlayerWheel::checkBattleHealingAmount() const { } int32_t PlayerWheel::checkAvatarSkill(WheelAvatarSkill_t skill) const { - if (skill == WheelAvatarSkill_t::NONE || getOnThinkTimer(WheelOnThink_t::AVATAR) <= OTSYS_TIME()) { + if (skill == WheelAvatarSkill_t::NONE || (getOnThinkTimer(WheelOnThink_t::AVATAR_SPELL) <= OTSYS_TIME() && getOnThinkTimer(WheelOnThink_t::AVATAR_FORGE) <= OTSYS_TIME())) { return 0; } uint8_t stage = 0; - if (getInstant("Avatar of Light")) { - stage = getStage(WheelStage_t::AVATAR_OF_LIGHT); - } else if (getInstant("Avatar of Steel")) { - stage = getStage(WheelStage_t::AVATAR_OF_STEEL); - } else if (getInstant("Avatar of Nature")) { - stage = getStage(WheelStage_t::AVATAR_OF_NATURE); - } else if (getInstant("Avatar of Storm")) { - stage = getStage(WheelStage_t::AVATAR_OF_STORM); + if (getOnThinkTimer(WheelOnThink_t::AVATAR_SPELL) > OTSYS_TIME()) { + if (getInstant("Avatar of Light")) { + stage = getStage(WheelStage_t::AVATAR_OF_LIGHT); + } else if (getInstant("Avatar of Steel")) { + stage = getStage(WheelStage_t::AVATAR_OF_STEEL); + } else if (getInstant("Avatar of Nature")) { + stage = getStage(WheelStage_t::AVATAR_OF_NATURE); + } else if (getInstant("Avatar of Storm")) { + stage = getStage(WheelStage_t::AVATAR_OF_STORM); + } else { + return 0; + } } else { - return 0; + stage = 3; } if (skill == WheelAvatarSkill_t::DAMAGE_REDUCTION) { diff --git a/src/creatures/players/wheel/wheel_definitions.hpp b/src/creatures/players/wheel/wheel_definitions.hpp index 8432e9dca21..c23d2adf53f 100644 --- a/src/creatures/players/wheel/wheel_definitions.hpp +++ b/src/creatures/players/wheel/wheel_definitions.hpp @@ -105,9 +105,10 @@ enum class WheelOnThink_t : uint8_t { FOCUS_MASTERY = 4, GIFT_OF_LIFE = 5, DIVINE_EMPOWERMENT = 6, - AVATAR = 7, + AVATAR_SPELL = 7, + AVATAR_FORGE = 8, - TOTAL_COUNT = 8 + TOTAL_COUNT = 9 }; enum class WheelStat_t : uint8_t { diff --git a/src/lua/callbacks/callbacks_definitions.hpp b/src/lua/callbacks/callbacks_definitions.hpp index 521a2c4cda7..3b8016f5f5b 100644 --- a/src/lua/callbacks/callbacks_definitions.hpp +++ b/src/lua/callbacks/callbacks_definitions.hpp @@ -56,6 +56,7 @@ enum class EventCallback_t : uint16_t { playerOnCombat, playerOnInventoryUpdate, playerOnRotateItem, + playerOnWalk, // Monster monsterOnDropLoot, monsterPostDropLoot, diff --git a/src/lua/callbacks/event_callback.cpp b/src/lua/callbacks/event_callback.cpp index 51c03131fa9..d2b88c30f3a 100644 --- a/src/lua/callbacks/event_callback.cpp +++ b/src/lua/callbacks/event_callback.cpp @@ -985,6 +985,29 @@ bool EventCallback::playerOnRotateItem(std::shared_ptr player, std::shar return getScriptInterface()->callFunction(3); } +void EventCallback::playerOnWalk(std::shared_ptr player, Direction &dir) const { + if (!getScriptInterface()->reserveScriptEnv()) { + g_logger().error("[EventCallback::eventOnWalk - " + "Player {}] " + "Call stack overflow. Too many lua script calls being nested.", + player->getName()); + return; + } + + ScriptEnvironment* scriptEnvironment = getScriptInterface()->getScriptEnv(); + scriptEnvironment->setScriptId(getScriptId(), getScriptInterface()); + + lua_State* L = getScriptInterface()->getLuaState(); + getScriptInterface()->pushFunction(getScriptId()); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + lua_pushnumber(L, dir); + + getScriptInterface()->callVoidFunction(2); +} + void EventCallback::playerOnStorageUpdate(std::shared_ptr player, const uint32_t key, const int32_t value, int32_t oldValue, uint64_t currentTime) const { if (!getScriptInterface()->reserveScriptEnv()) { g_logger().error("[EventCallback::eventOnStorageUpdate - " diff --git a/src/lua/callbacks/event_callback.hpp b/src/lua/callbacks/event_callback.hpp index d9dc4a9b110..9141235a028 100644 --- a/src/lua/callbacks/event_callback.hpp +++ b/src/lua/callbacks/event_callback.hpp @@ -116,6 +116,7 @@ class EventCallback : public Script { void playerOnCombat(std::shared_ptr player, std::shared_ptr target, std::shared_ptr item, CombatDamage &damage) const; void playerOnInventoryUpdate(std::shared_ptr player, std::shared_ptr item, Slots_t slot, bool equip) const; bool playerOnRotateItem(std::shared_ptr player, std::shared_ptr item, const Position &position) const; + void playerOnWalk(std::shared_ptr player, Direction &dir) const; // Monster void monsterOnDropLoot(std::shared_ptr monster, std::shared_ptr corpse) const; diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 3ba2c43841d..621023e5c52 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -3997,9 +3997,9 @@ int PlayerFunctions::luaPlayerAvatarTimer(lua_State* L) { } if (lua_gettop(L) == 1) { - lua_pushnumber(L, (lua_Number)player->wheel()->getOnThinkTimer(WheelOnThink_t::AVATAR)); + lua_pushnumber(L, (lua_Number)player->wheel()->getOnThinkTimer(WheelOnThink_t::AVATAR_SPELL)); } else { - player->wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR, getNumber(L, 2)); + player->wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR_SPELL, getNumber(L, 2)); pushBoolean(L, true); } return 1;