diff --git a/src/clan_arena.c b/src/clan_arena.c index ce7cccb9..f94b67fe 100644 --- a/src/clan_arena.c +++ b/src/clan_arena.c @@ -4,6 +4,21 @@ #include "g_local.h" +typedef struct ca_player_stats_t +{ + float dmg; // damage given + float dmgt; // damage taken + float frags; // frags + float kil; // kills + float dths; // deaths + float gl_h; // gl hits + float rl_h; // rl hits + float rl_d; // rl directs + float lg_h; // lg hits + float lg_a; // lg attacks + float lg_e; // lg efficiency +} ca_player_stats_t; + typedef struct wipeout_spawn_config_t { vec3_t origin; // spawn point coordinates @@ -24,13 +39,14 @@ typedef struct wipeout_map_spawns_t // { {coords}, angle, name, radius } static wipeout_spawn_config dm3_spawns[] = { { { -880, -232, -16 }, 90, "tele/sng", 400 }, - { { 192, -208, -176 }, 90, "big>ra", 300 }, + { { 192, -208, -176 }, 90, "ra/mega", 300 }, { { 1472, -928, -24 }, 90, "ya box", 200 }, { { 1520, 432, -88 }, 0, "rl", 400 }, { { -632, -680, -16 }, 90, "tele/ra", 400 }, { { 512, 768, 216 }, -90, "lifts", 300 }, { { -387, 233, -16 }, 0, "sng", 200 }, - { { 2021, -446, -24 }, 180, "bridge", 400 } + { { 1959, -449, -24 }, 180, "bridge", 1000 }, + { { 1846, 690, -180 }, 180, "pent", 2000 } }; static wipeout_map_spawns wipeout_spawn_configs[] = { @@ -48,6 +64,15 @@ static int loser_team; static qbool do_endround_stuff = false; static qbool print_stats = false; static float loser_respawn_time = 999; // number of seconds before a teammate would've respawned +static float best_dmgt; +static float best_dmg; +static float best_score; +static float best_kills; +static float best_deaths; +static float best_gl_hits; +static float best_rl_hits; +static float best_rl_directs; +static float best_lg_eff; void track_player(gedict_t *observer); void enable_player_tracking(gedict_t *e, int follow); @@ -280,6 +305,19 @@ void SM_PrepareCA(void) p->ca_ready = p->ready; p->seconds_to_respawn = 0; p->teamcolor = NULL; + + // must reset stats before new game starts + p->ca_round_frags = 0; + p->ca_round_kills = 0; + p->ca_round_dmg = 0; + p->ca_round_dmgt = 0; + p->ca_round_deaths = 0; + p->ca_round_glhit = 0; + p->ca_round_glfired = 0; + p->ca_round_rlhit = 0; + p->ca_round_rldirect = 0; + p->ca_round_lghit = 0; + p->ca_round_lgfired = 0; } } } @@ -740,11 +778,6 @@ void CA_SendTeamInfo(gedict_t *t) break; } - if (t->ct == ctSpec && t->trackent && (t->trackent == NUM_FOR_EDICT(p))) - { - continue; // if we're spectating the player, don't send info about him - } - if (p->ca_ready || match_in_progress != 2) // be sure to send info if in prewar { if (match_in_progress == 2) @@ -1086,10 +1119,73 @@ void team_round_summary(int alive_team) !alive_team ? "tied round" : (alive_team == 2 ? "round winner" : "")); } +static void CA_GetPlayerStats(gedict_t *p, qbool series_over, ca_player_stats_t *stats) +{ + qbool use_totals = (round_num == 1 || series_over); + + if (use_totals) + { + stats->frags = p->s.v.frags; + stats->dmg = p->ps.dmg_g; + stats->dmgt = p->ps.dmg_t; + stats->kil = stats->frags - ((int)(stats->dmg / 100.0)); + stats->dths = p->deaths; + stats->gl_h = p->ps.wpn[wpGL].vhits; + stats->rl_h = p->ps.wpn[wpRL].vhits; + stats->rl_d = p->ps.wpn[wpRL].hits; + stats->lg_h = p->ps.wpn[wpLG].hits; + stats->lg_a = p->ps.wpn[wpLG].attacks; + stats->lg_e = 100.0 * stats->lg_h / max(1, stats->lg_a); + } + else + { + stats->frags = p->s.v.frags - p->ca_round_frags; + stats->dmg = p->ps.dmg_g - p->ca_round_dmg; + stats->dmgt = p->ps.dmg_t - p->ca_round_dmgt; + stats->kil = stats->frags - ((int)(stats->dmg / 100.0)); + stats->dths = p->deaths - p->ca_round_deaths; + stats->gl_h = p->ps.wpn[wpGL].vhits - p->ca_round_glhit; + stats->rl_h = p->ps.wpn[wpRL].vhits - p->ca_round_rlhit; + stats->rl_d = p->ps.wpn[wpRL].hits - p->ca_round_rldirect; + stats->lg_h = p->ps.wpn[wpLG].hits - p->ca_round_lghit; + stats->lg_a = p->ps.wpn[wpLG].attacks - p->ca_round_lgfired; + stats->lg_e = 100.0 * stats->lg_h / max(1, stats->lg_a); + } +} + +static void CA_UpdateBestStats(gedict_t *p, qbool series_over) +{ + ca_player_stats_t stats; + + CA_GetPlayerStats(p, series_over, &stats); + + best_score = stats.frags > best_score ? stats.frags : best_score; + best_dmg = stats.dmg > best_dmg ? stats.dmg : best_dmg; + best_dmgt = stats.dmgt < best_dmgt ? stats.dmgt : best_dmgt; + best_kills = stats.kil > best_kills ? stats.kil : best_kills; + best_deaths = stats.dths < best_deaths ? stats.dths : best_deaths; + best_gl_hits = stats.gl_h > best_gl_hits ? stats.gl_h : best_gl_hits; + best_rl_hits = stats.rl_h > best_rl_hits ? stats.rl_h : best_rl_hits; + best_rl_directs = stats.rl_d > best_rl_directs ? stats.rl_d : best_rl_directs; + best_lg_eff = stats.lg_e > best_lg_eff ? stats.lg_e : best_lg_eff; +} + void print_player_stats(qbool series_over) { gedict_t *p; + // reset top stats before looping players + // some values set to 1 to avoid displaying 0s as top stats + best_dmgt = 999999.0f; + best_dmg = 0; + best_score = 0; + best_kills = 0; + best_deaths = 999999.0f; + best_gl_hits = 1; + best_rl_hits = 1; + best_rl_directs = 1; + best_lg_eff = 1; + G_bprint(2, "\nsco damg took k d gl rh rd lg%% player\n%s\n", redtext("--- ----- ---- -- -- --- --- --- ---- --------")); @@ -1099,6 +1195,16 @@ void print_player_stats(qbool series_over) if (p->ready && (streq(getteam(p), cvar_string(va("_k_team1"))) || streq(getteam(p), cvar_string(va("_k_team2"))) )) + { + CA_UpdateBestStats(p, series_over); + } + } + + for (p = world; (p = find_plr(p));) + { + if (p->ready && + (streq(getteam(p), cvar_string(va("_k_team1"))) || + streq(getteam(p), cvar_string(va("_k_team2"))) )) { CA_OnePlayerStats(p, series_over); } @@ -1109,54 +1215,39 @@ void print_player_stats(qbool series_over) void CA_OnePlayerStats(gedict_t *p, qbool series_over) { - qbool use_totals = (round_num == 1 || series_over); - float frags; - float rkills; - float dmg_g; - float dmg_t; - float vh_rl; - float h_rl; - float vh_gl; - float h_lg; - float a_lg; - float e_lg; - float round_elg; - char score[10]; - char damage[10]; - char dmg_took[10]; - char kills[10]; - char deaths[10]; - char gl_hits[10]; - char rl_hits[10]; - char rl_directs[10]; - char lg_eff[10]; - - frags = p->s.v.frags; - dmg_g = p->ps.dmg_g; - dmg_t = p->ps.dmg_t; - - rkills = frags - ((int)(dmg_g/100.0)); - h_rl = p->ps.wpn[wpRL].hits; - vh_rl = p->ps.wpn[wpRL].vhits; - vh_gl = p->ps.wpn[wpGL].vhits; - h_lg = p->ps.wpn[wpLG].hits; - a_lg = p->ps.wpn[wpLG].attacks; - e_lg = 100.0 * h_lg / max(1, a_lg); - - if (!use_totals) - { - round_elg = 100 * (h_lg - p->ca_round_lghit) / max(1, a_lg - p->ca_round_lgfired); - } - - snprintf(score, sizeof(score), "%.0f", use_totals ? p->s.v.frags : p->s.v.frags - p->ca_round_frags); - snprintf(damage, sizeof(damage), "%.0f", use_totals ? dmg_g : dmg_g - p->ca_round_dmg); - snprintf(dmg_took, sizeof(dmg_took), "%.0f", use_totals ? dmg_t : dmg_t - p->ca_round_dmgt); - snprintf(kills, sizeof(kills), "%.0f", use_totals ? rkills : rkills - p->ca_round_kills); - snprintf(deaths, sizeof(deaths), "%.0f", use_totals ? p->deaths : p->deaths - p->ca_round_deaths); - snprintf(gl_hits, sizeof(gl_hits), "%.0f", use_totals ? vh_gl : vh_gl - p->ca_round_glhit); - snprintf(rl_hits, sizeof(rl_hits), "%.0f", use_totals ? vh_rl : vh_rl - p->ca_round_rlhit); - snprintf(rl_directs, sizeof(rl_directs), "%.0f", use_totals ? h_rl : h_rl - p->ca_round_rldirect); - snprintf(lg_eff, sizeof(lg_eff), "%.0f", use_totals ? e_lg : round_elg); + ca_player_stats_t stats; + char score[20]; + char damage[20]; + char dmg_took[20]; + char kills[20]; + char deaths[20]; + char gl_hits[20]; + char rl_hits[20]; + char rl_directs[20]; + char lg_eff[20]; + + CA_GetPlayerStats(p, series_over, &stats); + + // debug RL directs (only meaningful for per-round deltas) + // if (!series_over && (stats.rl_d > 10 || stats.rl_d < 0 || p->ps.wpn[wpRL].hits < p->ca_round_rldirect)) + // { + // G_bprint(2, "CA stats anomaly: %s round=%d rl_hits=%d rl_base=%.0f rl_delta=%.0f\n", + // getname(p), round_num, p->ps.wpn[wpRL].hits, p->ca_round_rldirect, stats.rl_d); + // } + + snprintf(score, sizeof(score), "%s", stats.frags == best_score ? dig3(Q_rint(stats.frags)) : dig1s("%.0f", stats.frags)); + snprintf(damage, sizeof(damage), "%s", stats.dmg == best_dmg ? dig3(Q_rint(stats.dmg)) : dig1s("%.0f", stats.dmg)); + snprintf(dmg_took, sizeof(dmg_took), "%s", stats.dmgt == best_dmgt ? dig3(Q_rint(stats.dmgt)) : dig1s("%.0f", stats.dmgt)); + snprintf(kills, sizeof(kills), "%s", stats.kil == best_kills ? dig3(Q_rint(stats.kil)) : dig1s("%.0f", stats.kil)); + snprintf(deaths, sizeof(deaths), "%s", stats.dths == best_deaths ? dig3(Q_rint(stats.dths)) : dig1s("%.0f", stats.dths)); + snprintf(gl_hits, sizeof(gl_hits), "%s", stats.gl_h == best_gl_hits ? dig3(Q_rint(stats.gl_h)) : dig1s("%.0f", stats.gl_h)); + snprintf(rl_hits, sizeof(rl_hits), "%s", stats.rl_h == best_rl_hits ? dig3(Q_rint(stats.rl_h)) : dig1s("%.0f", stats.rl_h)); + snprintf(rl_directs, sizeof(rl_directs),"%s", stats.rl_d == best_rl_directs ? dig3(Q_rint(stats.rl_d)) : dig1s("%.0f", stats.rl_d)); + snprintf(lg_eff, sizeof(lg_eff), "%s", stats.lg_e == best_lg_eff ? dig3(Q_rint(stats.lg_e)) : dig1s("%.0f", stats.lg_e)); + + // debug stats row + // G_cprint("CA stats row: %s frags=%.0f dmg=%.0f dmgt=%.0f kills=%.0f deaths=%.0f gl=%.0f rl=%.0f rd=%.0f lg=%.0f\n", + // getname(p), stats.frags, stats.dmg, stats.dmgt, stats.kil, stats.dths, stats.gl_h, stats.rl_h, stats.rl_d, stats.lg_e); G_bprint(2, "%3s %5s %4s %2s %2s %3s %3s %3s %3s%s %s\n", strneq(score, "0") ? score : "-", @@ -1172,15 +1263,30 @@ void CA_OnePlayerStats(gedict_t *p, qbool series_over) getname(p)); p->ca_round_frags = p->s.v.frags; - p->ca_round_kills = rkills; - p->ca_round_dmg = dmg_g; - p->ca_round_dmgt = dmg_t; + p->ca_round_kills = p->s.v.frags - ((int)(p->ps.dmg_g/100.0)); + p->ca_round_dmg = p->ps.dmg_g; + p->ca_round_dmgt = p->ps.dmg_t; p->ca_round_deaths = p->deaths; - p->ca_round_glhit = vh_gl; - p->ca_round_rlhit = vh_rl; - p->ca_round_rldirect = h_rl; - p->ca_round_lghit = h_lg; - p->ca_round_lgfired = a_lg; + p->ca_round_glhit = p->ps.wpn[wpGL].vhits; + p->ca_round_rlhit = p->ps.wpn[wpRL].vhits; + p->ca_round_rldirect = p->ps.wpn[wpRL].hits; + p->ca_round_lghit = p->ps.wpn[wpLG].hits; + p->ca_round_lgfired = p->ps.wpn[wpLG].attacks; +} + +void UnlimitedEndRoundAmmo(void) +{ + gedict_t *p; + int ammo = 9999; + + for (p = world; (p = find_plr(p));) + { + p->s.v.ammo_nails = ammo; + p->s.v.ammo_shells = ammo; + p->s.v.ammo_rockets = ammo; + p->s.v.ammo_cells = ammo; + p->ca_ammo_grenades = ammo; + } } void EndRound(int alive_team) @@ -1198,6 +1304,8 @@ void EndRound(int alive_team) loser_respawn_time = loser_team ? team_last_alive_time(loser_team) : 999; } + UnlimitedEndRoundAmmo(); // Surviving players get unlimited ammo during endround phase + pause_count = Q_rint(pause_time - g_globalvars.time); if (pause_count <= 0) @@ -1319,11 +1427,6 @@ void EndRound(int alive_team) print_player_stats(false); team_round_summary(alive_team); } - - if (pause_count < 4) - { - ra_match_fight = 1; // disable firing - } } } diff --git a/src/client.c b/src/client.c index 2476c1e4..d730c130 100644 --- a/src/client.c +++ b/src/client.c @@ -398,7 +398,7 @@ void SetChangeParms(void) g_globalvars.parm12 = self->k_coach; g_globalvars.parm13 = self->k_stuff; g_globalvars.parm14 = self->ps.handicap; - g_globalvars.parm15 = self->ready; + g_globalvars.parm15 = ((match_in_progress == 2) || cvar("k_matchless")) ? self->ready : 0; } // @@ -4707,6 +4707,8 @@ void PlayerPostThink(void) self->client_predflags = PRDFL_FORCEOFF; else if (!CA_can_fire(self)) self->client_predflags = PRDFL_FORCEOFF; + else if ((cvar("k_clan_arena") == 2) && ca_round_pause && self->in_play) + self->client_predflags = PRDFL_FORCEOFF; else if ((match_in_progress == 1) || !can_prewar(true)) self->client_predflags = PRDFL_FORCEOFF; // disable LG prediction in prewar when underwater to avoid weird shit diff --git a/src/g_spawn.c b/src/g_spawn.c index 349d0af9..bb94a60c 100644 --- a/src/g_spawn.c +++ b/src/g_spawn.c @@ -129,7 +129,11 @@ field_t fields[] = { "noise2", FOFS(noise2), F_LSTRING }, { "noise3", FOFS(noise3), F_LSTRING }, { "noise4", FOFS(noise4), F_LSTRING }, - { "deathtype", FOFS(deathtype), F_LSTRING }, + + // KTX use deathtype as a damage type for earch T_Damage and T_RadiusDamage call. + // Vanila QC code uses it as a custom death message type for doors/plats/etc, but we broke it. + { "deathtype", 0, F_IGNORE }, + { "t_length", FOFS(t_length), F_FLOAT }, { "t_width", FOFS(t_width), F_FLOAT }, // TF diff --git a/src/g_utils.c b/src/g_utils.c index de94b373..8c036831 100644 --- a/src/g_utils.c +++ b/src/g_utils.c @@ -648,8 +648,10 @@ char* dig1(int d) { static char string[MAX_STRINGS][32]; static int index = 0; + + index %= MAX_STRINGS; snprintf(string[index], sizeof(string[0]), "%d", d); - return string[index++ % MAX_STRINGS]; + return string[index++]; } char* dig1s(const char *format, ...) @@ -658,11 +660,12 @@ char* dig1s(const char *format, ...) static int index = 0; va_list argptr; + index %= MAX_STRINGS; va_start(argptr, format); Q_vsnprintf(string[index], sizeof(string[0]), format, argptr); va_end(argptr); - return string[index++ % MAX_STRINGS]; + return string[index++]; } char* dig3(int d) diff --git a/src/match.c b/src/match.c index 4063f9f5..0f74d84a 100644 --- a/src/match.c +++ b/src/match.c @@ -3130,7 +3130,7 @@ void PlayerBreak(void) { p = find(world, FOFCLSN, "timer"); - if (p && p->cnt2 > 1) + if (p && p->cnt2 >= 1) { self->ready = 0; diff --git a/src/weapons.c b/src/weapons.c index f21e6ebe..4ecbf1fe 100644 --- a/src/weapons.c +++ b/src/weapons.c @@ -1839,6 +1839,13 @@ void superspike_touch(void) } else { + // no ricochet or sparks in wipeout endround phase + if (cvar("k_clan_arena") == 2 && ca_round_pause) + { + ent_remove(self); + return; + } + WriteByte( MSG_MULTICAST, SVC_TEMPENTITY); WriteByte( MSG_MULTICAST, TE_SUPERSPIKE); WriteCoord( MSG_MULTICAST, self->s.v.origin[0]); @@ -1853,12 +1860,13 @@ void superspike_touch(void) void W_FireSuperSpikes(void) { vec3_t dir, tmp; + qbool wipe_spike = (cvar("k_clan_arena") == 2) && ca_round_pause && self->in_play; WS_Mark(self, wpSNG); self->ps.wpn[wpSNG].attacks++; - sound(self, CHAN_WEAPON, "weapons/spike2.wav", 1, ATTN_NORM); + sound(self, CHAN_WEAPON, wipe_spike ? "misc/water1.wav" : "weapons/spike2.wav", 1, ATTN_NORM); self->attack_finished = self->client_time + 0.2; if (match_in_progress == 2) @@ -1876,7 +1884,15 @@ void W_FireSuperSpikes(void) tmp[2] += 16; launch_spike(tmp, dir); newmis->touch = (func_t) superspike_touch; - setmodel(newmis, "progs/s_spike.mdl"); + if (wipe_spike) + { + // Wipeout end-round flair for survivor SNG shots. + setmodel(newmis, "progs/s_bubble.spr"); + } + else + { + setmodel(newmis, "progs/s_spike.mdl"); + } setsize(newmis, 0, 0, 0, 0, 0, 0); g_globalvars.msg_entity = EDICT_TO_PROG(self); WriteByte( MSG_ONE, SVC_SMALLKICK);