Survival mode bugfixes and revive mechanism
All checks were successful
CI / lint (push) Successful in 31s
CI / build (push) Successful in 32s
CI / release (push) Successful in 32s

This commit is contained in:
Kamal Tufekcic 2026-07-08 18:10:09 +03:00
commit 2fa85e8b1f
14 changed files with 529 additions and 35 deletions

View file

@ -11,6 +11,7 @@ public static class CardKeys
public const string XpMult = "xp_mult"; // +% run-XP earned
public const string HsReduction = "hs_reduction"; // -% incoming headshot damage (per-player, leveled)
public const string BerserkPassive = "berserk_passive"; // +dmg scaled by missing HP (per-player, leveled)
public const string FieldMedic = "field_medic"; // unlock + scale the proximity teammate-revive (charges/wave, faster channel, more revive HP per level)
public const string GlobalDeal = "global_deal"; // TEAM: +dmg dealt, squad-wide, compounding (into MDeal)
public const string GlobalTake = "global_take"; // TEAM: -dmg taken, squad-wide, compounding (into MTake)
}

View file

@ -45,4 +45,30 @@ public static class SurvivalEconomy
public static double TeamMult(int level, double perPickPct, bool increase) =>
increase ? Math.Pow(1.0 + perPickPct / 100.0, level)
: Math.Pow(Math.Max(0.0, 1.0 - perPickPct / 100.0), level);
// ---- Field Medic teammate-revive scaling (all keyed on the medic's card LEVEL = FieldMedic picks; 0 = not a medic) ----
// Kept pure here so the numbers are testable and identical to what the engine-side channel machine (Survival.cs) reads.
public static double ReviveChannelSeconds(int medicLevel, SurvivalConfig c) =>
Math.Max(c.ReviveChannelMin, c.ReviveChannelSeconds - Math.Max(0, medicLevel - 1) * c.ReviveChannelPerLevel);
public static double ReviveHpFraction(int medicLevel, SurvivalConfig c) =>
Math.Clamp(c.ReviveHpBase + Math.Max(0, medicLevel - 1) * c.ReviveHpPerLevel, 0.05, 1.0);
public static int ReviveChargesForWave(int medicLevel, SurvivalConfig c) =>
medicLevel <= 0 ? 0 : medicLevel * Math.Max(0, c.ReviveChargesPerLevel);
// ---- HUD locator math (pure so the arrow direction + distance readout are golden-tested, not hidden behind a pawn) ----
// Wrap a degree delta into (-180, 180].
public static double NormalizeDeg(double d)
{
d %= 360.0;
if (d > 180) return d - 360;
if (d <= -180) return d + 360;
return d;
}
// Map a RELATIVE bearing (target bearing - view yaw, degrees) to one of 8 compass sectors: 0 = straight ahead, then
// counter-clockwise (positive = to the left). Indexes the caller's arrow table `{↑ ↖ ← ↙ ↓ ↘ → ↗}`.
public static int ArrowIndex(double relDeg) => ((int)Math.Round(NormalizeDeg(relDeg) / 45.0) % 8 + 8) % 8;
// The HUD "distance to the downed teammate" readout: game units -> whole metres, floored at 1 (a divisor <= 0 is guarded).
public static int LocatorMeters(double dist, double divisor) => Math.Max(1, (int)Math.Round(dist / Math.Max(1e-9, divisor)));
}