From 941540b4a2d79f0145c7ffd5ef48e18eb00556aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo?= Date: Mon, 20 Oct 2025 09:50:13 -0300 Subject: [PATCH 1/8] feat: PvP Expert --- config.lua.dist | 6 + data/modules/lib/modules.lua | 15 -- src/config/config_enums.hpp | 3 + src/config/configmanager.cpp | 3 + src/creatures/combat/combat.cpp | 42 ++- src/creatures/combat/combat.hpp | 3 + src/creatures/combat/condition.cpp | 2 +- src/creatures/players/player.cpp | 256 ++++++++++++++++++- src/creatures/players/player.hpp | 25 +- src/game/game.cpp | 57 ++++- src/game/game.hpp | 6 +- src/items/items_definitions.hpp | 3 + src/lua/functions/core/game/lua_enums.cpp | 3 + src/server/network/protocol/protocolgame.cpp | 26 +- src/server/network/protocol/protocolgame.hpp | 2 +- src/utils/tools.cpp | 9 + src/utils/utils_definitions.hpp | 10 + 17 files changed, 430 insertions(+), 41 deletions(-) diff --git a/config.lua.dist b/config.lua.dist index 4ed83ca20..1aff4093d 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -70,6 +70,12 @@ blackSkulledDeathMana = 0 fieldOwnershipDuration = 5 * 1000 loginProtectionPeriod = 10 * 1000 +-- Expert PvP +-- NOTE: toggleExpertPvp enables the PvP frames, similar to Tibia RL. +toggleExpertPvp = true +canWalkThroughOtherPlayers = false +canWalkThroughMagicWalls = false + -- Guild Wars -- guildWarsDefaultFrags: Default frags for guild war invitations if not specified. -- guildWarsMinimunFrags: Minimum frags required to initiate a guild war. diff --git a/data/modules/lib/modules.lua b/data/modules/lib/modules.lua index 3872b0ea7..799a62395 100644 --- a/data/modules/lib/modules.lua +++ b/data/modules/lib/modules.lua @@ -11,18 +11,3 @@ function addPlayerEvent(callable, delay, playerId, ...) end end, delay, callable, player.uid, ...) end - ---[[ -function Player.updateFightModes(self) - local msg = NetworkMessage() - - msg:addByte(0xA7) - - msg:addByte(self:getFightMode()) - msg:addByte(self:getChaseMode()) - msg:addByte(self:getSecureMode() and 1 or 0) - msg:addByte(self:getPvpMode()) - - msg:sendToPlayer(self) -end -]] diff --git a/src/config/config_enums.hpp b/src/config/config_enums.hpp index 0131f83f6..7f02d0b97 100644 --- a/src/config/config_enums.hpp +++ b/src/config/config_enums.hpp @@ -394,4 +394,7 @@ enum ConfigKey_t : uint16_t { TOGGLE_GUILD_WARS, GUILD_WARS_MINIMUM_FRAGS, GUILD_WARS_DEFAULT_FRAGS, + TOGGLE_EXPERT_PVP, + EXPERT_PVP_CANWALKTHROUGHOTHERPLAYERS, + EXPERT_PVP_CANWALKTHROUGHMAGICWALLS, }; diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index e4a29fd4b..afab6c1a3 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -184,6 +184,9 @@ bool ConfigManager::load() { loadBoolConfig(L, TOGGLE_GUILDHALL_NEED_GUILD, "toggleGuildHallNeedGuild", true); loadBoolConfig(L, TOGGLE_MAX_CONNECTIONS_BY_IP, "toggleMaxConnectionsByIP", false); loadBoolConfig(L, TOGGLE_GUILD_WARS, "toggleGuildWars", false); + loadBoolConfig(L, TOGGLE_EXPERT_PVP, "toggleExpertPvp", false); + loadBoolConfig(L, EXPERT_PVP_CANWALKTHROUGHOTHERPLAYERS, "canWalkThroughOtherPlayers", false); + loadBoolConfig(L, EXPERT_PVP_CANWALKTHROUGHMAGICWALLS, "canWalkThroughMagicWalls", 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/combat.cpp b/src/creatures/combat/combat.cpp index e422477d2..0bd6d1498 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -406,6 +406,8 @@ ReturnValue Combat::canDoCombat(const std::shared_ptr &attacker, const if (isProtected(attackerPlayer, targetPlayer)) { return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } else if (!attackerPlayer->canCombat(targetPlayer)) { + return RETURNVALUE_ADJUSTYOURCOMBAT; } // nopvp-zone @@ -433,6 +435,8 @@ ReturnValue Combat::canDoCombat(const std::shared_ptr &attacker, const if (isProtected(masterAttackerPlayer, targetPlayer)) { return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } else if (!masterAttackerPlayer->canCombat(targetPlayer)) { + return RETURNVALUE_ADJUSTYOURCOMBAT; } } } @@ -452,14 +456,30 @@ ReturnValue Combat::canDoCombat(const std::shared_ptr &attacker, const return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; } - if (target->isSummon() && targetMasterPlayer && target->getZoneType() == ZONE_NOPVP) { - return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + if (g_game().getOwnerPlayer(target)) { + if (target->getZoneType() == ZONE_NOPVP) { + return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + } else if (g_configManager().getBoolean(TOGGLE_EXPERT_PVP) && isProtected(attackerPlayer, targetPlayer)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; + } else if (!attackerPlayer->canCombat(target)) { + return RETURNVALUE_ADJUSTYOURCOMBAT; + } } } else if (attackerMonster) { if ((!targetMaster || !targetMasterPlayer) && attacker->getFaction() == FACTION_DEFAULT) { if (!attackerMaster || !masterAttackerPlayer) { return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; } + } else if (g_configManager().getBoolean(TOGGLE_EXPERT_PVP)) { + if (g_game().getOwnerPlayer(target)) { + if (target->getZoneType() == ZONE_NOPVP) { + return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + } else if (isProtected(attackerPlayer, targetPlayer)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; + } else if (!attackerPlayer->canCombat(target)) { + return RETURNVALUE_ADJUSTYOURCOMBAT; + } + } } } } else if (target && target->getNpc()) { @@ -2371,6 +2391,11 @@ void AreaCombat::setupExtArea(const std::list &list, uint32_t rows) { //**********************************************************// void MagicField::onStepInField(const std::shared_ptr &creature) { + const auto &target = g_game().getOwnerPlayer(creature); + if (target && !isAggressive(target)) { + return; + } + // remove magic walls/wild growth if ((!isBlocking() && g_game().getWorldType() == WORLDTYPE_OPTIONAL && id == ITEM_MAGICWALL_SAFE) || id == ITEM_WILDGROWTH_SAFE) { if (!creature->isInGhostMode()) { @@ -2583,6 +2608,19 @@ int32_t MagicField::getDamage() const { return 0; } +bool MagicField::isAggressive(const std::shared_ptr &player) const { + if (!g_configManager().getBoolean(TOGGLE_EXPERT_PVP) && g_configManager().getBoolean(EXPERT_PVP_CANWALKTHROUGHMAGICWALLS)) { + return true; + } + + const auto &caster = g_game().getOwnerPlayer(getOwnerId()); + if (!caster || pvpMode == PVP_MODE_RED_FIST) { + return true; + } + + return caster->isAggressiveCreature(player, pvpMode == PVP_MODE_WHITE_HAND, createTime) || pvpMode == PVP_MODE_YELLOW_HAND && player->getSkull() != SKULL_NONE; +} + MatrixArea::MatrixArea(uint32_t initRows, uint32_t initCols) : centerX(0), centerY(0), rows(initRows), cols(initCols) { data_ = new bool*[rows]; diff --git a/src/creatures/combat/combat.hpp b/src/creatures/combat/combat.hpp index de83aa50a..f2c6c636d 100644 --- a/src/creatures/combat/combat.hpp +++ b/src/creatures/combat/combat.hpp @@ -328,6 +328,9 @@ class MagicField final : public Item { CombatType_t getCombatType() const; int32_t getDamage() const; void onStepInField(const std::shared_ptr &creature); + bool isAggressive(const std::shared_ptr &player) const; + + PvpMode_t pvpMode = PVP_MODE_DOVE; private: int64_t createTime; diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index 33fd50450..dc8af43ac 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -1850,7 +1850,7 @@ bool ConditionDamage::doDamage(const std::shared_ptr &creature, int32_ } if (!creature->isAttackable() || Combat::canDoCombat(attacker, creature, damage.primary.type != COMBAT_HEALING) != RETURNVALUE_NOERROR) { - if (!creature->isInGhostMode() && !creature->getNpc()) { + if (!creature->isInGhostMode() && !creature->getNpc() && !g_configManager().getBoolean(TOGGLE_EXPERT_PVP)) { g_game().addMagicEffect(creature->getPosition(), CONST_ME_POFF); } return false; diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 5bfb5c30f..10530acf0 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -1373,12 +1373,62 @@ bool Player::canSeeCreature(const std::shared_ptr &creature) const { return true; } +bool Player::canCombat(const std::shared_ptr &creature) const { + if (!g_configManager().getBoolean(TOGGLE_EXPERT_PVP)) { + return true; + } + + if (const auto &monster = creature->getMonster()) { + if (!monster->isSummon()) { + return true; + } + + auto owner = monster->getMaster()->getPlayer(); + if (!owner || owner == getPlayer() || isPartner(owner) || isGuildMate(owner)) { + return true; + } + + return canCombat(owner); + } else if (const auto &player = creature->getPlayer()) { + if (player->getGroup()->access) { + return false; + } + + // red fist mode forces not to have secure mode. + // on all cases, if they have previous aggression, attacking attempt should be successful. + // if he is in white-hand mode, then the target must have aggression with the player OR with his partners + if (!hasSecureMode() || Combat::isInPvpZone(std::shared_ptr(const_cast(this)), player) || isAggressiveCreature(player, pvpMode == PVP_MODE_WHITE_HAND)) { + return true; + } + + // last case is that he is blocking him, without previous aggression details, as the target has a white skull or furtherer. + return pvpMode == PVP_MODE_YELLOW_HAND && player->getSkull() != SKULL_NONE; + } + + return false; +} + bool Player::canWalkthrough(const std::shared_ptr &creature) { if (group->access || creature->isInGhostMode()) { return true; } + bool expertPvpWalkThrough = g_configManager().getBoolean(TOGGLE_EXPERT_PVP) && g_configManager().getBoolean(EXPERT_PVP_CANWALKTHROUGHOTHERPLAYERS); const auto &player = creature->getPlayer(); + if (!player) { + if (expertPvpWalkThrough) { + if (const auto &monster = creature->getMonster()) { + if (!monster->isSummon() || !monster->getMaster()->getPlayer()) { + return false; + } + + const auto master = monster->getMaster()->getPlayer(); + return master != getPlayer() && canWalkthrough(master); + } + } + return false; + } + const auto &monster = creature->getMonster(); const auto &npc = creature->getNpc(); if (monster) { @@ -1438,7 +1488,11 @@ bool Player::canWalkthroughEx(const std::shared_ptr &creature) const { const auto &npc = creature->getNpc(); if (player) { const auto &playerTile = player->getTile(); - return playerTile && (playerTile->hasFlag(TILESTATE_NOPVPZONE) || playerTile->hasFlag(TILESTATE_PROTECTIONZONE) || player->getLevel() <= static_cast(g_configManager().getNumber(PROTECTION_LEVEL)) || g_game().getWorldType() == WORLDTYPE_OPTIONAL); + if (g_configManager().getBoolean(TOGGLE_EXPERT_PVP) && g_configManager().getBoolean(EXPERT_PVP_CANWALKTHROUGHOTHERPLAYERS)) { + return playerTile != nullptr; + } else { + return playerTile && (playerTile->hasFlag(TILESTATE_NOPVPZONE) || playerTile->hasFlag(TILESTATE_PROTECTIONZONE) || player->getLevel() <= static_cast(g_configManager().getNumber(PROTECTION_LEVEL)) || g_game().getWorldType() == WORLDTYPE_OPTIONAL); + } } else if (npc) { const auto &tile = npc->getTile(); const auto &houseTile = std::dynamic_pointer_cast(tile); @@ -2675,6 +2729,12 @@ void Player::onChangeZone(ZoneType_t zone) { } } + bool expertPvp = g_configManager().getBoolean(TOGGLE_EXPERT_PVP); + bool expertPvpWalkThrough = g_configManager().getBoolean(EXPERT_PVP_CANWALKTHROUGHOTHERPLAYERS); + if (!expertPvp || (expertPvp && !expertPvpWalkThrough)) { + g_game().updateCreatureWalkthrough(static_self_cast()); + } + updateImbuementTrackerStats(); wheel()->onThink(true); wheel()->sendGiftOfLifeCooldown(); @@ -3623,10 +3683,11 @@ bool Player::isPzLocked() const { BlockType_t Player::blockHit(const std::shared_ptr &attacker, const CombatType_t &combatType, int32_t &damage, bool checkDefense, bool checkArmor, bool field) { BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor, field); +/* if (attacker) { sendCreatureSquare(attacker, SQ_COLOR_BLACK); } - +*/ if (blockType != BLOCK_NONE) { return blockType; } @@ -5999,6 +6060,10 @@ void Player::setFightMode(FightMode_t mode) { fightMode = mode; } +void Player::setPvpMode(PvpMode_t mode) { + pvpMode = mode; +} + void Player::setSecureMode(bool mode) { secureMode = mode; } @@ -6171,6 +6236,9 @@ void Player::onEndCondition(ConditionType_t type) { onIdleStatus(); pzLocked = false; clearAttacked(); + if (g_configManager().getBoolean(TOGGLE_EXPERT_PVP)) { + clearAttackedBy(); + } if (getSkull() != SKULL_RED && getSkull() != SKULL_BLACK) { setSkull(SKULL_NONE); @@ -6237,7 +6305,12 @@ void Player::onAttackedCreature(const std::shared_ptr &target) { } const auto &targetPlayer = target->getPlayer(); - if (targetPlayer && !isPartner(targetPlayer) && !isGuildMate(targetPlayer)) { + if (targetPlayer && !isGuildMate(targetPlayer)) { + bool previousSituation = hasAttacked(targetPlayer) || targetPlayer->hasAttacked(getPlayer()); + if (isPartner(targetPlayer)) { + return; + } + if (!pzLocked && g_game().getWorldType() == WORLDTYPE_HARDCORE) { pzLocked = true; sendIcons(); @@ -6264,6 +6337,11 @@ void Player::onAttackedCreature(const std::shared_ptr &target) { } } } + + if (!previousSituation) { + g_game().updateCreatureSquare(getPlayer()); + g_game().updateCreatureSquare(targetPlayer); + } } addInFightTicks(); @@ -6927,12 +7005,17 @@ void Player::setSkullTicks(int64_t ticks) { skullTicks = ticks; } -bool Player::hasAttacked(const std::shared_ptr &attacked) const { +bool Player::hasAttacked(const std::shared_ptr &attacked, uint32_t time /*= 0*/) const { if (hasFlag(PlayerFlags_t::NotGainInFight) || !attacked) { return false; } - return attackedSet.contains(attacked->guid); + auto it = attackedSet.find(attacked->guid); + if (it == attackedSet.end()) { + return false; + } + + return time == 0 || it->second <= time; } void Player::addAttacked(const std::shared_ptr &attacked) { @@ -6940,7 +7023,7 @@ void Player::addAttacked(const std::shared_ptr &attacked) { return; } - attackedSet.emplace(attacked->guid); + attackedSet[attacked->guid] = OTSYS_TIME(); } void Player::removeAttacked(const std::shared_ptr &attacked) { @@ -6948,13 +7031,61 @@ void Player::removeAttacked(const std::shared_ptr &attacked) { return; } - attackedSet.erase(attacked->guid); + auto it = attackedSet.find(attacked->guid); + if (it != attackedSet.end()) { + attackedSet.erase(it); + } } void Player::clearAttacked() { + for (auto it : attackedSet) { + if (const auto &attacked = g_game().getPlayerByGUID(it.first)) { + attacked->removeAttackedBy(getPlayer()); + g_game().updateCreatureSquare(attacked); + } + } + attackedSet.clear(); } +bool Player::isAttackedBy(const std::shared_ptr &attacker) const { + if (hasFlag(PlayerFlags_t::NotGainInFight) || !attacker) { + return false; + } + + return attackedBySet.find(attacker->guid) != attackedBySet.end(); +} + +void Player::addAttackedBy(const std::shared_ptr &attacker) { + if (hasFlag(PlayerFlags_t::NotGainInFight) || !attacker || attacker == getPlayer()) { + return; + } + + attackedBySet.insert(attacker->guid); +} + +void Player::removeAttackedBy(const std::shared_ptr &attacker) { + if (!attacker || attacker == getPlayer()) { + return; + } + + auto it = attackedBySet.find(attacker->guid); + if (it != attackedBySet.end()) { + attackedBySet.erase(it); + } +} + +void Player::clearAttackedBy() { + for (auto it : attackedBySet) { + if (const auto &attacker = g_game().getPlayerByGUID(it)) { + attacker->removeAttacked(getPlayer()); + g_game().updateCreatureSquare(attacker); + } + } + + attackedBySet.clear(); +} + void Player::addUnjustifiedDead(const std::shared_ptr &attacked) { if (hasFlag(PlayerFlags_t::NotGainInFight) || hasFlag(PlayerFlags_t::NotGainUnjustified) || attacked == getPlayer() || g_game().getWorldType() == WORLDTYPE_HARDCORE) { return; @@ -8299,6 +8430,10 @@ void Player::onThink(uint32_t interval) { // Wheel of destiny major spells wheel()->onThink(); + if (g_configManager().getBoolean(TOGGLE_EXPERT_PVP)) { + g_game().updateCreatureSquare(std::const_pointer_cast(getPlayer())); + } + g_callbacks().executeCallback(EventCallback_t::playerOnThink, &EventCallback::playerOnThink, getPlayer(), interval); } @@ -8505,9 +8640,9 @@ void Player::sendPrivateMessage(const std::shared_ptr &speaker, SpeakCla } } -void Player::sendCreatureSquare(const std::shared_ptr &creature, SquareColor_t color) const { +void Player::sendCreatureSquare(const std::shared_ptr &creature, SquareColor_t color, SquareType_t type) const { if (client) { - client->sendCreatureSquare(creature, color); + client->sendCreatureSquare(creature, color, type); } } @@ -11041,6 +11176,11 @@ void Player::onRemoveCreature(const std::shared_ptr &creature, bool is g_game().internalCloseTrade(player); } + if (g_configManager().getBoolean(TOGGLE_EXPERT_PVP)) { + clearAttacked(); + clearAttackedBy(); + } + closeShopWindow(); IOLoginData::updateOnlineStatus(guid, false); g_saveManager().savePlayer(player); @@ -11990,3 +12130,101 @@ int16_t Player::getMantraAbsorbPercent(int16_t mantraAbsorbValue) const { return static_cast(std::floor(mantraAbsorbValue * multiplier)); } + +SquareColor_t Player::getCreatureSquare(const std::shared_ptr &creature) const { + if (!creature) { + return SQ_COLOR_NONE; + } + + if (creature == getPlayer()) { + if (isInPvpSituation()) { + return SQ_COLOR_YELLOW; + } + return SQ_COLOR_NONE; + } else if (creature->isSummon()) { + return getCreatureSquare(creature->getMaster()); + } + + const auto &otherPlayer = creature->getPlayer(); + if (!otherPlayer || otherPlayer->isAccessPlayer()) { + return SQ_COLOR_NONE; + } + + if (isAggressiveCreature(otherPlayer)) { + return SQ_COLOR_YELLOW; + } else if (otherPlayer->isInPvpSituation()) { + if (isAggressiveCreature(otherPlayer, true)) { + return SQ_COLOR_ORANGE; + } else { + return SQ_COLOR_BROWN; + } + } + + return SQ_COLOR_NONE; +} + +bool Player::hasPvpActivity(const std::shared_ptr &player, bool guildAndParty /* = false*/, uint32_t time /*= 0*/) const { + if (!player || player.get() == this) { + return false; + } + + auto playerHasAttacked = [time](const std::shared_ptr &a, const std::shared_ptr &b) { + if (!a || !b) { + return false; + } + + return a->hasAttacked(b, time) && b->isAttackedBy(a); + }; + + if (hasAttacked(player) || player->hasAttacked(std::const_pointer_cast(getPlayer()))) { + return true; + } + + if (guildAndParty) { + if (guild) { + for (auto it : guild->getMembersOnline()) { + if (it->hasPvpActivity(player, time)) { + return true; + } + } + } + + const auto &party = getParty(); + if (party) { + for (auto it : party->getMembers()) { + if (it->hasPvpActivity(player, time)) { + return true; + } + } + } + } + + return false; +} + +bool Player::isInPvpSituation() const { + return attackedSet.size() > 0 || attackedBySet.size() > 0; +} + +bool Player::isAggressiveCreature(const std::shared_ptr &creature, bool guildAndParty /*= false*/, uint32_t time /*= 0*/) const { + if (!creature) { + return false; + } + + const auto &player = creature->getPlayer(); + if (!player) { + if (!creature->isSummon()) { + return false; + } + + return isAggressiveCreature(creature->getMaster(), guildAndParty, time); + } + + if (player == getPlayer()) { + return true; + } else if (isPartner(player)) { + return false; + } + + return hasPvpActivity(player, guildAndParty, time); +} diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 952eb5235..1101ff875 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -609,6 +609,7 @@ class Player final : public Creature, public Cylinder, public Bankable { bool canSee(const Position &pos) override; bool canSeeCreature(const std::shared_ptr &creature) const override; + bool canCombat(const std::shared_ptr &creature) const; bool canWalkthrough(const std::shared_ptr &creature); bool canWalkthroughEx(const std::shared_ptr &creature) const; @@ -650,6 +651,7 @@ class Player final : public Creature, public Cylinder, public Bankable { void setChaseMode(bool mode); void setFightMode(FightMode_t mode); void setSecureMode(bool mode); + void setPvpMode(PvpMode_t mode); Faction_t getFaction() const override; @@ -767,10 +769,15 @@ class Player final : public Creature, public Cylinder, public Bankable { int64_t getSkullTicks() const; void setSkullTicks(int64_t ticks); - bool hasAttacked(const std::shared_ptr &attacked) const; + bool hasAttacked(const std::shared_ptr &attacked, uint32_t time = 0) const; void addAttacked(const std::shared_ptr &attacked); void removeAttacked(const std::shared_ptr &attacked); void clearAttacked(); + bool isAttackedBy(const std::shared_ptr &attacker) const; + void addAttackedBy(const std::shared_ptr &attacker); + void removeAttackedBy(const std::shared_ptr &attacker); + void clearAttackedBy(); + void addUnjustifiedDead(const std::shared_ptr &attacked); void sendCreatureEmblem(const std::shared_ptr &creature) const; void sendCreatureSkull(const std::shared_ptr &creature) const; @@ -818,7 +825,7 @@ class Player final : public Creature, public Cylinder, public Bankable { void sendCreatureSay(const std::shared_ptr &creature, SpeakClasses type, const std::string &text, const Position* pos = nullptr) const; void sendCreatureReload(const std::shared_ptr &creature) const; void sendPrivateMessage(const std::shared_ptr &speaker, SpeakClasses type, const std::string &text) const; - void sendCreatureSquare(const std::shared_ptr &creature, SquareColor_t color) const; + void sendCreatureSquare(const std::shared_ptr &creature, SquareColor_t color, SquareType_t type) const; void sendCreatureChangeOutfit(const std::shared_ptr &creature, const Outfit_t &outfit) const; void sendCreatureChangeVisible(const std::shared_ptr &creature, bool visible); void sendCreatureLight(const std::shared_ptr &creature) const; @@ -1051,6 +1058,16 @@ class Player final : public Creature, public Cylinder, public Bankable { void setWalkExhaust(int64_t value); + // PvP Expert + SquareColor_t getCreatureSquare(const std::shared_ptr &creature) const; + bool hasPvpActivity(const std::shared_ptr &player, bool guildAndParty = false, uint32_t time = 0) const; + bool isInPvpSituation() const; + bool isAggressiveCreature(const std::shared_ptr &creature, bool guildAndParty = false, uint32_t time = 0) const; + + PvpMode_t getPvPMode() const { + return pvpMode; + } + const std::map &getOpenContainers() const; uint16_t getBaseXpGain() const; @@ -1491,7 +1508,8 @@ class Player final : public Creature, public Cylinder, public Bankable { void addBestiaryKill(const std::shared_ptr &mType); void addBosstiaryKill(const std::shared_ptr &mType); - phmap::flat_hash_set attackedSet {}; + std::unordered_map attackedSet; + std::unordered_set attackedBySet; std::map openContainers; std::map> depotLockerMap; @@ -1675,6 +1693,7 @@ class Player final : public Creature, public Cylinder, public Bankable { BlockType_t lastAttackBlockType = BLOCK_NONE; TradeState_t tradeState = TRADE_NONE; FightMode_t fightMode = FIGHTMODE_ATTACK; + PvpMode_t pvpMode = PVP_MODE_DOVE; Faction_t faction = FACTION_PLAYER; QuickLootFilter_t quickLootFilter {}; PlayerPronoun_t pronoun = PLAYERPRONOUN_THEY; diff --git a/src/game/game.cpp b/src/game/game.cpp index b3465dd01..63205dcb2 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -1032,6 +1032,22 @@ std::shared_ptr Game::getMarketPlayerByGUID(uint32_t &guid) { return getPlayerByGUID(guid, true); } +std::shared_ptr Game::getOwnerPlayer(const std::shared_ptr &creature) { + if (!creature) { + return nullptr; + } + + if (creature->isSummon()) { + return getOwnerPlayer(creature->getMaster()); + } + + return creature->getPlayer(); +} + +std::shared_ptr Game::getOwnerPlayer(uint32_t creatureId) { + return getOwnerPlayer(getCreatureByID(creatureId)); +} + std::shared_ptr Game::getCreatureByName(const std::string &s) { if (s.empty()) { return nullptr; @@ -6051,7 +6067,7 @@ void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) { player->setFollowCreature(getCreatureByID(creatureId)); } -void Game::playerSetFightModes(uint32_t playerId, FightMode_t fightMode, bool chaseMode, bool secureMode) { +void Game::playerSetFightModes(uint32_t playerId, FightMode_t fightMode, PvpMode_t pvpMode, bool chaseMode, bool secureMode) { const auto &player = getPlayerByID(playerId); if (!player) { return; @@ -6059,7 +6075,34 @@ void Game::playerSetFightModes(uint32_t playerId, FightMode_t fightMode, bool ch player->setFightMode(fightMode); player->setChaseMode(chaseMode); - player->setSecureMode(secureMode); + + if (g_configManager().getBoolean(TOGGLE_EXPERT_PVP)) { + auto oldPvpMode = player->pvpMode; + if (worldType == WORLDTYPE_OPTIONAL && pvpMode == PVP_MODE_RED_FIST) { + player->setPvpMode(player->pvpMode); + } else if (worldType == WORLDTYPE_HARDCORE && pvpMode != PVP_MODE_RED_FIST) { + player->setPvpMode(PVP_MODE_RED_FIST); + } else { + player->setPvpMode(pvpMode); + } + + if ((worldType == WORLDTYPE_OPTIONAL && !secureMode) || (worldType == WORLDTYPE_HARDCORE && secureMode)) { + player->setSecureMode(!secureMode); + } else { + if (player->getPvPMode() == PVP_MODE_RED_FIST && oldPvpMode != PVP_MODE_RED_FIST) { + player->setSecureMode(false); + } else if (player->pvpMode != PVP_MODE_RED_FIST && oldPvpMode == PVP_MODE_RED_FIST) { + player->setSecureMode(true); + } else { + player->setSecureMode(secureMode); + } + } + + player->sendFightModes(); + } else { + player->setPvpMode(pvpMode); + player->setSecureMode(secureMode); + } } void Game::playerRequestAddVip(uint32_t playerId, const std::string &name) { @@ -8695,6 +8738,16 @@ void Game::updatePlayerHelpers(const std::shared_ptr &player) { } } +void Game::updateCreatureSquare(const std::shared_ptr &creature) { + if (!g_configManager().getBoolean(TOGGLE_EXPERT_PVP)) { + return; + } + + for (const auto &spectator : Spectators().find(creature->getPosition(), true)) { + spectator->getPlayer()->sendCreatureSquare(creature, spectator->getPlayer()->getCreatureSquare(creature), SQUARE_STAY); + } +} + void Game::playerJoinParty(uint32_t playerId, uint32_t leaderId) { const auto &player = getPlayerByID(playerId); if (!player) { diff --git a/src/game/game.hpp b/src/game/game.hpp index 26b386771..9f904046b 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -173,6 +173,9 @@ class Game { std::shared_ptr getMarketPlayerByGUID(uint32_t &guid); + std::shared_ptr getOwnerPlayer(const std::shared_ptr &creature); + std::shared_ptr getOwnerPlayer(uint32_t creatureId); + std::shared_ptr getPlayerByName(const std::string &s, bool allowOffline = false, bool isNewName = false); std::shared_ptr getPlayerByGUID(const uint32_t &guid, bool allowOffline = false); @@ -382,7 +385,7 @@ class Game { void playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId); void playerFollowCreature(uint32_t playerId, uint32_t creatureId); void playerCancelAttackAndFollow(uint32_t playerId); - void playerSetFightModes(uint32_t playerId, FightMode_t fightMode, bool chaseMode, bool secureMode); + void playerSetFightModes(uint32_t playerId, FightMode_t fightMode, PvpMode_t pvpMode, bool chaseMode, bool secureMode); void playerLookAt(uint32_t playerId, uint16_t itemId, const Position &pos, uint8_t stackPos); void playerLookInBattleList(uint32_t playerId, uint32_t creatureId); void playerQuickLootCorpse(const std::shared_ptr &player, const std::shared_ptr &corpse, const Position &position); @@ -476,6 +479,7 @@ class Game { void updateCreatureIcon(const std::shared_ptr &creature); void reloadCreature(const std::shared_ptr &creature); void updateCreatureSkull(const std::shared_ptr &creature) const; + void updateCreatureSquare(const std::shared_ptr &creature); void updatePlayerShield(const std::shared_ptr &player); void updateCreatureType(const std::shared_ptr &creature); void updateCreatureWalkthrough(const std::shared_ptr &creature); diff --git a/src/items/items_definitions.hpp b/src/items/items_definitions.hpp index ac53ebb77..1632b61b3 100644 --- a/src/items/items_definitions.hpp +++ b/src/items/items_definitions.hpp @@ -133,6 +133,9 @@ enum ReturnValue : uint16_t { RETURNVALUE_ITEMISNOTYOURS, RETURNVALUE_ITEMUNTRADEABLE, RETURNVALUE_NOTENOUGHHARMONY, + RETURNVALUE_YOUCANNOTPASSTHROUGHAGGRESSIVEPLAYERS, + RETURNVALUE_YOUCANNOTPASSTHROUGHAGGRESSIVECREATURES, + RETURNVALUE_ADJUSTYOURCOMBAT, }; enum ItemGroup_t { diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp index 9ce976482..7fd94070f 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -1268,6 +1268,9 @@ void LuaEnums::initReturnValueEnums(lua_State* L) { registerEnum(L, RETURNVALUE_ITEMISNOTYOURS); registerEnum(L, RETURNVALUE_ITEMUNTRADEABLE); registerEnum(L, RETURNVALUE_NOTENOUGHHARMONY); + registerEnum(L, RETURNVALUE_ADJUSTYOURCOMBAT); + registerEnum(L, RETURNVALUE_YOUCANNOTPASSTHROUGHAGGRESSIVEPLAYERS); + registerEnum(L, RETURNVALUE_YOUCANNOTPASSTHROUGHAGGRESSIVECREATURES); } // Reload diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 7df2b43bc..cec17bb2b 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -2022,7 +2022,7 @@ void ProtocolGame::parseFightModes(NetworkMessage &msg) { uint8_t rawFightMode = msg.getByte(); // 1 - offensive, 2 - balanced, 3 - defensive uint8_t rawChaseMode = msg.getByte(); // 0 - stand while fightning, 1 - chase opponent uint8_t rawSecureMode = msg.getByte(); // 0 - can't attack unmarked, 1 - can attack unmarked - // uint8_t rawPvpMode = msg.getByte(); // pvp mode introduced in 10.0 + uint8_t rawPvpMode = msg.getByte(); // pvp mode introduced in 10.0 FightMode_t fightMode; if (rawFightMode == 1) { @@ -2033,7 +2033,18 @@ void ProtocolGame::parseFightModes(NetworkMessage &msg) { fightMode = FIGHTMODE_DEFENSE; } - g_game().playerSetFightModes(player->getID(), fightMode, rawChaseMode != 0, rawSecureMode != 0); + PvpMode_t pvpMode; + if (rawPvpMode == 1) { + pvpMode = PVP_MODE_WHITE_HAND; + } else if (rawPvpMode == 2) { + pvpMode = PVP_MODE_YELLOW_HAND; + } else if (rawPvpMode == 3) { + pvpMode = PVP_MODE_RED_FIST; + } else { + pvpMode = PVP_MODE_DOVE; + } + + g_game().playerSetFightModes(player->getID(), fightMode, pvpMode, rawChaseMode != 0, rawSecureMode != 0); } void ProtocolGame::parseAttack(NetworkMessage &msg) { @@ -3552,7 +3563,7 @@ void ProtocolGame::sendCreatureType(const std::shared_ptr &creature, u writeToOutputBuffer(msg); } -void ProtocolGame::sendCreatureSquare(const std::shared_ptr &creature, SquareColor_t color) { +void ProtocolGame::sendCreatureSquare(const std::shared_ptr &creature, SquareColor_t color, SquareType_t type) { if (!canSee(creature)) { return; } @@ -3560,7 +3571,7 @@ void ProtocolGame::sendCreatureSquare(const std::shared_ptr &creature, NetworkMessage msg; msg.addByte(0x93); msg.add(creature->getID()); - msg.addByte(0x01); + msg.addByte(type); msg.addByte(color); writeToOutputBuffer(msg); } @@ -7105,7 +7116,7 @@ void ProtocolGame::sendFightModes() { msg.addByte(player->fightMode); msg.addByte(player->chaseMode); msg.addByte(player->secureMode); - msg.addByte(PVP_MODE_DOVE); + msg.addByte(player->pvpMode); writeToOutputBuffer(msg); } @@ -7173,8 +7184,9 @@ void ProtocolGame::sendAddCreature(const std::shared_ptr &creature, co } } - msg.addByte(0x00); // can change pvp framing option - msg.addByte(0x00); // expert mode button enabled + bool expertPvp = g_configManager().getBoolean(TOGGLE_EXPERT_PVP); + msg.addByte(expertPvp ? 0x01 : 0x00); // can change pvp framing option + msg.addByte(expertPvp ? 0x01 : 0x00); // expert mode button enabled msg.addString(g_configManager().getString(STORE_IMAGES_URL)); msg.add(static_cast(g_configManager().getNumber(STORE_COIN_PACKET))); diff --git a/src/server/network/protocol/protocolgame.hpp b/src/server/network/protocol/protocolgame.hpp index 70be60113..2e2e4592c 100644 --- a/src/server/network/protocol/protocolgame.hpp +++ b/src/server/network/protocol/protocolgame.hpp @@ -423,7 +423,7 @@ class ProtocolGame final : public Protocol { void sendWorldLight(const LightInfo &lightInfo); void sendTibiaTime(int32_t time); - void sendCreatureSquare(const std::shared_ptr &creature, SquareColor_t color); + void sendCreatureSquare(const std::shared_ptr &creature, SquareColor_t color, SquareType_t type); void sendSpellCooldown(uint16_t spellId, uint32_t time); void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time); diff --git a/src/utils/tools.cpp b/src/utils/tools.cpp index 9a78f8797..7d7a8ba84 100644 --- a/src/utils/tools.cpp +++ b/src/utils/tools.cpp @@ -1569,6 +1569,15 @@ const char* getReturnMessage(ReturnValue value) { case RETURNVALUE_NOTENOUGHHARMONY: return "You do not have enough harmony."; + case RETURNVALUE_YOUCANNOTPASSTHROUGHAGGRESSIVECREATURES: + return "You cannot pass creatures that are aggressive against."; + + case RETURNVALUE_YOUCANNOTPASSTHROUGHAGGRESSIVEPLAYERS: + return "You cannot pass players that are aggressive against."; + + case RETURNVALUE_ADJUSTYOURCOMBAT: + return "You need to adjust your PvP Mode."; + // Any unhandled ReturnValue will go enter here default: return "Unknown error."; diff --git a/src/utils/utils_definitions.hpp b/src/utils/utils_definitions.hpp index b6b6ace23..3f68d1486 100644 --- a/src/utils/utils_definitions.hpp +++ b/src/utils/utils_definitions.hpp @@ -415,8 +415,18 @@ enum Fluids_t : uint8_t { // 13.40 last fluid is 20, 21+ is a loop from 0 to 20 over and over again }; +enum SquareType_t : uint8_t { + SQUARE_REMOVE, + SQUARE_FLASH, + SQUARE_STAY +}; + enum SquareColor_t : uint8_t { SQ_COLOR_BLACK = 0, + SQ_COLOR_BROWN = 114, + SQ_COLOR_ORANGE = 198, + SQ_COLOR_YELLOW = 210, + SQ_COLOR_NONE = 255, // internal }; enum TextColor_t : uint8_t { From b6b2e0a6ca67e1a3317ecd63bd49a8e031ad9d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo?= Date: Mon, 20 Oct 2025 10:49:18 -0300 Subject: [PATCH 2/8] fix: Black skull cannot activate Red Fist --- src/game/game.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/game/game.cpp b/src/game/game.cpp index 63205dcb2..74114f399 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -6078,7 +6078,10 @@ void Game::playerSetFightModes(uint32_t playerId, FightMode_t fightMode, PvpMode if (g_configManager().getBoolean(TOGGLE_EXPERT_PVP)) { auto oldPvpMode = player->pvpMode; - if (worldType == WORLDTYPE_OPTIONAL && pvpMode == PVP_MODE_RED_FIST) { + if (player->getSkull() == SKULL_BLACK && pvpMode == PVP_MODE_RED_FIST) { + // Black skull cannot activate Red Fist + player->setPvpMode(oldPvpMode); + } else if (worldType == WORLDTYPE_OPTIONAL && pvpMode == PVP_MODE_RED_FIST) { player->setPvpMode(player->pvpMode); } else if (worldType == WORLDTYPE_HARDCORE && pvpMode != PVP_MODE_RED_FIST) { player->setPvpMode(PVP_MODE_RED_FIST); From 0e7cc8b2964d8f776656599394889a64f39d3ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo?= Date: Mon, 20 Oct 2025 17:40:32 -0300 Subject: [PATCH 3/8] fix: pvp expert crash when attacking player --- src/creatures/creature.cpp | 7 ++++- src/creatures/players/player.cpp | 52 ++++++++++++++++++++++++++------ src/game/game.cpp | 17 ++++++++++- 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index 0c87fe1b9..5f6e1d3c0 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -545,7 +545,12 @@ void Creature::onDeath() { const uint64_t gainExp = getGainedExperience(attacker); const auto &attackerMaster = attacker->getMaster() ? attacker->getMaster() : attacker; if (auto attackerPlayer = attackerMaster->getPlayer()) { - attackerPlayer->removeAttacked(getPlayer()); + if (auto thisPlayer = getPlayer()) { + attackerPlayer->removeAttacked(thisPlayer); + thisPlayer->removeAttackedBy(attackerPlayer); + } + + g_game().updateCreatureSquare(attackerPlayer); const auto &party = attackerPlayer->getParty(); killers.insert(attackerPlayer); diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 10530acf0..958dc21cf 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -1383,7 +1383,12 @@ bool Player::canCombat(const std::shared_ptr &creature) const { return true; } - auto owner = monster->getMaster()->getPlayer(); + const auto master = monster->getMaster(); + if (!master) { + return true; + } + + auto owner = master->getPlayer(); if (!owner || owner == getPlayer() || isPartner(owner) || isGuildMate(owner)) { return true; } @@ -1397,7 +1402,7 @@ bool Player::canCombat(const std::shared_ptr &creature) const { // red fist mode forces not to have secure mode. // on all cases, if they have previous aggression, attacking attempt should be successful. // if he is in white-hand mode, then the target must have aggression with the player OR with his partners - if (!hasSecureMode() || Combat::isInPvpZone(std::shared_ptr(const_cast(this)), player) || isAggressiveCreature(player, pvpMode == PVP_MODE_WHITE_HAND)) { + if (!hasSecureMode() || Combat::isInPvpZone(std::const_pointer_cast(static_self_cast()), player) || isAggressiveCreature(player, pvpMode == PVP_MODE_WHITE_HAND)) { return true; } @@ -1418,12 +1423,13 @@ bool Player::canWalkthrough(const std::shared_ptr &creature) { if (!player) { if (expertPvpWalkThrough) { if (const auto &monster = creature->getMonster()) { - if (!monster->isSummon() || !monster->getMaster()->getPlayer()) { + const auto master = monster->getMaster(); + if (!monster->isSummon() || !master || !master->getPlayer()) { return false; } - const auto master = monster->getMaster()->getPlayer(); - return master != getPlayer() && canWalkthrough(master); + const auto owner = master->getPlayer(); + return owner != getPlayer() && canWalkthrough(owner); } } return false; @@ -6318,6 +6324,7 @@ void Player::onAttackedCreature(const std::shared_ptr &target) { if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) { addAttacked(targetPlayer); + targetPlayer->addAttackedBy(static_self_cast()); targetPlayer->sendCreatureSkull(static_self_cast()); } else if (!targetPlayer->hasAttacked(static_self_cast())) { if (!pzLocked) { @@ -6327,6 +6334,7 @@ void Player::onAttackedCreature(const std::shared_ptr &target) { if (!Combat::isInPvpZone(static_self_cast(), targetPlayer) && !isInWar(targetPlayer)) { addAttacked(targetPlayer); + targetPlayer->addAttackedBy(static_self_cast()); if (targetPlayer->getSkull() == SKULL_NONE && getSkull() == SKULL_NONE && !targetPlayer->hasKilled(static_self_cast())) { setSkull(SKULL_WHITE); @@ -6982,6 +6990,11 @@ Skulls_t Player::getSkullClient(const std::shared_ptr &creature) { } } + bool expertPvp = g_configManager().getBoolean(TOGGLE_EXPERT_PVP); + if (!expertPvp && isInWar(player)) { + return SKULL_GREEN; + } + if (player->hasKilled(getPlayer())) { return SKULL_ORANGE; } @@ -6990,7 +7003,7 @@ Skulls_t Player::getSkullClient(const std::shared_ptr &creature) { return SKULL_YELLOW; } - if (m_party && m_party == player->m_party) { + if (!expertPvp && isPartner(player)) { return SKULL_GREEN; } } @@ -7053,6 +7066,10 @@ bool Player::isAttackedBy(const std::shared_ptr &attacker) const { return false; } + if (attacker->isRemoved()) { + return false; + } + return attackedBySet.find(attacker->guid) != attackedBySet.end(); } @@ -12168,15 +12185,19 @@ bool Player::hasPvpActivity(const std::shared_ptr &player, bool guildAnd return false; } + if (player->isRemoved()) { + return false; + } + auto playerHasAttacked = [time](const std::shared_ptr &a, const std::shared_ptr &b) { - if (!a || !b) { + if (!a || !b || a->isRemoved() || b->isRemoved()) { return false; } return a->hasAttacked(b, time) && b->isAttackedBy(a); }; - if (hasAttacked(player) || player->hasAttacked(std::const_pointer_cast(getPlayer()))) { + if (hasAttacked(player) || player->hasAttacked(std::const_pointer_cast(static_self_cast()))) { return true; } @@ -12191,6 +12212,10 @@ bool Player::hasPvpActivity(const std::shared_ptr &player, bool guildAnd const auto &party = getParty(); if (party) { + if (party->getLeader()->hasPvpActivity(player, time)) { + return true; + } + for (auto it : party->getMembers()) { if (it->hasPvpActivity(player, time)) { return true; @@ -12211,13 +12236,22 @@ bool Player::isAggressiveCreature(const std::shared_ptr &creature, boo return false; } + if (creature->isRemoved()) { + return false; + } + const auto &player = creature->getPlayer(); if (!player) { if (!creature->isSummon()) { return false; } - return isAggressiveCreature(creature->getMaster(), guildAndParty, time); + const auto &master = creature->getMaster(); + if (!master || master->isRemoved()) { + return false; + } + + return isAggressiveCreature(master, guildAndParty, time); } if (player == getPlayer()) { diff --git a/src/game/game.cpp b/src/game/game.cpp index 74114f399..53de96f2e 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -6078,8 +6078,8 @@ void Game::playerSetFightModes(uint32_t playerId, FightMode_t fightMode, PvpMode if (g_configManager().getBoolean(TOGGLE_EXPERT_PVP)) { auto oldPvpMode = player->pvpMode; + // Black skull cannot activate Red Fist if (player->getSkull() == SKULL_BLACK && pvpMode == PVP_MODE_RED_FIST) { - // Black skull cannot activate Red Fist player->setPvpMode(oldPvpMode); } else if (worldType == WORLDTYPE_OPTIONAL && pvpMode == PVP_MODE_RED_FIST) { player->setPvpMode(player->pvpMode); @@ -8720,6 +8720,16 @@ void Game::playerInviteToParty(uint32_t playerId, uint32_t invitedId) { return; } + if (player->isInPvpSituation()) { + player->sendTextMessage(MESSAGE_PARTY_MANAGEMENT, "You can't invite players while you are in an aggression."); + return; + } + + if (invitedPlayer->isInPvpSituation()) { + player->sendTextMessage(MESSAGE_PARTY_MANAGEMENT, "This player can't be invited while he is in an aggression."); + return; + } + std::shared_ptr party = player->getParty(); if (!party) { party = Party::create(player); @@ -8767,6 +8777,11 @@ void Game::playerJoinParty(uint32_t playerId, uint32_t leaderId) { return; } + if (player->isInPvpSituation()) { + player->sendTextMessage(MESSAGE_PARTY_MANAGEMENT, "You can't join while you are in an aggression."); + return; + } + if (player->getParty()) { player->sendTextMessage(MESSAGE_PARTY_MANAGEMENT, "You are already in a party."); return; From 08f5807050e7f2b9cdca9cd198b72b76515cd76e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo?= Date: Mon, 20 Oct 2025 18:59:45 -0300 Subject: [PATCH 4/8] fix: pvp expert pzlocked --- src/creatures/players/player.cpp | 84 +++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 958dc21cf..03121da59 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -1399,15 +1399,36 @@ bool Player::canCombat(const std::shared_ptr &creature) const { return false; } - // red fist mode forces not to have secure mode. - // on all cases, if they have previous aggression, attacking attempt should be successful. - // if he is in white-hand mode, then the target must have aggression with the player OR with his partners - if (!hasSecureMode() || Combat::isInPvpZone(std::const_pointer_cast(static_self_cast()), player) || isAggressiveCreature(player, pvpMode == PVP_MODE_WHITE_HAND)) { - return true; + // Cannot attack party/guild members in any mode + if (isPartner(player) || isGuildMate(player)) { + return false; } - // last case is that he is blocking him, without previous aggression details, as the target has a white skull or furtherer. - return pvpMode == PVP_MODE_YELLOW_HAND && player->getSkull() != SKULL_NONE; + // Apply PvP mode rules + switch (pvpMode) { + case PVP_MODE_DOVE: { + // Dove: Only attack those who have attacked you + return hasAttacked(player) || player->hasAttacked(std::const_pointer_cast(static_self_cast())); + } + + case PVP_MODE_WHITE_HAND: { + // White Hand: Attack those who attacked you OR your party/guild members + return isAggressiveCreature(player, true); // guildAndParty = true + } + + case PVP_MODE_YELLOW_HAND: { + // Yellow Hand: Attack any player with skull (except party/guild) + return player->getSkull() != SKULL_NONE; + } + + case PVP_MODE_RED_FIST: { + // Red Fist: Attack everyone (except party/guild) + return true; + } + + default: + return false; + } } return false; @@ -6317,17 +6338,58 @@ void Player::onAttackedCreature(const std::shared_ptr &target) { return; } - if (!pzLocked && g_game().getWorldType() == WORLDTYPE_HARDCORE) { + // Apply PvP mode specific rules for pz lock and skull + bool shouldPzLock = false; + bool shouldYellowSkull = false; + + switch (pvpMode) { + case PVP_MODE_DOVE: { + // Dove: No pz lock, no yellow skull + shouldPzLock = false; + shouldYellowSkull = false; + break; + } + + case PVP_MODE_WHITE_HAND: { + // White Hand: No pz lock, but yellow skull for target + shouldPzLock = false; + shouldYellowSkull = true; + break; + } + + case PVP_MODE_YELLOW_HAND: { + // Yellow Hand: pz lock and yellow skull for target + shouldPzLock = true; + shouldYellowSkull = true; + break; + } + + case PVP_MODE_RED_FIST: { + // Red Fist: pz lock, no yellow skull (can attack anyone) + shouldPzLock = true; + shouldYellowSkull = false; + break; + } + + default: + shouldPzLock = false; + shouldYellowSkull = false; + break; + } + + // Apply pz lock if needed + if (shouldPzLock && !pzLocked) { pzLocked = true; sendIcons(); } - - if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) { + + // Apply yellow skull if needed + if (shouldYellowSkull && getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) { addAttacked(targetPlayer); targetPlayer->addAttackedBy(static_self_cast()); targetPlayer->sendCreatureSkull(static_self_cast()); } else if (!targetPlayer->hasAttacked(static_self_cast())) { - if (!pzLocked) { + if (shouldPzLock && !pzLocked) { pzLocked = true; sendIcons(); } From 333989ea58f2f7fde207b9a96289a94856a84beb Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 21 Oct 2025 12:54:36 +0000 Subject: [PATCH 5/8] Code format - (Clang-format) --- src/creatures/players/player.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 03121da59..ef251aa09 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -1410,22 +1410,22 @@ bool Player::canCombat(const std::shared_ptr &creature) const { // Dove: Only attack those who have attacked you return hasAttacked(player) || player->hasAttacked(std::const_pointer_cast(static_self_cast())); } - + case PVP_MODE_WHITE_HAND: { // White Hand: Attack those who attacked you OR your party/guild members return isAggressiveCreature(player, true); // guildAndParty = true } - + case PVP_MODE_YELLOW_HAND: { // Yellow Hand: Attack any player with skull (except party/guild) return player->getSkull() != SKULL_NONE; } - + case PVP_MODE_RED_FIST: { // Red Fist: Attack everyone (except party/guild) return true; } - + default: return false; } @@ -3710,11 +3710,11 @@ bool Player::isPzLocked() const { BlockType_t Player::blockHit(const std::shared_ptr &attacker, const CombatType_t &combatType, int32_t &damage, bool checkDefense, bool checkArmor, bool field) { BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor, field); -/* - if (attacker) { - sendCreatureSquare(attacker, SQ_COLOR_BLACK); - } -*/ + /* + if (attacker) { + sendCreatureSquare(attacker, SQ_COLOR_BLACK); + } + */ if (blockType != BLOCK_NONE) { return blockType; } @@ -6341,7 +6341,7 @@ void Player::onAttackedCreature(const std::shared_ptr &target) { // Apply PvP mode specific rules for pz lock and skull bool shouldPzLock = false; bool shouldYellowSkull = false; - + switch (pvpMode) { case PVP_MODE_DOVE: { // Dove: No pz lock, no yellow skull @@ -6349,40 +6349,40 @@ void Player::onAttackedCreature(const std::shared_ptr &target) { shouldYellowSkull = false; break; } - + case PVP_MODE_WHITE_HAND: { // White Hand: No pz lock, but yellow skull for target shouldPzLock = false; shouldYellowSkull = true; break; } - + case PVP_MODE_YELLOW_HAND: { // Yellow Hand: pz lock and yellow skull for target shouldPzLock = true; shouldYellowSkull = true; break; } - + case PVP_MODE_RED_FIST: { // Red Fist: pz lock, no yellow skull (can attack anyone) shouldPzLock = true; shouldYellowSkull = false; break; } - + default: shouldPzLock = false; shouldYellowSkull = false; break; } - + // Apply pz lock if needed if (shouldPzLock && !pzLocked) { pzLocked = true; sendIcons(); } - + // Apply yellow skull if needed if (shouldYellowSkull && getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) { addAttacked(targetPlayer); From c26535fd96b605864a12fb88a34386b156579e3a Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 21 Oct 2025 12:58:23 +0000 Subject: [PATCH 6/8] Lua code format - (Stylua) --- .../creaturescript_duke_krule_tranform.lua | 202 +++++++++--------- data/scripts/actions/items/potions.lua | 14 +- 2 files changed, 107 insertions(+), 109 deletions(-) diff --git a/data-global/scripts/creaturescripts/monster/creaturescript_duke_krule_tranform.lua b/data-global/scripts/creaturescripts/monster/creaturescript_duke_krule_tranform.lua index 5e10c6fba..efde14a80 100644 --- a/data-global/scripts/creaturescripts/monster/creaturescript_duke_krule_tranform.lua +++ b/data-global/scripts/creaturescripts/monster/creaturescript_duke_krule_tranform.lua @@ -9,125 +9,123 @@ local toPosition = Position(33464, 31481, 13) local transformationDuration = 10000 local function isInArea(position, fromPos, toPos) - return position.x >= fromPos.x and position.x <= toPos.x and - position.y >= fromPos.y and position.y <= toPos.y and - position.z == fromPos.z + return position.x >= fromPos.x and position.x <= toPos.x and position.y >= fromPos.y and position.y <= toPos.y and position.z == fromPos.z end local function applyAreaEffect(player, transformationType) - if not player or not player:isPlayer() then - return - end + if not player or not player:isPlayer() then + return + end - local playerPos = player:getPosition() - if transformationType == "fire" then - for x = -2, 2 do - for y = -2, 2 do - local pos = Position(playerPos.x + x, playerPos.y + y, playerPos.z) - pos:sendMagicEffect(CONST_ME_HITBYFIRE) - end - end - elseif transformationType == "water" then - for x = -2, 2 do - for y = -2, 2 do - local pos = Position(playerPos.x + x, playerPos.y + y, playerPos.z) - pos:sendMagicEffect(CONST_ME_ICETORNADO) - end - end - end + local playerPos = player:getPosition() + if transformationType == "fire" then + for x = -2, 2 do + for y = -2, 2 do + local pos = Position(playerPos.x + x, playerPos.y + y, playerPos.z) + pos:sendMagicEffect(CONST_ME_HITBYFIRE) + end + end + elseif transformationType == "water" then + for x = -2, 2 do + for y = -2, 2 do + local pos = Position(playerPos.x + x, playerPos.y + y, playerPos.z) + pos:sendMagicEffect(CONST_ME_ICETORNADO) + end + end + end end function transformPlayers(position) - local players = Game.getSpectators(position, false, false, 30, 30, 30, 30) - local transformedPlayers = {} - for _, player in ipairs(players) do - if player:isPlayer() and isInArea(player:getPosition(), fromPosition, toPosition) then - local originalOutfit = player:getOutfit() - local randomTransformation = math.random(2) - if randomTransformation == 1 then - player:setOutfit({lookType = 49}) - player:sendCancelMessage("You have been transformed into a Fire Elemental!") - transformedPlayers[player:getId()] = {type = "fire", originalOutfit = originalOutfit} - else - player:setOutfit({lookType = 286}) - player:sendCancelMessage("You have been transformed into a Water Elemental!") - transformedPlayers[player:getId()] = {type = "water", originalOutfit = originalOutfit} - end + local players = Game.getSpectators(position, false, false, 30, 30, 30, 30) + local transformedPlayers = {} + for _, player in ipairs(players) do + if player:isPlayer() and isInArea(player:getPosition(), fromPosition, toPosition) then + local originalOutfit = player:getOutfit() + local randomTransformation = math.random(2) + if randomTransformation == 1 then + player:setOutfit({ lookType = 49 }) + player:sendCancelMessage("You have been transformed into a Fire Elemental!") + transformedPlayers[player:getId()] = { type = "fire", originalOutfit = originalOutfit } + else + player:setOutfit({ lookType = 286 }) + player:sendCancelMessage("You have been transformed into a Water Elemental!") + transformedPlayers[player:getId()] = { type = "water", originalOutfit = originalOutfit } + end - local function periodicEffect(playerId, transformationType) - local player = Player(playerId) - if player and player:getOutfit().lookType == (transformationType == "fire" and 49 or 286) then - applyAreaEffect(player, transformationType) - addEvent(periodicEffect, 2000, playerId, transformationType) - end - end - addEvent(periodicEffect, 2000, player:getId(), transformedPlayers[player:getId()].type) + local function periodicEffect(playerId, transformationType) + local player = Player(playerId) + if player and player:getOutfit().lookType == (transformationType == "fire" and 49 or 286) then + applyAreaEffect(player, transformationType) + addEvent(periodicEffect, 2000, playerId, transformationType) + end + end + addEvent(periodicEffect, 2000, player:getId(), transformedPlayers[player:getId()].type) - addEvent(function() - for _, p in ipairs(players) do - if p:isPlayer() and transformedPlayers[p:getId()] then - p:setOutfit(transformedPlayers[p:getId()].originalOutfit) - p:sendCancelMessage("You have returned to your normal form.") - p:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) - transformedPlayers[p:getId()] = nil - end - end - end, transformationDuration) + addEvent(function() + for _, p in ipairs(players) do + if p:isPlayer() and transformedPlayers[p:getId()] then + p:setOutfit(transformedPlayers[p:getId()].originalOutfit) + p:sendCancelMessage("You have returned to your normal form.") + p:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) + transformedPlayers[p:getId()] = nil + end + end + end, transformationDuration) - addEvent(checkProximityAndApplyDamage, 1000, transformedPlayers) - end - end + addEvent(checkProximityAndApplyDamage, 1000, transformedPlayers) + end + end end function checkProximityAndApplyDamage(transformedPlayers) - local currentTime = os.time() - local playerIds = {} - for playerId, _ in pairs(transformedPlayers) do - table.insert(playerIds, playerId) - end - for i = 1, #playerIds - 1 do - local player1 = Player(playerIds[i]) - if player1 then - local outfit1 = transformedPlayers[player1:getId()].type - for j = i + 1, #playerIds do - local player2 = Player(playerIds[j]) - if player2 and player1:getPosition():getDistance(player2:getPosition()) <= 3 then - local outfit2 = transformedPlayers[player2:getId()].type - if outfit1 ~= outfit2 then - local lastDamage1 = lastDamageTime[player1:getId()] or 0 - local lastDamage2 = lastDamageTime[player2:getId()] or 0 - if currentTime - lastDamage1 >= damageCooldown or currentTime - lastDamage2 >= damageCooldown then - if currentTime - lastDamage1 >= damageCooldown then - player1:addHealth(-damageAmount) - player1:sendCancelMessage("You took damage for being too close to an opposite elemental!") - player1:getPosition():sendMagicEffect(CONST_ME_BIGCLOUDS) - lastDamageTime[player1:getId()] = currentTime - end - if currentTime - lastDamage2 >= damageCooldown then - player2:addHealth(-damageAmount) - player2:sendCancelMessage("You took damage for being too close to an opposite elemental!") - player2:getPosition():sendMagicEffect(CONST_ME_BIGCLOUDS) - lastDamageTime[player2:getId()] = currentTime - end - end - end - end - end - end - end + local currentTime = os.time() + local playerIds = {} + for playerId, _ in pairs(transformedPlayers) do + table.insert(playerIds, playerId) + end + for i = 1, #playerIds - 1 do + local player1 = Player(playerIds[i]) + if player1 then + local outfit1 = transformedPlayers[player1:getId()].type + for j = i + 1, #playerIds do + local player2 = Player(playerIds[j]) + if player2 and player1:getPosition():getDistance(player2:getPosition()) <= 3 then + local outfit2 = transformedPlayers[player2:getId()].type + if outfit1 ~= outfit2 then + local lastDamage1 = lastDamageTime[player1:getId()] or 0 + local lastDamage2 = lastDamageTime[player2:getId()] or 0 + if currentTime - lastDamage1 >= damageCooldown or currentTime - lastDamage2 >= damageCooldown then + if currentTime - lastDamage1 >= damageCooldown then + player1:addHealth(-damageAmount) + player1:sendCancelMessage("You took damage for being too close to an opposite elemental!") + player1:getPosition():sendMagicEffect(CONST_ME_BIGCLOUDS) + lastDamageTime[player1:getId()] = currentTime + end + if currentTime - lastDamage2 >= damageCooldown then + player2:addHealth(-damageAmount) + player2:sendCancelMessage("You took damage for being too close to an opposite elemental!") + player2:getPosition():sendMagicEffect(CONST_ME_BIGCLOUDS) + lastDamageTime[player2:getId()] = currentTime + end + end + end + end + end + end + end - addEvent(checkProximityAndApplyDamage, 1000, transformedPlayers) + addEvent(checkProximityAndApplyDamage, 1000, transformedPlayers) end function dukeKruleTransformEvent.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) - if creature and creature:isMonster() and creature:getName():lower() == "duke krule" then - local currentTime = os.time() - if currentTime - lastTransformationTime >= transformationCooldown then - transformPlayers(creature:getPosition()) - lastTransformationTime = currentTime - end - end - return primaryDamage, primaryType, secondaryDamage, secondaryType + if creature and creature:isMonster() and creature:getName():lower() == "duke krule" then + local currentTime = os.time() + if currentTime - lastTransformationTime >= transformationCooldown then + transformPlayers(creature:getPosition()) + lastTransformationTime = currentTime + end + end + return primaryDamage, primaryType, secondaryDamage, secondaryType end dukeKruleTransformEvent:register() diff --git a/data/scripts/actions/items/potions.lua b/data/scripts/actions/items/potions.lua index f2a4f2e0c..7dd1aa114 100644 --- a/data/scripts/actions/items/potions.lua +++ b/data/scripts/actions/items/potions.lua @@ -97,13 +97,13 @@ function flaskPotion.onUse(player, item, fromPosition, target, toPosition, isHot target:say("Aaaah...", MESSAGE_POTION) local deactivatedFlasks = player:kv():get("talkaction.potions.flask") or false - if not deactivatedFlasks and configManager.getBoolean(configKeys.REMOVE_POTION_CHARGES) then - if fromPosition.x == CONTAINER_POSITION then - player:addItem(potion.flask, 1) - else - Game.createItem(potion.flask, 1, fromPosition) - end - end + if not deactivatedFlasks and configManager.getBoolean(configKeys.REMOVE_POTION_CHARGES) then + if fromPosition.x == CONTAINER_POSITION then + player:addItem(potion.flask, 1) + else + Game.createItem(potion.flask, 1, fromPosition) + end + end end player:getPosition():sendSingleSoundEffect(SOUND_EFFECT_TYPE_ITEM_USE_POTION, player:isInGhostMode() and nil or player) From 350bc0b9dda77a47023c299539c6a48b714d321c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo?= Date: Tue, 21 Oct 2025 11:02:28 -0300 Subject: [PATCH 7/8] fix: force Expert PvP in Hardcore worlds Based from https://www.tibiawiki.com.br/wiki/PvP --- src/creatures/players/player.cpp | 75 +++++++++++--------- src/game/game.cpp | 40 ++++++----- src/server/network/protocol/protocolgame.cpp | 6 +- 3 files changed, 67 insertions(+), 54 deletions(-) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index ef251aa09..96e665f3e 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -1374,7 +1374,8 @@ bool Player::canSeeCreature(const std::shared_ptr &creature) const { } bool Player::canCombat(const std::shared_ptr &creature) const { - if (!g_configManager().getBoolean(TOGGLE_EXPERT_PVP)) { + bool expertPvpActive = g_configManager().getBoolean(TOGGLE_EXPERT_PVP) || (g_game().getWorldType() == WORLDTYPE_HARDCORE); + if (!expertPvpActive) { return true; } @@ -6341,40 +6342,46 @@ void Player::onAttackedCreature(const std::shared_ptr &target) { // Apply PvP mode specific rules for pz lock and skull bool shouldPzLock = false; bool shouldYellowSkull = false; - - switch (pvpMode) { - case PVP_MODE_DOVE: { - // Dove: No pz lock, no yellow skull - shouldPzLock = false; - shouldYellowSkull = false; - break; - } - - case PVP_MODE_WHITE_HAND: { - // White Hand: No pz lock, but yellow skull for target - shouldPzLock = false; - shouldYellowSkull = true; - break; - } - - case PVP_MODE_YELLOW_HAND: { - // Yellow Hand: pz lock and yellow skull for target - shouldPzLock = true; - shouldYellowSkull = true; - break; - } - - case PVP_MODE_RED_FIST: { - // Red Fist: pz lock, no yellow skull (can attack anyone) - shouldPzLock = true; - shouldYellowSkull = false; - break; + + // In Hardcore worlds, always apply pz lock rules + if (g_game().getWorldType() == WORLDTYPE_HARDCORE) { + shouldPzLock = true; + shouldYellowSkull = false; // Red Fist mode in Hardcore + } else { + switch (pvpMode) { + case PVP_MODE_DOVE: { + // Dove: No pz lock, no yellow skull + shouldPzLock = false; + shouldYellowSkull = false; + break; + } + + case PVP_MODE_WHITE_HAND: { + // White Hand: No pz lock, but yellow skull for target + shouldPzLock = false; + shouldYellowSkull = true; + break; + } + + case PVP_MODE_YELLOW_HAND: { + // Yellow Hand: pz lock and yellow skull for target + shouldPzLock = true; + shouldYellowSkull = true; + break; + } + + case PVP_MODE_RED_FIST: { + // Red Fist: pz lock, no yellow skull (can attack anyone) + shouldPzLock = true; + shouldYellowSkull = false; + break; + } + + default: + shouldPzLock = false; + shouldYellowSkull = false; + break; } - - default: - shouldPzLock = false; - shouldYellowSkull = false; - break; } // Apply pz lock if needed diff --git a/src/game/game.cpp b/src/game/game.cpp index 53de96f2e..5d98edee8 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -6076,28 +6076,34 @@ void Game::playerSetFightModes(uint32_t playerId, FightMode_t fightMode, PvpMode player->setFightMode(fightMode); player->setChaseMode(chaseMode); - if (g_configManager().getBoolean(TOGGLE_EXPERT_PVP)) { + bool expertPvpActive = g_configManager().getBoolean(TOGGLE_EXPERT_PVP) || (worldType == WORLDTYPE_HARDCORE); + if (expertPvpActive) { auto oldPvpMode = player->pvpMode; - // Black skull cannot activate Red Fist - if (player->getSkull() == SKULL_BLACK && pvpMode == PVP_MODE_RED_FIST) { - player->setPvpMode(oldPvpMode); - } else if (worldType == WORLDTYPE_OPTIONAL && pvpMode == PVP_MODE_RED_FIST) { - player->setPvpMode(player->pvpMode); - } else if (worldType == WORLDTYPE_HARDCORE && pvpMode != PVP_MODE_RED_FIST) { + + // In Hardcore worlds, force Red Fist mode and disable secure mode + if (worldType == WORLDTYPE_HARDCORE) { player->setPvpMode(PVP_MODE_RED_FIST); + player->setSecureMode(false); } else { - player->setPvpMode(pvpMode); - } + // Black skull cannot activate Red Fist + if (player->getSkull() == SKULL_BLACK && pvpMode == PVP_MODE_RED_FIST) { + player->setPvpMode(oldPvpMode); + } else if (worldType == WORLDTYPE_OPTIONAL && pvpMode == PVP_MODE_RED_FIST) { + player->setPvpMode(player->pvpMode); + } else { + player->setPvpMode(pvpMode); + } - if ((worldType == WORLDTYPE_OPTIONAL && !secureMode) || (worldType == WORLDTYPE_HARDCORE && secureMode)) { - player->setSecureMode(!secureMode); - } else { - if (player->getPvPMode() == PVP_MODE_RED_FIST && oldPvpMode != PVP_MODE_RED_FIST) { - player->setSecureMode(false); - } else if (player->pvpMode != PVP_MODE_RED_FIST && oldPvpMode == PVP_MODE_RED_FIST) { - player->setSecureMode(true); + if (worldType == WORLDTYPE_OPTIONAL && !secureMode) { + player->setSecureMode(!secureMode); } else { - player->setSecureMode(secureMode); + if (player->getPvPMode() == PVP_MODE_RED_FIST && oldPvpMode != PVP_MODE_RED_FIST) { + player->setSecureMode(false); + } else if (player->pvpMode != PVP_MODE_RED_FIST && oldPvpMode == PVP_MODE_RED_FIST) { + player->setSecureMode(true); + } else { + player->setSecureMode(secureMode); + } } } diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index cec17bb2b..5e41d1ba6 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -7184,9 +7184,9 @@ void ProtocolGame::sendAddCreature(const std::shared_ptr &creature, co } } - bool expertPvp = g_configManager().getBoolean(TOGGLE_EXPERT_PVP); - msg.addByte(expertPvp ? 0x01 : 0x00); // can change pvp framing option - msg.addByte(expertPvp ? 0x01 : 0x00); // expert mode button enabled + bool expertPvpActive = g_configManager().getBoolean(TOGGLE_EXPERT_PVP); + msg.addByte(expertPvpActive ? 0x01 : 0x00); // can change pvp framing option + msg.addByte(expertPvpActive ? 0x01 : 0x00); // expert mode button enabled msg.addString(g_configManager().getString(STORE_IMAGES_URL)); msg.add(static_cast(g_configManager().getNumber(STORE_COIN_PACKET))); From 421118d9e00c542cf1307a32c26590a7f725a94c Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 21 Oct 2025 14:03:50 +0000 Subject: [PATCH 8/8] Code format - (Clang-format) --- src/creatures/players/player.cpp | 10 +++++----- src/game/game.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 96e665f3e..8768e03a3 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -6342,7 +6342,7 @@ void Player::onAttackedCreature(const std::shared_ptr &target) { // Apply PvP mode specific rules for pz lock and skull bool shouldPzLock = false; bool shouldYellowSkull = false; - + // In Hardcore worlds, always apply pz lock rules if (g_game().getWorldType() == WORLDTYPE_HARDCORE) { shouldPzLock = true; @@ -6355,28 +6355,28 @@ void Player::onAttackedCreature(const std::shared_ptr &target) { shouldYellowSkull = false; break; } - + case PVP_MODE_WHITE_HAND: { // White Hand: No pz lock, but yellow skull for target shouldPzLock = false; shouldYellowSkull = true; break; } - + case PVP_MODE_YELLOW_HAND: { // Yellow Hand: pz lock and yellow skull for target shouldPzLock = true; shouldYellowSkull = true; break; } - + case PVP_MODE_RED_FIST: { // Red Fist: pz lock, no yellow skull (can attack anyone) shouldPzLock = true; shouldYellowSkull = false; break; } - + default: shouldPzLock = false; shouldYellowSkull = false; diff --git a/src/game/game.cpp b/src/game/game.cpp index 5d98edee8..165721931 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -6079,7 +6079,7 @@ void Game::playerSetFightModes(uint32_t playerId, FightMode_t fightMode, PvpMode bool expertPvpActive = g_configManager().getBoolean(TOGGLE_EXPERT_PVP) || (worldType == WORLDTYPE_HARDCORE); if (expertPvpActive) { auto oldPvpMode = player->pvpMode; - + // In Hardcore worlds, force Red Fist mode and disable secure mode if (worldType == WORLDTYPE_HARDCORE) { player->setPvpMode(PVP_MODE_RED_FIST);