diff --git a/config.lua.dist b/config.lua.dist index ecb38964e..9f8b144a7 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 + -- Guilds -- ingameGuildManagement: set true to enable in-game guild commands. -- guildWarsDefaultFrags: Default frags for guild war invitations if not specified. 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 375890de6..dd4135d8f 100644 --- a/src/config/config_enums.hpp +++ b/src/config/config_enums.hpp @@ -393,6 +393,9 @@ 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, INGAME_GUILD_MANAGEMENT, LEVEL_TO_FORM_GUILD, CREATE_GUILD_ONLY_PREMIUM, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index 242a007de..0e1834af2 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -183,6 +183,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); loadBoolConfig(L, INGAME_GUILD_MANAGEMENT, "ingameGuildManagement", true); loadBoolConfig(L, CREATE_GUILD_ONLY_PREMIUM, "createGuildOnlyPremium", true); diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 42ce88d93..ef66bf2d4 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()) { @@ -2503,6 +2523,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()) { @@ -2757,6 +2782,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 73ee295d4..633b47e9f 100644 --- a/src/creatures/combat/combat.hpp +++ b/src/creatures/combat/combat.hpp @@ -331,6 +331,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/creature.cpp b/src/creatures/creature.cpp index 9e0724e69..e9679f26c 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 76c050a69..e368ea827 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -1385,12 +1385,90 @@ bool Player::canSeeCreature(const std::shared_ptr &creature) const { return true; } +bool Player::canCombat(const std::shared_ptr &creature) const { + bool expertPvpActive = g_configManager().getBoolean(TOGGLE_EXPERT_PVP) || (g_game().getWorldType() == WORLDTYPE_HARDCORE); + if (!expertPvpActive) { + return true; + } + + if (const auto &monster = creature->getMonster()) { + if (!monster->isSummon()) { + return true; + } + + const auto master = monster->getMaster(); + if (!master) { + return true; + } + + auto owner = master->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; + } + + // Cannot attack party/guild members in any mode + if (isPartner(player) || isGuildMate(player)) { + return false; + } + + // 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; +} + 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()) { + const auto master = monster->getMaster(); + if (!monster->isSummon() || !master || !master->getPlayer()) { + return false; + } + + const auto owner = master->getPlayer(); + return owner != getPlayer() && canWalkthrough(owner); + } + } + return false; + } + const auto &monster = creature->getMonster(); const auto &npc = creature->getNpc(); if (monster) { @@ -1450,7 +1528,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); @@ -2880,6 +2962,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(); @@ -3850,10 +3938,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; } @@ -6246,6 +6335,10 @@ void Player::setFightMode(FightMode_t mode) { fightMode = mode; } +void Player::setPvpMode(PvpMode_t mode) { + pvpMode = mode; +} + void Player::setSecureMode(bool mode) { secureMode = mode; } @@ -6418,6 +6511,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); @@ -6484,23 +6580,77 @@ void Player::onAttackedCreature(const std::shared_ptr &target) { } const auto &targetPlayer = target->getPlayer(); - if (targetPlayer && !isPartner(targetPlayer) && !isGuildMate(targetPlayer)) { - if (!pzLocked && g_game().getWorldType() == WORLDTYPE_HARDCORE) { + if (targetPlayer && !isGuildMate(targetPlayer)) { + bool previousSituation = hasAttacked(targetPlayer) || targetPlayer->hasAttacked(getPlayer()); + if (isPartner(targetPlayer)) { + return; + } + + // 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; + 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; + } + } + + // 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(); } 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); @@ -6511,6 +6661,11 @@ void Player::onAttackedCreature(const std::shared_ptr &target) { } } } + + if (!previousSituation) { + g_game().updateCreatureSquare(getPlayer()); + g_game().updateCreatureSquare(targetPlayer); + } } addInFightTicks(); @@ -7154,6 +7309,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; } @@ -7162,7 +7322,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; } } @@ -7177,12 +7337,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) { @@ -7190,7 +7355,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) { @@ -7198,13 +7363,65 @@ 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; + } + + if (attacker->isRemoved()) { + 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; @@ -8574,6 +8791,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); } @@ -8780,9 +9001,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); } } @@ -11324,6 +11545,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); @@ -12709,3 +12935,118 @@ void Player::removeEquippedWeaponProficiency(const uint16_t itemId) { sendStats(); sendSkills(); } + +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; + } + + if (player->isRemoved()) { + return false; + } + + auto playerHasAttacked = [time](const std::shared_ptr &a, const std::shared_ptr &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(static_self_cast()))) { + return true; + } + + if (guildAndParty) { + if (guild) { + for (auto it : guild->getMembersOnline()) { + if (it->hasPvpActivity(player, time)) { + return true; + } + } + } + + 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; + } + } + } + } + + 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; + } + + if (creature->isRemoved()) { + return false; + } + + const auto &player = creature->getPlayer(); + if (!player) { + if (!creature->isSummon()) { + return false; + } + + const auto &master = creature->getMaster(); + if (!master || master->isRemoved()) { + return false; + } + + return isAggressiveCreature(master, 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 2125a3305..fc2067ceb 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -701,6 +701,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; @@ -742,6 +743,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; @@ -859,10 +861,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; @@ -910,7 +917,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; @@ -1163,6 +1170,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; @@ -1615,7 +1632,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; @@ -1802,6 +1820,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 8c2b435e8..2f06ebe99 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -1041,6 +1041,22 @@ std::shared_ptr Game::getMarketPlayerByGUID(uint32_t &guid) { return tmpPlayer; } +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; @@ -6129,7 +6145,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; @@ -6137,7 +6153,43 @@ void Game::playerSetFightModes(uint32_t playerId, FightMode_t fightMode, bool ch player->setFightMode(fightMode); player->setChaseMode(chaseMode); - player->setSecureMode(secureMode); + + 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); + player->setSecureMode(false); + } else { + // 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) { + 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) { @@ -8834,6 +8886,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); @@ -8855,6 +8917,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) { @@ -8871,6 +8943,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; diff --git a/src/game/game.hpp b/src/game/game.hpp index 591aa7b66..7830bbeda 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -178,6 +178,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); @@ -400,7 +403,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); @@ -494,6 +497,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 c07b690ec..44988bb98 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 7dbb9667d..a0b1ef155 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -1280,6 +1280,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 70eabdad3..de3499cc6 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -2111,7 +2111,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) { @@ -2122,7 +2122,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) { @@ -3652,7 +3663,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; } @@ -3660,7 +3671,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); } @@ -7234,7 +7245,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); } @@ -7302,8 +7313,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 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))); diff --git a/src/server/network/protocol/protocolgame.hpp b/src/server/network/protocol/protocolgame.hpp index ed1c94801..0d5f2bfe4 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 b45b9c65d..54a5d8893 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 c7af07fba..2d46ce274 100644 --- a/src/utils/utils_definitions.hpp +++ b/src/utils/utils_definitions.hpp @@ -429,8 +429,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 {