diff --git a/README.md b/README.md index f8c9cc7..cead704 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ A legendary 90s era Quake 3 Arena mod. # TODO list: - [x] ~~Toggeable flight (bind key). Hint: FLIGHT POWERUP~~ -- [ ] Melee feature +- [x] ~~Melee feature~~ - [x] ~~Remove weapon visuals (models and stuff)~~ - [x] ~~Animations as listed on the old docs~~ - [x] ~~Bind key to recover ki energy~~ @@ -45,7 +45,7 @@ A legendary 90s era Quake 3 Arena mod. - [x] ~~Third person traceable crosshair~~ - [ ] Make ki energy regeneration, ki use, attacks, charging balance indicated on old docs - [ ] Powerlevel and Power Tiers indicated on old docs -- [ ] Hit Stun (makes player can't use ki, melee, block and charge) +- [x] ~~Hit Stun (makes player can't use ki, melee, block and charge)~~ - [ ] Power Struggles (when two beam attacks collide) - [x] ~~Blocking (consumes ki energy, transfers all damage to ki instead of health, deflect missile attacks, more info on old docs)~~ - [ ] Short-Range Teleport (when pressing 2 times left or right) diff --git a/docs/bfp_cvars_task.md b/docs/bfp_cvars_task.md index e76c269..b48fe66 100644 --- a/docs/bfp_cvars_task.md +++ b/docs/bfp_cvars_task.md @@ -12,9 +12,6 @@ - g_flightCostPct = "0" - g_flightCost = "50" - g_chargeDelay = "250" -- g_meleeRange = "32" -- g_meleeDiveRange = "700" -- g_meleeDamage = "10" #### Cvar Gametypes: @@ -29,7 +26,6 @@ ## WIP: -- g_hitStun [0/1]: turn on or off the melee hit stun. #### Cvar Gametypes: @@ -72,6 +68,11 @@ - [x] ~~g_blockDelay~~ - [x] ~~g_blockCost~~ - [x] ~~g_blockCostPct~~ +- [x] ~~g_hitStun~~ +- [x] ~~g_meleeOnly~~ +- [x] ~~g_meleeRange~~ +- [x] ~~g_meleeDiveRange~~ +- [x] ~~g_meleeDamage~~ #### Cvar Gametypes: diff --git a/source/cgame/cg_event.c b/source/cgame/cg_event.c index 3f71db2..9023f6a 100644 --- a/source/cgame/cg_event.c +++ b/source/cgame/cg_event.c @@ -222,9 +222,15 @@ static void CG_Obituary( entityState_t *ent ) { case MOD_GRAPPLE: message = "was caught by"; break; + // BFP - No gauntlet +#if 0 case MOD_GAUNTLET: message = "was pummeled by"; break; +#endif + case MOD_MELEE: // BFP - Melee + message = "was beaten up by"; + break; case MOD_MACHINEGUN: message = "was machinegunned by"; break; @@ -677,14 +683,34 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { CG_FireWeapon( cent ); break; - case EV_MELEE_READY: - // DEBUGNAME("EV_MELEE_READY"); - break; - - // BFP - TODO: Implement EV_MELEE (when punching someone using Melee) + // BFP - Melee case EV_MELEE: + { + int rndMeleeSnd = rand() % 5; DEBUGNAME("EV_MELEE"); + switch ( rndMeleeSnd ) { + case 0: { + trap_S_StartSound (NULL, es->number, CHAN_BODY, CG_CustomSound( es->number, "sound/bfp/melee_hit1.wav" ) ); + break; + } + case 1: { + trap_S_StartSound (NULL, es->number, CHAN_BODY, CG_CustomSound( es->number, "sound/bfp/melee_hit2.wav" ) ); + break; + } + case 2: { + trap_S_StartSound (NULL, es->number, CHAN_BODY, CG_CustomSound( es->number, "sound/bfp/melee_hit3.wav" ) ); + break; + } + case 3: { + trap_S_StartSound (NULL, es->number, CHAN_BODY, CG_CustomSound( es->number, "sound/bfp/melee_hit4.wav" ) ); + break; + } + default: { + trap_S_StartSound (NULL, es->number, CHAN_BODY, CG_CustomSound( es->number, "sound/bfp/melee_hit5.wav" ) ); + } + } break; + } // BFP - TODO: Implement EV_TIER_0-4 (Tiers) case EV_TIER_0: diff --git a/source/game/bg_pmove.c b/source/game/bg_pmove.c index 90043f9..938732e 100644 --- a/source/game/bg_pmove.c +++ b/source/game/bg_pmove.c @@ -48,7 +48,10 @@ float pm_spectatorfriction = 5.0f; int c_pmove = 0; // BFP - TODO: Macro for torso handling, since the code looked repetitive, so this macro makes the code a bit shorter -#define TORSOSTATUS_ANIM_HANDLING(other_torsostatus) ( pm->ps->pm_flags & PMF_BLOCK ) ? PM_ContinueTorsoAnim( TORSO_BLOCK ) : PM_ContinueTorsoAnim( other_torsostatus ) +#define TORSOSTATUS_ANIM_HANDLING(other_torsostatus) \ + ( pm->ps->pm_flags & PMF_BLOCK ) ? PM_ContinueTorsoAnim( TORSO_BLOCK ) : \ + ( ( pm->cmd.buttons & BUTTON_MELEE ) && !( pm->ps->pm_flags & PMF_MELEE ) ) ? PM_ContinueTorsoAnim( TORSO_MELEE_READY ) : \ + ( pm->ps->pm_flags & PMF_MELEE ) ? PM_ContinueTorsoAnim( TORSO_MELEE_STRIKE ) : PM_ContinueTorsoAnim( other_torsostatus ) // BFP - Macro for jump handling, since the code looked repetitive, so this macro makes the code a bit shorter #define FORCEJUMP_ANIM_HANDLING() ( pm->cmd.forwardmove >= 0 ) ? PM_ForceLegsAnim( LEGS_JUMP ) : PM_ForceLegsAnim( LEGS_JUMPB ) @@ -59,6 +62,12 @@ int c_pmove = 0; else if ( pm->cmd.forwardmove < 0 ) { TORSOSTATUS_ANIM_HANDLING( TORSO_FLYB ); PM_ContinueLegsAnim( LEGS_FLYB ); } \ else { TORSOSTATUS_ANIM_HANDLING( TORSO_STAND ); PM_ContinueLegsAnim( LEGS_FLYIDLE ); } +// BFP - Macro for melee strike handling, since the code looked repetitive, so this macro makes the code a bit shorter +#define CONTINUEMELEESTRIKE_LEGS_ANIM_HANDLING(condition) \ + /* Keep moving the legs when the player is attacking to the target through melee. If the condition variable isn't used leave using this value: 1 */ \ + if ( ( condition ) && ( pm->ps->pm_flags & PMF_MELEE ) \ + && !( pm->ps->pm_flags & PMF_HITSTUN ) && !( pm->ps->pm_flags & PMF_KI_CHARGE ) ) { PM_ContinueLegsAnim( LEGS_MELEE_STRIKE ); } + // BFP - Macro for movement handling in the slopes and when being near to the ground, since the code looked repetitive, so this macro makes the code a bit shorter #define SLOPES_NEARGROUND_ANIM_HANDLING(is_slope) \ if (is_slope) { \ @@ -78,7 +87,8 @@ int c_pmove = 0; return; \ } \ } \ - if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { PM_ContinueLegsAnim( LEGS_IDLE ); return; } \ + /* If it's very near to the other entity and the melee strike is executed, continue playing the melee strike legs animation */ \ + if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { PM_ContinueLegsAnim( LEGS_IDLE ); CONTINUEMELEESTRIKE_LEGS_ANIM_HANDLING( 1 ) return; } \ if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { \ if ( pm->cmd.forwardmove < 0 ) { PM_ContinueLegsAnim( LEGS_BACK ); TORSOSTATUS_ANIM_HANDLING( TORSO_STAND ); } \ else if ( pm->cmd.forwardmove > 0 \ @@ -88,7 +98,8 @@ int c_pmove = 0; else if ( pm->cmd.forwardmove > 0 \ || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { PM_ContinueLegsAnim( LEGS_WALK ); } \ TORSOSTATUS_ANIM_HANDLING( TORSO_STAND ); \ - } + } \ + CONTINUEMELEESTRIKE_LEGS_ANIM_HANDLING( 1 ) /* =============== @@ -589,6 +600,9 @@ static void PM_WaterMove( void ) { // BFP - Water animation handling, uses flying animation in that case CONTINUEFLY_ANIM_HANDLING() + // BFP - Melee strike legs animation + CONTINUEMELEESTRIKE_LEGS_ANIM_HANDLING( 1 ) + PM_SlideMove( qfalse ); } @@ -1055,16 +1069,32 @@ static void PM_CrashLand( void ) { PM_CheckStuck ============= */ -/* -void PM_CheckStuck(void) { +static void PM_CheckStuck(void) { + // BFP - NOTE: Curiously and originally, BFP uses this function to animate when the player is stuck, + // that can be tested when the player is pretty near to the other player + // or being stuck in the same origin as the other player, specially outside water. + // It has been implemented when melee animations were being used trace_t trace; pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask); if (trace.allsolid) { //int shit = qtrue; + + // BFP - TODO: Ki attack animation handling (these are for torso animations) + // Try to handle the animations when the player is shooting + + // BFP - Handle the animations when being stuck! (Only outside water) + if ( pm->waterlevel < 1 ) { + if ( pm->cmd.forwardmove < 0 ) { + PM_ContinueLegsAnim( LEGS_JUMPB ); + } else { + PM_ContinueLegsAnim( LEGS_JUMP ); + } + } + // BFP - Melee strike legs animation + CONTINUEMELEESTRIKE_LEGS_ANIM_HANDLING( 1 ) } } -*/ /* ============= @@ -1438,8 +1468,10 @@ static void PM_Footsteps( void ) { return; } - // BFP - Avoid when flying + // BFP - Avoid when flying (for melee strike animation, that's applied) if ( pm->ps->powerups[PW_FLIGHT] > 0 ) { + // BFP - Melee strike legs animation, don't apply if it's playing the starting jump animation in the flight status + CONTINUEMELEESTRIKE_LEGS_ANIM_HANDLING( pm->ps->pm_time <= 0 ) return; } @@ -1471,6 +1503,9 @@ static void PM_Footsteps( void ) { PM_ContinueLegsAnim( LEGS_SWIM ); } #endif + // BFP - PM_CheckStuck has been moved here, Q3 and the rest of mods hadn't used this + PM_CheckStuck(); + return; } @@ -1487,6 +1522,8 @@ static void PM_Footsteps( void ) { } else if ( !( pm->ps->pm_flags & PMF_DUCKED ) ) { // BFP - Handle the legs while it isn't doing nothing PM_ContinueLegsAnim( LEGS_IDLE ); } + // BFP - Melee strike legs animation + CONTINUEMELEESTRIKE_LEGS_ANIM_HANDLING( 1 ) return; } @@ -1539,6 +1576,9 @@ static void PM_Footsteps( void ) { } } + // BFP - Melee strike legs animation + CONTINUEMELEESTRIKE_LEGS_ANIM_HANDLING( 1 ) + // check for footstep / splash sounds old = pm->ps->bobCycle; pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; @@ -1670,7 +1710,7 @@ static void PM_TorsoAnimation( void ) { trace_t trace; vec3_t point; - // BFP - TODO: Melee and ki attack animation handling (these are for torso animations) + // BFP - TODO: Ki attack animation handling (these are for torso animations) // Keep in mind about the implementations of the steep slopes, // TORSOSTATUS_ANIM_HANDLING( TORSO_STAND ) and // !pm->cmd.forwardmove && !pm->cmd.rightmove && !pm->cmd.buttons thingies @@ -1717,6 +1757,9 @@ static void PM_TorsoAnimation( void ) { SLOPES_NEARGROUND_ANIM_HANDLING( 0 ) } + // BFP - Melee strike legs animation, don't apply if it isn't touching the ground + CONTINUEMELEESTRIKE_LEGS_ANIM_HANDLING( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + #if 0 if ( pm->ps->weaponstate == WEAPON_READY ) { if ( pm->ps->weapon == WP_GAUNTLET ) { @@ -1853,6 +1896,20 @@ static void PM_HitStunAnimation( void ) { // BFP - Hit stun } } +/* +============== +PM_Melee +============== +*/ +static void PM_Melee( void ) { // BFP - Melee + // Don't allow pressing ki attack and block buttons when melee is being used + if ( ( pm->ps->pm_flags & PMF_MELEE ) + || ( pm->cmd.buttons & BUTTON_MELEE ) ) { + pm->cmd.buttons &= ~( BUTTON_ATTACK | BUTTON_BLOCK ); + } +} + + /* ============== PM_Weapon @@ -1907,6 +1964,11 @@ static void PM_Weapon( void ) { pm->ps->weaponTime -= pml.msec; } + // BFP - Hit stun melee delay time + if ( pm->ps->stats[STAT_HITSTUN_MELEE_DELAY] > 0 ) { + pm->ps->stats[STAT_HITSTUN_MELEE_DELAY] -= pml.msec; + } + // check for weapon change // can't change if weapon is firing, but can change // again if lowering or raising @@ -1926,6 +1988,16 @@ static void PM_Weapon( void ) { return; } + // BFP - Melee, avoid shooting if the player is in this status + if ( pm->cmd.buttons & BUTTON_MELEE ) { + // Melee fight handling + if ( pm->gauntletHit && pm->ps->weaponTime <= 0 ) { + pm->ps->weaponTime += 300; + pm->ps->pm_flags |= PMF_MELEE; + } + return; + } + // BFP - No weapon raising handling #if 0 if ( pm->ps->weaponstate == WEAPON_RAISING ) { @@ -2422,6 +2494,9 @@ void PmoveSingle (pmove_t *pmove) { // BFP - Block PM_BlockTime(); + // BFP - Melee + PM_Melee(); + PM_DropTimers(); // BFP - Flight @@ -2532,14 +2607,12 @@ void Pmove (pmove_t *pmove) { pmove->cmd.upmove = 20; } } - - //PM_CheckStuck(); - } // BFP - Undefine the macros #undef TORSOSTATUS_ANIM_HANDLING #undef FORCEJUMP_ANIM_HANDLING #undef CONTINUEFLY_ANIM_HANDLING +#undef CONTINUEMELEESTRIKE_LEGS_ANIM_HANDLING #undef SLOPES_NEARGROUND_ANIM_HANDLING diff --git a/source/game/bg_public.h b/source/game/bg_public.h index fc541a8..e3dfcba 100644 --- a/source/game/bg_public.h +++ b/source/game/bg_public.h @@ -148,6 +148,7 @@ typedef enum { #define PMF_NEARGROUND 8 // BFP - Near ground check // BFP - PMF_BACKWARDS_RUN is unused // #define PMF_BACKWARDS_RUN 16 // coast down to backwards run +#define PMF_MELEE 16 // BFP - Melee // BFP - PMF_TIME_LAND is unused // #define PMF_TIME_LAND 32 // pm_time is time before rejump #define PMF_FALLING 32 // BFP - Falling status @@ -220,6 +221,7 @@ typedef enum { STAT_HEALTH, STAT_KI, // BFP - KI amount STAT_BLOCK, // BFP - Block amount treated as time + STAT_HITSTUN_MELEE_DELAY, // BFP - Hitstun delay time status STAT_HOLDABLE_ITEM, STAT_WEAPONS, // 16 bit fields STAT_ARMOR, @@ -680,6 +682,7 @@ typedef enum { typedef enum { MOD_UNKNOWN, MOD_SHOTGUN, + MOD_MELEE, // BFP - Melee MOD_GAUNTLET, MOD_MACHINEGUN, MOD_GRENADE, diff --git a/source/game/g_active.c b/source/game/g_active.c index 5f43620..57b9dfd 100644 --- a/source/game/g_active.c +++ b/source/game/g_active.c @@ -816,6 +816,11 @@ void ClientThink_real( gentity_t *ent ) { } // BFP - End of block handling + // BFP - Melee handling + if ( !( ucmd->buttons & BUTTON_MELEE ) ) { + client->ps.pm_flags &= ~PMF_MELEE; + } + // BFP - Ki Charge if ( ( ucmd->buttons & BUTTON_KI_CHARGE ) && client->ps.pm_time <= 0 && ( client->ps.pm_flags & PMF_KI_CHARGE ) ) { @@ -845,12 +850,22 @@ void ClientThink_real( gentity_t *ent ) { memset (&pm, 0, sizeof(pm)); +// BFP - No gauntlet hit check +#if 0 // check for the hit-scan gauntlet, don't let the action // go through as an attack unless it actually hits something if ( client->ps.weapon == WP_GAUNTLET && !( ucmd->buttons & BUTTON_TALK ) && ( ucmd->buttons & BUTTON_ATTACK ) && client->ps.weaponTime <= 0 ) { pm.gauntletHit = CheckGauntletAttack( ent ); } +#endif + + // BFP - Melee + if ( !( ucmd->buttons & BUTTON_TALK ) && ( ucmd->buttons & BUTTON_MELEE ) + && !( client->ps.pm_flags & PMF_KI_CHARGE ) + && client->ps.weaponTime <= 0 ) { + pm.gauntletHit = CheckMeleeAttack( ent ); + } if ( ent->flags & FL_FORCE_GESTURE ) { ent->flags &= ~FL_FORCE_GESTURE; diff --git a/source/game/g_cmds.c b/source/game/g_cmds.c index 1b891a6..e700764 100644 --- a/source/game/g_cmds.c +++ b/source/game/g_cmds.c @@ -1703,12 +1703,12 @@ void Cmd_BFP_Block_f( gentity_t* ent ) { // BFP - Block } } -void Cmd_BFP_StartMelee_f( gentity_t* ent ) { // BFP - TODO: Start melee - Com_Printf( "Cmd_BFP_StartMelee_f\n" ); +void Cmd_BFP_StartMelee_f( gentity_t* ent ) { // BFP - Start melee + // BFP - NOTE: That command was left without finishing the implementation to start melee } -void Cmd_BFP_StopMelee_f( gentity_t* ent ) { // BFP - TODO: Stop melee - Com_Printf( "Cmd_BFP_StopMelee_f\n" ); +void Cmd_BFP_StopMelee_f( gentity_t* ent ) { // BFP - Stop melee + // BFP - NOTE: That command was left without finishing the implementation to stop melee } @@ -1833,9 +1833,9 @@ void ClientCommand( int clientNum ) { Cmd_BFP_SelectCharacter_f( ent ); else if (Q_stricmp (cmd, "block") == 0) // BFP - Block Cmd_BFP_Block_f( ent ); - else if (Q_stricmp (cmd, "start_melee") == 0) // BFP - TODO: Start melee + else if (Q_stricmp (cmd, "start_melee") == 0) // BFP - Start melee Cmd_BFP_StartMelee_f( ent ); - else if (Q_stricmp (cmd, "stop_melee") == 0) // BFP - TODO: Stop melee + else if (Q_stricmp (cmd, "stop_melee") == 0) // BFP - Stop melee Cmd_BFP_StopMelee_f( ent ); else trap_SendServerCommand( clientNum, va("print \"unknown cmd %s\n\"", cmd ) ); diff --git a/source/game/g_combat.c b/source/game/g_combat.c index 5cc36b8..d02469e 100644 --- a/source/game/g_combat.c +++ b/source/game/g_combat.c @@ -764,14 +764,6 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, damage *= 0.5; } -// BFP - TODO: Make hit stun received from melee attack with PW_HASTE -#if 0 - if ( g_hitStun.integer >= 1 && ( attacker->client->ps.powerups[PW_HASTE] > 0 ) - && targ != attacker && !OnSameTeam (targ, attacker) ) { - attacker->client->ps.hitStunTime = -3; // just an idea, enable the hit stun with a conditional of == -3 - } -#endif - // add to the attacker's hit counter (if the target isn't a general entity like a prox mine) if ( attacker->client && targ != attacker && targ->health > 0 && targ->s.eType != ET_MISSILE diff --git a/source/game/g_cvar.h b/source/game/g_cvar.h index fa78c7b..c0092b8 100644 --- a/source/game/g_cvar.h +++ b/source/game/g_cvar.h @@ -64,12 +64,12 @@ G_CVAR( g_blood, "com_blood", "1", 0, 0, qfalse ) G_CVAR( g_basePL, "g_basePL", "150", 0, 0, qtrue ) // BFP - Base powerlevel G_CVAR( g_allowSpectatorChat, "g_allowSpectatorChat", "", 0, 0, qtrue ) // BFP - Allow spectator chat -G_CVAR( g_meleeDamage, "g_meleeDamage", "10", 0, 0, qtrue ) // BFP - Melee damage -G_CVAR( g_meleeDiveRange, "g_meleeDiveRange", "700", 0, 0, qtrue ) // BFP - Melee dive range -G_CVAR( g_meleeRange, "g_meleeRange", "32", 0, 0, qtrue ) // BFP - Melee range +G_CVAR( g_meleeDamage, "g_meleeDamage", "10", CVAR_ARCHIVE, 0, qtrue ) // BFP - Melee damage +G_CVAR( g_meleeDiveRange, "g_meleeDiveRange", "700", CVAR_ARCHIVE, 0, qtrue ) // BFP - Melee dive range +G_CVAR( g_meleeRange, "g_meleeRange", "32", CVAR_ARCHIVE, 0, qtrue ) // BFP - Melee range G_CVAR( g_chargeDelay, "g_chargeDelay", "750", 0, 0, qtrue ) // BFP - Charge delay G_CVAR( g_hitStun, "g_hitStun", "", 0, 0, qtrue ) // BFP - Hit stun -G_CVAR( g_meleeOnly, "g_meleeOnly", "", 0, 0, qtrue ) // BFP - Melee only +G_CVAR( g_meleeOnly, "g_meleeOnly", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qtrue ) // BFP - Melee only G_CVAR( g_noFlight, "g_noFlight", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qtrue ) // BFP - No flight G_CVAR( g_plKillBonusPct, "g_plKillBonusPct", ".1", 0, 0, qtrue ) // BFP - Kill bonus percentage G_CVAR( g_maxSpawnPL, "g_maxSpawnPL", "999", 0, 0, qtrue ) // BFP - Max spawn powerlevel diff --git a/source/game/g_local.h b/source/game/g_local.h index a4af6f6..dd4dc11 100644 --- a/source/game/g_local.h +++ b/source/game/g_local.h @@ -544,7 +544,8 @@ void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ); qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ); void CalcMuzzlePoint ( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ); void SnapVectorTowards( vec3_t v, vec3_t to ); -qboolean CheckGauntletAttack( gentity_t *ent ); +// qboolean CheckGauntletAttack( gentity_t *ent ); // BFP - No check gauntlet attack +qboolean CheckMeleeAttack( gentity_t *ent ); // BFP - Melee void Weapon_HookFree (gentity_t *ent); void Weapon_HookThink (gentity_t *ent); diff --git a/source/game/g_weapon.c b/source/game/g_weapon.c index 5dc371e..e0060aa 100644 --- a/source/game/g_weapon.c +++ b/source/game/g_weapon.c @@ -61,6 +61,8 @@ void Weapon_Gauntlet( gentity_t *ent ) { } +// BFP - No check gauntlet attack +#if 0 /* =============== CheckGauntletAttack @@ -112,7 +114,168 @@ qboolean CheckGauntletAttack( gentity_t *ent ) { return qtrue; } +#endif + +/* +=================== +GetEntityNearMeleeRadius +=================== +*/ +gentity_t *GetEntityNearMeleeRadius( vec3_t point, gentity_t *attacker, gentity_t *target ) { // BFP - Melee near radius detection + int i, num, touch[MAX_GENTITIES]; + vec3_t mins, maxs; + gentity_t *prevTarget = target; + + // if already exists, don't apply calculation detection + if ( prevTarget->client ) { + return prevTarget; + } + + VectorAdd( point, attacker->r.mins, mins ); + VectorAdd( point, attacker->r.maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for ( i = 0 ; i < num ; i++ ) { + target = &g_entities[ touch[i] ]; + if ( target->client && target->client != attacker->client ) { + return target; + } + } + + return prevTarget; +} + +/* +=============== +CheckMeleeAttack +=============== +*/ +qboolean CheckMeleeAttack( gentity_t *attacker ) { // BFP - Melee + trace_t tr; + vec3_t velocity, end; + gentity_t *traceTarget; + + // set aiming directions + AngleVectors( attacker->client->ps.viewangles, forward, NULL, NULL ); + + CalcMuzzlePoint( attacker, forward, NULL, NULL, muzzle ); + VectorMA( muzzle, g_meleeDiveRange.integer, forward, end ); + + // that part is where the target can be detected when the attacker is on air, if not, the trace will be different + trap_Trace( &tr, muzzle, attacker->r.mins, attacker->r.maxs, end, attacker->s.number, MASK_SHOT ); + if ( attacker->client->ps.groundEntityNum != ENTITYNUM_NONE ) { + trap_Trace( &tr, muzzle, NULL, NULL, end, attacker->s.number, MASK_SHOT ); + } + + // when the attacker is very near from the target, continue attacking if that happens + traceTarget = GetEntityNearMeleeRadius( muzzle, attacker, &g_entities[ tr.entityNum ] ); + + // stop melee if there's no entity + if ( !traceTarget->takedamage ) { + attacker->client->ps.pm_flags &= ~PMF_MELEE; + return qfalse; + } + + // the target's corpse is starting to sink, avoid interacting with a sinking corpse, nothing special happens + if ( traceTarget->physicsObject ) { + attacker->client->ps.pm_flags &= ~PMF_MELEE; + return qfalse; + } + + // BFP - NOTE: Apply g_friendlyFire for melee, originally in BFP, friendly fire wasn't never applied + // stop melee if the entity is in the same team + if ( OnSameTeam( traceTarget, attacker ) ) { + attacker->client->ps.pm_flags &= ~PMF_MELEE; + return qfalse; + } + + // ENTITY DETECTED! + if ( traceTarget->client ) { + gentity_t *target; + vec3_t direction; + float distance; + // BFP - Melee range, it isn't known why, but it's the approximation + float rangeMultiplier = g_meleeRange.integer + 45; + + VectorSubtract( traceTarget->client->ps.origin, attacker->client->ps.origin, direction ); + distance = VectorLength( direction ); + + // distance from the attacker and the target, more range = attacker can attack at that distance without being teleported + // the distance needs to be greater than 25, otherwise it won't respect the lengths around the target + if ( distance >= rangeMultiplier && distance >= 25 ) { + // trace only when the player is alive + if ( traceTarget->client->ps.pm_type != PM_DEAD ) { + trap_Trace( &tr, muzzle, attacker->r.mins, attacker->r.maxs, end, attacker->s.number, MASK_PLAYERSOLID ); + } + + // if the target is very near to some brush (solid or surface with no impact) from the map + // avoid the attacker teleporting there, otherwise gets stuck + if ( tr.startsolid || tr.allsolid ) { + attacker->client->ps.pm_flags &= ~PMF_MELEE; + return qfalse; + } + + // if the target position is being covered under something solid (e.g. a brush from the map), + // avoid the attacker teleporting there, otherwise gets stuck + target = &g_entities[ tr.entityNum ]; + if ( target->client->ps.pm_type != PM_DEAD && !target->takedamage ) { + attacker->client->ps.pm_flags &= ~PMF_MELEE; + return qfalse; + } + + // try to trace when the target is dead, that's what BFP originally did, and teleport near the corpse + if ( traceTarget->client->ps.pm_type == PM_DEAD ) { + trap_Trace( &tr, muzzle, attacker->r.mins, attacker->r.maxs, end, attacker->s.number, MASK_PLAYERSOLID ); + } + + // TELEPORT! + if ( attacker->client->ps.origin[2] == target->client->ps.origin[2] ) { + tr.endpos[2] = target->client->ps.origin[2]; + } + VectorCopy( tr.endpos, attacker->client->ps.origin ); + } + + // PUSH AND DEAL DAMAGE! + if ( g_meleeDamage.integer > 0 ) { + float forceMultiplier = 100.0f + 2.0f * (g_meleeDamage.integer - 10); + + // only when the target isn't dead + if ( traceTarget->client->ps.pm_type != PM_DEAD ) { + VectorSubtract( traceTarget->client->ps.origin, attacker->client->ps.origin, velocity ); + VectorNormalize( velocity ); + + // apply the push force (proportional to the melee damage) in the direction of the attack + VectorScale( velocity, g_meleeDamage.integer * forceMultiplier, traceTarget->client->ps.velocity ); + } + + G_Damage ( traceTarget, attacker, attacker, velocity, attacker->s.origin, + g_meleeDamage.integer, 0, MOD_MELEE ); + } + } + + // melee sound event is randomly executed + { + int rndSnd = rand() % 6; + if ( rndSnd > 4 ) { + G_AddEvent( attacker, EV_MELEE, 0 ); + } + } + + // if the attacker is using ki boost, stun the target! (g_hitStun enabled only) + if ( g_hitStun.integer >= 1 + && attacker->client->ps.powerups[PW_HASTE] > 0 + && !( traceTarget->client->ps.pm_flags & PMF_HITSTUN ) + && !( traceTarget->client->ps.pm_flags & PMF_BLOCK ) + && attacker->client->ps.stats[STAT_HITSTUN_MELEE_DELAY] <= 0 ) { + // add 3 seconds to the hitstun when there's no delay + traceTarget->client->ps.pm_time = 3000; + traceTarget->client->ps.pm_flags |= PMF_HITSTUN; + attacker->client->ps.stats[STAT_HITSTUN_MELEE_DELAY] += 6000; + } + + return qtrue; +} /* ======================================================================