From 664f4ff858bc1d1e3a52ed56ef6d36984b073701 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Fri, 23 Feb 2024 17:16:14 -0500 Subject: [PATCH 01/43] PVP Arena Rebalance Tweaks --- Source/missiles.cpp | 9 +++++++-- Source/player.cpp | 32 +++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 4e0e090bc3d..fe403265db1 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -358,8 +358,13 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i if (!shift) dam <<= 6; } - if (!missileData.isArrow()) - dam /= 2; + if (!missileData.isArrow()) { + // PVP REBALANCE: Only do 33% of spell damage to players on arena levels instead of the default 50%. + if (player.isOnArenaLevel()) + dam /= 3; + else + dam /= 2; + } if (resper > 0) { dam -= (dam * resper) / 100; if (&player == MyPlayer) diff --git a/Source/player.cpp b/Source/player.cpp index 66261e5861b..7456add1608 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -223,6 +223,10 @@ void StartAttack(Player &player, Direction d, bool includesFirstFrame) } } + // PVP REBALANCE: Make melee attacking rate 1 frame slower in arena levels. + if (player.isOnArenaLevel()) + skippedAnimationFrames--; + auto animationFlags = AnimationDistributionFlags::ProcessAnimationPending; if (player._pmode == PM_ATTACK) animationFlags = static_cast(animationFlags | AnimationDistributionFlags::RepeatedAction); @@ -249,6 +253,10 @@ void StartRangeAttack(Player &player, Direction d, WorldTileCoord cx, WorldTileC } } + // PVP REBALANCE: Make bow firing rate 1 frame slower in arena levels. + if (player.isOnArenaLevel()) + skippedAnimationFrames--; + auto animationFlags = AnimationDistributionFlags::ProcessAnimationPending; if (player._pmode == PM_RATTACK) animationFlags = static_cast(animationFlags | AnimationDistributionFlags::RepeatedAction); @@ -299,10 +307,20 @@ void StartSpell(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord c if (!isValid) return; + int8_t skippedAnimationFrames = 0; + + // PVP REBALANCE: Increase Warrior/Barbarian cast speed and reduce Sorcerer cast speed in arena levels. + if (player.isOnArenaLevel()) { + if (IsAnyOf(player._pClass, HeroClass::Warrior, HeroClass::Barbarian)) + skippedAnimationFrames++; + else if (player._pClass == HeroClass::Sorcerer) + skippedAnimationFrames--; + } + auto animationFlags = AnimationDistributionFlags::ProcessAnimationPending; if (player._pmode == PM_SPELL) animationFlags = static_cast(animationFlags | AnimationDistributionFlags::RepeatedAction); - NewPlrAnim(player, GetPlayerGraphicForSpell(player.queuedSpell.spellId), d, animationFlags, 0, player._pSFNum); + NewPlrAnim(player, GetPlayerGraphicForSpell(player.queuedSpell.spellId), d, animationFlags, skippedAnimationFrames, player._pSFNum); PlaySfxLoc(GetSpellData(player.queuedSpell.spellId).sSFX, player.position.tile); @@ -503,6 +521,10 @@ bool DamageWeapon(Player &player, unsigned damageFrequency) return false; } + // PVP REBALANCE: Do not damage weapon in arena levels. + if (player.isOnArenaLevel()) + return false; + if (WeaponDecay(player, INVLOC_HAND_LEFT)) return true; if (WeaponDecay(player, INVLOC_HAND_RIGHT)) @@ -959,6 +981,10 @@ void DamageParryItem(Player &player) return; } + // PVP REBALANCE: Do not damage parry items in arena levels. + if (player.isOnArenaLevel()) + return; + if (player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Shield || player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Staff) { if (player.InvBody[INVLOC_HAND_LEFT]._iDurability == DUR_INDESTRUCTIBLE) { return; @@ -1003,6 +1029,10 @@ void DamageArmor(Player &player) return; } + // PVP REBALANCE: Do not damage armor in arena levels. + if (player.isOnArenaLevel()) + return; + if (player.InvBody[INVLOC_CHEST].isEmpty() && player.InvBody[INVLOC_HEAD].isEmpty()) { return; } From 7b522a4918e4c0e81a38552db2a234cf1a3ccf63 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Fri, 23 Feb 2024 17:56:11 -0500 Subject: [PATCH 02/43] Do not allow target retention in arena --- Source/cursor.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Source/cursor.cpp b/Source/cursor.cpp index c2e5dd0e6e3..ed49f6fba20 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -726,17 +726,20 @@ void CheckCursMove() const Point currentTile { mx, my }; - // While holding the button down we should retain target (but potentially lose it if it dies, goes out of view, etc) - if ((sgbMouseDown != CLICK_NONE || ControllerActionHeld != GameActionType_NONE) && IsNoneOf(LastMouseButtonAction, MouseActionType::None, MouseActionType::Attack, MouseActionType::Spell)) { - InvalidateTargets(); + // PVP REBALANCE: Don't allow players to click and hold to retain targets in arena levels. + if (!myPlayer.isOnArenaLevel()) { + // While holding the button down we should retain target (but potentially lose it if it dies, goes out of view, etc) + if ((sgbMouseDown != CLICK_NONE || ControllerActionHeld != GameActionType_NONE) && IsNoneOf(LastMouseButtonAction, MouseActionType::None, MouseActionType::Attack, MouseActionType::Spell)) { + InvalidateTargets(); - if (pcursmonst == -1 && ObjectUnderCursor == nullptr && pcursitem == -1 && pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && PlayerUnderCursor == nullptr) { - cursPosition = { mx, my }; - CheckTrigForce(); - CheckTown(); - CheckRportal(); + if (pcursmonst == -1 && ObjectUnderCursor == nullptr && pcursitem == -1 && pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && PlayerUnderCursor == nullptr) { + cursPosition = { mx, my }; + CheckTrigForce(); + CheckTown(); + CheckRportal(); + } + return; } - return; } bool flipflag = (flipy && flipx) || ((flipy || flipx) && px < TILE_WIDTH / 2); From babf245a7e9bd7ad49cebe254d6bd4a6c5990543 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Fri, 23 Feb 2024 20:12:09 -0500 Subject: [PATCH 03/43] Diagonal Walk Punishment --- Source/automap.cpp | 11 +++++++++++ Source/player.cpp | 18 ++++++++++++++++++ Source/player.h | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/Source/automap.cpp b/Source/automap.cpp index 3a91776aa30..21795b93623 100644 --- a/Source/automap.cpp +++ b/Source/automap.cpp @@ -1428,6 +1428,17 @@ void DrawAutomapText(const Surface &out) } DrawString(out, description, linePosition); linePosition.y += 15; + + auto &myPlayer = *MyPlayer; + + // PVP REBALANCE: Diawalk Counter display for arena use. + if (myPlayer.isOnArenaLevel()) { + description = fmt::format(fmt::runtime(_("Diawalk Percentage: {:d}%")), myPlayer.calculateDiagonalMovementPercentage()); + const TextRenderOptions counterTextOptions { + .flags = myPlayer.calculateDiagonalMovementPercentage() >= DiawalkDamageThreshold ? UiFlags::ColorRed : UiFlags::ColorGold, + }; + DrawString(out, description, { (GetScreenWidth() / 2) - 72, 8 }, counterTextOptions); + } } if (setlevel) { diff --git a/Source/player.cpp b/Source/player.cpp index 7456add1608..229e755bf6b 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -464,6 +464,10 @@ bool DoWalk(Player &player, int variant) return false; } + // PVP REBALANCE: Track player movements in arena. + if (player.isOnArenaLevel()) + player.trackLastPlrMovement(variant); + // We reached the new tile -> update the player's tile position switch (variant) { case PM_WALK_NORTHWARDS: @@ -479,6 +483,13 @@ bool DoWalk(Player &player, int variant) player.position.tile = player.position.temp; // dPlayer is set here for backwards comparability, without it the player would be invisible if loaded from a vanilla save. player.occupyTile(player.position.tile, false); + + // PVP REBALANCE: Increment _pDiawalkCounter and punish reaching DiawalkDamageThreshold for arena use. + if (player.isOnArenaLevel()) { + if (player.calculateDiagonalMovementPercentage() > DiawalkDamageThreshold && &player == MyPlayer) + // Deal 20 HP worth of damage each diagonal movement multiplied by the amount of percent they are above the threshold. + NetSendCmdDamage(true, player, (player.calculateDiagonalMovementPercentage() - DiawalkDamageThreshold) * 20 * 64, DamageType::Physical); + } break; } @@ -488,6 +499,8 @@ bool DoWalk(Player &player, int variant) ChangeVisionXY(player.getId(), player.position.tile); } + + StartStand(player, player.tempDirection); ClearStateVariables(player); @@ -2560,6 +2573,9 @@ void InitPlayer(Player &player, bool firstTime) player._pInvincible = false; + // PVP REBALANCE: For arena use only. Initialize all movement history. + player._pMovements[MaxMovementHistory] = { PM_STAND }; + if (&player == MyPlayer) { MyPlayerIsDead = false; } @@ -2740,6 +2756,8 @@ StartPlayerKill(Player &player, DeathReason deathReason) player._pmode = PM_DEATH; player._pInvincible = true; SetPlayerHitPoints(player, 0); + // PVP REBALANCE: Reset player's movements for arena use. + player._pMovements[MaxMovementHistory] = { PM_STAND }; if (&player != MyPlayer && dropItems) { // Ensure that items are removed for remote players diff --git a/Source/player.h b/Source/player.h index 86631078f08..eff200b9df8 100644 --- a/Source/player.h +++ b/Source/player.h @@ -37,6 +37,10 @@ constexpr int PlayerNameLength = 32; constexpr size_t NumHotkeys = 12; +// PVP REBALANCE: The percentage of diagonal movements that _pMovements reaches to take punitive action against that player in the arena. +constexpr int16_t DiawalkDamageThreshold = 80; +constexpr int8_t MaxMovementHistory = 20; + /** Walking directions */ enum { // clang-format off @@ -269,6 +273,8 @@ struct Player { int destParam3; int destParam4; int _pGold; + // PVP REBALANCE: Movement history for arena. + int _pMovements[16]; /** * @brief Contains Information for current Animation @@ -894,6 +900,33 @@ struct Player { /** @brief Checks if the player level is owned by local client. */ bool isLevelOwnedByLocalClient() const; + + /** + * @brief Insert most recent player movement variant into member variable _pMovements for arena usage + */ + void trackLastPlrMovement(int variant) + { + for (int i = MaxMovementHistory - 1; i > 0; --i) { + this->_pMovements[i] = this->_pMovements[i - 1]; + } + this->_pMovements[0] = variant; + } + + /** + * @brief Calculate the percentage of diagonal movements made in the last MaxMovementHistory movements for arena usage + */ + int calculateDiagonalMovementPercentage() + { + int numDiagonalMovements = 0; + for (int i = 0; i < MaxMovementHistory; ++i) { + if (this->_pMovements[i] == PM_WALK_SIDEWAYS) { + numDiagonalMovements++; + } + } + + int diagonalMovementPercentage = (numDiagonalMovements * 100) / MaxMovementHistory; + return diagonalMovementPercentage; + } }; extern DVL_API_FOR_TEST uint8_t MyPlayerId; From b8901cf2d7e612fcb197d0643ef57e886ec229f3 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Fri, 23 Feb 2024 20:20:41 -0500 Subject: [PATCH 04/43] clang --- Source/player.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/player.h b/Source/player.h index eff200b9df8..6b40647043b 100644 --- a/Source/player.h +++ b/Source/player.h @@ -902,8 +902,8 @@ struct Player { bool isLevelOwnedByLocalClient() const; /** - * @brief Insert most recent player movement variant into member variable _pMovements for arena usage - */ + * @brief Insert most recent player movement variant into member variable _pMovements for arena usage + */ void trackLastPlrMovement(int variant) { for (int i = MaxMovementHistory - 1; i > 0; --i) { @@ -913,8 +913,8 @@ struct Player { } /** - * @brief Calculate the percentage of diagonal movements made in the last MaxMovementHistory movements for arena usage - */ + * @brief Calculate the percentage of diagonal movements made in the last MaxMovementHistory movements for arena usage + */ int calculateDiagonalMovementPercentage() { int numDiagonalMovements = 0; From 614734a0773877a236232adc05b1027aff7c1c12 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Fri, 23 Feb 2024 20:47:07 -0500 Subject: [PATCH 05/43] Fix array initialization --- Source/player.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index 229e755bf6b..798138db6ce 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -2574,7 +2574,9 @@ void InitPlayer(Player &player, bool firstTime) player._pInvincible = false; // PVP REBALANCE: For arena use only. Initialize all movement history. - player._pMovements[MaxMovementHistory] = { PM_STAND }; + for (int i = 0; i < MaxMovementHistory; ++i) { + player._pMovements[i] = PM_STAND; + } if (&player == MyPlayer) { MyPlayerIsDead = false; @@ -2757,7 +2759,9 @@ StartPlayerKill(Player &player, DeathReason deathReason) player._pInvincible = true; SetPlayerHitPoints(player, 0); // PVP REBALANCE: Reset player's movements for arena use. - player._pMovements[MaxMovementHistory] = { PM_STAND }; + for (int i = 0; i < MaxMovementHistory; ++i) { + player._pMovements[i] = PM_STAND; + } if (&player != MyPlayer && dropItems) { // Ensure that items are removed for remote players From fc8b966ebd1fa3e44dec179ea102afc9dc19d11d Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Fri, 23 Feb 2024 20:48:18 -0500 Subject: [PATCH 06/43] Increase threshold to 50 --- Source/player.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/player.h b/Source/player.h index 6b40647043b..c46523c0ec9 100644 --- a/Source/player.h +++ b/Source/player.h @@ -39,7 +39,7 @@ constexpr size_t NumHotkeys = 12; // PVP REBALANCE: The percentage of diagonal movements that _pMovements reaches to take punitive action against that player in the arena. constexpr int16_t DiawalkDamageThreshold = 80; -constexpr int8_t MaxMovementHistory = 20; +constexpr int8_t MaxMovementHistory = 50; /** Walking directions */ enum { From 96f3b529b8a9ac7bb3c5b97e9b39b9cb36774641 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Fri, 23 Feb 2024 21:06:05 -0500 Subject: [PATCH 07/43] Reduce damage from diawalk abuse --- Source/player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/player.cpp b/Source/player.cpp index 798138db6ce..03af495bdd6 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -488,7 +488,7 @@ bool DoWalk(Player &player, int variant) if (player.isOnArenaLevel()) { if (player.calculateDiagonalMovementPercentage() > DiawalkDamageThreshold && &player == MyPlayer) // Deal 20 HP worth of damage each diagonal movement multiplied by the amount of percent they are above the threshold. - NetSendCmdDamage(true, player, (player.calculateDiagonalMovementPercentage() - DiawalkDamageThreshold) * 20 * 64, DamageType::Physical); + NetSendCmdDamage(true, player, (player.calculateDiagonalMovementPercentage() - DiawalkDamageThreshold) * 5 * 64, DamageType::Physical); } break; } From 7914670ef64e6239e9d1ac355949d7d3e30ff2e9 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Fri, 23 Feb 2024 21:09:31 -0500 Subject: [PATCH 08/43] Fix hardcode --- Source/player.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/player.h b/Source/player.h index c46523c0ec9..1c39f4bc3f2 100644 --- a/Source/player.h +++ b/Source/player.h @@ -274,7 +274,7 @@ struct Player { int destParam4; int _pGold; // PVP REBALANCE: Movement history for arena. - int _pMovements[16]; + int _pMovements[MaxMovementHistory]; /** * @brief Contains Information for current Animation From 9050270179f72192029df08996802f008e2ea261 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 04:15:12 -0500 Subject: [PATCH 09/43] Spell damage per spell --- Source/missiles.cpp | 46 +++++++++++++++++++++++++++++++++++++++++---- Source/player.cpp | 2 +- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index fe403265db1..00eaa1cf441 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -359,11 +359,49 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i dam <<= 6; } if (!missileData.isArrow()) { - // PVP REBALANCE: Only do 33% of spell damage to players on arena levels instead of the default 50%. - if (player.isOnArenaLevel()) - dam /= 3; - else + // PVP REBALANCE: Adjust damage values for spells in arena. + if (player.isOnArenaLevel()) { + switch (mtype) { + case MissileID::BloodStar: // 200% (400% of default) + dam *= 2; + break; + case MissileID::BoneSpirit: // 100% (200% of default) + break; + case MissileID::ChainBall: // 200% (400% of default) + dam *= 2; + break; + case MissileID::ChargedBolt: // 100% (200% of default), don't allow diagonal dodge + break; + case MissileID::Elemental: // 40% damage (80% of default) + dam *= 4; + dam /= 10; + break; + case MissileID::Fireball: // 20% damage (40% of default) + dam *= 2; + dam /= 10; + break; + case MissileID::Firebolt: // 100% (200% of default) + case MissileID::FireWall: // 100% (200% of default) + case MissileID::FlameWave: // 100% (200% of default), don't allow diagonal dodge + break; + case MissileID::FlashBottom: // 50% (100% default) + case MissileID::FlashTop: // 50% (100% of default) + dam /= 2; + break; + case MissileID::Guardian: // 100% (200% of default), limit 1 at a time per player + break; + case MissileID::Inferno: // 400% (800% of default) + dam *= 4; + break; + case MissileID::Lightning: // 200% (400% of default) + dam *= 2; + break; + case MissileID::NovaBall: // 100% (200% of default) + break; + } + } else { dam /= 2; + } } if (resper > 0) { dam -= (dam * resper) / 100; diff --git a/Source/player.cpp b/Source/player.cpp index 03af495bdd6..fe472e9d877 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -487,7 +487,7 @@ bool DoWalk(Player &player, int variant) // PVP REBALANCE: Increment _pDiawalkCounter and punish reaching DiawalkDamageThreshold for arena use. if (player.isOnArenaLevel()) { if (player.calculateDiagonalMovementPercentage() > DiawalkDamageThreshold && &player == MyPlayer) - // Deal 20 HP worth of damage each diagonal movement multiplied by the amount of percent they are above the threshold. + // Deal 5 HP worth of damage each diagonal movement multiplied by the amount of percent they are above the threshold. NetSendCmdDamage(true, player, (player.calculateDiagonalMovementPercentage() - DiawalkDamageThreshold) * 5 * 64, DamageType::Physical); } break; From 8bc28e3d0e7a169c932dee3e01de504c28cb0da8 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 05:07:35 -0500 Subject: [PATCH 10/43] clang --- Source/player.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index fe472e9d877..8ae2aa42ef8 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -499,8 +499,6 @@ bool DoWalk(Player &player, int variant) ChangeVisionXY(player.getId(), player.position.tile); } - - StartStand(player, player.tempDirection); ClearStateVariables(player); From fe6706b6ddef339a950baecaf1e7ac7b53768407 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 05:36:38 -0500 Subject: [PATCH 11/43] FlameWave and ChargedBolt not affected by diawalk --- Source/missiles.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 00eaa1cf441..8c8f0d9ecb3 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -287,7 +287,8 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i *blocked = false; - if (target.isOnArenaLevel() && target._pmode == PM_WALK_SIDEWAYS) + // PVP REBALANCE: Diagonal walk makes missiles that aren't Charged Bolt or Flame Wave miss. + if (target.isOnArenaLevel() && target._pmode == PM_WALK_SIDEWAYS && IsNoneOf(mtype, MissileID::ChargedBolt, MissileID::FlameWave)) return false; if (target._pInvincible) { From 8a74f71ad95a3c3080dd2a53ee2ade7118b32946 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 05:51:36 -0500 Subject: [PATCH 12/43] Increase Warrior Axe/Staff by 1 frame speed --- Source/player.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index 8ae2aa42ef8..433915a835b 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -223,9 +223,10 @@ void StartAttack(Player &player, Direction d, bool includesFirstFrame) } } - // PVP REBALANCE: Make melee attacking rate 1 frame slower in arena levels. - if (player.isOnArenaLevel()) - skippedAnimationFrames--; + // PVP REBALANCE: Increase Warrior Axe and Staff speed by 1 frame. + auto gn = static_cast(player._pgfxnum & 0xFU); + if (player.isOnArenaLevel() && player._pClass == HeroClass::Warrior && IsAnyOf(gn, PlayerWeaponGraphic::Axe, PlayerWeaponGraphic::Staff)) + skippedAnimationFrames++; auto animationFlags = AnimationDistributionFlags::ProcessAnimationPending; if (player._pmode == PM_ATTACK) From 07c30e8c1610d32ed1c37e7fff6eb09995755411 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 05:54:28 -0500 Subject: [PATCH 13/43] Only apply slower bow speed to Rogue --- Source/player.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index 433915a835b..40a194cf1d6 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -254,8 +254,8 @@ void StartRangeAttack(Player &player, Direction d, WorldTileCoord cx, WorldTileC } } - // PVP REBALANCE: Make bow firing rate 1 frame slower in arena levels. - if (player.isOnArenaLevel()) + // PVP REBALANCE: Make Rogue bow firing rate 1 frame slower in arena levels. + if (player.isOnArenaLevel() && player._pClass == HeroClass::Rogue) skippedAnimationFrames--; auto animationFlags = AnimationDistributionFlags::ProcessAnimationPending; From 2211424ec62009fd819bcc8d9f168d6dedf3c6e5 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 06:01:36 -0500 Subject: [PATCH 14/43] Revise _pMovements Changed diagonal walk threshold to 60%. changed _pMovements to std::array --- Source/player.cpp | 8 ++------ Source/player.h | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index 40a194cf1d6..dc0d7bbfe28 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -2573,9 +2573,7 @@ void InitPlayer(Player &player, bool firstTime) player._pInvincible = false; // PVP REBALANCE: For arena use only. Initialize all movement history. - for (int i = 0; i < MaxMovementHistory; ++i) { - player._pMovements[i] = PM_STAND; - } + std::fill(player._pMovements.begin(), player._pMovements.end(), PM_STAND); if (&player == MyPlayer) { MyPlayerIsDead = false; @@ -2758,9 +2756,7 @@ StartPlayerKill(Player &player, DeathReason deathReason) player._pInvincible = true; SetPlayerHitPoints(player, 0); // PVP REBALANCE: Reset player's movements for arena use. - for (int i = 0; i < MaxMovementHistory; ++i) { - player._pMovements[i] = PM_STAND; - } + std::fill(player._pMovements.begin(), player._pMovements.end(), PM_STAND); if (&player != MyPlayer && dropItems) { // Ensure that items are removed for remote players diff --git a/Source/player.h b/Source/player.h index 1c39f4bc3f2..95e7aa078dd 100644 --- a/Source/player.h +++ b/Source/player.h @@ -38,8 +38,8 @@ constexpr int PlayerNameLength = 32; constexpr size_t NumHotkeys = 12; // PVP REBALANCE: The percentage of diagonal movements that _pMovements reaches to take punitive action against that player in the arena. -constexpr int16_t DiawalkDamageThreshold = 80; -constexpr int8_t MaxMovementHistory = 50; +constexpr int16_t DiawalkDamageThreshold = 60; +constexpr int8_t MaxMovementHistory = 30; /** Walking directions */ enum { @@ -274,7 +274,7 @@ struct Player { int destParam4; int _pGold; // PVP REBALANCE: Movement history for arena. - int _pMovements[MaxMovementHistory]; + std::array _pMovements; /** * @brief Contains Information for current Animation From 7353b4ef1b9716283d0fbba0b5f913847b9ddda0 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 06:14:03 -0500 Subject: [PATCH 15/43] Only track local player movements history to save resources --- Source/player.cpp | 4 ++-- Source/player.h | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index dc0d7bbfe28..bb3ecf44ac3 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -466,7 +466,7 @@ bool DoWalk(Player &player, int variant) } // PVP REBALANCE: Track player movements in arena. - if (player.isOnArenaLevel()) + if (player.isOnArenaLevel() && player.isMyPlayer()) player.trackLastPlrMovement(variant); // We reached the new tile -> update the player's tile position @@ -487,7 +487,7 @@ bool DoWalk(Player &player, int variant) // PVP REBALANCE: Increment _pDiawalkCounter and punish reaching DiawalkDamageThreshold for arena use. if (player.isOnArenaLevel()) { - if (player.calculateDiagonalMovementPercentage() > DiawalkDamageThreshold && &player == MyPlayer) + if (player.calculateDiagonalMovementPercentage() > DiawalkDamageThreshold && player.isMyPlayer()) // Deal 5 HP worth of damage each diagonal movement multiplied by the amount of percent they are above the threshold. NetSendCmdDamage(true, player, (player.calculateDiagonalMovementPercentage() - DiawalkDamageThreshold) * 5 * 64, DamageType::Physical); } diff --git a/Source/player.h b/Source/player.h index 95e7aa078dd..e194f27443e 100644 --- a/Source/player.h +++ b/Source/player.h @@ -874,6 +874,11 @@ struct Player { { return plrIsOnSetLevel && IsArenaLevel(static_cast<_setlevels>(plrlevel)); } + /** @brief Checks if the player is the local client player. */ + bool isMyPlayer() const + { + return this == MyPlayer; + } void setLevel(uint8_t level) { this->plrlevel = level; From 332e4bbe011d524a50df08f1d294b185cfdefe89 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 06:19:01 -0500 Subject: [PATCH 16/43] Remove Barbarian cast buff --- Source/player.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index bb3ecf44ac3..f20a7aa8efd 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -310,9 +310,9 @@ void StartSpell(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord c int8_t skippedAnimationFrames = 0; - // PVP REBALANCE: Increase Warrior/Barbarian cast speed and reduce Sorcerer cast speed in arena levels. + // PVP REBALANCE: Increase Warrior cast speed and reduce Sorcerer cast speed in arena levels. if (player.isOnArenaLevel()) { - if (IsAnyOf(player._pClass, HeroClass::Warrior, HeroClass::Barbarian)) + if (player._pClass == HeroClass::Warrior) skippedAnimationFrames++; else if (player._pClass == HeroClass::Sorcerer) skippedAnimationFrames--; From 03adfb8a297a7608833da09cc0578c233a81a9cf Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 07:24:32 -0500 Subject: [PATCH 17/43] Rebalance critical hit --- Source/missiles.cpp | 18 +++++++++++++++--- Source/player.cpp | 13 +++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 8c8f0d9ecb3..7c740f50742 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -371,7 +371,7 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i case MissileID::ChainBall: // 200% (400% of default) dam *= 2; break; - case MissileID::ChargedBolt: // 100% (200% of default), don't allow diagonal dodge + case MissileID::ChargedBolt: // 100% (200% of default) break; case MissileID::Elemental: // 40% damage (80% of default) dam *= 4; @@ -383,13 +383,13 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i break; case MissileID::Firebolt: // 100% (200% of default) case MissileID::FireWall: // 100% (200% of default) - case MissileID::FlameWave: // 100% (200% of default), don't allow diagonal dodge + case MissileID::FlameWave: // 100% (200% of default) break; case MissileID::FlashBottom: // 50% (100% default) case MissileID::FlashTop: // 50% (100% of default) dam /= 2; break; - case MissileID::Guardian: // 100% (200% of default), limit 1 at a time per player + case MissileID::Guardian: // 100% (200% of default) break; case MissileID::Inferno: // 400% (800% of default) dam *= 4; @@ -404,6 +404,18 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i dam /= 2; } } + + bool isOnArena = player.isOnArenaLevel(); + int charLevel = player.getCharacterLevel(); + bool isSpell = !missileData.isArrow(); + int critChance = (charLevel * 2 + (isSpell ? player._pMagic : player._pDexterity)) / 10; + + // PVP REBALANCE: Crit chance for arrows and spells in arenas. + if (isOnArena && GenerateRnd(100) < critChance) { + dam = isSpell ? dam * 5 / 4 : dam * 3 / 2; // Arrow: +50% damage, Spell: +25% damage + } + + if (resper > 0) { dam -= (dam * resper) / 100; if (&player == MyPlayer) diff --git a/Source/player.cpp b/Source/player.cpp index f20a7aa8efd..e622ce11292 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -811,11 +811,16 @@ bool PlrHitPlr(Player &attacker, Player &target) dam += (dam * attacker._pIBonusDam) / 100; dam += attacker._pIBonusDamMod + attacker._pDamageMod; - if (attacker._pClass == HeroClass::Warrior || attacker._pClass == HeroClass::Barbarian) { - if (GenerateRnd(100) < attacker.getCharacterLevel()) { - dam *= 2; - } + bool isOnArena = attacker.isOnArenaLevel(); + int charLevel = attacker.getCharacterLevel(); + HeroClass charClass = attacker._pClass; + int critChance = isOnArena ? (charLevel * 2 + attacker._pStrength) / 10 : charLevel; + + // PVP REBALANCE: New formula for crit chance in arenas. + if ((isOnArena || charClass == HeroClass::Warrior || charClass == HeroClass::Barbarian) && GenerateRnd(100) < critChance) { + dam *= 2; } + int skdam = dam << 6; if (HasAnyOf(attacker._pIFlags, ItemSpecialEffect::RandomStealLife)) { int tac = GenerateRnd(skdam / 8); From 2bbd28de84b99bfa4dc5cbbc0c06b0b55387b4cb Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 14:47:33 -0500 Subject: [PATCH 18/43] Rebalance hit recovery --- Source/player.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index e622ce11292..5273bdb2b34 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -2691,7 +2691,16 @@ void StartPlrHit(Player &player, int dam, bool forcehit) player.Say(HeroSpeech::ArghClang); RedrawComponent(PanelDrawComponent::Health); - if (player._pClass == HeroClass::Barbarian) { + + // PVP REBALANCE: New formula for calculating whether or not hit recovery should occur based on Life and Damage in arenas. + if (player.isOnArenaLevel()) { + int rper = GenerateRnd(100); + int recovery = dam - (player._pMaxHP / 5) + 100; + recovery = std::clamp(recovery, 5, 95); + + if (rper >= recovery) + return; + } else if (player._pClass == HeroClass::Barbarian) { if (dam >> 6 < player.getCharacterLevel() + player.getCharacterLevel() / 4 && !forcehit) { return; } @@ -2712,6 +2721,10 @@ void StartPlrHit(Player &player, int dam, bool forcehit) skippedAnimationFrames = 0; } + // PVP REBALANCE: Critical hits in arena will force a hit recovery and cause a longer hit recovery animation. + if (player.isOnArenaLevel() && forcehit) + skippedAnimationFrames--; + NewPlrAnim(player, player_graphic::Hit, pd, AnimationDistributionFlags::None, skippedAnimationFrames); player._pmode = PM_GOTHIT; @@ -2761,7 +2774,8 @@ StartPlayerKill(Player &player, DeathReason deathReason) player._pInvincible = true; SetPlayerHitPoints(player, 0); // PVP REBALANCE: Reset player's movements for arena use. - std::fill(player._pMovements.begin(), player._pMovements.end(), PM_STAND); + if (player.isMyPlayer()) + std::fill(player._pMovements.begin(), player._pMovements.end(), PM_STAND); if (&player != MyPlayer && dropItems) { // Ensure that items are removed for remote players From 64f49c5ff68d9b5c4b8716d6cee74e37b90104ff Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 16:16:07 -0500 Subject: [PATCH 19/43] Modify crits, spells direct at players --- Source/missiles.cpp | 95 +++++++++++++++++++++++++++++++++++++-------- Source/player.cpp | 7 +++- Source/player.h | 1 + 3 files changed, 85 insertions(+), 18 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 7c740f50742..ff7a83a762d 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -97,6 +97,23 @@ Monster *FindClosest(Point source, int rad) return nullptr; } +Player *FindClosestPlayer(Point source, int rad) +{ + std::optional playerPosition = FindClosestValidPosition( + [&source](Point target) { + // search for a player with clear line of sight + return InDungeonBounds(target) && dPlayer[target.x][target.y] > 0 && !CheckBlock(source, target); + }, + source, 1, rad); + + if (playerPosition) { + int pid = dPlayer[playerPosition->x][playerPosition->y]; + return &Players[pid - 1]; + } + + return nullptr; +} + constexpr Direction16 Direction16Flip(Direction16 x, Direction16 pivot) { std::underlying_type_t ret = (2 * static_cast>(pivot) + 16 - static_cast>(x)) % 16; @@ -409,13 +426,14 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i int charLevel = player.getCharacterLevel(); bool isSpell = !missileData.isArrow(); int critChance = (charLevel * 2 + (isSpell ? player._pMagic : player._pDexterity)) / 10; + bool forcehit = false; - // PVP REBALANCE: Crit chance for arrows and spells in arenas. + // PVP REBALANCE: Crit chance for arrows and spells in arenas. Crits force hit recovery. if (isOnArena && GenerateRnd(100) < critChance) { dam = isSpell ? dam * 5 / 4 : dam * 3 / 2; // Arrow: +50% damage, Spell: +25% damage + forcehit = true; } - if (resper > 0) { dam -= (dam * resper) / 100; if (&player == MyPlayer) @@ -430,7 +448,7 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i } else { if (&player == MyPlayer) NetSendCmdDamage(true, target, dam, damageType); - StartPlrHit(target, dam, false); + StartPlrHit(target, dam, forcehit); } return true; @@ -3584,13 +3602,18 @@ void ProcessChainLightning(Missile &missile) Point position = missile.position.tile; Point dst { missile.var1, missile.var2 }; Direction dir = GetDirection(position, dst); - AddMissile(position, dst, dir, MissileID::LightningControl, TARGET_MONSTERS, id, 1, missile._mispllvl); + const Player &player = Players[missile._misource]; + mienemy_type targetType = TARGET_MONSTERS; + if (player.isOnArenaLevel()) { + targetType = TARGET_PLAYERS; + } + AddMissile(position, dst, dir, MissileID::LightningControl, targetType, id, 1, missile._mispllvl); int rad = std::min(missile._mispllvl + 3, MaxCrawlRadius); Crawl(1, rad, [&](Displacement displacement) { Point target = position + displacement; - if (InDungeonBounds(target) && dMonster[target.x][target.y] > 0) { + if (InDungeonBounds(target) && (missile.sourcePlayer()->isOnArenaLevel() ? dPlayer[target.x][target.y] : dMonster[target.x][target.y]) > 0) { dir = GetDirection(position, target); - AddMissile(position, target, dir, MissileID::LightningControl, TARGET_MONSTERS, id, 1, missile._mispllvl); + AddMissile(position, target, dir, MissileID::LightningControl, targetType, id, 1, missile._mispllvl); } return false; }); @@ -4085,11 +4108,30 @@ void ProcessElemental(Missile &missile) if (missile.var3 == 1) { missile.var3 = 2; missile._mirange = 255; - auto *nextMonster = FindClosest(missilePosition, 19); - if (nextMonster != nullptr) { - Direction sd = GetDirection(missilePosition, nextMonster->position.tile); - SetMissDir(missile, sd); - UpdateMissileVelocity(missile, nextMonster->position.tile, 16); + + std::function targetAction; + if (missile.sourcePlayer()->isOnArenaLevel()) { + auto *nextPlayer = FindClosestPlayer(missilePosition, 19); + if (nextPlayer != nullptr) { + targetAction = [&]() { + Direction sd = GetDirection(missilePosition, nextPlayer->position.tile); + SetMissDir(missile, sd); + UpdateMissileVelocity(missile, nextPlayer->position.tile, 16); + }; + } + } else { + auto *nextMonster = FindClosest(missilePosition, 19); + if (nextMonster != nullptr) { + targetAction = [&]() { + Direction sd = GetDirection(missilePosition, nextMonster->position.tile); + SetMissDir(missile, sd); + UpdateMissileVelocity(missile, nextMonster->position.tile, 16); + }; + } + } + + if (targetAction) { + targetAction(); } else { Direction sd = Players[missile._misource]._pdir; SetMissDir(missile, sd); @@ -4130,17 +4172,38 @@ void ProcessBoneSpirit(Missile &missile) if (missile.var3 == 1) { missile.var3 = 2; missile._mirange = 255; - auto *monster = FindClosest(c, 19); - if (monster != nullptr) { - missile._midam = monster->hitPoints >> 7; - SetMissDir(missile, GetDirection(c, monster->position.tile)); - UpdateMissileVelocity(missile, monster->position.tile, 16); + + std::function targetAction; + + if (missile.sourcePlayer()->isOnArenaLevel()) { + auto *player = FindClosestPlayer(c, 19); + if (player != nullptr) { + targetAction = [&]() { + missile._midam = player->_pHitPoints >> 7; + SetMissDir(missile, GetDirection(c, player->position.tile)); + UpdateMissileVelocity(missile, player->position.tile, 16); + }; + } + } else { + auto *monster = FindClosest(c, 19); + if (monster != nullptr) { + targetAction = [&]() { + missile._midam = monster->hitPoints >> 7; + SetMissDir(missile, GetDirection(c, monster->position.tile)); + UpdateMissileVelocity(missile, monster->position.tile, 16); + }; + } + } + + if (targetAction) { + targetAction(); } else { Direction sd = Players[missile._misource]._pdir; SetMissDir(missile, sd); UpdateMissileVelocity(missile, c + sd, 16); } } + if (c != Point { missile.var1, missile.var2 }) { missile.var1 = c.x; missile.var2 = c.y; diff --git a/Source/player.cpp b/Source/player.cpp index 5273bdb2b34..48463c0db0c 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -815,10 +815,13 @@ bool PlrHitPlr(Player &attacker, Player &target) int charLevel = attacker.getCharacterLevel(); HeroClass charClass = attacker._pClass; int critChance = isOnArena ? (charLevel * 2 + attacker._pStrength) / 10 : charLevel; + bool forcehit = false; - // PVP REBALANCE: New formula for crit chance in arenas. + // PVP REBALANCE: New crit chance formula for arenas. Crits always cause hit recovery. if ((isOnArena || charClass == HeroClass::Warrior || charClass == HeroClass::Barbarian) && GenerateRnd(100) < critChance) { dam *= 2; + if (isOnArena) + forcehit = true; } int skdam = dam << 6; @@ -837,7 +840,7 @@ bool PlrHitPlr(Player &attacker, Player &target) if (&attacker == MyPlayer) { NetSendCmdDamage(true, target, skdam, DamageType::Physical); } - StartPlrHit(target, skdam, false); + StartPlrHit(target, skdam, forcehit); return true; } diff --git a/Source/player.h b/Source/player.h index e194f27443e..1b832e49f7b 100644 --- a/Source/player.h +++ b/Source/player.h @@ -29,6 +29,7 @@ namespace devilution { +extern Player *MyPlayer; constexpr int InventoryGridCells = 40; constexpr int MaxBeltItems = 8; constexpr int MaxResistance = 75; From 479b44dfe285504314286fa00336d10d3bb7b57d Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 17:18:40 -0500 Subject: [PATCH 20/43] Adjust damage values for spells, fix lightning damage bug --- Source/missiles.cpp | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index ff7a83a762d..1435ef025e8 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -380,27 +380,25 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i // PVP REBALANCE: Adjust damage values for spells in arena. if (player.isOnArenaLevel()) { switch (mtype) { - case MissileID::BloodStar: // 200% (400% of default) - dam *= 2; + case MissileID::BloodStar: // 400% (800% of default) + dam *= 4; break; case MissileID::BoneSpirit: // 100% (200% of default) break; - case MissileID::ChainBall: // 200% (400% of default) - dam *= 2; - break; case MissileID::ChargedBolt: // 100% (200% of default) break; - case MissileID::Elemental: // 40% damage (80% of default) - dam *= 4; - dam /= 10; + case MissileID::Elemental: // 300% damage (150% of default) + dam *= 3; break; - case MissileID::Fireball: // 20% damage (40% of default) - dam *= 2; - dam /= 10; + case MissileID::Fireball: // 150% damage (75% of default) + dam = dam * 3 / 2; break; case MissileID::Firebolt: // 100% (200% of default) + break; case MissileID::FireWall: // 100% (200% of default) - case MissileID::FlameWave: // 100% (200% of default) + dam = dam * 3 / 2; // 150% (300% of default) + case MissileID::FlameWave: // 200% (400% of default) + dam *= 2; break; case MissileID::FlashBottom: // 50% (100% default) case MissileID::FlashTop: // 50% (100% of default) @@ -408,11 +406,10 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i break; case MissileID::Guardian: // 100% (200% of default) break; - case MissileID::Inferno: // 400% (800% of default) - dam *= 4; + case MissileID::Inferno: // 500% (1000% of default) + dam *= 5; break; - case MissileID::Lightning: // 200% (400% of default) - dam *= 2; + case MissileID::Lightning: // 100% (200% of default) break; case MissileID::NovaBall: // 100% (200% of default) break; @@ -3373,8 +3370,9 @@ void ProcessLightningControl(Missile &missile) if (missile.IsTrap()) { // BUGFIX: damage of missile should be encoded in missile struct; monster can be dead before missile arrives. dam = GenerateRnd(currlevel) + 2 * currlevel; - } else if (missile._micaster == TARGET_MONSTERS) { + } else if (missile._micaster == TARGET_MONSTERS || Players[missile._misource].isOnArenaLevel()) { // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. + // PVP REBALANCE: Use the TARGET_MONSTERS damage formula in arena levels, since Chain Lightning will direct towards players. Change needs revision if monsters included in arena. dam = (GenerateRnd(2) + GenerateRnd(Players[missile._misource].getCharacterLevel()) + 2) << 6; } else { auto &monster = Monsters[missile._misource]; From 007511ed2b6f962dfaa4c0ff703577e79af1ce2f Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 17:28:10 -0500 Subject: [PATCH 21/43] divide by 6 instead of 5 for stun --- Source/player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/player.cpp b/Source/player.cpp index 48463c0db0c..b60d689e783 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -2698,7 +2698,7 @@ void StartPlrHit(Player &player, int dam, bool forcehit) // PVP REBALANCE: New formula for calculating whether or not hit recovery should occur based on Life and Damage in arenas. if (player.isOnArenaLevel()) { int rper = GenerateRnd(100); - int recovery = dam - (player._pMaxHP / 5) + 100; + int recovery = dam - (player._pMaxHP / 6) + 100; recovery = std::clamp(recovery, 5, 95); if (rper >= recovery) From 91276f02fe698083a993955ae9f5bc7685786111 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 17:34:34 -0500 Subject: [PATCH 22/43] clang --- Source/missiles.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 1435ef025e8..72324cb919f 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -393,10 +393,10 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i case MissileID::Fireball: // 150% damage (75% of default) dam = dam * 3 / 2; break; - case MissileID::Firebolt: // 100% (200% of default) + case MissileID::Firebolt: // 100% (200% of default) break; case MissileID::FireWall: // 100% (200% of default) - dam = dam * 3 / 2; // 150% (300% of default) + dam = dam * 3 / 2; // 150% (300% of default) case MissileID::FlameWave: // 200% (400% of default) dam *= 2; break; From 5823a5400e95388ebc1e22a67399feac04621cd8 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 17:40:29 -0500 Subject: [PATCH 23/43] Adjust diawalk threshold and movement history amount --- Source/player.cpp | 2 +- Source/player.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index b60d689e783..d234e1197a5 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -489,7 +489,7 @@ bool DoWalk(Player &player, int variant) if (player.isOnArenaLevel()) { if (player.calculateDiagonalMovementPercentage() > DiawalkDamageThreshold && player.isMyPlayer()) // Deal 5 HP worth of damage each diagonal movement multiplied by the amount of percent they are above the threshold. - NetSendCmdDamage(true, player, (player.calculateDiagonalMovementPercentage() - DiawalkDamageThreshold) * 5 * 64, DamageType::Physical); + NetSendCmdDamage(true, player, (player.calculateDiagonalMovementPercentage() - DiawalkDamageThreshold) * 1 * 64, DamageType::Physical); } break; } diff --git a/Source/player.h b/Source/player.h index 1b832e49f7b..90477f8cff0 100644 --- a/Source/player.h +++ b/Source/player.h @@ -39,8 +39,8 @@ constexpr int PlayerNameLength = 32; constexpr size_t NumHotkeys = 12; // PVP REBALANCE: The percentage of diagonal movements that _pMovements reaches to take punitive action against that player in the arena. -constexpr int16_t DiawalkDamageThreshold = 60; -constexpr int8_t MaxMovementHistory = 30; +constexpr int16_t DiawalkDamageThreshold = 80; +constexpr int8_t MaxMovementHistory = 32; /** Walking directions */ enum { From b8d8e76bfe9e40e60868494e7a4b289ff302e790 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 20:41:59 -0500 Subject: [PATCH 24/43] Make missiles more accurate --- Source/player.cpp | 60 ++++++++++++++++++++++++++++++++++------------- Source/player.h | 7 ++++-- Source/spells.cpp | 11 ++++++++- Source/spells.h | 2 +- 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index d234e1197a5..ef1bc0c1bb4 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -187,6 +187,9 @@ void ClearStateVariables(Player &player) player.position.temp = { 0, 0 }; player.tempDirection = Direction::South; player.queuedSpell.spellLevel = 0; + player.hasMonsterTarget = false; + player.hasPlayerTarget = false; + player.targetId = 0; } void StartAttack(Player &player, Direction d, bool includesFirstFrame) @@ -237,7 +240,7 @@ void StartAttack(Player &player, Direction d, bool includesFirstFrame) SetPlayerOld(player); } -void StartRangeAttack(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord cy, bool includesFirstFrame) +void StartRangeAttack(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord cy, bool includesFirstFrame, bool hasMonsterTarget, bool hasPlayerTarget, int targetId) { if (player._pInvincible && player._pHitPoints == 0 && &player == MyPlayer) { SyncPlrKill(player, DeathReason::Unknown); @@ -267,6 +270,9 @@ void StartRangeAttack(Player &player, Direction d, WorldTileCoord cx, WorldTileC FixPlayerLocation(player, d); SetPlayerOld(player); player.position.temp = WorldTilePosition { cx, cy }; + player.hasMonsterTarget = hasMonsterTarget; + player.hasPlayerTarget = hasPlayerTarget; + player.targetId = targetId; } player_graphic GetPlayerGraphicForSpell(SpellID spellId) @@ -281,7 +287,7 @@ player_graphic GetPlayerGraphicForSpell(SpellID spellId) } } -void StartSpell(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord cy) +void StartSpell(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord cy, bool hasMonsterTarget, bool hasPlayerTarget, int targetId) { if (player._pInvincible && player._pHitPoints == 0 && &player == MyPlayer) { SyncPlrKill(player, DeathReason::Unknown); @@ -331,6 +337,9 @@ void StartSpell(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord c SetPlayerOld(player); player.position.temp = WorldTilePosition { cx, cy }; + player.hasMonsterTarget = hasMonsterTarget; + player.hasPlayerTarget = hasPlayerTarget; + player.targetId = targetId; player.queuedSpell.spellLevel = player.GetSpellLevel(player.queuedSpell.spellId); player.executedSpell = player.queuedSpell; } @@ -940,6 +949,18 @@ bool DoRangeAttack(Player &player) arrows = 2; } + // Obtain target position upon arrow shot, rather than when initiating the cast for accuracy + auto targetId = player.targetId; + if (player.hasPlayerTarget) { + assert(targetId >= 0 && targetId < Players.size()); + auto &targetPlayer = Players[player.targetId]; + player.position.temp = targetPlayer.position.future; + } else if (player.hasMonsterTarget) { + assert(targetId >= 0 && targetId < MaxMonsters); + auto &targetMonster = Monsters[player.targetId]; + player.position.temp = targetMonster.position.future; + } + for (int arrow = 0; arrow < arrows; arrow++) { int xoff = 0; int yoff = 0; @@ -1096,7 +1117,11 @@ bool DoSpell(Player &player) player.executedSpell.spellId, player.position.tile, player.position.temp, - player.executedSpell.spellLevel); + player.executedSpell.spellLevel, + player.hasMonsterTarget, + player.hasPlayerTarget, + player.targetId + ); if (IsAnyOf(player.executedSpell.spellType, SpellType::Scroll, SpellType::Charges)) { EnsureValidReadiedSpell(player); @@ -1326,38 +1351,38 @@ void CheckNewPath(Player &player, bool pmWillBeCalled) break; case ACTION_RATTACK: d = GetDirection(player.position.tile, { player.destParam1, player.destParam2 }); - StartRangeAttack(player, d, player.destParam1, player.destParam2, pmWillBeCalled); + StartRangeAttack(player, d, player.destParam1, player.destParam2, pmWillBeCalled, false, false, 0); break; case ACTION_RATTACKMON: d = GetDirection(player.position.future, monster->position.future); if (monster->talkMsg != TEXT_NONE && monster->talkMsg != TEXT_VILE14) { TalktoMonster(player, *monster); } else { - StartRangeAttack(player, d, monster->position.future.x, monster->position.future.y, pmWillBeCalled); + StartRangeAttack(player, d, monster->position.future.x, monster->position.future.y, pmWillBeCalled, true, false, targetId); } break; case ACTION_RATTACKPLR: d = GetDirection(player.position.future, target->position.future); - StartRangeAttack(player, d, target->position.future.x, target->position.future.y, pmWillBeCalled); + StartRangeAttack(player, d, target->position.future.x, target->position.future.y, pmWillBeCalled, false, true, targetId); break; case ACTION_SPELL: d = GetDirection(player.position.tile, { player.destParam1, player.destParam2 }); - StartSpell(player, d, player.destParam1, player.destParam2); + StartSpell(player, d, player.destParam1, player.destParam2, false, false, 0); player.executedSpell.spellLevel = player.destParam3; break; case ACTION_SPELLWALL: - StartSpell(player, static_cast(player.destParam3), player.destParam1, player.destParam2); + StartSpell(player, static_cast(player.destParam3), player.destParam1, player.destParam2, false, false, 0); player.tempDirection = static_cast(player.destParam3); player.executedSpell.spellLevel = player.destParam4; break; case ACTION_SPELLMON: d = GetDirection(player.position.tile, monster->position.future); - StartSpell(player, d, monster->position.future.x, monster->position.future.y); + StartSpell(player, d, monster->position.future.x, monster->position.future.y, true, false, targetId); player.executedSpell.spellLevel = player.destParam2; break; case ACTION_SPELLPLR: d = GetDirection(player.position.tile, target->position.future); - StartSpell(player, d, target->position.future.x, target->position.future.y); + StartSpell(player, d, target->position.future.x, target->position.future.y, false, true, targetId); player.executedSpell.spellLevel = player.destParam2; break; case ACTION_OPERATE: @@ -1455,15 +1480,15 @@ void CheckNewPath(Player &player, bool pmWillBeCalled) if (player._pmode == PM_RATTACK && player.AnimInfo.currentFrame >= player._pAFNum) { if (player.destAction == ACTION_RATTACK) { d = GetDirection(player.position.tile, { player.destParam1, player.destParam2 }); - StartRangeAttack(player, d, player.destParam1, player.destParam2, pmWillBeCalled); + StartRangeAttack(player, d, player.destParam1, player.destParam2, pmWillBeCalled, false, false, 0); player.destAction = ACTION_NONE; } else if (player.destAction == ACTION_RATTACKMON) { d = GetDirection(player.position.tile, monster->position.future); - StartRangeAttack(player, d, monster->position.future.x, monster->position.future.y, pmWillBeCalled); + StartRangeAttack(player, d, monster->position.future.x, monster->position.future.y, pmWillBeCalled, true, false, targetId); player.destAction = ACTION_NONE; } else if (player.destAction == ACTION_RATTACKPLR) { d = GetDirection(player.position.tile, target->position.future); - StartRangeAttack(player, d, target->position.future.x, target->position.future.y, pmWillBeCalled); + StartRangeAttack(player, d, target->position.future.x, target->position.future.y, pmWillBeCalled, false, true, targetId); player.destAction = ACTION_NONE; } } @@ -1471,15 +1496,15 @@ void CheckNewPath(Player &player, bool pmWillBeCalled) if (player._pmode == PM_SPELL && player.AnimInfo.currentFrame >= player._pSFNum) { if (player.destAction == ACTION_SPELL) { d = GetDirection(player.position.tile, { player.destParam1, player.destParam2 }); - StartSpell(player, d, player.destParam1, player.destParam2); + StartSpell(player, d, player.destParam1, player.destParam2, false, false, 0); player.destAction = ACTION_NONE; } else if (player.destAction == ACTION_SPELLMON) { d = GetDirection(player.position.tile, monster->position.future); - StartSpell(player, d, monster->position.future.x, monster->position.future.y); + StartSpell(player, d, monster->position.future.x, monster->position.future.y, true, false, targetId); player.destAction = ACTION_NONE; } else if (player.destAction == ACTION_SPELLPLR) { d = GetDirection(player.position.tile, target->position.future); - StartSpell(player, d, target->position.future.x, target->position.future.y); + StartSpell(player, d, target->position.future.x, target->position.future.y, false, true, targetId); player.destAction = ACTION_NONE; } } @@ -2535,6 +2560,9 @@ void InitPlayer(Player &player, bool firstTime) player._pSBkSpell = SpellID::Invalid; player.queuedSpell.spellId = player._pRSpell; player.queuedSpell.spellType = player._pRSplType; + player.hasMonsterTarget = false; + player.hasPlayerTarget = false; + player.targetId = 0; player.pManaShield = false; player.wReflections = 0; } diff --git a/Source/player.h b/Source/player.h index 90477f8cff0..6af2e43e76e 100644 --- a/Source/player.h +++ b/Source/player.h @@ -29,7 +29,8 @@ namespace devilution { -extern Player *MyPlayer; +extern DVL_API_FOR_TEST Player *MyPlayer; + constexpr int InventoryGridCells = 40; constexpr int MaxBeltItems = 8; constexpr int MaxResistance = 75; @@ -309,6 +310,9 @@ struct Player { uint8_t plrlevel; bool plrIsOnSetLevel; ActorPosition position; + bool hasMonsterTarget; + bool hasPlayerTarget; + int8_t targetId; Direction _pdir; // Direction faced by player (direction enum) HeroClass _pClass; @@ -936,7 +940,6 @@ struct Player { }; extern DVL_API_FOR_TEST uint8_t MyPlayerId; -extern DVL_API_FOR_TEST Player *MyPlayer; extern DVL_API_FOR_TEST std::vector Players; /** @brief What Player items and stats should be displayed? Normally this is identical to MyPlayer but can differ when /inspect was used. */ extern Player *InspectPlayer; diff --git a/Source/spells.cpp b/Source/spells.cpp index 795ea66ef5b..eb1019fa71c 100644 --- a/Source/spells.cpp +++ b/Source/spells.cpp @@ -209,13 +209,22 @@ SpellCheckResult CheckSpell(const Player &player, SpellID sn, SpellType st, bool return SpellCheckResult::Success; } -void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosition dst, int spllvl) +void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosition dst, int spllvl, bool hasMonsterTarget /*= false*/, bool hasPlayerTarget /*= false*/, int targetId /*= 0*/) { Direction dir = player._pdir; if (IsWallSpell(spl)) { dir = player.tempDirection; } + // Obtain target position upon spell effect, rather than when initiating the cast for accuracy + if (hasPlayerTarget) { + assert(targetId >= 0 && targetId < Players.size()); + dst = Players[targetId].position.future; + } else if (hasMonsterTarget) { + assert(targetId >= 0 && targetId < MaxMonsters); + dst = Monsters[targetId].position.future; + } + bool fizzled = false; const SpellData &spellData = GetSpellData(spl); for (size_t i = 0; i < sizeof(spellData.sMissiles) / sizeof(spellData.sMissiles[0]) && spellData.sMissiles[i] != MissileID::Null; i++) { diff --git a/Source/spells.h b/Source/spells.h index 81064f9a63c..949fbfce9b9 100644 --- a/Source/spells.h +++ b/Source/spells.h @@ -35,7 +35,7 @@ SpellCheckResult CheckSpell(const Player &player, SpellID sn, SpellType st, bool * @param player The player whose readied spell is to be checked. */ void EnsureValidReadiedSpell(Player &player); -void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosition dst, int spllvl); +void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosition dst, int spllvl, bool hasMonsterTarget = false, bool hasPlayerTarget = false, int targetId = 0); /** * @param pnum player index From 1db35df11ef9d54da010e710002d3f66b1f70099 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 20:56:34 -0500 Subject: [PATCH 25/43] Only change missile behavior in arenas --- Source/player.cpp | 22 ++++++++++++---------- Source/spells.cpp | 16 +++++++++------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index ef1bc0c1bb4..d33b54e611d 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -949,16 +949,18 @@ bool DoRangeAttack(Player &player) arrows = 2; } - // Obtain target position upon arrow shot, rather than when initiating the cast for accuracy - auto targetId = player.targetId; - if (player.hasPlayerTarget) { - assert(targetId >= 0 && targetId < Players.size()); - auto &targetPlayer = Players[player.targetId]; - player.position.temp = targetPlayer.position.future; - } else if (player.hasMonsterTarget) { - assert(targetId >= 0 && targetId < MaxMonsters); - auto &targetMonster = Monsters[player.targetId]; - player.position.temp = targetMonster.position.future; + // PVP REBALANCE: Obtain target position upon arrow shot, rather than when initiating the cast for accuracy in arenas. + if (player.isOnArenaLevel()) { + auto targetId = player.targetId; + if (player.hasPlayerTarget) { + assert(targetId >= 0 && targetId < Players.size()); + auto &targetPlayer = Players[player.targetId]; + player.position.temp = targetPlayer.position.future; + } else if (player.hasMonsterTarget) { + assert(targetId >= 0 && targetId < MaxMonsters); + auto &targetMonster = Monsters[player.targetId]; + player.position.temp = targetMonster.position.future; + } } for (int arrow = 0; arrow < arrows; arrow++) { diff --git a/Source/spells.cpp b/Source/spells.cpp index eb1019fa71c..4bed33c6d09 100644 --- a/Source/spells.cpp +++ b/Source/spells.cpp @@ -216,13 +216,15 @@ void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosi dir = player.tempDirection; } - // Obtain target position upon spell effect, rather than when initiating the cast for accuracy - if (hasPlayerTarget) { - assert(targetId >= 0 && targetId < Players.size()); - dst = Players[targetId].position.future; - } else if (hasMonsterTarget) { - assert(targetId >= 0 && targetId < MaxMonsters); - dst = Monsters[targetId].position.future; + // PVP REBALANCE: Obtain target position upon spell effect, rather than when initiating the cast for accuracy in arenas. + if (player.isOnArenaLevel()) { + if (hasPlayerTarget) { + assert(targetId >= 0 && targetId < Players.size()); + dst = Players[targetId].position.future; + } else if (hasMonsterTarget) { + assert(targetId >= 0 && targetId < MaxMonsters); + dst = Monsters[targetId].position.future; + } } bool fizzled = false; From 50ea3bff63337b2a7495bee399d3197a1f98187f Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 21:26:18 -0500 Subject: [PATCH 26/43] Adjust fireball/elemental damage, modify bone spirit --- Source/missiles.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 72324cb919f..bd0015b268e 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -368,7 +368,7 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i int dam; if (mtype == MissileID::BoneSpirit) { - dam = target._pHitPoints / 3; + dam = target._pMaxHP / 3; } else { dam = mindam + GenerateRnd(maxdam - mindam + 1); if (missileData.isArrow() && damageType == DamageType::Physical) @@ -383,15 +383,13 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i case MissileID::BloodStar: // 400% (800% of default) dam *= 4; break; - case MissileID::BoneSpirit: // 100% (200% of default) - break; case MissileID::ChargedBolt: // 100% (200% of default) break; - case MissileID::Elemental: // 300% damage (150% of default) - dam *= 3; + case MissileID::Elemental: // 60% damage (120% of default) + dam = dam * 6 / 10; break; - case MissileID::Fireball: // 150% damage (75% of default) - dam = dam * 3 / 2; + case MissileID::Fireball: // 40% damage (80% of default) + dam = dam * 4 / 10; break; case MissileID::Firebolt: // 100% (200% of default) break; @@ -410,7 +408,6 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i dam *= 5; break; case MissileID::Lightning: // 100% (200% of default) - break; case MissileID::NovaBall: // 100% (200% of default) break; } @@ -431,7 +428,7 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i forcehit = true; } - if (resper > 0) { + if (resper > 0 && mtype != MissileID::BoneSpirit) { dam -= (dam * resper) / 100; if (&player == MyPlayer) NetSendCmdDamage(true, target, dam, damageType); From 040d63053fe1757d239538d4341373fce7d083f0 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 22:19:29 -0500 Subject: [PATCH 27/43] Prevent teleporting onto players at new location --- Source/missiles.cpp | 2 +- Source/player.cpp | 7 +++---- Source/spells.cpp | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index bd0015b268e..09eff82a9bd 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -408,7 +408,7 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i dam *= 5; break; case MissileID::Lightning: // 100% (200% of default) - case MissileID::NovaBall: // 100% (200% of default) + case MissileID::NovaBall: // 100% (200% of default) break; } } else { diff --git a/Source/player.cpp b/Source/player.cpp index d33b54e611d..a3b6dd9e523 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -1120,10 +1120,9 @@ bool DoSpell(Player &player) player.position.tile, player.position.temp, player.executedSpell.spellLevel, - player.hasMonsterTarget, - player.hasPlayerTarget, - player.targetId - ); + player.hasMonsterTarget, + player.hasPlayerTarget, + player.targetId); if (IsAnyOf(player.executedSpell.spellType, SpellType::Scroll, SpellType::Charges)) { EnsureValidReadiedSpell(player); diff --git a/Source/spells.cpp b/Source/spells.cpp index 4bed33c6d09..756cd6ac536 100644 --- a/Source/spells.cpp +++ b/Source/spells.cpp @@ -217,7 +217,7 @@ void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosi } // PVP REBALANCE: Obtain target position upon spell effect, rather than when initiating the cast for accuracy in arenas. - if (player.isOnArenaLevel()) { + if (player.isOnArenaLevel() && spl != SpellID::Teleport) { if (hasPlayerTarget) { assert(targetId >= 0 && targetId < Players.size()); dst = Players[targetId].position.future; From 8010b905e38afc3e3f52abc44c82590a6e05e9c7 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 25 Feb 2024 22:32:48 -0500 Subject: [PATCH 28/43] Reduce fireball velocity --- Source/missiles.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 09eff82a9bd..ebb370557fa 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -1963,7 +1963,14 @@ void AddFireball(Missile &missile, AddMissileParameter ¶meter) if (missile.position.start == dst) { dst += parameter.midir; } + int sp = 16; + + // PVP REBALANCE: Reduce fireball velocity by 4 in arenas. + if (missile.sourcePlayer() != nullptr) { + if (missile.sourcePlayer()->isOnArenaLevel()) + sp = 8; + } if (missile._micaster == TARGET_MONSTERS) { sp += std::min(missile._mispllvl * 2, 34); Player &player = Players[missile._misource]; From fc9393a29ccd493511505980dbabe597fe64ff11 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Mon, 26 Feb 2024 20:04:53 -0500 Subject: [PATCH 29/43] Revise spell and ranged accuracy --- Source/player.cpp | 52 +++++++++++++++++++++++++++++------------------ Source/spells.cpp | 13 +----------- Source/spells.h | 2 +- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index a3b6dd9e523..a82f4a488df 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -940,6 +940,23 @@ bool DoAttack(Player &player) bool DoRangeAttack(Player &player) { + WorldTilePosition dst = player.position.temp; + + if (player.isOnArenaLevel() && player.AnimInfo.currentFrame == player._pAFNum - 6) { + // PVP REBALANCE: Obtain target position 6 frames before the action frame to ensure same accuracy regardless of ranged speed. + // 6 frames should ensure the accuracy of shooting a bow for all classes is similar to that of Rogue with Swiftness in vanilla. + auto id = player.targetId; + if (player.hasPlayerTarget) { + assert(id >= 0 && id < Players.size()); + auto &targetPlayer = Players[id]; + dst = targetPlayer.position.future; + } else if (player.hasMonsterTarget) { + assert(id >= 0 && id < MaxMonsters); + auto &targetMonster = Monsters[id]; + dst = targetMonster.position.future; + } + } + int arrows = 0; if (player.AnimInfo.currentFrame == player._pAFNum - 1) { arrows = 1; @@ -949,20 +966,6 @@ bool DoRangeAttack(Player &player) arrows = 2; } - // PVP REBALANCE: Obtain target position upon arrow shot, rather than when initiating the cast for accuracy in arenas. - if (player.isOnArenaLevel()) { - auto targetId = player.targetId; - if (player.hasPlayerTarget) { - assert(targetId >= 0 && targetId < Players.size()); - auto &targetPlayer = Players[player.targetId]; - player.position.temp = targetPlayer.position.future; - } else if (player.hasMonsterTarget) { - assert(targetId >= 0 && targetId < MaxMonsters); - auto &targetMonster = Monsters[player.targetId]; - player.position.temp = targetMonster.position.future; - } - } - for (int arrow = 0; arrow < arrows; arrow++) { int xoff = 0; int yoff = 0; @@ -991,7 +994,7 @@ bool DoRangeAttack(Player &player) AddMissile( player.position.tile, - player.position.temp + Displacement { xoff, yoff }, + dst + Displacement { xoff, yoff }, player._pdir, mistype, TARGET_MONSTERS, @@ -1113,16 +1116,25 @@ void DamageArmor(Player &player) bool DoSpell(Player &player) { + WorldTilePosition dst = player.position.temp; + if (player.isOnArenaLevel() && player.AnimInfo.currentFrame == player._pSFNum - 7) { + // PVP REBALANCE: Obtain target position 7 frames before the action frame to ensure same accuracy regardless of casting speed. + // 7 frames should ensure the accuracy of casting spells for all classes is similar to that of Sorcerer in vanilla. + if (player.hasPlayerTarget) { + assert(player.targetId >= 0 && player.targetId < Players.size()); + dst = Players[player.targetId].position.future; + } else if (player.hasMonsterTarget) { + assert(player.targetId >= 0 && player.targetId < MaxMonsters); + dst = Monsters[player.targetId].position.future; + } + } if (player.AnimInfo.currentFrame == player._pSFNum) { CastSpell( player, player.executedSpell.spellId, player.position.tile, - player.position.temp, - player.executedSpell.spellLevel, - player.hasMonsterTarget, - player.hasPlayerTarget, - player.targetId); + dst, + player.executedSpell.spellLevel); if (IsAnyOf(player.executedSpell.spellType, SpellType::Scroll, SpellType::Charges)) { EnsureValidReadiedSpell(player); diff --git a/Source/spells.cpp b/Source/spells.cpp index 756cd6ac536..795ea66ef5b 100644 --- a/Source/spells.cpp +++ b/Source/spells.cpp @@ -209,24 +209,13 @@ SpellCheckResult CheckSpell(const Player &player, SpellID sn, SpellType st, bool return SpellCheckResult::Success; } -void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosition dst, int spllvl, bool hasMonsterTarget /*= false*/, bool hasPlayerTarget /*= false*/, int targetId /*= 0*/) +void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosition dst, int spllvl) { Direction dir = player._pdir; if (IsWallSpell(spl)) { dir = player.tempDirection; } - // PVP REBALANCE: Obtain target position upon spell effect, rather than when initiating the cast for accuracy in arenas. - if (player.isOnArenaLevel() && spl != SpellID::Teleport) { - if (hasPlayerTarget) { - assert(targetId >= 0 && targetId < Players.size()); - dst = Players[targetId].position.future; - } else if (hasMonsterTarget) { - assert(targetId >= 0 && targetId < MaxMonsters); - dst = Monsters[targetId].position.future; - } - } - bool fizzled = false; const SpellData &spellData = GetSpellData(spl); for (size_t i = 0; i < sizeof(spellData.sMissiles) / sizeof(spellData.sMissiles[0]) && spellData.sMissiles[i] != MissileID::Null; i++) { diff --git a/Source/spells.h b/Source/spells.h index 949fbfce9b9..81064f9a63c 100644 --- a/Source/spells.h +++ b/Source/spells.h @@ -35,7 +35,7 @@ SpellCheckResult CheckSpell(const Player &player, SpellID sn, SpellType st, bool * @param player The player whose readied spell is to be checked. */ void EnsureValidReadiedSpell(Player &player); -void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosition dst, int spllvl, bool hasMonsterTarget = false, bool hasPlayerTarget = false, int targetId = 0); +void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosition dst, int spllvl); /** * @param pnum player index From 505ed369f8136f5435cd77b10856d15a5f28a590 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Mon, 26 Feb 2024 21:04:36 -0500 Subject: [PATCH 30/43] Reduce fire wall duration, add per spell frame skips --- Source/missiles.cpp | 4 +++ Source/player.cpp | 87 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index ebb370557fa..7ad185168da 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -1948,6 +1948,10 @@ void AddFireWall(Missile &missile, AddMissileParameter ¶meter) missile._midam <<= 3; UpdateMissileVelocity(missile, parameter.dst, 16); int i = missile._mispllvl; + if (missile.sourcePlayer() != nullptr) { + if (missile.sourcePlayer()->isOnArenaLevel()) + i = 0; + } missile._mirange = 10; if (i > 0) missile._mirange *= i + 1; diff --git a/Source/player.cpp b/Source/player.cpp index a82f4a488df..f1ca3c908c6 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -240,7 +240,7 @@ void StartAttack(Player &player, Direction d, bool includesFirstFrame) SetPlayerOld(player); } -void StartRangeAttack(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord cy, bool includesFirstFrame, bool hasMonsterTarget, bool hasPlayerTarget, int targetId) +void StartRangeAttack(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord cy, bool includesFirstFrame, bool hasMonsterTarget, bool hasPlayerTarget, int8_t targetId) { if (player._pInvincible && player._pHitPoints == 0 && &player == MyPlayer) { SyncPlrKill(player, DeathReason::Unknown); @@ -287,7 +287,82 @@ player_graphic GetPlayerGraphicForSpell(SpellID spellId) } } -void StartSpell(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord cy, bool hasMonsterTarget, bool hasPlayerTarget, int targetId) +int GetSpellSkippedFrames(SpellID spl) +{ + switch (spl) { + // Page 1 + //case SpellID::ItemRepair: + //case SpellID::TrapDisarm: + //case SpellID::StaffRecharge: + case SpellID::Rage: + return 0; + case SpellID::Firebolt: + return 3; + case SpellID::ChargedBolt: + return 2; + case SpellID::Healing: + case SpellID::HealOther: + return 0; + //case SpellID::HolyBolt: + case SpellID::Inferno: + return 2; + // Page 2 + case SpellID::Resurrect: + case SpellID::FireWall: + return 0; + //case SpellID::Telekinesis: + case SpellID::Lightning: + return 2; + case SpellID::TownPortal: + return 4; + case SpellID::Flash: + return 1; + //case SpellID::StoneCurse: + // Page 3 + case SpellID::Phasing: + return 1; + case SpellID::ManaShield: + case SpellID::Elemental: + case SpellID::Fireball: + case SpellID::FlameWave: + case SpellID::ChainLightning: + return 0; + case SpellID::Guardian: + return 4; + // Page 4 + case SpellID::Nova: + case SpellID::Golem: + return 0; + case SpellID::Teleport: + return 1; + case SpellID::Apocalypse: + case SpellID::BoneSpirit: + return 1; + case SpellID::BloodStar: + return 2; + // Page 5 + case SpellID::LightningWall: + case SpellID::Immolation: + return 0; + case SpellID::Warp: + return 4; + case SpellID::Reflect: + //case SpellID::Berserk: + case SpellID::RingOfFire: + //case SpellID::Search: + // Extra + //case SpellID::Infravision: + //case SpellID::Identify: + case SpellID::Jester: + case SpellID::Magi: + case SpellID::Mana: + return 0; + default: + return 0; + } +} + +void StartSpell(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord cy, bool hasMonsterTarget, bool hasPlayerTarget, int8_t targetId) { if (player._pInvincible && player._pHitPoints == 0 && &player == MyPlayer) { SyncPlrKill(player, DeathReason::Unknown); @@ -318,10 +393,12 @@ void StartSpell(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord c // PVP REBALANCE: Increase Warrior cast speed and reduce Sorcerer cast speed in arena levels. if (player.isOnArenaLevel()) { - if (player._pClass == HeroClass::Warrior) - skippedAnimationFrames++; - else if (player._pClass == HeroClass::Sorcerer) + if (player._pClass == HeroClass::Barbarian) skippedAnimationFrames--; + if (player._pClass == HeroClass::Sorcerer) + skippedAnimationFrames -= 2; + + skippedAnimationFrames += GetSpellSkippedFrames(player.queuedSpell.spellId); } auto animationFlags = AnimationDistributionFlags::ProcessAnimationPending; From c51ef144a5171a08a538be2858de1b29798455cb Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Tue, 27 Feb 2024 20:58:14 -0500 Subject: [PATCH 31/43] Fix Bone Spirit, adjust crit formula --- Source/missiles.cpp | 25 ++++++++++++++++--------- Source/player.cpp | 19 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 7ad185168da..95ee4790e5a 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -368,7 +368,7 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i int dam; if (mtype == MissileID::BoneSpirit) { - dam = target._pMaxHP / 3; + dam = player.isOnArenaLevel() ? target._pMaxHP / 3 : target._pHitPoints / 3; } else { dam = mindam + GenerateRnd(maxdam - mindam + 1); if (missileData.isArrow() && damageType == DamageType::Physical) @@ -393,8 +393,8 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i break; case MissileID::Firebolt: // 100% (200% of default) break; - case MissileID::FireWall: // 100% (200% of default) - dam = dam * 3 / 2; // 150% (300% of default) + case MissileID::FireWall: // 150% (300% of default) + dam = dam * 3 / 2; case MissileID::FlameWave: // 200% (400% of default) dam *= 2; break; @@ -418,17 +418,24 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i bool isOnArena = player.isOnArenaLevel(); int charLevel = player.getCharacterLevel(); + + // Error handling for critical hit calculation to avoid dividing by 0 in case of bad actor. + charLevel = std::clamp(charLevel, 1, static_cast(GetMaximumCharacterLevel())); + bool isSpell = !missileData.isArrow(); - int critChance = (charLevel * 2 + (isSpell ? player._pMagic : player._pDexterity)) / 10; - bool forcehit = false; + int crit = (isSpell ? player._pMagic : player._pDexterity) / (charLevel / 4); + + crit = std::clamp(crit, 0, 50); + + int critper = GenerateRnd(100); // PVP REBALANCE: Crit chance for arrows and spells in arenas. Crits force hit recovery. - if (isOnArena && GenerateRnd(100) < critChance) { + if (isOnArena && critper < crit && mtype != MissileID::BoneSpirit) { dam = isSpell ? dam * 5 / 4 : dam * 3 / 2; // Arrow: +50% damage, Spell: +25% damage - forcehit = true; } - if (resper > 0 && mtype != MissileID::BoneSpirit) { + // PVP REBALANCE: Bone Spirit is unaffected by resistances on arena levels. + if (resper > 0 && (mtype != MissileID::BoneSpirit || !player.isOnArenaLevel())) { dam -= (dam * resper) / 100; if (&player == MyPlayer) NetSendCmdDamage(true, target, dam, damageType); @@ -442,7 +449,7 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i } else { if (&player == MyPlayer) NetSendCmdDamage(true, target, dam, damageType); - StartPlrHit(target, dam, forcehit); + StartPlrHit(target, dam, false); } return true; diff --git a/Source/player.cpp b/Source/player.cpp index f1ca3c908c6..e1283d49c49 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -316,7 +316,6 @@ int GetSpellSkippedFrames(SpellID spl) case SpellID::TownPortal: return 4; case SpellID::Flash: - return 1; //case SpellID::StoneCurse: // Page 3 case SpellID::Phasing: @@ -336,6 +335,7 @@ int GetSpellSkippedFrames(SpellID spl) case SpellID::Teleport: return 1; case SpellID::Apocalypse: + return 0; case SpellID::BoneSpirit: return 1; case SpellID::BloodStar: @@ -899,15 +899,20 @@ bool PlrHitPlr(Player &attacker, Player &target) bool isOnArena = attacker.isOnArenaLevel(); int charLevel = attacker.getCharacterLevel(); + + // Error handling for critical hit calculation to avoid dividing by 0 in case of bad actor. + charLevel = std::clamp(charLevel, 1, static_cast(GetMaximumCharacterLevel())); + + int crit = isOnArena ? attacker._pStrength / (charLevel / 4) : charLevel; + + crit = std::clamp(crit, 0, 50); + + int critper = GenerateRnd(100); HeroClass charClass = attacker._pClass; - int critChance = isOnArena ? (charLevel * 2 + attacker._pStrength) / 10 : charLevel; - bool forcehit = false; // PVP REBALANCE: New crit chance formula for arenas. Crits always cause hit recovery. - if ((isOnArena || charClass == HeroClass::Warrior || charClass == HeroClass::Barbarian) && GenerateRnd(100) < critChance) { + if ((isOnArena || charClass == HeroClass::Warrior || charClass == HeroClass::Barbarian) && critper < crit) { dam *= 2; - if (isOnArena) - forcehit = true; } int skdam = dam << 6; @@ -926,7 +931,7 @@ bool PlrHitPlr(Player &attacker, Player &target) if (&attacker == MyPlayer) { NetSendCmdDamage(true, target, skdam, DamageType::Physical); } - StartPlrHit(target, skdam, forcehit); + StartPlrHit(target, skdam, false); return true; } From ff72441103a16581fe7fcf8de8bacc2989c9042d Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Wed, 28 Feb 2024 19:33:28 -0500 Subject: [PATCH 32/43] Fixes Fire Wall didn't have damage adjusted. Bone Spirit targeted the caster. --- Source/missiles.cpp | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 95ee4790e5a..824a438b59b 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -297,6 +297,9 @@ bool MonsterMHit(const Player &player, int monsterId, int mindam, int maxdam, in return true; } +/** + * @brief Handles the interaction that occurs when a missile that cannot damage the player that created it collides with a another player. +*/ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, int dist, MissileID mtype, DamageType damageType, bool shift, bool *blocked) { if (sgGameInitInfo.bFriendlyFire == 0 && player.friendlyMode) @@ -368,7 +371,8 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i int dam; if (mtype == MissileID::BoneSpirit) { - dam = player.isOnArenaLevel() ? target._pMaxHP / 3 : target._pHitPoints / 3; + // PVP REBALANCE: Bone Spirit does 1/3 Max HP in arenas instead of 1/3 current HP. + dam = target.isOnArenaLevel() ? target._pMaxHP / 3 : target._pHitPoints / 3; } else { dam = mindam + GenerateRnd(maxdam - mindam + 1); if (missileData.isArrow() && damageType == DamageType::Physical) @@ -376,6 +380,7 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i if (!shift) dam <<= 6; } + if (!missileData.isArrow()) { // PVP REBALANCE: Adjust damage values for spells in arena. if (player.isOnArenaLevel()) { @@ -393,8 +398,6 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i break; case MissileID::Firebolt: // 100% (200% of default) break; - case MissileID::FireWall: // 150% (300% of default) - dam = dam * 3 / 2; case MissileID::FlameWave: // 200% (400% of default) dam *= 2; break; @@ -429,14 +432,15 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i int critper = GenerateRnd(100); - // PVP REBALANCE: Crit chance for arrows and spells in arenas. Crits force hit recovery. + // PVP REBALANCE: Crit chance for arrows and spells in arenas. if (isOnArena && critper < crit && mtype != MissileID::BoneSpirit) { dam = isSpell ? dam * 5 / 4 : dam * 3 / 2; // Arrow: +50% damage, Spell: +25% damage } - // PVP REBALANCE: Bone Spirit is unaffected by resistances on arena levels. - if (resper > 0 && (mtype != MissileID::BoneSpirit || !player.isOnArenaLevel())) { - dam -= (dam * resper) / 100; + if (resper > 0) { + // PVP REBALANCE: Bone Spirit is unaffected by resistances on arena levels. + if (!target.isOnArenaLevel() || (target.isOnArenaLevel() && mtype != MissileID::BoneSpirit)) + dam -= (dam * resper) / 100; if (&player == MyPlayer) NetSendCmdDamage(true, target, dam, damageType); target.Say(HeroSpeech::ArghClang); @@ -1050,6 +1054,9 @@ bool MonsterTrapHit(int monsterId, int mindam, int maxdam, int dist, MissileID t return true; } +/** + * @brief Handles the interaction that occurs when a missile that can damage a player (even if they created it) collides with a player. +*/ bool PlayerMHit(Player &player, Monster *monster, int dist, int mind, int maxd, MissileID mtype, DamageType damageType, bool shift, DeathReason deathReason, bool *blocked) { *blocked = false; @@ -1154,6 +1161,14 @@ bool PlayerMHit(Player &player, Monster *monster, int dist, int mind, int maxd, dam = std::max(dam, 64); } + // PVP REBALANCE: Adjust damage values for spells in arena. + if (player.isOnArenaLevel()) { + switch (mtype) { + case MissileID::FireWall: // 150% + dam = dam * 3 / 2; + } + } + if ((resper <= 0 || gbIsHellfire) && blk < blkper) { Direction dir = player._pdir; if (monster != nullptr) { @@ -1166,6 +1181,7 @@ bool PlayerMHit(Player &player, Monster *monster, int dist, int mind, int maxd, if (resper > 0) { dam -= dam * resper / 100; + if (&player == MyPlayer) { ApplyPlrDamage(damageType, player, 0, 0, dam, deathReason); } @@ -4190,9 +4206,8 @@ void ProcessBoneSpirit(Missile &missile) if (missile.sourcePlayer()->isOnArenaLevel()) { auto *player = FindClosestPlayer(c, 19); - if (player != nullptr) { + if (player != nullptr && player != missile.sourcePlayer()) { targetAction = [&]() { - missile._midam = player->_pHitPoints >> 7; SetMissDir(missile, GetDirection(c, player->position.tile)); UpdateMissileVelocity(missile, player->position.tile, 16); }; From d68327ad010d5b2f60d79c27a9dc31f735de7927 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Wed, 28 Feb 2024 19:38:40 -0500 Subject: [PATCH 33/43] Spell adjustments - Fireball: 1 frame skip - Elemental: 1 frame skip - Bone Spirit: 0 frame skips - Blood Star: 0 frame skips - Lightning: +300% damage - Chain Lightning: +300% damage --- Source/missiles.cpp | 4 +++- Source/player.cpp | 16 ++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 824a438b59b..8f411e30c09 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -410,7 +410,9 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i case MissileID::Inferno: // 500% (1000% of default) dam *= 5; break; - case MissileID::Lightning: // 100% (200% of default) + case MissileID::Lightning: // 200% (400% of default) + dam *= 2; + break; case MissileID::NovaBall: // 100% (200% of default) break; } diff --git a/Source/player.cpp b/Source/player.cpp index e1283d49c49..0e7de74f6a4 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -291,9 +291,6 @@ int GetSpellSkippedFrames(SpellID spl) { switch (spl) { // Page 1 - //case SpellID::ItemRepair: - //case SpellID::TrapDisarm: - //case SpellID::StaffRecharge: case SpellID::Rage: return 0; case SpellID::Firebolt: @@ -303,26 +300,25 @@ int GetSpellSkippedFrames(SpellID spl) case SpellID::Healing: case SpellID::HealOther: return 0; - //case SpellID::HolyBolt: case SpellID::Inferno: return 2; // Page 2 case SpellID::Resurrect: case SpellID::FireWall: return 0; - //case SpellID::Telekinesis: case SpellID::Lightning: return 2; case SpellID::TownPortal: return 4; case SpellID::Flash: - //case SpellID::StoneCurse: // Page 3 case SpellID::Phasing: return 1; case SpellID::ManaShield: + return 0; case SpellID::Elemental: case SpellID::Fireball: + return 1; case SpellID::FlameWave: case SpellID::ChainLightning: return 0; @@ -337,9 +333,9 @@ int GetSpellSkippedFrames(SpellID spl) case SpellID::Apocalypse: return 0; case SpellID::BoneSpirit: - return 1; + return 0; case SpellID::BloodStar: - return 2; + return 0; // Page 5 case SpellID::LightningWall: case SpellID::Immolation: @@ -347,12 +343,8 @@ int GetSpellSkippedFrames(SpellID spl) case SpellID::Warp: return 4; case SpellID::Reflect: - //case SpellID::Berserk: case SpellID::RingOfFire: - //case SpellID::Search: // Extra - //case SpellID::Infravision: - //case SpellID::Identify: case SpellID::Jester: case SpellID::Magi: case SpellID::Mana: From f13542750d93c65037f549f14afe46b879a74fa4 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Thu, 29 Feb 2024 21:06:24 -0500 Subject: [PATCH 34/43] Remove forced hit recovery --- Source/player.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index 0e7de74f6a4..9a464c3751f 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -2839,10 +2839,6 @@ void StartPlrHit(Player &player, int dam, bool forcehit) skippedAnimationFrames = 0; } - // PVP REBALANCE: Critical hits in arena will force a hit recovery and cause a longer hit recovery animation. - if (player.isOnArenaLevel() && forcehit) - skippedAnimationFrames--; - NewPlrAnim(player, player_graphic::Hit, pd, AnimationDistributionFlags::None, skippedAnimationFrames); player._pmode = PM_GOTHIT; From 37c61c981af0c77d374135d8c637b88aa7c83df3 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Fri, 1 Mar 2024 00:23:04 -0500 Subject: [PATCH 35/43] Fix Elemental --- Source/missiles.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 8f411e30c09..bcc40126a6e 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -4143,7 +4143,7 @@ void ProcessElemental(Missile &missile) std::function targetAction; if (missile.sourcePlayer()->isOnArenaLevel()) { auto *nextPlayer = FindClosestPlayer(missilePosition, 19); - if (nextPlayer != nullptr) { + if (nextPlayer != nullptr && nextPlayer != missile.sourcePlayer()) { targetAction = [&]() { Direction sd = GetDirection(missilePosition, nextPlayer->position.tile); SetMissDir(missile, sd); From 064309106a13f51daf18abe5d439a54b87a5aec5 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Fri, 22 Mar 2024 22:04:58 -0400 Subject: [PATCH 36/43] Modify melee crit chance formula --- Source/player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/player.cpp b/Source/player.cpp index 9a464c3751f..331f325b2b4 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -895,7 +895,7 @@ bool PlrHitPlr(Player &attacker, Player &target) // Error handling for critical hit calculation to avoid dividing by 0 in case of bad actor. charLevel = std::clamp(charLevel, 1, static_cast(GetMaximumCharacterLevel())); - int crit = isOnArena ? attacker._pStrength / (charLevel / 4) : charLevel; + int crit = isOnArena ? attacker._pStrength / (charLevel / 8) : charLevel; crit = std::clamp(crit, 0, 50); From e4c9b8d521f1ef88d0f901e1fdef83216442fe12 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Fri, 22 Mar 2024 22:28:13 -0400 Subject: [PATCH 37/43] Adjust warrior attack speed --- Source/player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/player.cpp b/Source/player.cpp index 331f325b2b4..fafbc09ee14 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -228,7 +228,7 @@ void StartAttack(Player &player, Direction d, bool includesFirstFrame) // PVP REBALANCE: Increase Warrior Axe and Staff speed by 1 frame. auto gn = static_cast(player._pgfxnum & 0xFU); - if (player.isOnArenaLevel() && player._pClass == HeroClass::Warrior && IsAnyOf(gn, PlayerWeaponGraphic::Axe, PlayerWeaponGraphic::Staff)) + if (player.isOnArenaLevel() && player._pClass == HeroClass::Warrior) skippedAnimationFrames++; auto animationFlags = AnimationDistributionFlags::ProcessAnimationPending; From dc63ac51f55a7a67ea73e90b8a9abf3b4828fa96 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 24 Mar 2024 21:50:05 -0400 Subject: [PATCH 38/43] Modify FB velocity --- Source/missiles.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index bcc40126a6e..4abcb7c9d8b 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -1994,14 +1994,15 @@ void AddFireball(Missile &missile, AddMissileParameter ¶meter) } int sp = 16; - // PVP REBALANCE: Reduce fireball velocity by 4 in arenas. + int cap = 34; + if (missile.sourcePlayer() != nullptr) { if (missile.sourcePlayer()->isOnArenaLevel()) - sp = 8; + cap = 26; } if (missile._micaster == TARGET_MONSTERS) { - sp += std::min(missile._mispllvl * 2, 34); + sp += std::min(missile._mispllvl * 2, cap); Player &player = Players[missile._misource]; int dmg = 2 * (player.getCharacterLevel() + GenerateRndSum(10, 2)) + 4; From bb026b4214d8d8f8eb9e84ea8ea9bcdeda3e060c Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 24 Mar 2024 21:50:41 -0400 Subject: [PATCH 39/43] Modify FB velocity Cap velocity in arena at slvl 14 --- Source/missiles.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 4abcb7c9d8b..0465177eb14 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -1999,7 +1999,7 @@ void AddFireball(Missile &missile, AddMissileParameter ¶meter) if (missile.sourcePlayer() != nullptr) { if (missile.sourcePlayer()->isOnArenaLevel()) - cap = 26; + cap = 28; } if (missile._micaster == TARGET_MONSTERS) { sp += std::min(missile._mispllvl * 2, cap); From 4205400ad2f90f5a359464802af7c438ff878a1d Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 12 May 2024 19:41:49 -0400 Subject: [PATCH 40/43] Remove crit on spells, increase fb cast rate --- Source/missiles.cpp | 18 +++++++++--------- Source/player.cpp | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 0465177eb14..33553ccfd21 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -393,9 +393,9 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i case MissileID::Elemental: // 60% damage (120% of default) dam = dam * 6 / 10; break; - case MissileID::Fireball: // 40% damage (80% of default) - dam = dam * 4 / 10; - break; + //case MissileID::Fireball: // 40% damage (80% of default) + // dam = dam * 4 / 10; + // break; case MissileID::Firebolt: // 100% (200% of default) break; case MissileID::FlameWave: // 200% (400% of default) @@ -428,16 +428,16 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i charLevel = std::clamp(charLevel, 1, static_cast(GetMaximumCharacterLevel())); bool isSpell = !missileData.isArrow(); - int crit = (isSpell ? player._pMagic : player._pDexterity) / (charLevel / 4); + //int crit = (isSpell ? player._pMagic : player._pDexterity) / (charLevel / 4); - crit = std::clamp(crit, 0, 50); + //crit = std::clamp(crit, 0, 50); - int critper = GenerateRnd(100); + //int critper = GenerateRnd(100); // PVP REBALANCE: Crit chance for arrows and spells in arenas. - if (isOnArena && critper < crit && mtype != MissileID::BoneSpirit) { - dam = isSpell ? dam * 5 / 4 : dam * 3 / 2; // Arrow: +50% damage, Spell: +25% damage - } + //if (isOnArena && critper < crit && mtype != MissileID::BoneSpirit) { + // dam = isSpell ? dam * 5 / 4 : dam * 3 / 2; // Arrow: +50% damage, Spell: +25% damage + //} if (resper > 0) { // PVP REBALANCE: Bone Spirit is unaffected by resistances on arena levels. diff --git a/Source/player.cpp b/Source/player.cpp index fafbc09ee14..50410c54412 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -318,7 +318,7 @@ int GetSpellSkippedFrames(SpellID spl) return 0; case SpellID::Elemental: case SpellID::Fireball: - return 1; + return 2; case SpellID::FlameWave: case SpellID::ChainLightning: return 0; From cce6c14b630b064eee302566f8d2126949a28899 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 12 May 2024 19:43:24 -0400 Subject: [PATCH 41/43] Remove per spell frame skips --- Source/player.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index 50410c54412..264b2f21963 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -384,14 +384,14 @@ void StartSpell(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord c int8_t skippedAnimationFrames = 0; // PVP REBALANCE: Increase Warrior cast speed and reduce Sorcerer cast speed in arena levels. - if (player.isOnArenaLevel()) { - if (player._pClass == HeroClass::Barbarian) - skippedAnimationFrames--; - if (player._pClass == HeroClass::Sorcerer) - skippedAnimationFrames -= 2; - - skippedAnimationFrames += GetSpellSkippedFrames(player.queuedSpell.spellId); - } + //if (player.isOnArenaLevel()) { + // if (player._pClass == HeroClass::Barbarian) + // skippedAnimationFrames--; + // if (player._pClass == HeroClass::Sorcerer) + // skippedAnimationFrames -= 2; + // + // skippedAnimationFrames += GetSpellSkippedFrames(player.queuedSpell.spellId); + //} auto animationFlags = AnimationDistributionFlags::ProcessAnimationPending; if (player._pmode == PM_SPELL) From 7e39bd0eb702f338f4ad5833820290313dba950a Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 12 May 2024 19:47:07 -0400 Subject: [PATCH 42/43] Reinstate 20% damage decrease to fireball --- Source/missiles.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 33553ccfd21..f5759f91856 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -393,9 +393,9 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i case MissileID::Elemental: // 60% damage (120% of default) dam = dam * 6 / 10; break; - //case MissileID::Fireball: // 40% damage (80% of default) - // dam = dam * 4 / 10; - // break; + case MissileID::Fireball: // 40% damage (80% of default) + dam = dam * 4 / 10; + break; case MissileID::Firebolt: // 100% (200% of default) break; case MissileID::FlameWave: // 200% (400% of default) From 1f9be76432092257d6a40681b44d09e10ec2a615 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Sun, 12 May 2024 19:53:29 -0400 Subject: [PATCH 43/43] Adjustments --- Source/player.cpp | 18 ++++++++++-------- Source/player.h | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Source/player.cpp b/Source/player.cpp index 264b2f21963..2d6317723be 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -384,14 +384,16 @@ void StartSpell(Player &player, Direction d, WorldTileCoord cx, WorldTileCoord c int8_t skippedAnimationFrames = 0; // PVP REBALANCE: Increase Warrior cast speed and reduce Sorcerer cast speed in arena levels. - //if (player.isOnArenaLevel()) { - // if (player._pClass == HeroClass::Barbarian) - // skippedAnimationFrames--; - // if (player._pClass == HeroClass::Sorcerer) - // skippedAnimationFrames -= 2; - // - // skippedAnimationFrames += GetSpellSkippedFrames(player.queuedSpell.spellId); - //} + if (player.isOnArenaLevel()) { + if (player._pClass == HeroClass::Warrior) + skippedAnimationFrames++; + //if (player._pClass == HeroClass::Barbarian) + // skippedAnimationFrames--; + //if (player._pClass == HeroClass::Sorcerer) + // skippedAnimationFrames -= 2; + + //skippedAnimationFrames += GetSpellSkippedFrames(player.queuedSpell.spellId); + } auto animationFlags = AnimationDistributionFlags::ProcessAnimationPending; if (player._pmode == PM_SPELL) diff --git a/Source/player.h b/Source/player.h index 6af2e43e76e..84f498da19a 100644 --- a/Source/player.h +++ b/Source/player.h @@ -40,7 +40,7 @@ constexpr int PlayerNameLength = 32; constexpr size_t NumHotkeys = 12; // PVP REBALANCE: The percentage of diagonal movements that _pMovements reaches to take punitive action against that player in the arena. -constexpr int16_t DiawalkDamageThreshold = 80; +constexpr int16_t DiawalkDamageThreshold = 95; constexpr int8_t MaxMovementHistory = 32; /** Walking directions */