diff --git a/csqc/csextradefs.qc b/csqc/csextradefs.qc index 6fb4449c..6aff7a82 100644 --- a/csqc/csextradefs.qc +++ b/csqc/csextradefs.qc @@ -328,6 +328,7 @@ typedef struct { float clanmode; float quadmode; float duelmode; + float new_balance; float quad_rounds; float quad_round_time; float fo_login_required; @@ -345,14 +346,6 @@ typedef struct { float class_limit_pyro; float class_limit_spy; float class_limit_engineer; - float spurs_on; - float spurs_scout; - float spurs_spy; - float spurs_engineer; - float spurs_duration; - float spurs_boost; - float spurs_consume; - float spurs_flag; } SERVER_SETTINGS; SERVER_SETTINGS SERVER_ADMIN; @@ -925,3 +918,5 @@ void cvar_parse4(string s, __out veci target) { target.v[2] = stof(argv(2)); target.i = stof(argv(3)); } + +float servertime; diff --git a/csqc/events.qc b/csqc/events.qc index 4e55bcb9..fb1fc752 100644 --- a/csqc/events.qc +++ b/csqc/events.qc @@ -272,6 +272,7 @@ void() CSQC_Parse_Event = { SERVER_ADMIN.clanmode = readfloat(); SERVER_ADMIN.quadmode = readfloat(); SERVER_ADMIN.duelmode = readfloat(); + SERVER_ADMIN.new_balance = readfloat(); SERVER_ADMIN.pubmode = (((SERVER_ADMIN.clanmode & 1) || (SERVER_ADMIN.quadmode & 1) || (SERVER_ADMIN.duelmode & 1))?1:0) + (((SERVER_ADMIN.clanmode & 2) || (SERVER_ADMIN.quadmode & 2) || (SERVER_ADMIN.duelmode & 2))?2:0); SERVER_ADMIN.pubmode = 3 - SERVER_ADMIN.pubmode; //Invert diff --git a/csqc/hud.qc b/csqc/hud.qc index 0ab8b15f..94d154d3 100644 --- a/csqc/hud.qc +++ b/csqc/hud.qc @@ -1001,6 +1001,15 @@ void Hud_DrawClassInfoPanel(float playerclass) } break; case PC_ENGINEER: + if (NewBalanceActive()) { + basepos = pos; + msg = pstate_pred.server_time >= pstate_pred.special_next ? + "Impeller charged" : "Impeller recharging"; + HRC_drawOffsetString(panel->Orientation, msg, size, fontSize, + pos, basepos, FALSE, colour); + + pos = [pos_x, pos_y + size_y + 4]; + } if (SBAR.HasSentry) { icon = ICON_ENGINEER_SG; //HudIcons[playerclass+6].icon; @@ -1049,6 +1058,9 @@ void Hud_DrawIdentifyPanel(string identify) { // click event } + if (time > last_id_time + 3) + return; + vector fontSize = FO_Hud_Icon_Font_Size * panel->Scale; float count = tokenizebyseparator(identify, "\n"); diff --git a/csqc/menu.qc b/csqc/menu.qc index bf23d658..2f746daa 100644 --- a/csqc/menu.qc +++ b/csqc/menu.qc @@ -7,6 +7,7 @@ void (float force) FO_Menu_Admin_Timelimit; void (float force) FO_Menu_Admin_Fraglimit; void (float force) FO_Menu_Admin_QuadTimelimit; void (float force) FO_Menu_Admin_FoMatchRated; +void (float force) FO_Menu_Admin_NewBalance; void (float force) FO_Menu_Spy; void (float force) FO_Menu_Spy_Skin; void (float force) FO_Menu_Spy_Team; @@ -438,10 +439,9 @@ var fo_menu FO_MENU_ADMIN_MODES = { {"2","Clan Mode","","Game has a prematch",FO_MENU_STATE_NORMAL,{localcmd("cmd clanmode\n");},MENU_BORDER_WARNING}, {"3","Quad Mode","","Play for a set number of rounds, designed for attack vs defence",FO_MENU_STATE_NORMAL,{localcmd("cmd quadmode\n");},MENU_BORDER_WARNING}, {"4","Duel Mode","","Simplifies 1 on 1 action",FO_MENU_STATE_NORMAL,{localcmd("cmd duelmode\n");},MENU_BORDER_WARNING}, - {"5","Quad Rounds...","","Number of rounds in Quad mode. Usually 2",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Rounds(TRUE);},MENU_BORDER_WARNING}, - {"6","Quad Round Time...","","Round time for each quad round",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_QuadTimelimit(TRUE);},MENU_BORDER_WARNING}, + {"5","Captains Mode","","Select captains who can then pick their teams",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Players(TRUE, CLIENT_MENU_CAPTAIN_1, 0);},MENU_BORDER_WARNING}, {"7","Rated/Unrated","","Will player ratings be affected?",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_FoMatchRated(TRUE);},MENU_BORDER_WARNING}, - {"8","Captains Mode","","Select captains who can then pick their teams",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Players(TRUE, CLIENT_MENU_CAPTAIN_1, 0);},MENU_BORDER_WARNING}, + {"8","New Balance","","New Balance Test",FO_MENU_STATE_NORMAL,{localcmd("cmd new_balance");},MENU_BORDER_WARNING}, {"9","Force Start","","Skip prematch and start the game\nPlease use sparingly",FO_MENU_STATE_NORMAL,{localcmd("cmd forcestart\n");},MENU_BORDER_WARNING}, {"0","Close Menu","","",FO_MENU_STATE_NORMAL,{Menu_Cancel();},MENU_BUTTON}, {"+","Next - Settings","","",FO_MENU_STATE_NORMAL,{Menu_Cancel(); FO_Menu_Admin_Settings(TRUE);},MENU_BUTTON}, @@ -452,16 +452,17 @@ var fo_menu FO_MENU_ADMIN_MODES = { FO_MENU_ADMIN_MODES.options[1].value = modeStatus(SERVER_ADMIN.clanmode); FO_MENU_ADMIN_MODES.options[2].value = modeStatus(SERVER_ADMIN.quadmode); FO_MENU_ADMIN_MODES.options[3].value = modeStatus(SERVER_ADMIN.duelmode); - FO_MENU_ADMIN_MODES.options[4].value = ftos(SERVER_ADMIN.quad_rounds); - FO_MENU_ADMIN_MODES.options[5].value = ftos(SERVER_ADMIN.quad_round_time); + FO_MENU_ADMIN_MODES.options[4].value = SERVER_ADMIN.captainmode?"on":"off"; FO_MENU_ADMIN_MODES.options[6].value = ratedStatus(SERVER_ADMIN.fo_matchrated); - FO_MENU_ADMIN_MODES.options[7].value = SERVER_ADMIN.captainmode?"on":"off"; + FO_MENU_ADMIN_MODES.options[6].value = modeStatus(SERVER_ADMIN.new_balance); } }; var fo_menu FO_MENU_ADMIN_SETTINGS = { [0,0], [300,300], "Settings [3/3]", FO_MENU_FLAG_USE_MOUSE | FO_MENU_FLAG_CENTER | FO_MENU_FLAG_SHOW_SHORTCUTS | FO_MENU_FLAG_SHOW_VALUES | FO_MENU_FLAG_WARNING | FO_MENU_FLAG_ALLOW_INTERMISSION, { {"1","Timelimit","","",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Timelimit(TRUE);},MENU_BORDER_WARNING}, {"2","Fraglimit","","",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Fraglimit(TRUE);},MENU_BORDER_WARNING}, + {"3","Quad Rounds...","","Number of rounds in Quad mode. Usually 2",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_Rounds(TRUE);},MENU_BORDER_WARNING}, + {"4","Quad Round Time...","","Round time for each quad round",FO_MENU_STATE_NORMAL,{FO_Menu_Admin_QuadTimelimit(TRUE);},MENU_BORDER_WARNING}, MenuSpacer, MenuSpacer, MenuSpacer, @@ -476,6 +477,8 @@ var fo_menu FO_MENU_ADMIN_SETTINGS = { updateAdminMenuInfo(); FO_MENU_ADMIN_SETTINGS.options[0].value = ftos(SERVER_ADMIN.timelimit); FO_MENU_ADMIN_SETTINGS.options[1].value = ftos(SERVER_ADMIN.fraglimit); + FO_MENU_ADMIN_SETTINGS.options[2].value = ftos(SERVER_ADMIN.quad_rounds); + FO_MENU_ADMIN_SETTINGS.options[3].value = ftos(SERVER_ADMIN.quad_round_time); } }; var void execute_admin_players(float choice, float page) { diff --git a/csqc/pmove.qc b/csqc/pmove.qc index fb975262..95db379e 100644 --- a/csqc/pmove.qc +++ b/csqc/pmove.qc @@ -387,6 +387,11 @@ static void PM_NudgeExplosion(PM_Nudge* nudge, entity ent) { if (vlen(ent.origin - nudge->org) > dmg + 40) return; + const float MOVE_WORLDONLY = 3; + traceline(ent.origin, nudge->org, MOVE_WORLDONLY, pm.ent); + if (trace_fraction < 1) // No knock from occluded explosions + return; + float flg = KF_BOTH_PLAYER; // Assumes everything is from a player for now if (nudge->src.owner == pengine.player_ent) flg |= KF_SELF; @@ -465,6 +470,9 @@ static void PM_NudgeBounce(PM_Nudge* nudge, entity ent) { float points = dmg - vlen(targ_org - nudge->org) / 2; ent.velocity = (targ_org - nudge->org) * points/20; + + NB_ConcCapAction(ent, pstate_pred.playerclass, &pstate_pred.tfstate, + pm.interp_t, &pstate_pred.conc_cap_time, kLaunch); } @@ -625,6 +633,12 @@ static void PM_RunMovement(PMS_Data* pmsd, float endframe) { if ((ent.pmove_flags & PMF_JUMP_HELD) && (pstate_pred.tfstate & TFSTATE_CONC)) Conc_Jump(&pstate_pred.conc_state, ent); + + if ((ent.pmove_flags & PMF_JUMP_HELD) && (pstate_pred.tfstate & TFSTATE_CONC_CAP)) { + NB_ConcCapAction(ent, pstate_pred.playerclass, &pstate_pred.tfstate, + pm.interp_t, &pstate_pred.conc_cap_time, + kLand); + } } pm.seq++; @@ -803,6 +817,9 @@ void PM_Update(float sendflags) { float enabled = (pstate_server.predict_flags & PF_PMOVE) && game_state.is_player; + if (isdemo()) + enabled = 0; + if (enabled != was_enabled || game_state.is_player != prev_game_state.is_player) PM_SetEnabled(enabled); diff --git a/csqc/status.qc b/csqc/status.qc index 16752c7e..6999f0d1 100644 --- a/csqc/status.qc +++ b/csqc/status.qc @@ -305,9 +305,9 @@ string gameClockString() { if (!end) end = timelimit * 60; - if (end) { - minutes = (end - time)/60; - seconds = (end - time)%60; + if (end && end > servertime) { + minutes = (end - servertime)/60; + seconds = (end - servertime)%60; } else { minutes = time/60; seconds = time%60; diff --git a/csqc/weapon_predict.qc b/csqc/weapon_predict.qc index 96c549c5..ac0eb1c3 100644 --- a/csqc/weapon_predict.qc +++ b/csqc/weapon_predict.qc @@ -275,6 +275,7 @@ void WPP_Status() { PRINT_CONFIG(max_rewind_fast_projectile_ms); PRINT_CONFIG(max_rewind_grenade_ms); PRINT_CONFIG(rewind_fast_projectile_thresh); + PRINT_CONFIG(new_balance); printf("\n"); PrintFlagField("Rewind Settings [rewind_flags=%d]\n", @@ -286,6 +287,9 @@ void WPP_Status() { PrintFlagField("Clown mode [clown_flags=%d]\n", fo_config.clown_flags, CLOWN_DESC.length, CLOWN_DESC); + PrintFlagField("NewBalance Flags [newbalance_flags=%d]:\n", + fo_config.new_balance_flags, NBF_DESC.length, NBF_DESC); + PrintFlagField("Concs [fo_concuss=%d]\n", fo_config.fo_concuss, FO_CONC.length, FO_CONC); } @@ -1027,11 +1031,10 @@ void WP_Impulse() { } static float* WP_ClipFired(Slot slot) { - float index = SlotIndex(slot); - if (pstate_pred.playerclass == PC_DEMOMAN && index == 1) - index = 0; // Special case: Pipebomb shares with Grenadel launcher. + if (self_class() == PC_DEMOMAN && SlotIndex(slot) == 1) + slot = MakeSlot(1); // Special case: pipebomb shares with grenade launcher. - return &pstate_pred.clip_fired[index]; + return self_clip_fired(slot); } float WP_CurrentClipFired() { @@ -1115,6 +1118,22 @@ float WP_ReloadSlot(Slot slot) { return TRUE; } +void WP_CheckSpecialReady() { + if (!IsEffectFrame()) + return; + + if (pstate_pred.special_next > pstate_pred.server_time || + pstate_pred.special_next < pstate_pred.server_time - input_timelength) + return; + + switch (pstate_pred.playerclass) { + case PC_ENGINEER: + if (NewBalanceActive()) + printf("Impeller ready\n"); + break; + } +} + void WP_CheckReloadFinished() { if (WP_IsReloading() && pstate_pred.reload_finished && pstate_pred.client_time >= pstate_pred.reload_finished) { pstate_pred.tfstate &= ~TFSTATE_RELOADING; @@ -1707,6 +1726,13 @@ void FO_FireAssCanPellet(vector org, vector dir, float proj_speed, int index) { proj.fpp.ammo_index = WP_GetAmmo(AMMO_SHELLS) * 100 + index; } +float NB_ImpellerCoolDown(FO_WeapInfo* wi) { + if (!NewBalanceActive() || wi->weapon != WEAP_IMPELLER) + return FALSE; + + return pstate_pred.server_time < pstate_pred.special_next; +} + void WP_Attack() { if (getstatf(STAT_NOFIRE)) return; @@ -1719,13 +1745,16 @@ void WP_Attack() { if (!WP_CheckAmmo(wi)) return; + if (NB_ImpellerCoolDown(wi)) + return; + // Whether firing occurs here, or is embedded in the frame animation code // (because continuous fire). int in_anim = wi->weapon == WEAP_NAILGUN || wi->weapon == WEAP_SUPER_NAILGUN || wi->weapon == WEAP_ASSAULT_CANNON; - if (!in_anim && !WP_ConsumeAmmo(CurrentSlot())) + if (!wi->fire_in_anim && !WP_ConsumeAmmo(CurrentSlot())) return; // OK. We're ready to pew. @@ -1733,7 +1762,7 @@ void WP_Attack() { // Must be set prior to animation code, which might internally modify. pstate_pred.client_thinkindex = 1; - if (IsEffectFrame() && !in_anim && (time > filter_pproj_time)) { + if (IsEffectFrame() && !wi->fire_in_anim && (time > filter_pproj_time)) { float send_event = FALSE; switch (wi->weapon) { case WEAP_ROCKET_LAUNCHER: @@ -1744,9 +1773,6 @@ void WP_Attack() { PP_CreateProjectile(FPP_INCENDIARY, PM_Org() + v_forward * 8 + '0 0 16'); send_event = TRUE; break; - case WEAP_FLAMETHROWER: - PP_CreateProjectile(FPP_FLAMETHROWER, PM_Org() + v_forward * 16 + '0 0 16'); - break; case WEAP_TRANQ: PP_CreateProjectile(FPP_TRANQ, PM_Org() + v_forward * 8 + '0 0 16'); break; @@ -1754,9 +1780,6 @@ void WP_Attack() { PP_CreateProjectile(FPP_RAILGUN, PM_Org() + '0 0 16'); break; - case WEAP_ASSAULT_CANNON: - break; - case WEAP_GRENADE_LAUNCHER: send_event = TRUE; case WEAP_PIPE_LAUNCHER: @@ -1790,7 +1813,7 @@ void WP_Attack() { } } - if (!in_anim) + if (!wi->fire_in_anim) Attack_Finished(wi->attack_time); // Start the AC state machine when necessary. @@ -1974,7 +1997,8 @@ void WP_UpdateViewModel() { alph = CVARF(fo_reloadalpha); } else { alph = CVARF(r_drawviewmodel); - if (!WP_WeaponReady() && CVARF(fo_nofirealpha) != -1) + float ready = WP_WeaponReady() && !NB_ImpellerCoolDown(wi); + if (!ready && CVARF(fo_nofirealpha) != -1) alph = CVARF(fo_nofirealpha); } diff --git a/share/animate.qc b/share/animate.qc index 466ba598..3dbd8482 100644 --- a/share/animate.qc +++ b/share/animate.qc @@ -211,7 +211,13 @@ static inline float FO_CheckForReload() { return WP_ReloadCurrentIfNeeded(); } void W_FireSpikes(float ox); // Shared by both client and server side. void nail_extra_csqc_ssqc() { - if (!is_attacking() || is_intermission()) { + if (!is_attacking() || is_intermission() || !IsSlotNull(self_queue_slot())) { + player_run(); + return; + } + + FO_WeapInfo* wi = FO_GetWeapInfo(FO_CurrentWeapon()); + if (wi->needs_reload && FO_CheckForReload()) { player_run(); return; } diff --git a/share/classes.qc b/share/classes.qc index b690274b..49441d68 100644 --- a/share/classes.qc +++ b/share/classes.qc @@ -166,3 +166,50 @@ float Class_ScaleMoment(float playerclass, float damage) { return damage; return damage <= 50 ? 0 : damage / 4; } + +enum { + kLaunch, + kLand +}; + +float NB_UseNewConc() { + return fo_config.new_balance_flags & NBF_CONC_NEW_CAP; +} + +float NB_NoCap() { + return fo_config.new_balance_flags & NBF_NO_CAP; +} + + +void NB_ConcCap(entity ent, float speed) { + vector planar_vel = [ent.velocity_x, ent.velocity_y, 0]; + + if (vlen(planar_vel) > speed) { + planar_vel = normalize(planar_vel) * speed; + } + + ent.velocity_x = planar_vel.x; + ent.velocity_y = planar_vel.y; +} + +void NB_ConcCapAction(entity ent, float player_class, float* tfstate, + float gtime, float* cap_time, float action) { + if (!NewBalanceActive() || !NB_UseNewConc()) + return; + + const float kLockout = 0.5; + + if (action == kLaunch) { + if (player_class == PC_MEDIC && !NB_NoCap()) { + *tfstate |= TFSTATE_CONC_CAP; + *cap_time = gtime + kLockout; + } + + NB_ConcCap(ent, NB_CONC_CAP_AIR); + } else if (action == kLand && (*tfstate & TFSTATE_CONC_CAP) && + gtime > *cap_time) { + float cap = *cap_time; + *tfstate &= ~TFSTATE_CONC_CAP; + NB_ConcCap(ent, NB_CONC_CAP_LAND); + } +} diff --git a/share/commondefs.qc b/share/commondefs.qc index a6bc12d4..ea918842 100644 --- a/share/commondefs.qc +++ b/share/commondefs.qc @@ -24,7 +24,6 @@ struct ConcState { enumflags { FPF_NO_REWIND, - FPF_FIXED_DYNAMIC, }; var struct { @@ -53,6 +52,9 @@ var struct { float fo_concuss; float rj; + + float new_balance; + float new_balance_flags; } fo_config; enumflags { @@ -61,6 +63,7 @@ enumflags { REWIND_GRENADES, REWIND_SENDEVENT, REWIND_FORWARD_PROJ_SELFKNOCK, + REWIND_DOUBLE_COL, REWIND_FORWARD_DOORS, }; @@ -70,13 +73,15 @@ string REWIND_DESC[] = { "rewind grenade throws", "use sendevent augmentation (allows race w/ death rewind)", "forward projectile self-knockback", + "additional collision checks", "open doors earlier for hpbs [requires rfd=1]", }; const float REWIND_DEFAULT_FLAGS = REWIND_PROJ_FIRE | REWIND_PROJ_TRAVEL | REWIND_FORWARD_PROJ_SELFKNOCK | - REWIND_SENDEVENT; + REWIND_SENDEVENT | + REWIND_DOUBLE_COL; float RewindFlagEnabled(float flag) { @@ -168,7 +173,6 @@ const float SERVER_FRAME_DT = 1/SERVER_FPS; const float SERVER_FRAME_MS = SERVER_FRAME_DT * 1000.0; .vector s_origin; -.float s_time; #define MAX_FLAGINFO_LINES 10 #define ENG_BUILDING_DISMANTLE_DISTANCE 100 @@ -289,3 +293,25 @@ enumflags { CSQC_FORCE_POS, CSQC_SNIPER_SIGHT, }; + +#ifdef SSQC +float new_balance; +#endif + +float NewBalanceActive() { +#ifdef CSQC + return fo_config.new_balance; +#else + return new_balance & 1; +#endif +} + +enumflags { + NBF_CONC_NEW_CAP, + NBF_NO_CAP, +}; + +string NBF_DESC[] = { + "Reworked conc speed cap", + "No conc speed cap", +}; diff --git a/share/defs.h b/share/defs.h index 01baa576..414687d7 100644 --- a/share/defs.h +++ b/share/defs.h @@ -249,6 +249,7 @@ enumflags { TFSTATE_AIMING, // is using the laser sight or spinning TFSTATE_CANT_MOVE, TFSTATE_CONC, + TFSTATE_CONC_CAP, // Speed-cap next jump TFSTATE_NO_WEAPON, // Weapon is disabled and not visible (e.g. detpack) // (Note: We don't use NO_WEAPON for reloading // as it could result in stacked no-weapon states.) @@ -258,21 +259,25 @@ enumflags { TFSTATE_AC_SPINDOWN, TFSTATE_LOCK, // assault cannon locked TFSTATE_INFECTED, // set when player is infected by the bioweapon - TFSTATE_INVINCIBLE, // Player has permanent Invincibility (Usually by GoalItem) - TFSTATE_INVISIBLE, // Player has permanent Invisibility (Usually by GoalItem) - TFSTATE_QUAD, // Player has permanent Quad Damage (Usually by GoalItem) - TFSTATE_RADSUIT, // Player has permanent Radsuit (Usually by GoalItem) TFSTATE_BURNING, // Is on fire TFSTATE_FEIGNED, // Is feigned TFSTATE_AIMING, // is using the laser sight or spinning cannon TFSTATE_RESPAWN_READY, // is waiting for respawn, and has pressed fire, // as sentry gun,indicate it needs to die TFSTATE_HALLUCINATING, // set when player is hallucinating - TFSTATE_TRANQUILISED, // set when player is tranquilised + TFSTATE_FLAMES_MAX, // Peak burnination. + TFSTATE_TRANQUILISED, // set when player is tranquilised TFSTATE_RANDOMPC, + // QC/Compiler limitation: Bits past-24 unsafe with bitops }; +enumflags { + PSTATE_INVINCIBLE, // Player has permanent Invincibility (Usually by GoalItem) + PSTATE_INVISIBLE, // Player has permanent Invisibility (Usually by GoalItem) + PSTATE_QUAD, // Player has permanent Quad Damage (Usually by GoalItem) + PSTATE_RADSUIT, // Player has permanent Radsuit (Usually by GoalItem) +}; #define TFSTATE_GREN_MASK_PRIMED (TFSTATE_GREN1_PRIMED|TFSTATE_GREN2_PRIMED) #define TFSTATE_GREN_MASK_ALL (TFSTATE_GREN_MASK_PRIMED|TFSTATE_GRENTHROWING) @@ -733,8 +738,8 @@ struct Slot { int id; }; /*======================================================*/ #define WEAP_NONE 0 -enumflags { - WEAP_HOOK, +enum { + WEAP_HOOK = 1, WEAP_KNIFE, WEAP_MEDIKIT, WEAP_SPANNER, @@ -755,7 +760,8 @@ enumflags { WEAP_DETPACK, WEAP_TRANQ, WEAP_RAILGUN, - WEAP_LAST = WEAP_RAILGUN, + WEAP_IMPELLER, + WEAP_LAST = WEAP_IMPELLER, }; // still room for 12 more weapons @@ -1062,7 +1068,6 @@ enumflags { #define PC_PYRO_GRENADE_MAX_2 4 #define PC_PYRO_TF_ITEMS 0 #define PC_PYRO_AIRBLAST_RANGE 400 -#define PC_PYRO_AIRBLAST_COOLDOWN 5 #define PC_PYRO_AIRBLAST_CELLS 55 #define PC_PYRO_AIRBLASTJUMP_CELLS 75 #define PC_PYRO_LAVA_LIFETIME 3 @@ -1126,7 +1131,6 @@ enumflags { #define PC_ENGINEER_GRENADE_MAX_1 4 #define PC_ENGINEER_GRENADE_MAX_2 4 #define PC_ENGINEER_TF_ITEMS 0 -#define PC_ENGINEER_GRENADE_TYPE_2_RANGE 240 #define PC_ENGINEER_RAILSPEED 1500 // Class Details for CIVILIAN @@ -1308,6 +1312,7 @@ enumflags { #define DMSG_GREN_SHOCK 42 #define DMSG_GREN_BURST 43 #define DMSG_KNIFE 44 +#define DMSG_IMPELLER 45 /*======================================================*/ /* Menus */ @@ -1665,3 +1670,6 @@ TFAlias csqc_aliases[] = { {"+dropflag", 0, "+button7"}, {"-dropflag", 0, "-button7"}, }; + +#define NB_CONC_CAP_AIR 1100 +#define NB_CONC_CAP_LAND 950 diff --git a/share/physics.qc b/share/physics.qc index 35584b0a..8a3e6bb2 100644 --- a/share/physics.qc +++ b/share/physics.qc @@ -27,6 +27,7 @@ void AugmentGrenadeImpact(); static void Phys_Impact(entity e, float dt, float phys_flags) { other = trace_ent; + if (other.solid == SOLID_NOT) return; diff --git a/share/prediction.qc b/share/prediction.qc index 495bc7b8..0f1c0651 100644 --- a/share/prediction.qc +++ b/share/prediction.qc @@ -67,6 +67,7 @@ struct predict_tf_state { float conc_amp; float conc_finished; + float conc_cap_time; float special_next; float client_time, server_time; @@ -314,14 +315,23 @@ float FO_MaxRewindGrenWinDt() { .predict_tf_state predict_state; .entity predict_entity; +.float conc_cap_time; inline float *self_tf_state() { return &self.tfstate; } +float self_class() { return self.playerclass; } +Slot self_current_slot() { return self.current_slot; } +Slot self_queue_slot() { return self.queue_slot; } +float* self_clip_fired(Slot slot) { return &self.clip_fired[SlotIndex(slot)]; } #else predict_tf_state pstate_pred, pstate_server; inline float *self_tf_state() { return &pstate_pred.tfstate; } +float self_class() { return pstate_pred.playerclass; } +Slot self_current_slot() { return pstate_pred.current_slot; } +Slot self_queue_slot() { return pstate_pred.queue_slot; } +float* self_clip_fired(Slot slot) { return &pstate_pred.clip_fired[SlotIndex(slot)]; } #define MASK_PRED_ENT 256 #define MASK_PRED_PROJECTILE 512 @@ -367,7 +377,7 @@ static float Prediction_ChangedMask(entity player, entity src) { M2(FOWP_TFSTATE, tfstate, csqc_maxspeed); M1(FOWP_LASTPRIME, last_prime); M2(FOWP_CLASSIC_CONC, conc_state.next, conc_state.mag); - M2(FOWP_CONCFINISH, conc_finished, conc_amp); + M3(FOWP_CONCFINISH, conc_finished, conc_amp, conc_cap_time); M1(FOWP_WF, weaponframe); M1(FOWP_AF, attack_finished); M2(FOWP_THINK, client_nextthink, client_thinkindex); @@ -451,6 +461,7 @@ void InitProjectileEnt(float sendflags); void WPP_UpdateEnable(float force); .float owner_entnum; +.float s_time; #endif #ifdef SSQC @@ -479,6 +490,8 @@ void EntUpdate_Config() { COMM(Byte, gren_beta_disable); COMM(Byte, old_ng_rof); COMM(Short, fo_concuss); + COMM(Byte, new_balance); + COMM(Byte, new_balance_flags); #ifdef SSQC return TRUE; @@ -564,6 +577,7 @@ void EntUpdate_WeaponPred(float isnew) { if (sendflags & FOWP_CONCFINISH) { COMM(Byte, conc_amp); COMM(Float, conc_finished); + COMM(Float, conc_cap_time); } if (sendflags & FOWP_THINK) { @@ -724,6 +738,8 @@ void Predict_InitDefaultConfig() { fo_config.clown_flags = 0; fo_config.clown_grav = 400; fo_config.fo_concuss = 0; + fo_config.new_balance = 0; + fo_config.new_balance_flags = 0; } #ifdef SSQC @@ -790,6 +806,11 @@ static void WeaponPred_CheckConfigUpdate() { update = TRUE; } + if ((new_balance & 1) != fo_config.new_balance) { + fo_config.new_balance = new_balance & 1; + update = TRUE; + } + // Not dynamically updatable. static float read_rof_once = FALSE; if (!read_rof_once) { @@ -1153,3 +1174,4 @@ void AugmentGrenadeImpact() { self.SendFlags |= FOPP_MOVETYPE; #endif } + diff --git a/share/weapons.qc b/share/weapons.qc index 1d44c5dc..acc8ee22 100644 --- a/share/weapons.qc +++ b/share/weapons.qc @@ -37,18 +37,6 @@ float SlotIndex(Slot slot) { return slot.id - 1; } -// Convert a weapon-bit to a linear index. -static float WEAP_to_index(float weapon) { - float index = 0; - for (; weapon > 0; weapon >>= 1, index++) { - if (weapon >= 64) { - weapon >>= 6; - index += 6; - } - } - return index; -} - enum AmmoType { AMMO_NONE, AMMO_SHELLS, @@ -75,6 +63,7 @@ struct FO_WeapInfo { float ammo_per_shot; float attack_time; float full_reload_time; + float fire_in_anim; // firing triggered from animation (e.g. continuous fire) // Fields below this are automatically initialized by Init. // ** Do not include in table. ** @@ -83,7 +72,6 @@ struct FO_WeapInfo { FO_WeapToItem* items; }; -// REQUIRES: weapon at index i == WEAP_to_index(weap) // -ve values are placeholders signifying conditional init based on game modes. FO_WeapInfo weapon_info[] = { { WEAP_NONE, NO_PREDICT, AMMO_NONE, 0, 0, 0.5, 0 }, @@ -96,22 +84,23 @@ FO_WeapInfo weapon_info[] = { { WEAP_AUTO_RIFLE, PRED_MODEL, AMMO_SHELLS, 0, 1, 0.1, 0 }, { WEAP_SHOTGUN, PRED_MODEL, AMMO_SHELLS, 8, 1, 0.5, 2 }, { WEAP_SUPER_SHOTGUN, PRED_MODEL, AMMO_SHELLS, 16, 2, 0.7, 3 }, - { WEAP_NAILGUN, PRED_PROJ, AMMO_NAILS, 0, 1, 0.2, 0 }, - { WEAP_SUPER_NAILGUN, PRED_PROJ, AMMO_NAILS, 0, 2, 0.2, 0 }, + { WEAP_NAILGUN, PRED_PROJ, AMMO_NAILS, 0, 1, 0.2, 0, 1 }, + { WEAP_SUPER_NAILGUN, PRED_PROJ, AMMO_NAILS, 0, 2, 0.2, 0, 1 }, { WEAP_GRENADE_LAUNCHER , PRED_PROJ, AMMO_ROCKETS, 6, 1, 0.6, 4 }, { WEAP_PIPE_LAUNCHER, PRED_PROJ, AMMO_ROCKETS, 6, 1, 0.6, 4 }, // Overlaps GL - { WEAP_FLAMETHROWER, PRED_PROJ, AMMO_CELLS, 0, 1, 0.15, 0 }, + { WEAP_FLAMETHROWER, PRED_PROJ, AMMO_CELLS, 0, 1, 0.15, 0, 1 }, { WEAP_ROCKET_LAUNCHER, PRED_PROJ, AMMO_ROCKETS, 4, 1, 0.8, 5 }, { WEAP_INCENDIARY, PRED_PROJ, AMMO_ROCKETS, 0, 3, 0.9, 0 }, - { WEAP_ASSAULT_CANNON, PRED_PROJ, AMMO_SHELLS, 100, 1, 0.2, 4 }, + { WEAP_ASSAULT_CANNON, PRED_PROJ, AMMO_SHELLS, 100, 1, 0.2, 4, 1 }, { WEAP_LIGHTNING, NO_PREDICT, AMMO_CELLS, 0, 1, 0.1, 0 }, { WEAP_DETPACK, NO_PREDICT, AMMO_NONE, 0, 0, 0, 0 }, { WEAP_TRANQ, PRED_PROJ, AMMO_SHELLS, 0, 1, 1.5, 0 }, - { WEAP_RAILGUN, PRED_PROJ, AMMO_NAILS, 0, 1, 0.4, 0 }, + { WEAP_RAILGUN, PRED_PROJ, AMMO_NAILS, 0, 1, 0.4, 0 }, + { WEAP_IMPELLER, PRED_PROJ, AMMO_NAILS, 0, 1, 0.6, 0 }, }; inline var FO_WeapInfo* FO_GetWeapInfo(float weapon) { - return &weapon_info[WEAP_to_index(weapon)]; + return &weapon_info[weapon]; } struct FO_ClassWeapons { @@ -131,7 +120,7 @@ FO_ClassWeapons class_weapons[] = { { PC_HVYWEAP, { WEAP_ASSAULT_CANNON, WEAP_SUPER_SHOTGUN, WEAP_SHOTGUN, WEAP_AXE } }, { PC_PYRO, { WEAP_INCENDIARY, WEAP_FLAMETHROWER, WEAP_SHOTGUN, WEAP_AXE } }, { PC_SPY, { WEAP_TRANQ, WEAP_SUPER_SHOTGUN, WEAP_NAILGUN, WEAP_KNIFE } }, - { PC_ENGINEER, { WEAP_RAILGUN, WEAP_SUPER_SHOTGUN, 0, WEAP_SPANNER } }, + { PC_ENGINEER, { WEAP_RAILGUN, WEAP_SUPER_SHOTGUN, WEAP_IMPELLER, WEAP_SPANNER } }, { PC_RANDOM, { 0, 0, 0, WEAP_AXE } }, // TODO: Probably needs attention { PC_CIVILIAN, { 0, 0, 0, WEAP_AXE } }, }; @@ -159,7 +148,7 @@ enum { struct FO_GrenInfo { int id; - string logname; + string name; string model; int skin; string icon; @@ -167,6 +156,7 @@ struct FO_GrenInfo { // Automatically initialized below this line. float modelindex; + string logname; }; FO_GrenInfo fo_grenades[] = { @@ -318,22 +308,18 @@ float IsHoldGrenades() { return csqc_get_user_setting("hg", "hold_grens", "off"); } #endif -// Indexed by WEAP_to_index(). We keep them out of the main table just to keep -// things a little more wieldly/readable. +// We keep these out of the main table just to keep things a little more +// wieldly/readable. static string weapon_names[] = { "None", "Hook", "Knife", "Medikit", "Spanner", "Axe", "Sniper Rifle", "Auto Rifle", "Shotgun", "Super Shotgun", "Nailgun", "Super Nailgun", "Grenade Launcher", "Pipebomb Launcher", "Flamethrower", "Rocket Launcher", "Incendiary Launcher", "Assault Cannon", "Lightning Gun", "Detpack", - "Tranquilizer", "Railgun" + "Tranquilizer", "Railgun", "Impeller" }; string FO_GetWeapName(float weapon) { - // Seeing a weird compiler bug here, passing WEAP_to_index directly to the - // array dereference will randomly cause OOB with length 13 limit, which is - // much smaller than `weapon_names`. - int index = WEAP_to_index(weapon); - return weapon_names[index]; + return weapon_names[weapon]; } @@ -346,7 +332,6 @@ struct FO_WeapModels { }; -// Indexed by WEAP_to_index() static FO_WeapModels weapon_models[] = { { WEAP_NONE, ""}, { WEAP_HOOK, "progs/v_grap.mdl" }, @@ -370,6 +355,7 @@ static FO_WeapModels weapon_models[] = { { WEAP_DETPACK, "" }, { WEAP_TRANQ, "progs/v_tranq.mdl" }, { WEAP_RAILGUN, "progs/v_rail.mdl" }, + { WEAP_IMPELLER, "progs/v_light.mdl" }, }; // REQUIRES: Order must match above. @@ -414,29 +400,35 @@ FO_WeapToItem weapon_to_items[] = { { WEAP_DETPACK, 0}, { WEAP_TRANQ, IT_SHOTGUN}, { WEAP_RAILGUN, IT_SHOTGUN}, + { WEAP_IMPELLER, IT_SHOTGUN}, }; +float FO_WeapAvailable(float weapon) { + if (weapon == WEAP_IMPELLER && !NewBalanceActive()) + return FALSE; + + return TRUE; +} + FO_WeapInfo* FO_SlotWeapInfo(float playerclass, Slot slot) { if (IsSlotNull(slot)) return FO_GetWeapInfo(WEAP_NONE); + float si = SlotIndex(slot); + if (playerclass == PC_ENGINEER && si == 2 && !NewBalanceActive()) + return FO_GetWeapInfo(WEAP_NONE); + float idx = SlotIndex(slot); float cf_pyro_impulses = IsPyroSlotSwapped(); - float si = SlotIndex(slot); if (playerclass == PC_PYRO && cf_pyro_impulses && (idx == 0 || idx == 1)) idx = 1 - idx; - return class_weapons[playerclass]->info[idx] ?: FO_GetWeapInfo(WEAP_NONE); -} + FO_WeapInfo* wi = class_weapons[playerclass]->info[idx]; -float FO_ClassWeapItemMask(float class) { - float result = 0; - for (int i = 1; i <= TF_NUM_SLOTS; i++) { - FO_WeapInfo* wi = FO_SlotWeapInfo(class, MakeSlot(i)); - if (wi) - result |= (wi->items)->it_weapon; - } - return result; + if (!wi || !FO_WeapAvailable(wi->weapon)) + return FO_GetWeapInfo(WEAP_NONE); + + return wi; } #ifndef CSQC @@ -475,7 +467,25 @@ float FO_CanReloadMsg(FO_WeapInfo* wi, int ammo_remaining, int clip_fired, return FALSE; // msg filled in above. } +float self_class(); +Slot self_current_slot(); +float* self_clip_fired(Slot slot); + +float FO_CurrentWeapon() { + FO_WeapInfo* wi = FO_SlotWeapInfo(self_class(), self_current_slot()); + if (!wi) + return WEAP_NONE; + return wi->weapon; +} + #ifdef SSQC +float FO_PlayerCurrentWeapon(entity player) { + FO_WeapInfo* wi = FO_SlotWeapInfo(player.playerclass, player.current_slot); + if (!wi) + return WEAP_NONE; + return wi->weapon; +} + struct FO_WeapState { float weapon; Slot slot; @@ -511,17 +521,6 @@ void FO_FillWeapState(entity player, Slot slot, FO_WeapState* result) { } -float FO_PlayerCurrentWeapon(entity player) { - FO_WeapInfo* wi = FO_SlotWeapInfo(player.playerclass, player.current_slot); - if (!wi) - return WEAP_NONE; - return wi->weapon; -} - -inline float FO_CurrentWeapon() { - return FO_PlayerCurrentWeapon(self); -} - inline void FO_FillCurrentWeapState(FO_WeapState* result) { FO_FillWeapState(self, self.current_slot, result); } @@ -678,7 +677,7 @@ ImpulseWeapon impulse_weapons[] = { { PC_SPY, { WEAP_KNIFE, WEAP_TRANQ, WEAP_SUPER_SHOTGUN, WEAP_NAILGUN } }, { PC_ENGINEER, - { WEAP_SPANNER, WEAP_RAILGUN, WEAP_SUPER_SHOTGUN } }, + { WEAP_SPANNER, WEAP_RAILGUN, WEAP_SUPER_SHOTGUN, WEAP_IMPELLER } }, { PC_RANDOM, { WEAP_AXE } }, // Never instantiated { PC_CIVILIAN, { WEAP_AXE } }, }; @@ -706,7 +705,7 @@ Slot FO_FindPrevNextWeaponSlot(float playerclass, Slot slot, float is_prev) { float direction = is_prev ? -1 : 1; if (IsUsingOldImpulses()) { - Slot* impulse_table = impulse_weapons[playerclass].slots; + ImpulseWeapon* impulse_table = &impulse_weapons[playerclass]; // For reasons that seem to rhyme with -ompilerbug, reads from // impulse_table[offset] return junk so we use a pointer cursor. Slot* cur; @@ -714,13 +713,15 @@ Slot FO_FindPrevNextWeaponSlot(float playerclass, Slot slot, float is_prev) { direction = (direction + len) % len; for (i = 0; i < len; i++) { - cur = &impulse_table[i]; + cur = &impulse_table.slots[i]; if (IsSameSlot(*cur, slot)) break; } do { i = (i + direction) % len; - cur = &impulse_table[i]; + if (!FO_WeapAvailable(impulse_table.weapons[i])) + continue; + cur = &impulse_table.slots[i]; } while (IsSlotNull(*cur)); // Technically this will flip OWI+CFPI, but this is a nonsense combo. @@ -751,7 +752,6 @@ void FO_Weapons_Init() { for (i = 0; i < weapon_info.length; i++) { FO_WeapInfo* wi = &weapon_info[i]; - ASSERTD_EQ(WEAP_to_index(wi->weapon), i); if (wi->weapon == WEAP_PIPE_LAUNCHER) { FO_WeapInfo* parent = FO_GetWeapInfo(WEAP_GRENADE_LAUNCHER); @@ -766,7 +766,6 @@ void FO_Weapons_Init() { } FO_WeapModels* wm = &weapon_models[i]; - ASSERTD_EQ(WEAP_to_index(wm->weapon), i); precache_model(wm->model); #ifdef CSQC wm->modelindex = getmodelindex(wm->model); @@ -774,7 +773,6 @@ void FO_Weapons_Init() { wi->models = wm; FO_WeapToItem* wti = &weapon_to_items[i]; - ASSERTD_EQ(WEAP_to_index(wti->weapon), i); wi->items = wti; switch (wi->ammo_type) { @@ -797,7 +795,7 @@ void FO_Weapons_Init() { FO_ClassWeapons* cw = &class_weapons[i]; if (cw->slots[j]) - cw->info[j] = &weapon_info[WEAP_to_index(cw->slots[j])]; + cw->info[j] = &weapon_info[cw->slots[j]]; else cw->info[j] = &weapon_info[0]; // WEAP_NONE } @@ -815,6 +813,7 @@ void FO_Weapons_Init() { FO_GrenInfo* gdesc = &fo_grenades[i]; ASSERTD_EQ(GREN_FIRST + i, gdesc->id); + gdesc->logname = gdesc->name; if (i < GREN_RED) gdesc->logname = strcat(strtolower(gdesc->logname), "grenade"); diff --git a/ssqc/client.qc b/ssqc/client.qc index 95755c2a..a0bca49c 100644 --- a/ssqc/client.qc +++ b/ssqc/client.qc @@ -223,6 +223,60 @@ void InitPrematch() } } +static void SetAllRoles(float pc, float gren, float limit) { + if (gren == 1) + Role_None.gren1_limits[pc] = Role_Attack.gren1_limits[pc] = + Role_Defence.gren1_limits[pc] = limit; + else + Role_None.gren2_limits[pc] = Role_Attack.gren2_limits[pc] = + Role_Defence.gren2_limits[pc] = limit; +} + +static float NB_UseMinPing() { + if (ServerIsOCE()) + return FALSE; + + if (ServerRegion() == kRegionUnknown) + return FALSE; // e.g. random server, including LAN + return TRUE; +} + +void ActivateNewBalance() { + disable_resup_gren = 2; // No gren2 pickups + + drop_gren1 = 0; // No grenades in backpacks + + int i; + for (i = 1; i <= 9; i++) // 3 gren spawn + SetAllRoles(i, 1, 3); + + SetAllRoles(4, 2, 1); // 1 mirv for demoman + SetAllRoles(6, 2, 1); // 1 mirv for hwguy + SetAllRoles(9, 2, 1); // 1 emp for engineer + SetAllRoles(9, 1, 4); // 4 gren1 for engineer + SetAllRoles(3, 1, 4); // 4 gren1 for soldier + + PC_PYRO_AIRBLAST_COOLDOWN = 10; + PC_ENGINEER_GRENADE_TYPE_2_RANGE = 200; + + float use_new_cap = CF_GetSetting("nbcc", "new_balance_conc_cap", "4"); + if (use_new_cap == 4 && ServerRegion() == kRegionOCE) + use_new_cap = 1; + else + use_new_cap = 0; + + if (use_new_cap) + fo_config.new_balance_flags |= NBF_CONC_NEW_CAP; + + if (CF_GetSetting("nbnc", "new_balance_no_cap", "0")) + fo_config.new_balance_flags |= NBF_NO_CAP; + + if (NB_UseMinPing()) { + if (fo_config.min_ping_ms < 50) // 50 min-ping + localcmd("localinfo mpm 50"); + } +} + void () DecodeLevelParms = { local float fl; local string st; @@ -244,7 +298,6 @@ void () DecodeLevelParms = { self.armortype = parm9 * 0.01; if (!(toggleflags & TFLAG_FIRSTENTRY)) { - toggleflags = parm10; flagem_checked = 0; invis_only = 0; @@ -587,10 +640,6 @@ void () DecodeLevelParms = { asscanrange = CF_GetSetting("acr", "asscanrange", "0"); asscanrangedie = CF_GetSetting("acrd", "asscanrangedie", "0"); - // remote_client_time max delta - antilag_settings.rewind_detpipe = - CF_GetSetting("alrd", "al_rewind_detpipe", "on"); - // CSQC projectiles fo_projectiles = CF_GetSetting("focp", "fo_csqc_projectiles", "on"); @@ -1037,6 +1086,19 @@ void () DecodeLevelParms = { p = find(p, classname, "player"); } */ + + // Overrides other settings. + new_balance = CF_GetSetting("new_balance", "new_balance", "4"); + + float new_balance_region = (ServerRegion() == kRegionUS || + ServerRegion() == kRegionOCE); + if (new_balance == 4) + new_balance = (new_balance_region && ServerIsStaging()) ? 1 : 0; + + // mirror current state into desired state bit + new_balance |= (new_balance << 1); + if (NewBalanceActive()) + ActivateNewBalance(); }; entity() FindIntermission = @@ -2495,6 +2557,7 @@ void () PlayerJump = { if (self.flags & FL_WATERJUMP) return; + if (self.waterlevel >= 2) { if (self.watertype == CONTENT_WATER) self.velocity_z = 100; @@ -2526,6 +2589,8 @@ void () PlayerJump = { self.flags = self.flags - (self.flags & FL_JUMPRELEASED); self.button2 = 0; + NB_ConcCapAction(self, self.playerclass, &self.tfstate, + time, &self.conc_cap_time, kLand); FO_Sound(self, CHAN_AUTO, "player/plyrjmp8.wav", 1, ATTN_NORM); if (!cannon_air && self.tfstate & TFSTATE_AC_FIRING) { @@ -2780,7 +2845,7 @@ void () CheckPowerups = { self.modelindex = modelindex_eyes; } else { if (self.invisible_finished) { - if (self.tfstate & TFSTATE_INVISIBLE) { + if (self.pstate & PSTATE_INVISIBLE) { if (self.invisible_finished < (time + 10)) { self.invisible_finished = time + 666; } @@ -2815,7 +2880,7 @@ void () CheckPowerups = { } } if (self.invincible_finished) { - if (self.tfstate & TFSTATE_INVINCIBLE) { + if (self.pstate & PSTATE_INVINCIBLE) { if (self.invincible_finished < (time + 10)) { self.invincible_finished = time + 666; } @@ -2859,7 +2924,7 @@ void () CheckPowerups = { } } if (self.super_damage_finished) { - if (self.tfstate & TFSTATE_QUAD) { + if (self.pstate & PSTATE_QUAD) { if (self.super_damage_finished == (time + 10)) { self.super_damage_finished = time + 666; } @@ -2901,7 +2966,7 @@ void () CheckPowerups = { } if (self.radsuit_finished) { self.air_finished = time + 12; - if (self.tfstate & TFSTATE_RADSUIT) { + if (self.pstate & PSTATE_RADSUIT) { if (self.radsuit_finished == (time + 10)) { self.radsuit_finished = time + 666; } @@ -3322,6 +3387,8 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage s_deathstring = " gets too friendly with his sentry gun\n"; else if (pf_deathmsg == DMSG_DISPENSER_EXPLODE) s_deathstring = " dispenses with himself\n"; + else if (pf_deathmsg == DMSG_IMPELLER) + s_deathstring = " couldn't resist their inner impelsions\n"; return strcat(pe_target.netname, s_deathstring); @@ -3589,6 +3656,9 @@ string (entity pe_target, entity pe_attacker, float pf_deathmsg) GetDeathMessage } else if (pf_deathmsg == DMSG_LASERBOLT) { s_deathstring = " gets a hole in his heart from "; s_deathstring2 = "'s railgun\n"; + } else if (pf_deathmsg == DMSG_IMPELLER) { + s_deathstring = " gets pushed around by "; + s_deathstring2 = "\n"; } else if (pf_deathmsg == DMSG_INCENDIARY) { s_deathstring = " gets well done by "; s_deathstring2 = "'s incendiary rocket\n"; diff --git a/ssqc/commands.qc b/ssqc/commands.qc index 481527fe..2cbcbf7d 100644 --- a/ssqc/commands.qc +++ b/ssqc/commands.qc @@ -123,6 +123,14 @@ void () DuelMode = bprint(PRINT_HIGH, "Map Restart needed to take effect!\n"); } +void new_balance_mode() { + new_balance ^= 2; + if (new_balance & 2) + localcmd("localinfo new_balance 1\n"); + else + localcmd("localinfo new_balance 0\n"); +} + void (entity pe) SetEquipmentForPlayer = { entity oldself = self; self = pe; @@ -1228,6 +1236,10 @@ float (string arg1, string arg2, string arg3) ParseCmds = { processedCmd = TRUE; DuelMode(); break; + case "new_balance": + processedCmd = TRUE; + new_balance_mode(); + break; case "forcestart": processedCmd = TRUE; ForceStartMatch(); diff --git a/ssqc/csmenu.qc b/ssqc/csmenu.qc index 2dff5fc4..7f9988bc 100644 --- a/ssqc/csmenu.qc +++ b/ssqc/csmenu.qc @@ -184,6 +184,7 @@ void Update_ServerAdminInfo(entity pl) = { WriteFloat(MSG_MULTICAST, (clanbattle?1:0) + (cm?2:0)); WriteFloat(MSG_MULTICAST, (quadmode?1:0) + (qm?2:0)); WriteFloat(MSG_MULTICAST, (duelmode?1:0) + (dm?2:0)); + WriteFloat(MSG_MULTICAST, new_balance); WriteFloat(MSG_MULTICAST, captainmode); multicast('0 0 0', MULTICAST_ONE_NOSPECS); } diff --git a/ssqc/debug.qc b/ssqc/debug.qc index a5d81b51..4d9ccf4c 100644 --- a/ssqc/debug.qc +++ b/ssqc/debug.qc @@ -16,17 +16,17 @@ void (entity te) dremove = remove(te); }; +.float created_at; // Forward dec void dremove_sent(entity te) { - static const float epsilon = 3*SERVER_FRAME_DT; - float stime = te.s_time; - if (!stime) - stime = time; + static const float epsilon = 2*SERVER_FRAME_DT; - if (time > stime + epsilon) { - dremove(self); + float expires = te.created_at + epsilon; // In the past for 0 created at + + if (time > expires) { + dremove(te); } else { - self.nextthink = stime + epsilon; - self.think = SUB_Remove; + te.nextthink = expires; + te.think = SUB_Remove; } } diff --git a/ssqc/demoman.qc b/ssqc/demoman.qc index 94575ceb..c996e18d 100644 --- a/ssqc/demoman.qc +++ b/ssqc/demoman.qc @@ -146,6 +146,7 @@ void (float timer) TeamFortress_SetDetpack = { self.impulse = 0; self.last_impulse = 0; + if (self.ammo_detpack <= 0) { sprint(self, PRINT_HIGH, "You don't have any detpacks left\n"); return; @@ -202,6 +203,17 @@ void (float timer) TeamFortress_SetDetpack = { return; } + float quad_limit = quadmode && round_active && + (time < round_end_time) && (rounds % 2) != (self.team_no - 1); + if (NewBalanceActive() && quad_limit) { + float last = max(self.detpack_last, round_start_time); + float next = last + 30; // TODO: tunable if this sticks + if (next > time) { + sprint(self, PRINT_HIGH, sprintf("%0.1f seconds until next detpack\n", next - time)); + return; + } + } + self.is_detpacking = 3; self.detpack_left = timer; self.ammo_detpack = self.ammo_detpack - 1; @@ -217,6 +229,7 @@ void (float timer) TeamFortress_SetDetpack = { " seconds...\n"); Menu_Demoman_Cancel(); + self.detpack_last = time; newmis = spawn(); newmis.owner = self; newmis.classname = "timer"; diff --git a/ssqc/engineer.qc b/ssqc/engineer.qc index 1255aa27..f1cf18cb 100644 --- a/ssqc/engineer.qc +++ b/ssqc/engineer.qc @@ -78,6 +78,139 @@ void () LaserBolt_Touch = { dremove(self); }; +static const float kImpellerTargetRange = 1500; + +static float ValidImpellerTarget(entity t) { + if (t.classname != "player") + return FALSE; + + if (vlen(t.origin - self.origin) > kImpellerTargetRange) + return FALSE; + + if (cb_prematch) // Shoot anyone in prematch + return TRUE; + + return (t.team_no != self.team_no && t.has_flag); +} + + +static entity FindImpellerTarget(vector org, float min_a, float* direct) { + + traceline(org, org + 1500 * v_forward, MOVE_NORMAL, world); + if (trace_fraction != 1 && ValidImpellerTarget(trace_ent)) + return trace_ent; + + int count; + entity* players; + float best_a = min_a; + entity best_p = world; + + players = find_list(classname, "player", EV_STRING, count); + for (int i = 0; i < count; i++) { + entity p = players[i]; + + if (!ValidImpellerTarget(p)) + continue; + + vector dir = normalize(p.origin - org); + float a = v_forward * dir; + + traceline(org, p.origin, MOVE_NORMAL, world); + if (trace_fraction < 1) { + if (trace_ent != p) { + continue; // Something in the way + } else { + *direct = TRUE; + return p; + } + } + + if (a < best_a) + continue; // Not in AA cone + + best_a = a; + best_p = p; + } + + return best_p; +} + +static vector find_closest(vector a, vector b, vector p) { + vector v = b - a, u = a - p; + float t = -(v * u) / (v * v); + + if (t >= 0 && t <= 1) + return (1 - t) * a + t * b; + return b; +} + +static void draw_beam(vector a, vector b, int effect) { + WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); + WriteByte(MSG_MULTICAST, effect); + WriteEntity(MSG_MULTICAST, world); + WriteCoord(MSG_MULTICAST, a_x); + WriteCoord(MSG_MULTICAST, a_y); + WriteCoord(MSG_MULTICAST, a_z); + WriteCoord(MSG_MULTICAST, b_x); + WriteCoord(MSG_MULTICAST, b_y); + WriteCoord(MSG_MULTICAST, b_z); + multicast(a, MULTICAST_PHS); +} + +void W_FireImpeller() { + FO_Sound(self, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM); + + float rewound = RewindPlayersExceptSelf(0); + + if (!cb_prematch) + self.special_next = time + 3; + + vector org = self.origin + '0 0 16'; + float direct = FALSE; + entity hit = FindImpellerTarget(org, 0.985, &direct); + + if (rewound) + FOPlayer::RestoreAll(); + + float mag = 250; + vector end; + + if (hit == world) { + traceline(org, org + 350 * v_forward, MOVE_WORLDONLY, world); + if (trace_fraction == 1) + return; + + end = trace_endpos; + hit = self; + draw_beam(org, end, TE_LIGHTNING2); + } else { + if (direct) { + end = hit.origin; + draw_beam(org, end, TE_LIGHTNING2); + } else { + vector a = org, b = org + kImpellerTargetRange * v_forward; + traceline(a, b, MOVE_WORLDONLY, world); + end = trace_endpos; + vector x = find_closest(a, end, hit.origin); + + draw_beam(a, x, TE_LIGHTNING2); + draw_beam(x, hit.origin, TE_LIGHTNING2); + + // Discount knockback by help given + mag -= max(10, vlen(hit.origin - x)); + } + + end = hit.origin; + } + + deathmsg = DMSG_IMPELLER; + TF_T_Damage(hit, self, self, 5, TF_TD_NOTTEAM, TF_TD_ELECTRICITY | TF_TD_NOMOMENTUM); + hit.flags &= ~FL_ONGROUND; + if (hit.has_flag) + mag *= 2; + hit.velocity -= mag * normalize(end - org); +} + void () W_FireRailgun = { local vector vec, org; @@ -340,7 +473,7 @@ void () FO_Engineer_ToggleDispenser = { void () FO_Engineer_ToggleSentry = { if (self.has_sentry) { - DestroyBuilding(self, "building_sentrygun"); + Menu_Engineer_Input(3); } else { if (self.health <= 0) { sprint(self, PRINT_HIGH, "Can't build while dead.\n"); diff --git a/ssqc/helpers.qc b/ssqc/helpers.qc new file mode 100644 index 00000000..2b85c437 --- /dev/null +++ b/ssqc/helpers.qc @@ -0,0 +1,59 @@ +enum { + kRegionUS = 1, + kRegionEU, + kRegionOCE, + kRegionLeague, + kRegionUnknown, +}; + +struct RegionMatch { + string channel; + float region; +}; + +static RegionMatch region_matches[] = { + { "504171613793681408", kRegionUS }, // US pug + { "513699536846323712", kRegionEU }, // EU pug + { "542237808895459338", kRegionOCE }, // OCE pug + { "1147341454851719219", kRegionLeague }, // Scrim + { "1026405619231625257", kRegionLeague }, // Tourney +}; + +float ServerRegion() { + static float region; + + if (region) + return region; + + // Can also query FO_REGION env but that does not appear to be + // reliably/consistently set at the moment. + region = kRegionUnknown; + for (int i = 0; i < region_matches.length; i++) { + if (discord_channel_id == region_matches[i].channel) { + region = region_matches[i].region; + break; + } + } + + return region; +} + +float ServerIsOCE() { + if (ServerRegion() == kRegionOCE) + return TRUE; + + // Additional check beyond kRegion for picking up OCE Scrim/Tourney etc + // This is pretty awful, for at least these servers we should just fix FO_REGION + // to be consistent in the future. + string hostname = serverkey("hostname"); + + return strstrofs(hostname, "Sydney") >= 0 || + strstrofs(hostname, "Melbourne") >= 0 || + strstrofs(hostname, "New Zealand") >= 0; +} + +float ServerIsStaging() { + string hostname = serverkey("hostname"); + + return strstrofs(hostname, "Staging") >= 0; +} diff --git a/ssqc/items.qc b/ssqc/items.qc index ed1875cb..75505f9b 100644 --- a/ssqc/items.qc +++ b/ssqc/items.qc @@ -704,36 +704,31 @@ void () weapon_lightning = { StartItem(); }; -void (entity pl, float type) PrintGrenadeType = { - // TODO: this is still awful - string st = FO_GrenDesc(type)->logname; - sprint(pl, PRINT_HIGH, st); -}; - void (entity pe_player, float pf_grenade_type, float pf_grenade_count) PrintFoundGrenade = { if (!pf_grenade_count) return; - sprint(pe_player, PRINT_HIGH, "You found "); - if (pf_grenade_count > 1) - sprint(pe_player, PRINT_HIGH, ftos(pf_grenade_count), " "); - else - sprint(pe_player, PRINT_HIGH, "a "); - PrintGrenadeType(pe_player, pf_grenade_type); - if (pf_grenade_type == GREN_CALTROP) - sprint(pe_player, PRINT_HIGH, " canister"); - else - sprint(pe_player, PRINT_HIGH, " grenade"); - if (pf_grenade_count > 1) - sprint(pe_player, PRINT_HIGH, "s"); - sprint(pe_player, PRINT_HIGH, "\n"); + string buf = sprintf("You found %s%s %s%s\n", + pf_grenade_count > 1 ? " " : "a ", + FO_GrenDesc(pf_grenade_type)->name, + pf_grenade_type == GREN_CALTROP ? "cannister" : "grenade", + pf_grenade_count > 1 ? "s" : ""); + sprint(pe_player, PRINT_HIGH, buf); }; float () GetGrenadePossibility = { if (random() < 0.500) return 0; - float index = random() < 0.5 ? 0 : 1; + float index = -1; + switch (disable_resup_gren) { + case 0: index = random() < 0.5 ? 0 : 1; break; + case 1: index = 1; break; + case 2: index = 0; break; + default: + return 0; + } + FO_GrenInfo* gt = FO_PlayerGren(other, index); if (gt->id == GREN_NONE) return 0; diff --git a/ssqc/player.qc b/ssqc/player.qc index 1757e6bb..a934ed92 100644 --- a/ssqc/player.qc +++ b/ssqc/player.qc @@ -411,7 +411,7 @@ void () GibPlayer = { ThrowGib("progs/gib1.mdl", self.health); ThrowGib("progs/gib2.mdl", self.health); ThrowGib("progs/gib3.mdl", self.health); - if (deathmsg == 36) { + if (deathmsg == DMSG_TRIGGER) { newmis = spawn(); newmis.owner = self; newmis.think = KillPlayer; @@ -424,8 +424,7 @@ void () GibPlayer = { FO_Sound(self, CHAN_VOICE, "player/teledth1.wav", 1, 0); self.respawn_time = (self.respawn_time + 2) + (random() * 2); return; - } - if (damage_attacker.classname == "teledeath2") { + } else if (damage_attacker.classname == "teledeath2") { FO_Sound(self, CHAN_VOICE, "player/teledth1.wav", 1, 0); self.respawn_time = (self.respawn_time + 2) + (random() * 2); return; @@ -435,6 +434,8 @@ void () GibPlayer = { } else { FO_Sound(self, CHAN_VOICE, "player/udeath.wav", 1, 0); } + + FO_SetClientThink(think_nop, 0); }; void () PlayerDie = { diff --git a/ssqc/progs.src b/ssqc/progs.src index 33cf1839..e563dbaa 100644 --- a/ssqc/progs.src +++ b/ssqc/progs.src @@ -23,7 +23,8 @@ time.qc ../share/prediction.qc ../share/classes.qc ../share/animate.qc -../share/mcp_precache.qc +../share/mcp_precache.qc +helpers.qc events.qc roles.qc q3defs.qc diff --git a/ssqc/pyro.qc b/ssqc/pyro.qc index d6036f47..e67e7b88 100644 --- a/ssqc/pyro.qc +++ b/ssqc/pyro.qc @@ -169,8 +169,11 @@ void () NapalmGrenadeExplode2 = { if (head.takedamage) { deathmsg = DMSG_FLAME; - TF_T_Damage(head, self, self.owner, explodeDam, TF_TD_NOTTEAM, - TF_TD_FIRE); + float aflags = TF_TD_FIRE; + if (NewBalanceActive()) + aflags |= TF_TD_NOMOMENTUM; + + TF_T_Damage(head, self, self.owner, explodeDam, TF_TD_NOTTEAM, aflags); other = head; Napalm_touch(); if (other.classname == "player") { diff --git a/ssqc/quadmode.qc b/ssqc/quadmode.qc index 3f673ad0..8693e192 100644 --- a/ssqc/quadmode.qc +++ b/ssqc/quadmode.qc @@ -518,7 +518,8 @@ void () QuadRoundBegin = { if (rounds == 1) PostFOQuadFinalRoundStarted(); - round_end_time = time + round_time * 60 + 1; + round_start_time = time; + round_end_time = round_start_time + round_time * 60 + 1; UpdateClientQuadRoundBegin(te, round_time); if (!self.cnt) { diff --git a/ssqc/qw.qc b/ssqc/qw.qc index 1c98aad5..a7f5b256 100644 --- a/ssqc/qw.qc +++ b/ssqc/qw.qc @@ -9,11 +9,6 @@ enum { CT_FAST_PROJECTILE, // Fast proj => less judder (e.g. heavy bullet) }; -struct antilag_settings_t { - float rewind_detpipe; -} antilag_settings; - -.vector antilag_origin; .float last_death_ctime; .float last_attack_ctime; @@ -108,6 +103,7 @@ float remote_client_time(); .entity nopickup; // Don't pick up this backpack/ammobox because it doesn't contain any of your ammo types +.float pstate; // Player state flags .float tfstate; // State flags for TeamFortress .float last_tfstate; // Internal cache of tfstate for detecting updates .entity linked_list; // Used just like chain. Has to be separate so @@ -117,7 +113,6 @@ float remote_client_time(); // Identify variables .string ident_string; // Status bar string for identify -.string last_ident_string; // Last Status bar string for identify .float ident_time; // The time when last identify found a player .float autoid_type; // 0 = ignore noone, 1 = ignore teammates, 2 = ignore enemies .float autoid_time; // Time when autoid settings were last checked @@ -145,6 +140,8 @@ float remote_client_time(); .float menu_displaytime; .f_void_float menu_input; +.float has_flag; + float toggleflags; // toggleable flags string nextmap; @@ -468,6 +465,7 @@ float rounds; float round_active; float round_over; float round_delay_time; +float round_start_time; float round_end_time; float map_restart_time; float gametime; @@ -621,7 +619,6 @@ float walls_block_emp; float new_emp; float fo_sentry_targeting; // new improved targeting for sentry guns float cb_keepteams; -float fo_matchrated; float fo_flashtime; float fo_repair_ratio; float solid_nailgren; @@ -800,3 +797,7 @@ string match_id; string backend_address; float fo_login_required; .entity filter_ent; + +var float PC_PYRO_AIRBLAST_COOLDOWN = 5; +var float PC_ENGINEER_GRENADE_TYPE_2_RANGE = 240; +.float detpack_last; diff --git a/ssqc/rewind.qc b/ssqc/rewind.qc index 58d4ea1b..0e5d6efa 100644 --- a/ssqc/rewind.qc +++ b/ssqc/rewind.qc @@ -8,7 +8,7 @@ #define rw_printd(...) #endif -#define MAX_SNAPSHOTS 25 +#define MAX_SNAPSHOTS 50 inline int NextRewindIdx(int idx) { return (idx + 1) % MAX_SNAPSHOTS; @@ -104,6 +104,32 @@ static SeekResult RewindSeek(RewindState* rstate, float rtime) { return r; } +static void DoubleCheckCollision(entity player, + RewindSnapshot* a, + RewindSnapshot* b) { + if (player.deadflag) + return; // Dead + if (vlen(b->origin - a->origin) > 48) + return; // Teleport + + const vector PLAYER_MINS = [-16, -16, -24], PLAYER_MAXS = [16, 16, 32]; + tracebox(a->origin, PLAYER_MINS, PLAYER_MAXS, b->origin, 0, player); + if (trace_fraction == 1) + return; + + entity hit = trace_ent; + + if (!hit.voided && hit.fpp.index && hit.touch) { + setorigin(player, trace_endpos); + + entity held_self = self; + self = hit; + other = player; + self.touch(); + self = held_self; + } +} + RewindSnapshot* RewindLog(RewindState* target) { if (target->owner != self) error("Log mismatch\n"); @@ -111,7 +137,8 @@ RewindSnapshot* RewindLog(RewindState* target) { target->owner->client_lastupdate = time; RewindSnapshot* rs = &target->snapshot[target->cur]; - if (time > rs->time + 0.05) { + RewindSnapshot* last = rs; + if (time > rs->time) { if (rs->time) { target->cur = NextRewindIdx(target->cur); rs = &target->snapshot[target->cur]; @@ -125,6 +152,9 @@ RewindSnapshot* RewindLog(RewindState* target) { rs->origin = target->owner.origin; rs->velocity = target->owner.velocity; + if (RewindFlagEnabled(REWIND_DOUBLE_COL) && rs != last) + DoubleCheckCollision(target->owner, last, rs); + return rs; } @@ -292,9 +322,6 @@ void RW_RestoreAll() { float (string ps_short, string ps_setting, string ps_default) CF_GetSetting; float RewindPlayersExceptSelf(float farthest_rewind_point) { - if (!antilag_settings.rewind_detpipe) - return FALSE; - float rewind_max_offset = (MAX_SNAPSHOTS - 1) * SERVER_FRAME_DT; farthest_rewind_point = max(farthest_rewind_point, time - rewind_max_offset); @@ -336,32 +363,31 @@ void Forward_Projectile(int fpp_type, entity proj, float use_ctime) { float static_dt = offset.static_ms / 1000.0; float dynamic_dt = offset.dynamic_ms / 1000.0; - if (proj.fpp.flags & FPF_FIXED_DYNAMIC) - dynamic_dt = proj.fpp.dynamic_dt; - float stime = time - dynamic_dt; - float no_rewind = proj.fpp.flags & FPF_NO_REWIND; - float rewind_hit = !no_rewind && RewindFlagEnabled(REWIND_PROJ_FIRE); + float rewind_hit = RewindFlagEnabled(REWIND_PROJ_FIRE) && + !(proj.fpp.flags & FPF_NO_REWIND) && + dynamic_dt > 0; + float phys_flags = PHYSF_CONSUME_ALL; + float stime = time; + if (rewind_hit) { + stime -= dynamic_dt; phys_flags |= PHYSF_REWIND_PLAYERS; RL_StashPositions(rewind_players); RL_RewindTo(rewind_players, proj.owner, stime); } - if (!no_rewind && RewindFlagEnabled(REWIND_FORWARD_PROJ_SELFKNOCK)) - phys_flags |= PHYSF_FORWARD_KNOCK; - // Static projection happens instantly. If rewind is active, we'll do it at // a prior point in time, but we don't advance time while stepping. - proj.s_origin = proj.origin; - proj.s_time = 0; float ft = Phys_Init(proj, stime, static_dt, PHYSF_CONSUME_ALL); - // We initialize s_origin/s_time after Phys_Init, they are used when + // We initialize s_origin after Phys_Init, they are used when // knockback forwarding is on to determine delay. - proj.s_origin = proj.origin; - proj.s_time = time; + if (rewind_hit && RewindFlagEnabled(REWIND_FORWARD_PROJ_SELFKNOCK)) { + phys_flags |= PHYSF_FORWARD_KNOCK; + proj.s_origin = proj.origin; + } if (!proj.voided) { RewindSyncTime = ProjRewindForPhys; diff --git a/ssqc/scout.qc b/ssqc/scout.qc index 36f325e3..aa2957b5 100644 --- a/ssqc/scout.qc +++ b/ssqc/scout.qc @@ -830,6 +830,9 @@ void (entity inflictor, entity attacker, float bounce, local entity te; local vector org; + if (NewBalanceActive() && inflictor.owner.playerclass == PC_MEDIC) + actual_cuss_time = 2; + head = findradius(inflictor.origin, bounce + 40); while (head) { if (head != ignore) { @@ -861,12 +864,20 @@ void (entity inflictor, entity attacker, float bounce, head.velocity = head.velocity * (points / 20); Predict_AddFilterEnt(head, inflictor); + NB_ConcCapAction(head, head.playerclass, &head.tfstate, + time, &head.conc_cap_time, kLaunch); + if (head.classname != "player") { if (head.flags & FL_ONGROUND) head.flags = head.flags - FL_ONGROUND; } else { if (head.playerclass == PC_MEDIC) { + if ((NewBalanceActive() && NB_UseNewConc()) || NB_NoCap()) { + head = head.chain; + continue; + } + if (medicnocuss && (medic_type != MEDIC_TYPE_BLAST)) { entity speedbump; diff --git a/ssqc/status.qc b/ssqc/status.qc index 85b9efae..1be22004 100644 --- a/ssqc/status.qc +++ b/ssqc/status.qc @@ -800,10 +800,6 @@ void UpdateClientGrenadeThrown(entity pl) = { void UpdateClientIDString(entity pl) { string ident = time < pl.ident_time ? pl.ident_string : ""; - if (ident == pl.last_ident_string) // only send updates - return; - pl.last_ident_string = ident; - if (ident == "") // No need to send null, we'll expire clientside. return; diff --git a/ssqc/tfort.qc b/ssqc/tfort.qc index c9a9920c..3c0f1426 100644 --- a/ssqc/tfort.qc +++ b/ssqc/tfort.qc @@ -1218,7 +1218,6 @@ void () TeamFortress_SetEquipment = { Team_Role * role = GetTeamRole(self.team_no); kept_items = self.tf_items & (IT_KEY1 | IT_KEY2); - self.items = FO_ClassWeapItemMask(self.playerclass) | kept_items; if (!remember_weapon || self.last_playerclass != self.playerclass || (self.tfstate & TFSTATE_RANDOMPC)) { @@ -2398,12 +2397,6 @@ void () TeamFortress_ExplodePerson = { proj.angles = vectoangles(proj.velocity); proj.think = SUB_Null; float expires = time + 0.1; // Server generated, no client time here. - float rw_dt = FO_RewindGrenWinDt(gtype); - if (rw_dt > 0) { - proj.fpp.flags |= FPF_FIXED_DYNAMIC; - proj.fpp.dynamic_dt = rw_dt; - expires -= rw_dt; - } proj.nextthink = expires; if (self.weapon == GREN_FLARE) { diff --git a/ssqc/tfortmap.qc b/ssqc/tfortmap.qc index f682d517..4a44d286 100644 --- a/ssqc/tfortmap.qc +++ b/ssqc/tfortmap.qc @@ -908,7 +908,7 @@ void (entity Goal, entity Player, entity AP, float addb) Apply_Results = { Player.invincible_finished = time + Goal.invincible_finished; if (Goal.classname == "item_tfgoal") { - Player.tfstate = Player.tfstate | TFSTATE_INVINCIBLE; + Player.pstate |= PSTATE_INVINCIBLE; Player.invincible_finished = time + 666; } } @@ -917,7 +917,7 @@ void (entity Goal, entity Player, entity AP, float addb) Apply_Results = { Player.invisible_time = 1; Player.invisible_finished = time + Goal.invisible_finished; if (Goal.classname == "item_tfgoal") { - Player.tfstate = Player.tfstate | TFSTATE_INVISIBLE; + Player.pstate |= PSTATE_INVISIBLE; Player.invisible_finished = time + 666; } } @@ -927,7 +927,7 @@ void (entity Goal, entity Player, entity AP, float addb) Apply_Results = { Player.super_damage_finished = time + Goal.super_damage_finished; if (Goal.classname == "item_tfgoal") { - Player.tfstate = Player.tfstate | TFSTATE_QUAD; + Player.pstate |= PSTATE_QUAD; Player.super_damage_finished = time + 666; } } @@ -936,7 +936,7 @@ void (entity Goal, entity Player, entity AP, float addb) Apply_Results = { Player.rad_time = 1; Player.radsuit_finished = time + Goal.radsuit_finished; if (Goal.classname == "item_tfgoal") { - Player.tfstate = Player.tfstate | TFSTATE_RADSUIT; + Player.pstate |= PSTATE_RADSUIT; Player.radsuit_finished = time + 666; } } @@ -1179,25 +1179,25 @@ void (entity Goal, entity Player) RemoveResults = { te = find(te, classname, "item_tfgoal"); } if ((Goal.invincible_finished > 0) && !puinvin) { - Player.tfstate &= ~TFSTATE_INVINCIBLE; + Player.pstate &= ~PSTATE_INVINCIBLE; Player.items = Player.items | IT_INVULNERABILITY; Player.invincible_time = 1; Player.invincible_finished = time + Goal.invincible_finished; } if ((Goal.invisible_finished > 0) && !puinvis) { - Player.tfstate &= ~TFSTATE_INVISIBLE; + Player.pstate &= ~PSTATE_INVISIBLE; Player.items = Player.items | IT_INVISIBILITY; Player.invisible_time = 1; Player.invisible_finished = time + Goal.invisible_finished; } if ((Goal.super_damage_finished > 0) && !puquad) { - Player.tfstate &= ~TFSTATE_QUAD; + Player.pstate &= ~PSTATE_QUAD; Player.items = Player.items | IT_QUAD; Player.super_time = 1; Player.super_damage_finished = time + Goal.super_damage_finished; } if ((Goal.radsuit_finished > 0) && !purad) { - Player.tfstate &= ~TFSTATE_RADSUIT; + Player.pstate &= ~PSTATE_RADSUIT; Player.items = Player.items | IT_SUIT; Player.rad_time = 1; Player.radsuit_finished = time + Goal.radsuit_finished; @@ -2265,6 +2265,7 @@ void (entity Item, entity AP, entity Goal) tfgoalitem_GiveToPlayer = { DoResults(Item, AP, 1); DoItemGroupWork(Item, AP); AP.goalrunningtime = gametime; + AP.has_flag = TRUE; LogEventPickupGoal(AP); }; @@ -2337,6 +2338,7 @@ void (entity Item, entity AP, float method) tfgoalitem_RemoveFromPlayer = { //Flag if ((Item.goal_activation & 1)) { local float timecarried = gametime - AP.goalrunningtime; + AP.has_flag = FALSE; RemoveFlagFollow(AP); LogEventFumble(AP, timecarried); } @@ -2928,6 +2930,7 @@ void () DropGoalItems = { //Always allow dropping 4096 else if (self.effects & EF_DIMLIGHT || te.goal_activation & TFGI_ALLOWTHROW) { timecarried = gametime - self.goalrunningtime; + self.has_flag = FALSE; RemoveFlagFollow(self); LogEventFumble(self, timecarried); te.angles = '0 0 0'; diff --git a/ssqc/weapons.qc b/ssqc/weapons.qc index 7f7574fb..12726a34 100644 --- a/ssqc/weapons.qc +++ b/ssqc/weapons.qc @@ -99,6 +99,7 @@ void () W_FireFlame; void W_FireIncendiaryCannon(vector org, vector angles, float use_ctime=0); void () W_FireTranq; void () W_FireRailgun; +void () W_FireImpeller; void () HallucinationTimer; void () TranquiliserTimer; @@ -1043,9 +1044,7 @@ void T_Knock_Antilag() { } float AntilagKnock(entity e, float dmg) { - if (!RewindFlagEnabled(REWIND_FORWARD_PROJ_SELFKNOCK) || e.s_time == 0) - return FALSE; - + e.forward_knock = -1; // Only applies during initial antilag forward if (e.flags & FL_FORWARD_KNOCK == 0) return FALSE; @@ -1070,7 +1069,6 @@ float AntilagKnock(entity e, float dmg) { e.forward_knock = time + ttime; } else { Antilag_Knock(knock_e); - e.forward_knock = -1; } return TRUE; @@ -1095,9 +1093,6 @@ void () T_MissileTouch = { entity ignore_self = AntilagKnock(self, dmg) ? self.owner : __NULL__; T_RadiusDamage(self, self.owner, dmg, other, ignore_self); - if (!self.s_time) - self.forward_knock = -1; - self.origin = self.origin - 8 * normalize(self.velocity); WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); @@ -1412,6 +1407,9 @@ void (float ox) W_FireSpikes = { } *ws->ammo_remaining -= wi->ammo_per_shot; + if (wi->needs_reload) + *ws->clip_fired += wi->ammo_per_shot; + if (wi->weapon == WEAP_NAILGUN) W_FireNail(FPP_NAIL, v_right * ox); else @@ -1752,21 +1750,24 @@ void () W_Attack = { } else if (ws.weapon == WEAP_RAILGUN) { player_shot1(); W_FireRailgun(); + } else if (ws.weapon == WEAP_IMPELLER) { + if (!new_balance || time < self.special_next) + return; + + player_shot1(); + W_FireImpeller(); } - if (ws.weapon != WEAP_FLAMETHROWER && // Variable - ws.weapon != WEAP_ASSAULT_CANNON && // In animation - ws.weapon != WEAP_NAILGUN && // In animation - ws.weapon != WEAP_SUPER_NAILGUN) // In animation + if (!wi->fire_in_anim) { Attack_Finished(wi->attack_time); - - if (wi->needs_reload) { - if (ws.weapon != WEAP_ASSAULT_CANNON) // In animation + if (wi->needs_reload) { *ws->clip_fired += wi->ammo_per_shot; - FO_CheckForReload(); + FO_CheckForReload(); + } } //These weapons have to log each projectile launched/bullet fired + // TODO: probably equivalent to fire_in_anim... if (ws.weapon != WEAP_ASSAULT_CANNON || ws.weapon != WEAP_NAILGUN || ws.weapon != WEAP_SUPER_NAILGUN) LogEventAttack(self); @@ -1882,7 +1883,7 @@ void W_ChangeWeaponSlot(Slot slot) { FO_WeapState ws; FO_FillWeapState(self, slot, &ws); - if (ws.weapon == WEAP_NONE) + if (ws.weapon == WEAP_NONE || !FO_WeapAvailable(ws.weapon)) return; // queue next weapon if queue is not empty or has changed @@ -2675,16 +2676,16 @@ void () SuperDamageSound = { }; void () ToggleInvincibility = { - if(self.tfstate & TFSTATE_INVINCIBLE) { + if(self.pstate & PSTATE_INVINCIBLE) { self.items &= ~IT_INVULNERABILITY; self.invincible_time = 0; self.invincible_finished = 0; - self.tfstate &= ~TFSTATE_INVINCIBLE; + self.pstate &= ~PSTATE_INVINCIBLE; self.effects &= ~(EF_RED | EF_DIMLIGHT | EF_BRIGHTLIGHT); } else { self.items |= IT_INVULNERABILITY; self.invincible_time = 1; - self.tfstate |= TFSTATE_INVINCIBLE; + self.pstate |= PSTATE_INVINCIBLE; self.invincible_finished = time + 666; } };