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

@ -227,7 +227,20 @@ public sealed class SurvivalConfig
// ---- death / revive ----
[JsonPropertyName("ReviveOnWaveClear")] public bool ReviveOnWaveClear { get; set; } = true; // false = hardcore "you die, you spectate the rest of the run"
[JsonPropertyName("ReviveHpPercent")] public double ReviveHpPercent { get; set; } = 0.5;
[JsonPropertyName("ReviveHpPercent")] public double ReviveHpPercent { get; set; } = 0.5; // HP fraction on the automatic wave-clear revive (NOT the Field Medic revive below)
// ---- Field Medic teammate-revive (survival card CardKeys.FieldMedic; card-only — no card, no revive) ----
// A downed teammate drops a world beacon at their death spot; a medic who stands within ReviveRadius channels a revive
// (no keypress — proximity IS the input). All knobs scale with the medic's card LEVEL (picks): more charges/wave, a
// faster channel, and a higher revive HP%. Charges refresh each wave. Live-tunable.
[JsonPropertyName("ReviveRadius")] public double ReviveRadius { get; set; } = 90.0; // units the medic must be within to channel (≈ standing next to the body)
[JsonPropertyName("ReviveChannelSeconds")] public double ReviveChannelSeconds { get; set; } = 4.0; // channel time at medic level 1
[JsonPropertyName("ReviveChannelPerLevel")] public double ReviveChannelPerLevel { get; set; } = 0.7; // seconds shaved per extra medic level
[JsonPropertyName("ReviveChannelMin")] public double ReviveChannelMin { get; set; } = 1.5; // floor on channel time (however many levels)
[JsonPropertyName("ReviveHpBase")] public double ReviveHpBase { get; set; } = 0.35; // revive HP fraction at medic level 1
[JsonPropertyName("ReviveHpPerLevel")] public double ReviveHpPerLevel { get; set; } = 0.1; // +HP fraction per extra medic level
[JsonPropertyName("ReviveChargesPerLevel")] public int ReviveChargesPerLevel { get; set; } = 1; // revives per wave = medic level × this (L1=1, L2=2, L3=3)
[JsonPropertyName("ReviveXp")] public double ReviveXp { get; set; } = 150.0; // FLAT XP to the reviver per revive (granted straight to the main table, NOT wave-multiplied; only for a combat death, once per saved teammate/wave)
// ---- XP economy (granted PER WAVE at each wave clear, NOT once at run-end). Each cleared wave grants:
// rawWaveXp (HP-damage + HS/crit, handicap-mult EXCLUDED) x prestige x waveMult(wave).
@ -256,18 +269,22 @@ public sealed class SurvivalConfig
[JsonPropertyName("MaxNerfWave")] public int MaxNerfWave { get; set; } = 25;
// Full per-mode handicap override (all bands tunable). Seeded with a higher MTakeCeiling than base (10x) because
// cards + permanent stats stack — a maxed-HP/lifesteal/regen/thorns build can be unkillable at 8x. Tune live.
// In survival the WAVE FLOOR is the difficulty driver, so the inherited factors are heavily down-weighted —
// otherwise a high-level player is pre-nerfed to near-max-take on wave 1 (level/KD maxing the factor before the
// floor even ramps). Low weights keep a mild skill component; the floor (-> max nerf at MaxNerfWave) does the work.
// Survival's handicap is driven SOLELY by wave progression (the monotonic HandicapFloor -> max nerf at MaxNerfWave),
// never by in-run performance: all four skill factors (K/D, headshot rate, streak, level) are weighted 0, so a wave's
// handicap is CONSTANT no matter how many bots you kill or how few times you die, and only steps up between waves.
// This is deliberate — a nonzero weight reintroduces within-wave escalation: K/D and headshot-rate both saturate to
// their max factor after ~5-7 kills-without-death, which (x MasterDifficulty) drove a good player straight to the
// floor mid-wave-1. With every weight 0, ComputeT can only return the wave floor, so MasterDifficulty is inert here;
// the wave ramp is shaped by MaxNerfWave (where the floor hits max nerf), Curve (eases it), and the bands below.
[JsonPropertyName("Handicap")]
public HandicapOverride? Handicap { get; set; } = new()
{
ProgressWeight = 0.0,
MTakeCeiling = 10.0,
KdWeight = 0.3,
HsWeight = 0.3,
StreakWeight = 0.3,
LevelWeight = 0.1,
KdWeight = 0.0, // in-run performance must NOT move the handicap — the wave floor is the only driver
HsWeight = 0.0,
StreakWeight = 0.0,
LevelWeight = 0.0,
MDealFloor = 0.1, // pin: the base dropped to 0.033 for TDM, but waves need a viable deal to be clearable
};
@ -298,6 +315,7 @@ public sealed class SurvivalConfig
// Leveled (Cap 3): per-player, scale per pick.
new("hs_reduction", "Headshot Armor", 15, 3, detail: "-{0:0}% headshot dmg"), // armor doesn't help vs HS; this does
new("berserk_passive", "Berserker", 40, 3, detail: "+{0:0}% dmg near death"), // scaled by missing HP (always-on, no streak gate)
new("field_medic", "Field Medic", 1, 3), // proximity teammate-revive: walk up to a downed ally's beacon to channel a revive. Level scales charges/wave + channel speed + revive HP% (detail computed in code from the Revive* knobs)
// Leveled (Cap 3) but TEAM-WIDE: one shared squad level (max 3), compounding, applied to EVERY survivor via MDeal/MTake.
new("global_deal", "Team: +Damage", 10, 3, isTeam: true), // +10%/level ALL outgoing dmg, compounding -> x1.331 squad-wide maxed
new("global_take", "Team: -Damage Taken", 10, 3, isTeam: true),// -10%/level ALL incoming dmg, compounding -> x0.729 squad-wide maxed