From e3743c5b2c4f2c508f7d1decf70fc11260bf3904 Mon Sep 17 00:00:00 2001 From: Jaime Moreira Date: Wed, 5 Jul 2023 15:50:12 -0400 Subject: [PATCH 1/4] Added support for ANIM_REVERSE Allows to define animations that go in the opposite direction of the usual flow. Added a couple of animations from Q2 3.21 that use ANIM_REVERSE. Make the "laser trip bomb" to use Weapon_Generic instead of the "copy & paste" version it used. Modified the original Weapon_Generic function to get the proper ammo quantity the weapon uses; useful for IT_AMMO weapons. --- src/g_weapon.c | 1 - src/header/local.h | 3 +- src/player/view.c | 10 +++- src/player/weapon.c | 92 ++++++++++++++++++++++++++++++++- src/zaero/weapon.c | 121 +------------------------------------------- 5 files changed, 104 insertions(+), 123 deletions(-) diff --git a/src/g_weapon.c b/src/g_weapon.c index dbd9568..280a2e4 100644 --- a/src/g_weapon.c +++ b/src/g_weapon.c @@ -1009,4 +1009,3 @@ void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, f gi.linkentity (bfg); } - diff --git a/src/header/local.h b/src/header/local.h index 9a09739..fbc6f77 100644 --- a/src/header/local.h +++ b/src/header/local.h @@ -850,6 +850,7 @@ void DeathmatchScoreboardMessage (edict_t *client, edict_t *killer); // g_pweapon.c // void PlayerNoise(edict_t *who, vec3_t where, int type); +int get_ammo_usage(gitem_t *weap); // // m_move.c @@ -905,7 +906,7 @@ void ai_schoolSideStepLeft (edict_t *self, float dist); #define ANIM_PAIN 3 #define ANIM_ATTACK 4 #define ANIM_DEATH 5 - +#define ANIM_REVERSE 6 // client data that stays across multiple level loads typedef struct diff --git a/src/player/view.c b/src/player/view.c index af68a1c..3b100d3 100644 --- a/src/player/view.c +++ b/src/player/view.c @@ -996,7 +996,15 @@ void G_SetClientFrame (edict_t *ent) if (!ent->groundentity && client->anim_priority <= ANIM_WAVE) goto newanim; - if (ent->s.frame < client->anim_end) + if (client->anim_priority == ANIM_REVERSE) + { + if (ent->s.frame > client->anim_end) + { + ent->s.frame--; + return; + } + } + else if (ent->s.frame < client->anim_end) { // continue an animation ent->s.frame++; return; diff --git a/src/player/weapon.c b/src/player/weapon.c index aba22e6..4a00cb7 100644 --- a/src/player/weapon.c +++ b/src/player/weapon.c @@ -244,6 +244,19 @@ void ChangeWeapon (edict_t *ent) ent->client->weaponstate = WEAPON_ACTIVATING; ent->client->ps.gunframe = 0; ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); + + ent->client->anim_priority = ANIM_PAIN; + + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain1; + ent->client->anim_end = FRAME_crpain4; + } + else + { + ent->s.frame = FRAME_pain301; + ent->client->anim_end = FRAME_pain304; + } } /* @@ -425,6 +438,52 @@ void Drop_Weapon (edict_t *ent, gitem_t *item) ent->client->pers.inventory[index]--; } +/* + * Returns ammo used on a single shot for the given weapon + */ +int +get_ammo_usage(gitem_t *weap) +{ + if (!weap) + { + return 0; + } + + /* handles grenades and tesla which only use 1 ammo per shot */ + /* have to check this because they don't store their ammo usage in weap->quantity */ + if (weap->flags & IT_AMMO) + { + return 1; + } + + /* weapons store their ammo usage in the quantity field */ + return weap->quantity; +} + +/* + * Client (player) animation for changing weapon + */ +static void +Change_Weap_Animation(edict_t *ent) +{ + if (!ent) + { + return; + } + + ent->client->anim_priority = ANIM_REVERSE; + + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4 + 1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304 + 1; + ent->client->anim_end = FRAME_pain301; + } +} /* ================ @@ -453,6 +512,15 @@ void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, ChangeWeapon (ent); return; } + else if ( (FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) >= 4 ) + { + unsigned short int remainder = FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe; + // "if (remainder == 4)" at change_speed == 1 + if (remainder == 4) + { + Change_Weap_Animation(ent); + } + } ent->client->ps.gunframe++; return; @@ -475,6 +543,10 @@ void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, { ent->client->weaponstate = WEAPON_DROPPING; ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + if ( (FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4 ) + { + Change_Weap_Animation(ent); + } return; } @@ -484,7 +556,7 @@ void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, { ent->client->latched_buttons &= ~BUTTON_ATTACK; if ((!ent->client->ammo_index) || - ( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity)) + ( ent->client->pers.inventory[ent->client->ammo_index] >= get_ammo_usage(ent->client->pers.weapon) )) { ent->client->ps.gunframe = FRAME_FIRE_FIRST; ent->client->weaponstate = WEAPON_FIRING; @@ -603,6 +675,24 @@ void weapon_grenade_fire (edict_t *ent, qboolean held) // play quad damage sound playQuadSound(ent); + + if (ent->deadflag || ent->health <= 0 || ent->s.modelindex != 255) + { + return; // VWep animations screw up corpses + } + + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->client->anim_priority = ANIM_ATTACK; + ent->s.frame = FRAME_crattak1 - 1; + ent->client->anim_end = FRAME_crattak3; + } + else + { + ent->client->anim_priority = ANIM_REVERSE; + ent->s.frame = FRAME_wave08; + ent->client->anim_end = FRAME_wave01; + } } void Weapon_Grenade (edict_t *ent) diff --git a/src/zaero/weapon.c b/src/zaero/weapon.c index 91a17c7..c8ac173 100644 --- a/src/zaero/weapon.c +++ b/src/zaero/weapon.c @@ -520,124 +520,8 @@ void Weapon_LaserTripBomb(edict_t *ent) static int pause_frames[] = {24, 33, 43, 0}; static int fire_frames[] = {6, 10, 15, 0}; - const int deactivateFirst = 44; - const int deactivateLast = 48; - const int idleFirst = 16; - const int idleLast = 43; - const int fireFirst = 7; - const int activateLast = 6; - - if (!ent) - { - return; - } - - if (ent->client->weaponstate == WEAPON_DROPPING) - { - if (ent->client->ps.gunframe == deactivateLast) - { - ChangeWeapon (ent); - return; - } - - ent->client->ps.gunframe++; - return; - } - - if (ent->client->weaponstate == WEAPON_ACTIVATING) - { - if (ent->client->ps.gunframe == activateLast) - { - ent->client->weaponstate = WEAPON_READY; - ent->client->ps.gunframe = idleFirst; - return; - } - - ent->client->ps.gunframe++; - return; - } - - if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING)) - { - ent->client->weaponstate = WEAPON_DROPPING; - ent->client->ps.gunframe = deactivateFirst; - return; - } - - if (ent->client->weaponstate == WEAPON_READY) - { - if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) - { - ent->client->latched_buttons &= ~BUTTON_ATTACK; - if(ent->client->pers.inventory[ent->client->ammo_index]) - { - ent->client->ps.gunframe = fireFirst; - ent->client->weaponstate = WEAPON_FIRING; - - // start the animation - ent->client->anim_priority = ANIM_ATTACK; - if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) - { - ent->s.frame = FRAME_crattak1-1; - ent->client->anim_end = FRAME_crattak9; - } - else - { - ent->s.frame = FRAME_attack1-1; - ent->client->anim_end = FRAME_attack8; - } - } - else - { - if (level.time >= ent->pain_debounce_time) - { - gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); - ent->pain_debounce_time = level.time + 1; - } - NoAmmoWeaponChange (ent); - } - } - else - { - if (ent->client->ps.gunframe == idleLast) - { - ent->client->ps.gunframe = idleFirst; - return; - } - - int n = 0; - for (n = 0; pause_frames[n]; n++) - { - if (ent->client->ps.gunframe == pause_frames[n]) - { - if (rand()&15) - return; - } - } - - ent->client->ps.gunframe++; - return; - } - } - - if (ent->client->weaponstate == WEAPON_FIRING) - { - int n = 0; - for (n = 0; fire_frames[n]; n++) - { - if (ent->client->ps.gunframe == fire_frames[n]) - { - weapon_lasertripbomb_fire(ent); - break; - } - } - - if (!fire_frames[n]) - ent->client->ps.gunframe++; - - if (ent->client->ps.gunframe == idleFirst+1) - ent->client->weaponstate = WEAPON_READY; - } + Weapon_Generic(ent, 6, 15, 43, 48, pause_frames, + fire_frames, weapon_lasertripbomb_fire); } void SP_misc_lasertripbomb(edict_t *bomb) @@ -1649,4 +1533,3 @@ void Action_Push (edict_t *ent) else ent->client->ps.gunframe++; } - From 82f72cca77e8e57ffedee001dc3f5b3e96dbd149 Mon Sep 17 00:00:00 2001 From: Jaime Moreira Date: Wed, 5 Jul 2023 16:18:13 -0400 Subject: [PATCH 2/4] Added Yamagi cheat cvars "g_machinegun_norecoil" and "g_swap_speed" --- src/g_main.c | 3 ++- src/header/local.h | 2 ++ src/player/weapon.c | 20 ++++++++++++-------- src/savegame/savegame.c | 3 ++- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/g_main.c b/src/g_main.c index 88c328f..5cd7036 100644 --- a/src/g_main.c +++ b/src/g_main.c @@ -46,6 +46,8 @@ cvar_t *gamedir; cvar_t *sv_cheats; cvar_t *aimfix; +cvar_t *g_machinegun_norecoil; +cvar_t *g_swap_speed; void SpawnEntities (char *mapname, char *entities, char *spawnpoint); void ClientThink (edict_t *ent, usercmd_t *cmd); @@ -357,4 +359,3 @@ void G_RunFrame (void) // build the playerstate_t structures for all players ClientEndServerFrames (); } - diff --git a/src/header/local.h b/src/header/local.h index fbc6f77..4e7405f 100644 --- a/src/header/local.h +++ b/src/header/local.h @@ -610,6 +610,8 @@ extern cvar_t *grenadeammo; extern cvar_t *bettyammo; extern cvar_t *aimfix; +extern cvar_t *g_machinegun_norecoil; +extern cvar_t *g_swap_speed; #define world (&g_edicts[0]) diff --git a/src/player/weapon.c b/src/player/weapon.c index 4a00cb7..55a4b5e 100644 --- a/src/player/weapon.c +++ b/src/player/weapon.c @@ -499,6 +499,7 @@ A generic function to handle the basics of weapon thinking void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) { int n; + const unsigned short int change_speed = (g_swap_speed->value > 0)?(unsigned short int)g_swap_speed->value:1; if (!ent) { @@ -507,35 +508,36 @@ void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, if (ent->client->weaponstate == WEAPON_DROPPING) { - if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST) + if (ent->client->ps.gunframe >= FRAME_DEACTIVATE_LAST - change_speed + 1) { ChangeWeapon (ent); return; } - else if ( (FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) >= 4 ) + else if ( (FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) >= (4 * change_speed) ) { unsigned short int remainder = FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe; // "if (remainder == 4)" at change_speed == 1 - if (remainder == 4) + if ( ( remainder <= (4 * change_speed) ) + && ( remainder > (3 * change_speed) ) ) { Change_Weap_Animation(ent); } } - ent->client->ps.gunframe++; + ent->client->ps.gunframe += change_speed; return; } if (ent->client->weaponstate == WEAPON_ACTIVATING) { - if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST) + if (ent->client->ps.gunframe >= FRAME_ACTIVATE_LAST - change_speed + 1) { ent->client->weaponstate = WEAPON_READY; ent->client->ps.gunframe = FRAME_IDLE_FIRST; return; } - ent->client->ps.gunframe++; + ent->client->ps.gunframe += change_speed; return; } @@ -543,7 +545,7 @@ void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, { ent->client->weaponstate = WEAPON_DROPPING; ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; - if ( (FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4 ) + if ( (FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < (4 * change_speed) ) { Change_Weap_Animation(ent); } @@ -1184,11 +1186,13 @@ void Machinegun_Fire (edict_t *ent) ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5; // raise the gun as it is firing - if (!deathmatch->value) + if (!(deathmatch->value || g_machinegun_norecoil->value)) { ent->client->machinegun_shots++; if (ent->client->machinegun_shots > 9) + { ent->client->machinegun_shots = 9; + } } // get start / end positions diff --git a/src/savegame/savegame.c b/src/savegame/savegame.c index 93eb5c2..251e43e 100644 --- a/src/savegame/savegame.c +++ b/src/savegame/savegame.c @@ -236,6 +236,8 @@ InitGame(void) /* others */ aimfix = gi.cvar("aimfix", "0", CVAR_ARCHIVE); + g_machinegun_norecoil = gi.cvar("g_machinegun_norecoil", "0", CVAR_ARCHIVE); + g_swap_speed = gi.cvar("g_swap_speed", "1", 0); /* items */ InitItems (); @@ -1156,4 +1158,3 @@ ReadLevel(const char *filename) } } } - From 1e7596aeefea5d9905b8a9dd7eb34c2e10b6ef96 Mon Sep 17 00:00:00 2001 From: Jaime Moreira Date: Wed, 5 Jul 2023 16:24:39 -0400 Subject: [PATCH 3/4] Implemented Yamagi's commands to switch weapons 'cycleweap' defines a custom group of weapons. Press the bound key repeteadly to to skip weapons inside this group. 'prefweap' defines a "panic button" where the first weapon found will be used, no cycling. --- src/g_cmds.c | 267 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 266 insertions(+), 1 deletion(-) diff --git a/src/g_cmds.c b/src/g_cmds.c index f60dfaf..bff955b 100644 --- a/src/g_cmds.c +++ b/src/g_cmds.c @@ -1105,6 +1105,264 @@ void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0) } } +/* Yamagi's cycleweap / prefweap */ + +static gitem_t * +cycle_weapon(edict_t *ent) +{ + gclient_t *cl; + gitem_t *noammo_fallback; + gitem_t *noweap_fallback; + gitem_t *weap; + gitem_t *ammo; + int i; + int start; + int num_weaps; + const char *weapname = NULL; + + if (!ent) + { + return NULL; + } + + cl = ent->client; + + if (!cl) + { + return NULL; + } + + num_weaps = gi.argc(); + + /* find where we want to start the search for the next eligible weapon */ + if (cl->newweapon) + { + weapname = cl->newweapon->classname; + } + else if (cl->pers.weapon) + { + weapname = cl->pers.weapon->classname; + } + + if (weapname) + { + for (i = 1; i < num_weaps; i++) + { + if (Q_stricmp(weapname, gi.argv(i)) == 0) + { + break; + } + } + + i++; + + if (i >= num_weaps) + { + i = 1; + } + } + else + { + i = 1; + } + + start = i; + noammo_fallback = NULL; + noweap_fallback = NULL; + + /* find the first eligible weapon in the list we can switch to */ + do + { + weap = FindItemByClassname(gi.argv(i)); + + if (weap && weap != cl->pers.weapon && (weap->flags & IT_WEAPON) && weap->use) + { + if (cl->pers.inventory[ITEM_INDEX(weap)] > 0) + { + if (weap->ammo) + { + ammo = FindItem(weap->ammo); + if (ammo) + { + if (cl->pers.inventory[ITEM_INDEX(ammo)] >= get_ammo_usage(weap)) + { + return weap; + } + + if (!noammo_fallback) + { + noammo_fallback = weap; + } + } + } + else + { + return weap; + } + } + else if (!noweap_fallback) + { + noweap_fallback = weap; + } + } + + i++; + + if (i >= num_weaps) + { + i = 1; + } + } while (i != start); + + /* if no weapon was found, the fallbacks will be used for + printing the appropriate error message to the console + */ + + if (noammo_fallback) + { + return noammo_fallback; + } + + return noweap_fallback; +} + +static void +Cmd_CycleWeap_f(edict_t *ent) +{ + gitem_t *weap; + + if (!ent) + { + return; + } + + if (gi.argc() <= 1) + { + gi.cprintf(ent, PRINT_HIGH, "Usage: cycleweap classname1 classname2 .. classnameN\n"); + return; + } + + weap = cycle_weapon(ent); + if (weap) + { + if (ent->client->pers.inventory[ITEM_INDEX(weap)] <= 0) + { + gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", weap->pickup_name); + } + else + { + weap->use(ent, weap); + } + } +} + +static gitem_t * +preferred_weapon(edict_t *ent) +{ + gclient_t *cl; + gitem_t *noammo_fallback; + gitem_t *noweap_fallback; + gitem_t *weap; + gitem_t *ammo; + int i; + int num_weaps; + + if (!ent) + { + return NULL; + } + + cl = ent->client; + + if (!cl) + { + return NULL; + } + + num_weaps = gi.argc(); + noammo_fallback = NULL; + noweap_fallback = NULL; + + /* find the first eligible weapon in the list we can switch to */ + for (i = 1; i < num_weaps; i++) + { + weap = FindItemByClassname(gi.argv(i)); + + if (weap && (weap->flags & IT_WEAPON) && weap->use) + { + if (cl->pers.inventory[ITEM_INDEX(weap)] > 0) + { + if (weap->ammo) + { + ammo = FindItem(weap->ammo); + if (ammo) + { + if (cl->pers.inventory[ITEM_INDEX(ammo)] >= get_ammo_usage(weap)) + { + return weap; + } + + if (!noammo_fallback) + { + noammo_fallback = weap; + } + } + } + else + { + return weap; + } + } + else if (!noweap_fallback) + { + noweap_fallback = weap; + } + } + } + + /* if no weapon was found, the fallbacks will be used for + printing the appropriate error message to the console + */ + + if (noammo_fallback) + { + return noammo_fallback; + } + + return noweap_fallback; +} + +static void +Cmd_PrefWeap_f(edict_t *ent) +{ + gitem_t *weap; + + if (!ent) + { + return; + } + + if (gi.argc() <= 1) + { + gi.cprintf(ent, PRINT_HIGH, "Usage: prefweap classname1 classname2 .. classnameN\n"); + return; + } + + weap = preferred_weapon(ent); + if (weap) + { + if (ent->client->pers.inventory[ITEM_INDEX(weap)] <= 0) + { + gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", weap->pickup_name); + } + else + { + weap->use(ent, weap); + } + } +} + + /* ================= ClientCommand @@ -1248,7 +1506,14 @@ void ClientCommand (edict_t *ent) else if(Q_stricmp (cmd, "anim") == 0) anim_player_cmd(ent); #endif + else if (Q_stricmp(cmd, "cycleweap") == 0) + { + Cmd_CycleWeap_f(ent); + } + else if (Q_stricmp(cmd, "prefweap") == 0) + { + Cmd_PrefWeap_f(ent); + } else // anything that doesn't match a command will be a chat Cmd_Say_f (ent, false, true); } - From 0fedfa14af1c0c75cdb7b91752c720b50b321389 Mon Sep 17 00:00:00 2001 From: Jaime Moreira Date: Sun, 27 Aug 2023 00:12:16 -0400 Subject: [PATCH 4/4] Better validation for g_swap_speed value Port of yquake2's #1049 --- src/player/weapon.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/player/weapon.c b/src/player/weapon.c index 55a4b5e..570f5fe 100644 --- a/src/player/weapon.c +++ b/src/player/weapon.c @@ -499,7 +499,9 @@ A generic function to handle the basics of weapon thinking void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) { int n; - const unsigned short int change_speed = (g_swap_speed->value > 0)?(unsigned short int)g_swap_speed->value:1; + const unsigned short int change_speed = (g_swap_speed->value > 1)? + (g_swap_speed->value < USHRT_MAX)? (unsigned short int)g_swap_speed->value : 1 + : 1; if (!ent) {