Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PVP Arena Rebalance Tweaks #6986

Closed
wants to merge 43 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
664f4ff
PVP Arena Rebalance Tweaks
kphoenix137 Feb 23, 2024
7b522a4
Do not allow target retention in arena
kphoenix137 Feb 23, 2024
babf245
Diagonal Walk Punishment
kphoenix137 Feb 24, 2024
b8901cf
clang
kphoenix137 Feb 24, 2024
614734a
Fix array initialization
kphoenix137 Feb 24, 2024
fc8b966
Increase threshold to 50
kphoenix137 Feb 24, 2024
96f3b52
Reduce damage from diawalk abuse
kphoenix137 Feb 24, 2024
7914670
Fix hardcode
kphoenix137 Feb 24, 2024
9050270
Spell damage per spell
kphoenix137 Feb 25, 2024
8bc28e3
clang
kphoenix137 Feb 25, 2024
fe6706b
FlameWave and ChargedBolt not affected by diawalk
kphoenix137 Feb 25, 2024
8a74f71
Increase Warrior Axe/Staff by 1 frame speed
kphoenix137 Feb 25, 2024
07c30e8
Only apply slower bow speed to Rogue
kphoenix137 Feb 25, 2024
2211424
Revise _pMovements
kphoenix137 Feb 25, 2024
7353b4e
Only track local player movements history to save resources
kphoenix137 Feb 25, 2024
332e4bb
Remove Barbarian cast buff
kphoenix137 Feb 25, 2024
03adfb8
Rebalance critical hit
kphoenix137 Feb 25, 2024
2bbd28d
Rebalance hit recovery
kphoenix137 Feb 25, 2024
64f49c5
Modify crits, spells direct at players
kphoenix137 Feb 25, 2024
479b44d
Adjust damage values for spells, fix lightning damage bug
kphoenix137 Feb 25, 2024
007511e
divide by 6 instead of 5 for stun
kphoenix137 Feb 25, 2024
91276f0
clang
kphoenix137 Feb 25, 2024
5823a54
Adjust diawalk threshold and movement history amount
kphoenix137 Feb 25, 2024
b8d8e76
Make missiles more accurate
kphoenix137 Feb 26, 2024
1db35df
Only change missile behavior in arenas
kphoenix137 Feb 26, 2024
50ea3bf
Adjust fireball/elemental damage, modify bone spirit
kphoenix137 Feb 26, 2024
040d630
Prevent teleporting onto players at new location
kphoenix137 Feb 26, 2024
8010b90
Reduce fireball velocity
kphoenix137 Feb 26, 2024
fc9393a
Revise spell and ranged accuracy
kphoenix137 Feb 27, 2024
505ed36
Reduce fire wall duration, add per spell frame skips
kphoenix137 Feb 27, 2024
c51ef14
Fix Bone Spirit, adjust crit formula
kphoenix137 Feb 28, 2024
ff72441
Fixes
kphoenix137 Feb 29, 2024
d68327a
Spell adjustments
kphoenix137 Feb 29, 2024
f135427
Remove forced hit recovery
kphoenix137 Mar 1, 2024
37c61c9
Fix Elemental
kphoenix137 Mar 1, 2024
0643091
Modify melee crit chance formula
kphoenix137 Mar 23, 2024
e4c9b8d
Adjust warrior attack speed
kphoenix137 Mar 23, 2024
dc63ac5
Modify FB velocity
kphoenix137 Mar 25, 2024
bb026b4
Modify FB velocity
kphoenix137 Mar 25, 2024
4205400
Remove crit on spells, increase fb cast rate
kphoenix137 May 12, 2024
cce6c14
Remove per spell frame skips
kphoenix137 May 12, 2024
7e39bd0
Reinstate 20% damage decrease to fireball
kphoenix137 May 12, 2024
1f9be76
Adjustments
kphoenix137 May 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Source/automap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
21 changes: 12 additions & 9 deletions Source/cursor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
190 changes: 170 additions & 20 deletions Source/missiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,23 @@ Monster *FindClosest(Point source, int rad)
return nullptr;
}

Player *FindClosestPlayer(Point source, int rad)
{
std::optional<Point> 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<Direction16> ret = (2 * static_cast<std::underlying_type_t<Direction16>>(pivot) + 16 - static_cast<std::underlying_type_t<Direction16>>(x)) % 16;
Expand Down Expand Up @@ -280,14 +297,18 @@ 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)
return false;

*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) {
Expand Down Expand Up @@ -350,18 +371,78 @@ bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, i

int dam;
if (mtype == MissileID::BoneSpirit) {
dam = 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)
dam += player._pIBonusDamMod + player._pDamageMod + dam * player._pIBonusDam / 100;
if (!shift)
dam <<= 6;
}
if (!missileData.isArrow())
dam /= 2;

if (!missileData.isArrow()) {
// PVP REBALANCE: Adjust damage values for spells in arena.
if (player.isOnArenaLevel()) {
switch (mtype) {
case MissileID::BloodStar: // 400% (800% of default)
dam *= 4;
break;
case MissileID::ChargedBolt: // 100% (200% of default)
break;
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::Firebolt: // 100% (200% of default)
break;
case MissileID::FlameWave: // 200% (400% of default)
dam *= 2;
break;
case MissileID::FlashBottom: // 50% (100% default)
case MissileID::FlashTop: // 50% (100% of default)
dam /= 2;
break;
case MissileID::Guardian: // 100% (200% of default)
break;
case MissileID::Inferno: // 500% (1000% of default)
dam *= 5;
break;
case MissileID::Lightning: // 200% (400% of default)
dam *= 2;
break;
case MissileID::NovaBall: // 100% (200% of default)
break;
}
} else {
dam /= 2;
}
}

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<int>(GetMaximumCharacterLevel()));

bool isSpell = !missileData.isArrow();
//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.
//if (isOnArena && critper < crit && mtype != MissileID::BoneSpirit) {
// dam = isSpell ? dam * 5 / 4 : dam * 3 / 2; // Arrow: +50% damage, Spell: +25% damage
//}

if (resper > 0) {
dam -= (dam * resper) / 100;
// 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);
Expand Down Expand Up @@ -975,6 +1056,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;
Expand Down Expand Up @@ -1079,6 +1163,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) {
Expand All @@ -1091,6 +1183,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);
}
Expand Down Expand Up @@ -1880,6 +1973,10 @@ void AddFireWall(Missile &missile, AddMissileParameter &parameter)
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;
Expand All @@ -1895,9 +1992,17 @@ void AddFireball(Missile &missile, AddMissileParameter &parameter)
if (missile.position.start == dst) {
dst += parameter.midir;
}

int sp = 16;
// PVP REBALANCE: Reduce fireball velocity by 4 in arenas.
int cap = 34;

if (missile.sourcePlayer() != nullptr) {
if (missile.sourcePlayer()->isOnArenaLevel())
cap = 28;
}
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;
Expand Down Expand Up @@ -3299,8 +3404,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];
Expand Down Expand Up @@ -3528,13 +3634,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<int>(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;
});
Expand Down Expand Up @@ -4029,11 +4140,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<void()> targetAction;
if (missile.sourcePlayer()->isOnArenaLevel()) {
auto *nextPlayer = FindClosestPlayer(missilePosition, 19);
if (nextPlayer != nullptr && nextPlayer != missile.sourcePlayer()) {
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);
Expand Down Expand Up @@ -4074,17 +4204,37 @@ 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<void()> targetAction;

if (missile.sourcePlayer()->isOnArenaLevel()) {
auto *player = FindClosestPlayer(c, 19);
if (player != nullptr && player != missile.sourcePlayer()) {
targetAction = [&]() {
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;
Expand Down
Loading