diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bc10b1b995..aebcbccb5f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,6 +62,14 @@ jobs: shell: bash run: cmake --build . --config ${{ matrix.build_type }} -j $NUMBER_OF_PROCESSORS + - name: Copy PK3s + if: ${{ matrix.build_type == 'Release' }} + working-directory: ${{ github.workspace }} + shell: bash + run: | + mkdir -p ./build/bin/JediAcademy/base/ + cp ./pk3/assets_fpls.pk3 ./build/bin/JediAcademy/base/ + - name: Install if: ${{ matrix.build_type == 'Release' }} working-directory: ${{ github.workspace }}/build @@ -137,6 +145,12 @@ jobs: shell: bash run: cmake --install . + - name: Copy PK3s + if: ${{ matrix.build_type == 'Release' }} + working-directory: ${{ github.workspace }} + shell: bash + run: cp ./pk3/assets_fpls.pk3 ./install/JediAcademy/base/ + - name: Create OpenJK binary archive if: ${{ matrix.build_type == 'Release' }} working-directory: ${{ github.workspace }}/install/JediAcademy @@ -210,6 +224,14 @@ jobs: shell: bash run: cmake --install . + - name: Copy PK3s + if: ${{ matrix.build_type == 'Release' }} + working-directory: ${{ github.workspace }} + shell: bash + run: | + mkdir -p ./install/JediAcademy/openjk_sp.${{ matrix.arch }}.app/Contents/MacOS/base/ + cp ./pk3/assets_fpls.pk3 ./install/JediAcademy/openjk_sp.${{ matrix.arch }}.app/Contents/MacOS/base/ + - name: Create OpenJK binary archive if: ${{ matrix.build_type == 'Release' }} working-directory: ${{ github.workspace }}/install/JediAcademy diff --git a/code/cgame/cg_consolecmds.cpp b/code/cgame/cg_consolecmds.cpp index eb4b79a7aa..2fc24edae3 100644 --- a/code/cgame/cg_consolecmds.cpp +++ b/code/cgame/cg_consolecmds.cpp @@ -119,7 +119,7 @@ void CG_ToggleBinoculars( void ) cg.zoomMode = 1; cg.zoomLocked = qfalse; - if ( cg.weaponSelect == WP_SABER ) + if ( cg.weaponSelect == WP_SABER || cg.weaponSelect == WP_MELEE ) { cg.weaponSelect = WP_NONE; } @@ -151,6 +151,10 @@ void CG_ToggleBinoculars( void ) // FIXME: this is pretty damn ugly but whatever cg.weaponSelect = WP_SABER; } + else if( cg.weaponSelect == WP_NONE && cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << WP_MELEE ) ) + { + cg.weaponSelect = WP_MELEE; + } } } @@ -177,6 +181,12 @@ void CG_ToggleLAGoggles( void ) cg.zoomMode = 3; cg.zoomLocked = qfalse; + + if ( cg.weaponSelect == WP_SABER || cg.weaponSelect == WP_MELEE ) + { + cg.weaponSelect = WP_NONE; + } + if ( cg.overrides.active & CG_OVERRIDE_FOV ) { cg_zoomFov = cg.overrides.fov; @@ -193,6 +203,16 @@ void CG_ToggleLAGoggles( void ) cg.zoomMode = 0; cg.zoomTime = cg.time; cgi_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.media.zoomEnd ); + + if( cg.weaponSelect == WP_NONE && cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << WP_SABER ) ) + { + // FIXME: this is pretty damn ugly but whatever + cg.weaponSelect = WP_SABER; + } + else if( cg.weaponSelect == WP_NONE && cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << WP_MELEE ) ) + { + cg.weaponSelect = WP_MELEE; + } } } diff --git a/code/cgame/cg_draw.cpp b/code/cgame/cg_draw.cpp index f6c589aabc..a0f2b9ef8b 100644 --- a/code/cgame/cg_draw.cpp +++ b/code/cgame/cg_draw.cpp @@ -328,7 +328,7 @@ static void CG_DrawAmmo(const centity_t *cent,const int xPos,const int yPos) return; } - if ( cent->currentState.weapon == WP_STUN_BATON ) + if ( cent->currentState.weapon == WP_STUN_BATON || cent->currentState.weapon == WP_MELEE ) { return; } diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 8c0960f8b4..c13d8b29b4 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -624,6 +624,7 @@ extern vmCvar_t cg_thirdPersonPitchOffset; extern vmCvar_t cg_thirdPersonVertOffset; extern vmCvar_t cg_thirdPersonCameraDamp; extern vmCvar_t cg_thirdPersonTargetDamp; +// extern vmCvar_t cg_saberAutoThird; extern vmCvar_t cg_gunAutoFirst; extern vmCvar_t cg_stereoSeparation; diff --git a/code/cgame/cg_main.cpp b/code/cgame/cg_main.cpp index db6f7dc4e0..6b6c9fc834 100644 --- a/code/cgame/cg_main.cpp +++ b/code/cgame/cg_main.cpp @@ -299,6 +299,7 @@ vmCvar_t cg_thirdPersonPitchOffset; vmCvar_t cg_thirdPersonVertOffset; vmCvar_t cg_thirdPersonCameraDamp; vmCvar_t cg_thirdPersonTargetDamp; +// vmCvar_t cg_saberAutoThird; vmCvar_t cg_gunAutoFirst; vmCvar_t cg_thirdPersonAlpha; @@ -352,7 +353,7 @@ static cvarTable_t cvarTable[] = { { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE }, { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, { &cg_fov, "cg_fov", "80", CVAR_ARCHIVE }, - { &cg_fovAspectAdjust, "cg_fovAspectAdjust", "0", CVAR_ARCHIVE }, + { &cg_fovAspectAdjust, "cg_fovAspectAdjust", "1", CVAR_ARCHIVE }, { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, { &cg_renderToTextureFX, "cg_renderToTextureFX", "1", CVAR_ARCHIVE }, @@ -424,6 +425,7 @@ static cvarTable_t cvarTable[] = { { &cg_thirdPersonAlpha, "cg_thirdPersonAlpha", "1.0", CVAR_ARCHIVE }, { &cg_thirdPersonAutoAlpha, "cg_thirdPersonAutoAlpha", "0", 0 }, // NOTE: also declare this in UI_Init + // { &cg_saberAutoThird, "cg_saberAutoThird", "1", CVAR_ARCHIVE }, { &cg_gunAutoFirst, "cg_gunAutoFirst", "1", CVAR_ARCHIVE }, { &cg_pano, "pano", "0", 0 }, @@ -1368,7 +1370,7 @@ static void CG_RegisterGraphics( void ) { // FIXME: do these conditionally cgi_R_RegisterShader( "gfx/2d/workingCamera" ); cgi_R_RegisterShader( "gfx/2d/brokenCamera" ); - //cgi_R_RegisterShader( "gfx/effects/irid_shield" ); // for galak, but he doesn't have his own weapon so I can't register the shader there. + cgi_R_RegisterShader( "gfx/effects/irid_shield" ); // for galak, but he doesn't have his own weapon so I can't register the shader there. //interface for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) { diff --git a/code/cgame/cg_players.cpp b/code/cgame/cg_players.cpp index 9a6e6aa4f8..bc4a870691 100644 --- a/code/cgame/cg_players.cpp +++ b/code/cgame/cg_players.cpp @@ -4855,7 +4855,7 @@ void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, centity_t *cen //------------------------------------------------------ if ( powerups & ( 1 << PW_GALAK_SHIELD )) { -/* refEntity_t tent; + refEntity_t tent; memset( &tent, 0, sizeof( refEntity_t )); @@ -4882,7 +4882,7 @@ void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, centity_t *cen tent.endTime = gent->fx_time + 1000; // if you want the shell to build around the guy, pass in a time that is 1000ms after the start of the turn-on-effect tent.customShader = cgi_R_RegisterShader( "gfx/effects/irid_shield" ); - cgi_R_AddRefEntityToScene( &tent );*/ + cgi_R_AddRefEntityToScene( &tent ); } // Invincibility -- effect needs work @@ -6838,7 +6838,7 @@ void CG_Player( centity_t *cent ) { return; } - if( cent->gent->s.number == 0 && cg.weaponSelect == WP_NONE && cg.zoomMode == 1 ) + if( cent->gent->s.number == 0 && cg.weaponSelect == WP_NONE && (cg.zoomMode == 1 || cg.zoomMode == 3) ) { // HACK return; @@ -7233,6 +7233,17 @@ extern vmCvar_t cg_thirdPersonAlpha; } } + /*if ( !cg.renderingThirdPerson + && ( cg.snap->ps.weapon == WP_SABER || cg.snap->ps.weapon == WP_MELEE ) + && !cent->gent->s.number ) + {// Yeah um, this needs to not do this quite this way + ent.customSkin = cgi_R_RegisterSkin( "models/players/kyle/model_fpls.skin" ); //precached in g_client.cpp + } + else + { + ent.customSkin = 0; + }*/ + if ( cg_debugHealthBars.integer ) { if ( cent->gent && cent->gent->health > 0 && cent->gent->max_health > 0 ) diff --git a/code/cgame/cg_view.cpp b/code/cgame/cg_view.cpp index 7b8b2a9d4f..be8381d628 100644 --- a/code/cgame/cg_view.cpp +++ b/code/cgame/cg_view.cpp @@ -463,7 +463,7 @@ static void CG_CalcIdealThirdPersonViewLocation(void) VectorMA(cameraIdealTarget, -(cg_thirdPersonRange.value), camerafwd, cameraIdealLoc); } - if ( cg.renderingThirdPerson && (cg.snap->ps.forcePowersActive&(1<client->ps.forcePowerDuration[FP_SPEED] ) + /*if ( cg.renderingThirdPerson && (cg.snap->ps.forcePowersActive&(1<client->ps.forcePowerDuration[FP_SPEED] ) { float timeLeft = player->client->ps.forcePowerDuration[FP_SPEED] - cg.time; float length = FORCE_SPEED_DURATION*forceSpeedValue[player->client->ps.forcePowerLevel[FP_SPEED]]; @@ -480,7 +480,7 @@ static void CG_CalcIdealThirdPersonViewLocation(void) { VectorMA(cameraIdealLoc, amt, camerafwd, cameraIdealLoc); } - } + }*/ } diff --git a/code/client/snd_dma.cpp b/code/client/snd_dma.cpp index bb539e4cd6..6e4bbd757f 100644 --- a/code/client/snd_dma.cpp +++ b/code/client/snd_dma.cpp @@ -4805,14 +4805,14 @@ static qboolean S_UpdateBackgroundTrack_Actual( MusicInfo_t *pMusicInfo, qboolea // or if it's a dynamic music specifier (which can't literally exist), in which case it should set // a return flag then exit... // - char sTestName[MAX_QPATH*2];// *2 so COM_DefaultExtension doesn't do an ERR_DROP if there was no space + char sTestName[MAX_QPATH/**2*/];// *2 so COM_DefaultExtension doesn't do an ERR_DROP if there was no space // for an extension, since this is a "soft" test - Q_strncpyz( sTestName, sMusic_BackgroundLoop, sizeof(sTestName)); + Q_strncpyz( sTestName, sMusic_BackgroundLoop, sizeof(sTestName) - 4 ); COM_DefaultExtension(sTestName, sizeof(sTestName), ".mp3"); if (S_FileExists( sTestName )) { - S_StartBackgroundTrack_Actual( pMusicInfo, qfalse, sMusic_BackgroundLoop, sMusic_BackgroundLoop ); + S_StartBackgroundTrack_Actual( pMusicInfo, qfalse, sTestName, sTestName ); } else { diff --git a/code/game/AI_GalakBoss.cpp b/code/game/AI_GalakBoss.cpp new file mode 100644 index 0000000000..5f27eeedeb --- /dev/null +++ b/code/game/AI_GalakBoss.cpp @@ -0,0 +1,1277 @@ +/* +=========================================================================== +Copyright (C) 2000 - 2013, Raven Software, Inc. +Copyright (C) 2001 - 2013, Activision, Inc. +Copyright (C) 2013 - 2015, OpenJK contributors + +This file is part of the OpenJK source code. + +OpenJK is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see . +=========================================================================== +*/ +#include "b_local.h" +#include "g_functions.h" +#include "g_nav.h" +#include "g_navigator.h" +#include "../cgame/cg_local.h" +#include "anims.h" +#include "wp_saber.h" + +extern qboolean G_StandardHumanoid( gentity_t *self ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); +extern void NPC_AimAdjust( int change ); +extern qboolean WP_LobFire( gentity_t *self, vec3_t start, vec3_t target, vec3_t mins, vec3_t maxs, int clipmask, + vec3_t velocity, qboolean tracePath, int ignoreEntNum, int enemyNum, + float minSpeed, float maxSpeed, float idealSpeed, qboolean mustHit ); +extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f ); +extern void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast ); +extern void G_SoundOnEnt (gentity_t *ent, soundChannel_t channel, const char *soundPath); +extern qboolean PM_CrouchAnim( int anim ); +//extern void NPC_Mark1_Part_Explode(gentity_t *self,int bolt); + +#define MELEE_DIST_SQUARED 6400//80*80 +#define MIN_LOB_DIST_SQUARED 65536//256*256 +#define MAX_LOB_DIST_SQUARED 200704//448*448 +#define REPEATER_ALT_SIZE 3 // half of bbox size +#define GENERATOR_HEALTH 25 +#define TURN_ON 0x00000000 +#define TURN_OFF 0x00000100 +#define GALAK_SHIELD_HEALTH 500 + +static vec3_t shieldMins = {-60, -60, -24 }; +static vec3_t shieldMaxs = {60, 60, 80}; + +extern qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS; +static qboolean enemyCS; +static qboolean hitAlly; +static qboolean faceEnemy; +static qboolean AImove; +static qboolean shoot; +static float enemyDist; +static vec3_t impactPos; + +void NPC_GalakMech_Precache( void ) +{ + G_SoundIndex( "sound/weapons/galak/skewerhit.wav" ); + G_SoundIndex( "sound/weapons/galak/lasercharge.wav" ); + G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); + G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ); + + G_EffectIndex( "galak/trace_beam" ); + G_EffectIndex( "galak/beam_warmup" ); +// G_EffectIndex( "small_chunks"); + G_EffectIndex( "env/med_explode2"); + G_EffectIndex( "env/small_explode2"); + G_EffectIndex( "galak/explode"); + G_EffectIndex( "blaster/smoke_bolton"); +// G_EffectIndex( "env/exp_trail_comp"); +} + +void NPC_GalakMech_Init( gentity_t *ent ) +{ + if (ent->NPC->behaviorState != BS_CINEMATIC) + { + ent->client->ps.stats[STAT_ARMOR] = GALAK_SHIELD_HEALTH; + ent->NPC->investigateCount = ent->NPC->investigateDebounceTime = 0; + ent->flags |= FL_SHIELDED;//reflect normal shots + ent->client->ps.powerups[PW_GALAK_SHIELD] = Q3_INFINITE;//temp, for effect + ent->fx_time = level.time; + VectorSet( ent->mins, -60, -60, -24 ); + VectorSet( ent->maxs, 60, 60, 80 ); + ent->flags |= FL_NO_KNOCKBACK;//don't get pushed + TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels + TIMER_Set( ent, "flee", 0 ); + TIMER_Set( ent, "smackTime", 0 ); + TIMER_Set( ent, "beamDelay", 0 ); + TIMER_Set( ent, "noLob", 0 ); + TIMER_Set( ent, "noRapid", 0 ); + TIMER_Set( ent, "talkDebounce", 0 ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "torso_shield_off", TURN_ON ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "torso_galakface_off", TURN_OFF ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "torso_galakhead_off", TURN_OFF ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "torso_eyes_mouth_off", TURN_OFF ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "torso_collar_off", TURN_OFF ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "torso_galaktorso_off", TURN_OFF ); + } + else + { +// gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "helmet", TURN_OFF ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "torso_shield_off", TURN_OFF ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "torso_galakface_off", TURN_ON ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "torso_galakhead_off", TURN_ON ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "torso_eyes_mouth_off", TURN_ON ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "torso_collar_off", TURN_ON ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "torso_galaktorso_off", TURN_ON ); + } + +} + +//----------------------------------------------------------------- +static void GM_CreateExplosion( gentity_t *self, const int boltID, qboolean doSmall = qfalse ) +{ + if ( boltID >=0 ) + { + mdxaBone_t boltMatrix; + vec3_t org, dir; + + gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, + boltID, + &boltMatrix, self->currentAngles, self->currentOrigin, (cg.time?cg.time:level.time), + NULL, self->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir ); + + if ( doSmall ) + { + G_PlayEffect( "env/small_explode2", org, dir ); + } + else + { + G_PlayEffect( "env/med_explode2", org, dir ); + } + } +} + +/* +------------------------- +GM_Dying +------------------------- +*/ + +void GM_Dying( gentity_t *self ) +{ + if ( level.time - self->s.time < 4000 ) + {//FIXME: need a real effect + self->s.powerups |= ( 1 << PW_SHOCKED ); + self->client->ps.powerups[PW_SHOCKED] = level.time + 1000; + if ( TIMER_Done( self, "dyingExplosion" ) ) + { + int newBolt; + switch ( Q_irand( 1, 14 ) ) + { + // Find place to generate explosion + case 1: + if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "r_hand" )) + {//r_hand still there + GM_CreateExplosion( self, self->handRBolt, qtrue ); + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "r_hand", TURN_OFF ); + } + else if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "r_arm_middle" )) + {//r_arm_middle still there + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*r_arm_elbow" ); + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "r_arm_middle", TURN_OFF ); + } + break; + case 2: + //FIXME: do only once? + if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_hand" )) + {//l_hand still there + GM_CreateExplosion( self, self->handLBolt ); + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_hand", TURN_OFF ); + } + else if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_arm_wrist" )) + {//l_arm_wrist still there + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*l_arm_cap_l_hand" ); + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_arm_wrist", TURN_OFF ); + } + else if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_arm_middle" )) + {//l_arm_middle still there + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*l_arm_cap_l_hand" ); + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_arm_middle", TURN_OFF ); + } + else if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_arm_augment" )) + {//l_arm_augment still there + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*l_arm_elbow" ); + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_arm_augment", TURN_OFF ); + } + break; + case 3: + case 4: + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*hip_fr" ); + GM_CreateExplosion( self, newBolt ); + break; + case 5: + case 6: + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*shldr_l" ); + GM_CreateExplosion( self, newBolt ); + break; + case 7: + case 8: + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*uchest_r" ); + GM_CreateExplosion( self, newBolt ); + break; + case 9: + case 10: + GM_CreateExplosion( self, self->headBolt ); + break; + case 11: + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*l_leg_knee" ); + GM_CreateExplosion( self, newBolt, qtrue ); + break; + case 12: + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*r_leg_knee" ); + GM_CreateExplosion( self, newBolt, qtrue ); + break; + case 13: + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*l_leg_foot" ); + GM_CreateExplosion( self, newBolt, qtrue ); + break; + case 14: + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*r_leg_foot" ); + GM_CreateExplosion( self, newBolt, qtrue ); + break; + } + + TIMER_Set( self, "dyingExplosion", Q_irand( 300, 1100 ) ); + } + } + else + {//one final, huge explosion + G_PlayEffect( "galak/explode", self->currentOrigin ); +// G_PlayEffect( "small_chunks", self->currentOrigin ); +// G_PlayEffect( "env/exp_trail_comp", self->currentOrigin, self->currentAngles ); + self->nextthink = level.time + FRAMETIME; + self->e_ThinkFunc = thinkF_G_FreeEntity; + } +} + +/* +------------------------- +NPC_GM_Pain +------------------------- +*/ + +extern void NPC_SetPainEvent( gentity_t *self ); +void NPC_GM_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + if ( self->client->ps.powerups[PW_GALAK_SHIELD] == 0 ) + {//shield is currently down + //FIXME: allow for radius damage? + if ( (hitLoc==HL_GENERIC1) && (self->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH) ) + { + int newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*antenna_base" ); + if ( newBolt != -1 ) + { + GM_CreateExplosion( self, newBolt ); + } + + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "torso_shield_off", TURN_OFF ); + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "torso_antenna", TURN_OFF ); + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "torso_antenna_base_cap_off", TURN_ON ); + self->client->ps.powerups[PW_GALAK_SHIELD] = 0;//temp, for effect + self->client->ps.stats[STAT_ARMOR] = 0;//no more armor + self->NPC->investigateDebounceTime = 0;//stop recharging + + // NPC_SetAnim( self, SETANIM_BOTH, BOTH_ALERT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( self, "attackDelay", self->client->ps.torsoAnimTimer ); + G_AddEvent( self, Q_irand( EV_DEATH1, EV_DEATH3 ), self->health ); + } + } + else + {//store the point for shield impact + if ( point ) + { + VectorCopy( point, self->pos4 ); + self->client->poisonTime = level.time; + } + } + + if ( !self->lockCount && !self->client->ps.torsoAnimTimer ) + {//don't interrupt laser sweep attack or other special attacks/moves + if ( self->count < 4 && self->health > 100 && hitLoc != HL_GENERIC1 ) + { + if ( self->delay < level.time ) + { + int speech; + switch( self->count ) + { + default: + case 0: + speech = EV_PUSHED1; + break; + case 1: + speech = EV_PUSHED2; + break; + case 2: + speech = EV_PUSHED3; + break; + case 3: + speech = EV_DETECTED1; + break; + } + self->count++; + self->NPC->blockedSpeechDebounceTime = 0; + G_AddVoiceEvent( self, speech, Q_irand( 3000, 5000 ) ); + self->delay = level.time + Q_irand( 5000, 7000 ); + } + } + else + { + NPC_Pain( self, inflictor, other, point, damage, mod, hitLoc ); + } + } + else if ( hitLoc == HL_GENERIC1 ) + { + NPC_SetPainEvent( self ); + self->s.powerups |= ( 1 << PW_SHOCKED ); + self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); + } + + if ( inflictor && inflictor->lastEnemy == self ) + {//He force-pushed my own lobfires back at me + if ( mod == MOD_REPEATER_ALT && !Q_irand( 0, 2 ) ) + { + if ( TIMER_Done( self, "noRapid" ) ) + { + self->NPC->scriptFlags &= ~SCF_ALT_FIRE; + self->alt_fire = qfalse; + TIMER_Set( self, "noLob", Q_irand( 2000, 6000 ) ); + } + else + {//hopefully this will make us fire the laser + TIMER_Set( self, "noLob", Q_irand( 1000, 2000 ) ); + } + } + else if ( mod == MOD_REPEATER && !Q_irand( 0, 5 ) ) + { + if ( TIMER_Done( self, "noLob" ) ) + { + self->NPC->scriptFlags |= SCF_ALT_FIRE; + self->alt_fire = qtrue; + TIMER_Set( self, "noRapid", Q_irand( 2000, 6000 ) ); + } + else + {//hopefully this will make us fire the laser + TIMER_Set( self, "noRapid", Q_irand( 1000, 2000 ) ); + } + } + } +} + +/* +------------------------- +GM_HoldPosition +------------------------- +*/ + +static void GM_HoldPosition( void ) +{ + NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); + if ( !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//don't have a script waiting for me to get to my point, okay to stop trying and stand + NPCInfo->goalEntity = NULL; + } +} + +/* +------------------------- +GM_Move +------------------------- +*/ +static qboolean GM_Move( void ) +{ + NPCInfo->combatMove = qtrue;//always move straight toward our goal + + qboolean moved = NPC_MoveToGoal( qtrue ); + // navInfo_t info; + + // //Get the move info + // NAV_GetLastMove( info ); + + // //FIXME: if we bump into another one of our guys and can't get around him, just stop! + // //If we hit our target, then stop and fire! + // if ( info.flags & NIF_COLLISION ) + // { + // if ( info.blocker == NPC->enemy ) + // { + // GM_HoldPosition(); + // } + // } + if (NPCInfo->blockedEntity && NPCInfo->blockedEntity == NPC->enemy) + { + GM_HoldPosition(); + } + + //If our move failed, then reset + if ( moved == qfalse ) + {//FIXME: if we're going to a combat point, need to pick a different one + if ( !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//can't transfer movegoal or stop when a script we're running is waiting to complete + GM_HoldPosition(); + } + } + + return moved; +} + +/* +------------------------- +NPC_BSGM_Patrol +------------------------- +*/ + +void NPC_BSGM_Patrol( void ) +{ + if ( NPC_CheckPlayerTeamStealth() ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +GM_CheckMoveState +------------------------- +*/ + +static void GM_CheckMoveState( void ) +{ + if ( Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//moving toward a goal that a script is waiting on, so don't stop for anything! + AImove = qtrue; + } + + //See if we're moving towards a goal, not the enemy + if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) + { + //Did we make it? + if ( STEER::Reached(NPC, NPCInfo->goalEntity, 16, qfalse) || + ( !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) && enemyLOS && enemyDist <= 10000 ) ) + {//either hit our navgoal or our navgoal was not a crucial (scripted) one (maybe a combat point) and we're scouting and found our enemy + NPC_ReachedGoal(); + //don't attack right away + TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels + return; + } + } +} + +/* +------------------------- +GM_CheckFireState +------------------------- +*/ + +static void GM_CheckFireState( void ) +{ + if ( enemyCS ) + {//if have a clear shot, always try + return; + } + + if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//if moving at all, don't do this + return; + } + + //See if we should continue to fire on their last position + if ( !hitAlly && NPCInfo->enemyLastSeenTime > 0 ) + { + if ( level.time - NPCInfo->enemyLastSeenTime < 10000 ) + { + if ( !Q_irand( 0, 10 ) ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + qboolean tooClose = qfalse; + qboolean tooFar = qfalse; + + CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); + if ( VectorCompare( impactPos, vec3_origin ) ) + {//never checked ShotEntity this frame, so must do a trace... + trace_t tr; + //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2}; + vec3_t forward, end; + AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 8192, forward, end ); + gi.trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 ); + VectorCopy( tr.endpos, impactPos ); + } + + //see if impact would be too close to me + float distThreshold = 16384/*128*128*/;//default + if ( NPC->s.weapon == WP_REPEATER ) + { + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 65536/*256*256*/; + } + } + + float dist = DistanceSquared( impactPos, muzzle ); + + if ( dist < distThreshold ) + {//impact would be too close to me + tooClose = qtrue; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 ) + {//we've haven't seen them in the last 5 seconds + //see if it's too far from where he is + distThreshold = 65536/*256*256*/;//default + if ( NPC->s.weapon == WP_REPEATER ) + { + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 262144/*512*512*/; + } + } + dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation ); + if ( dist > distThreshold ) + {//impact would be too far from enemy + tooFar = qtrue; + } + } + + if ( !tooClose && !tooFar ) + {//okay too shoot at last pos + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + VectorNormalize( dir ); + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + shoot = qtrue; + faceEnemy = qfalse; + return; + } + } + } + } +} + +void NPC_GM_StartLaser( void ) +{ + if ( !NPC->lockCount ) + {//haven't already started a laser attack + //warm up for the beam attack + // NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_RAISEWEAP2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "beamDelay", NPC->client->ps.torsoAnimTimer ); + TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer+3000 ); + NPC->lockCount = 1; + //turn on warmup effect + G_PlayEffect( "galak/beam_warmup", NPC->s.number ); + G_SoundOnEnt( NPC, CHAN_AUTO, "sound/weapons/galak/lasercharge.wav" ); + } +} + +void GM_StartGloat( void ) +{ + NPC->wait = 0; + /*gi.G2API_SetSurfaceOnOff( &NPC->ghoul2[NPC->playerModel], "torso_galakface_off", TURN_ON ); + gi.G2API_SetSurfaceOnOff( &NPC->ghoul2[NPC->playerModel], "torso_galakhead_off", TURN_ON ); + gi.G2API_SetSurfaceOnOff( &NPC->ghoul2[NPC->playerModel], "torso_eyes_mouth_off", TURN_ON ); + gi.G2API_SetSurfaceOnOff( &NPC->ghoul2[NPC->playerModel], "torso_collar_off", TURN_ON ); + gi.G2API_SetSurfaceOnOff( &NPC->ghoul2[NPC->playerModel], "torso_galaktorso_off", TURN_ON ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND2TO1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.legsAnimTimer += 500; + NPC->client->ps.torsoAnimTimer += 500;*/ + G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); + NPC->enemy == NULL; +} +/* +------------------------- +NPC_BSGM_Attack +------------------------- +*/ + +void NPC_BSGM_Attack( void ) +{ + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //FIXME: if killed enemy, use victory anim + if ( NPC->enemy && NPC->enemy->health <= 0 + && !NPC->enemy->s.number ) + {//my enemy is dead + /*if ( NPC->client->ps.torsoAnim == BOTH_STAND2TO1 ) + { + if ( NPC->client->ps.torsoAnimTimer <= 500 ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.legsAnimTimer += 500; + NPC->client->ps.torsoAnimTimer += 500; + } + } + else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1START ) + { + if ( NPC->client->ps.torsoAnimTimer <= 500 ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STARTGESTURE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.legsAnimTimer += 500; + NPC->client->ps.torsoAnimTimer += 500; + } + } + else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STARTGESTURE ) + { + if ( NPC->client->ps.torsoAnimTimer <= 500 ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.legsAnimTimer += 500; + NPC->client->ps.torsoAnimTimer += 500; + } + } + else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STOP ) + { + if ( NPC->client->ps.torsoAnimTimer <= 500 ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.legsAnimTimer = -1; + NPC->client->ps.torsoAnimTimer = -1; + } + } + else*/ if ( NPC->wait ) + { + if ( TIMER_Done( NPC, "gloatTime" ) ) + { + GM_StartGloat(); + } + else if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared + { + NPCInfo->goalEntity = NPC->enemy; + GM_Move(); + } + else + {//got there + GM_StartGloat(); + } + } + NPC_FaceEnemy( qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt() == qfalse || !NPC->enemy ) + { + NPC->enemy = NULL; + NPC_BSGM_Patrol(); + return; + } + + enemyLOS = enemyCS = qfalse; + AImove = qtrue; + faceEnemy = qfalse; + shoot = qfalse; + hitAlly = qfalse; + VectorClear( impactPos ); + enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + + if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 || + NPC->client->ps.torsoAnim == BOTH_ATTACK5 ) + { + shoot = qfalse; + if ( TIMER_Done( NPC, "smackTime" ) && !NPCInfo->blockedDebounceTime ) + {//time to smack + //recheck enemyDist and InFront + if ( enemyDist < MELEE_DIST_SQUARED && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) ) + { + vec3_t smackDir; + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, smackDir ); + smackDir[2] += 30; + VectorNormalize( smackDir ); + //hurt them + G_Sound( NPC->enemy, G_SoundIndex( "sound/weapons/galak/skewerhit.wav" ) ); + G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->currentOrigin, (g_spskill->integer+1)*Q_irand( 5, 10), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); + if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 ) + {//smackdown + int knockAnim = BOTH_KNOCKDOWN1; + if ( PM_CrouchAnim( NPC->enemy->client->ps.legsAnim ) ) + {//knockdown from crouch + knockAnim = BOTH_KNOCKDOWN4; + } + //throw them + smackDir[2] = 1; + VectorNormalize( smackDir ); + G_Throw( NPC->enemy, smackDir, 50 ); + NPC_SetAnim( NPC->enemy, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + {//uppercut + //throw them + G_Throw( NPC->enemy, smackDir, 100 ); + //make them backflip + NPC_SetAnim( NPC->enemy, SETANIM_BOTH, BOTH_KNOCKDOWN5, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + //done with the damage + NPCInfo->blockedDebounceTime = 1; + } + } + } + else if ( NPC->lockCount ) //already shooting laser + {//sometimes use the laser beam attack, but only after he's taken down our generator + shoot = qfalse; + if ( NPC->lockCount == 1 ) + {//charging up + if ( TIMER_Done( NPC, "beamDelay" ) ) + {//time to start the beam + int laserAnim; + if ( Q_irand( 0, 1 ) ) + { + laserAnim = BOTH_ATTACK2; + } + else + { + laserAnim = BOTH_ATTACK7; + } + NPC_SetAnim( NPC, SETANIM_BOTH, laserAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer + Q_irand( 1000, 3000 ) ); + //turn on beam effect + NPC->lockCount = 2; + G_PlayEffect( "galak/trace_beam", NPC->s.number ); + NPC->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); + if ( !NPCInfo->coverTarg ) + {//for moving looping sound at end of trace + NPCInfo->coverTarg = G_Spawn(); + if ( NPCInfo->coverTarg ) + { + G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); + NPCInfo->coverTarg->svFlags |= SVF_BROADCAST; + NPCInfo->coverTarg->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); + } + } + } + } + else + {//in the actual attack now + if ( !NPC->client->ps.torsoAnimTimer ) + {//attack done! + NPC->lockCount = 0; + G_FreeEntity( NPCInfo->coverTarg ); + NPC->s.loopSound = 0; + // NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_DROPWEAP2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer ); + } + else + {//attack still going + //do the trace and damage + trace_t trace; + vec3_t end, mins={-3,-3,-3}, maxs={3,3,3}; + VectorMA( NPC->client->renderInfo.muzzlePoint, 1024, NPC->client->renderInfo.muzzleDir, end ); + gi.trace( &trace, NPC->client->renderInfo.muzzlePoint, mins, maxs, end, NPC->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 ); + if ( trace.allsolid || trace.startsolid ) + {//oops, in a wall + if ( NPCInfo->coverTarg ) + { + G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); + } + } + else + {//clear + if ( trace.fraction < 1.0f ) + {//hit something + gentity_t *traceEnt = &g_entities[trace.entityNum]; + if ( traceEnt && traceEnt->takedamage ) + {//damage it + G_SoundAtSpot( trace.endpos, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ), qfalse ); + G_Damage( traceEnt, NPC, NPC, NPC->client->renderInfo.muzzleDir, trace.endpos, 10, 0, MOD_ENERGY ); + } + } + if ( NPCInfo->coverTarg ) + { + G_SetOrigin( NPCInfo->coverTarg, trace.endpos ); + } + if ( !Q_irand( 0, 5 ) ) + { + G_SoundAtSpot( trace.endpos, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ), qfalse ); + } + } + } + } + } + else + {//Okay, we're not in a special attack, see if we should switch weapons or start a special attack + /* + if ( NPC->s.weapon == WP_REPEATER + && !(NPCInfo->scriptFlags & SCF_ALT_FIRE)//using rapid-fire + && NPC->enemy->s.weapon == WP_SABER //enemy using saber + && NPC->client && (NPC->client->ps.saberEventFlags&SEF_DEFLECTED) + && !Q_irand( 0, 50 ) ) + {//he's deflecting my shots, switch to the laser or the lob fire for a while + TIMER_Set( NPC, "noRapid", Q_irand( 2000, 6000 ) ); + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + NPC->alt_fire = qtrue; + if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && (Q_irand( 0, 1 )||enemyDist < MAX_LOB_DIST_SQUARED) ) + {//shield down, use laser + NPC_GM_StartLaser(); + } + } + else*/ + if ( !NPC->client->ps.powerups[PW_GALAK_SHIELD] + && enemyDist < MELEE_DIST_SQUARED + && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) + && G_StandardHumanoid( NPC->enemy ) )//within 80 and in front + {//our shield is down, and enemy within 80, if very close, use melee attack to slap away + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + //animate me + int swingAnim; + if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH ) + {//generator down, use random melee + swingAnim = Q_irand( BOTH_ATTACK4, BOTH_ATTACK5 );//smackdown or uppercut + } + else + {//always knock-away + swingAnim = BOTH_ATTACK5;//uppercut + } + //FIXME: swing sound + NPC_SetAnim( NPC, SETANIM_BOTH, swingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer + Q_irand( 1000, 3000 ) ); + //delay the hurt until the proper point in the anim + TIMER_Set( NPC, "smackTime", 600 ); + NPCInfo->blockedDebounceTime = 0; + //FIXME: say something? + } + } + else if ( !NPC->lockCount && NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH + && TIMER_Done( NPC, "attackDelay" ) + && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) + && ((!Q_irand( 0, 10*(2-g_spskill->integer))&& enemyDist > MIN_LOB_DIST_SQUARED&& enemyDist < MAX_LOB_DIST_SQUARED) + ||(!TIMER_Done( NPC, "noLob" )&&!TIMER_Done( NPC, "noRapid" ))) + && NPC->enemy->s.weapon != WP_TURRET ) + {//sometimes use the laser beam attack, but only after he's taken down our generator + shoot = qfalse; + NPC_GM_StartLaser(); + } + else if ( enemyDist < MIN_LOB_DIST_SQUARED + && (NPC->enemy->s.weapon != WP_TURRET || Q_stricmp( "PAS", NPC->enemy->classname )) + && TIMER_Done( NPC, "noRapid" ) )//256 + {//enemy within 256 + if ( (NPC->client->ps.weapon == WP_REPEATER) && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//shooting an explosive, but enemy too close, switch to primary fire + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + NPC->alt_fire = qfalse; + //FIXME: use weap raise & lower anims + NPC_ChangeWeapon( WP_REPEATER ); + } + } + else if ( (enemyDist > MAX_LOB_DIST_SQUARED || (NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ))) + && TIMER_Done( NPC, "noLob" ) )//448 + {//enemy more than 448 away and we are ready to try lob fire again + if ( (NPC->client->ps.weapon == WP_REPEATER) && !(NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//enemy far enough away to use lobby explosives + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + NPC->alt_fire = qtrue; + //FIXME: use weap raise & lower anims + NPC_ChangeWeapon( WP_REPEATER ); + } + } + } + + //can we see our target? + if ( NPC_ClearLOS( NPC->enemy ) ) + { + NPCInfo->enemyLastSeenTime = level.time;//used here for aim debouncing, not always a clear LOS + enemyLOS = qtrue; + + if ( NPC->client->ps.weapon == WP_NONE ) + { + enemyCS = qfalse;//not true, but should stop us from firing + NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon + } + else + {//can we shoot our target? + if ( ((NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_LOB_DIST_SQUARED )//256 + { + enemyCS = qfalse;//not true, but should stop us from firing + hitAlly = qtrue;//us! + //FIXME: if too close, run away! + } + else + { + int hit = NPC_ShotEntity( NPC->enemy, impactPos ); + gentity_t *hitEnt = &g_entities[hit]; + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage ) ) + {//can hit enemy or will hit glass or other breakable, so shoot anyway + enemyCS = qtrue; + NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + else + {//Hmm, have to get around this bastard + NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy + if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) + {//would hit an ally, don't fire!!! + hitAlly = qtrue; + } + else + {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire + } + } + } + } + } + else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) + { + if ( TIMER_Done( NPC, "talkDebounce" ) && !Q_irand( 0, 10 ) ) + { + if ( NPCInfo->enemyCheckDebounceTime < 8 ) + { + int speech = -1; + switch( NPCInfo->enemyCheckDebounceTime ) + { + case 0: + case 1: + case 2: + speech = EV_CHASE1 + NPCInfo->enemyCheckDebounceTime; + break; + case 3: + case 4: + case 5: + speech = EV_COVER1 + NPCInfo->enemyCheckDebounceTime-3; + break; + case 6: + case 7: + speech = EV_ESCAPING1 + NPCInfo->enemyCheckDebounceTime-6; + break; + } + NPCInfo->enemyCheckDebounceTime++; + if ( speech != -1 ) + { + G_AddVoiceEvent( NPC, speech, Q_irand( 3000, 5000 ) ); + TIMER_Set( NPC, "talkDebounce", Q_irand( 5000, 7000 ) ); + } + } + } + + NPCInfo->enemyLastSeenTime = level.time; + + int hit = NPC_ShotEntity( NPC->enemy, impactPos ); + gentity_t *hitEnt = &g_entities[hit]; + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage ) ) + {//can hit enemy or will hit glass or other breakable, so shoot anyway + enemyCS = qtrue; + } + else + { + faceEnemy = qtrue; + NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } + } + + if ( enemyLOS ) + { + faceEnemy = qtrue; + } + else + { + if ( !NPCInfo->goalEntity ) + { + NPCInfo->goalEntity = NPC->enemy; + } + if ( NPCInfo->goalEntity == NPC->enemy ) + {//for now, always chase the enemy + AImove = qtrue; + } + } + if ( enemyCS ) + { + shoot = qtrue; + //NPCInfo->enemyCheckDebounceTime = level.time;//actually used here as a last actual LOS + } + else + { + if ( !NPCInfo->goalEntity ) + { + NPCInfo->goalEntity = NPC->enemy; + } + if ( NPCInfo->goalEntity == NPC->enemy ) + {//for now, always chase the enemy + AImove = qtrue; + } + } + + //Check for movement to take care of + GM_CheckMoveState(); + + //See if we should override shooting decision with any special considerations + GM_CheckFireState(); + + if ( NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE) && shoot && TIMER_Done( NPC, "attackDelay" ) ) + { + vec3_t muzzle; + vec3_t angles; + vec3_t target; + vec3_t velocity = {0,0,0}; + vec3_t mins = {-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE}, maxs = {REPEATER_ALT_SIZE,REPEATER_ALT_SIZE,REPEATER_ALT_SIZE}; + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + VectorCopy( NPC->enemy->currentOrigin, target ); + + target[0] += Q_flrand( -5, 5 )+(Q_flrand(-1.0f, 1.0f)*(6-NPCInfo->currentAim)*2); + target[1] += Q_flrand( -5, 5 )+(Q_flrand(-1.0f, 1.0f)*(6-NPCInfo->currentAim)*2); + target[2] += Q_flrand( -5, 5 )+(Q_flrand(-1.0f, 1.0f)*(6-NPCInfo->currentAim)*2); + + //Find the desired angles + qboolean clearshot = WP_LobFire( NPC, muzzle, target, mins, maxs, MASK_SHOT|CONTENTS_LIGHTSABER, + velocity, qtrue, NPC->s.number, NPC->enemy->s.number, + 300, 1100, 1500, qtrue ); + if ( VectorCompare( vec3_origin, velocity ) || (!clearshot&&enemyLOS&&enemyCS) ) + {//no clear lob shot and no lob shot that will hit something breakable + if ( enemyLOS && enemyCS && TIMER_Done( NPC, "noRapid" ) ) + {//have a clear straight shot, so switch to primary + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + NPC->alt_fire = qfalse; + NPC_ChangeWeapon( WP_REPEATER ); + //keep this weap for a bit + TIMER_Set( NPC, "noLob", Q_irand( 500, 1000 ) ); + } + else + { + shoot = qfalse; + } + } + else + { + vectoangles( velocity, angles ); + + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); + + VectorCopy( velocity, NPC->client->hiddenDir ); + NPC->client->hiddenDist = VectorNormalize ( NPC->client->hiddenDir ); + } + } + else if ( faceEnemy ) + {//face the enemy + NPC_FaceEnemy( qtrue ); + } + + if ( !TIMER_Done( NPC, "standTime" ) ) + { + AImove = qfalse; + } + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + {//not supposed to chase my enemies + if ( NPCInfo->goalEntity == NPC->enemy ) + {//goal is my entity, so don't move + AImove = qfalse; + } + } + + if ( AImove && !NPC->lockCount ) + {//move toward goal + if ( NPCInfo->goalEntity + // && NPC->client->ps.legsAnim != BOTH_ALERT1 + && NPC->client->ps.legsAnim != BOTH_ATTACK2 + && NPC->client->ps.legsAnim != BOTH_ATTACK4 + && NPC->client->ps.legsAnim != BOTH_ATTACK5 + && NPC->client->ps.legsAnim != BOTH_ATTACK7 ) + { + AImove = GM_Move(); + } + else + { + AImove = qfalse; + } + } + + if ( !TIMER_Done( NPC, "flee" ) ) + {//running away + faceEnemy = qfalse; + } + + //FIXME: check scf_face_move_dir here? + + if ( !faceEnemy ) + {//we want to face in the dir we're running + if ( !AImove ) + {//if we haven't moved, we should look in the direction we last looked? + VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles ); + } + if ( AImove ) + {//don't run away and shoot + NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPCInfo->desiredPitch = 0; + shoot = qfalse; + } + } + NPC_UpdateAngles( qtrue, qtrue ); + + if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) + { + shoot = qfalse; + } + + if ( NPC->enemy && NPC->enemy->enemy ) + { + if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) + {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) + shoot = qfalse; + } + } + //FIXME: don't shoot right away! + if ( shoot ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + } + } + + //also: + if ( NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) ) + {//crush turrets + if ( G_BoundsOverlap( NPC->absmin, NPC->absmax, NPC->enemy->absmin, NPC->enemy->absmax ) ) + {//have to do this test because placed turrets are not solid to NPCs (so they don't obstruct navigation) + if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) + { + NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; + G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_ELECTROCUTE ); + } + else + { + G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); + } + } + } + else if ( NPCInfo->touchedByPlayer != NULL && NPCInfo->touchedByPlayer == NPC->enemy ) + {//touched enemy + if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) + {//zap him! + //animate me + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK6, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer ); + TIMER_Set( NPC, "standTime", NPC->client->ps.legsAnimTimer ); + //FIXME: debounce this? + NPCInfo->touchedByPlayer = NULL; + //FIXME: some shield effect? + NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; + vec3_t smackDir; + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, smackDir ); + smackDir[2] += 30; + VectorNormalize( smackDir ); + G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->currentOrigin, (g_spskill->integer+1)*Q_irand( 5, 10), DAMAGE_NO_KNOCKBACK, MOD_ELECTROCUTE ); + //throw them + G_Throw( NPC->enemy, smackDir, 100 ); + NPC->enemy->s.powerups |= ( 1 << PW_SHOCKED ); + if ( NPC->enemy->client ) + { + NPC->enemy->client->ps.powerups[PW_SHOCKED] = level.time + 1000; + } + //stop any attacks + ucmd.buttons = 0; + } + } + + if ( NPCInfo->movementSpeech < 3 && NPCInfo->blockedSpeechDebounceTime <= level.time ) + { + if ( NPC->enemy && NPC->enemy->health > 0 && NPC->enemy->painDebounceTime > level.time ) + { + if ( NPC->enemy->health < 50 && NPCInfo->movementSpeech == 2 ) + { + G_AddVoiceEvent( NPC, EV_ANGER2, Q_irand( 2000, 4000 ) ); + NPCInfo->movementSpeech = 3; + } + else if ( NPC->enemy->health < 75 && NPCInfo->movementSpeech == 1 ) + { + G_AddVoiceEvent( NPC, EV_ANGER1, Q_irand( 2000, 4000 ) ); + NPCInfo->movementSpeech = 2; + } + else if ( NPC->enemy->health < 100 && NPCInfo->movementSpeech == 0 ) + { + G_AddVoiceEvent( NPC, EV_ANGER3, Q_irand( 2000, 4000 ) ); + NPCInfo->movementSpeech = 1; + } + } + } +} + +void NPC_BSGM_Default( void ) +{ + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if ( NPC->client->ps.stats[STAT_ARMOR] <= 0 ) + {//armor gone + if ( !NPCInfo->investigateDebounceTime ) + {//start regenerating the armor + gi.G2API_SetSurfaceOnOff( &NPC->ghoul2[NPC->playerModel], "torso_shield_off", TURN_OFF ); + NPC->flags &= ~FL_SHIELDED;//no more reflections + VectorSet( NPC->mins, -20, -20, -24 ); + VectorSet( NPC->maxs, 20, 20, 64 ); + NPC->client->crouchheight = NPC->client->standheight = 64; + if ( NPC->locationDamage[HL_GENERIC1] < GENERATOR_HEALTH ) + {//still have the generator bolt-on + if ( NPCInfo->investigateCount < 12 ) + { + NPCInfo->investigateCount++; + } + NPCInfo->investigateDebounceTime = level.time + (NPCInfo->investigateCount * 5000); + } + } + else if ( NPCInfo->investigateDebounceTime < level.time ) + {//armor regenerated, turn shield back on + //do a trace and make sure we can turn this back on? + trace_t tr; + gi.trace( &tr, NPC->currentOrigin, shieldMins, shieldMaxs, NPC->currentOrigin, NPC->s.number, NPC->clipmask, G2_NOCOLLIDE, 0 ); + if ( !tr.startsolid ) + { + VectorCopy( shieldMins, NPC->mins ); + VectorCopy( shieldMaxs, NPC->maxs ); + NPC->client->crouchheight = NPC->client->standheight = shieldMaxs[2]; + NPC->client->ps.stats[STAT_ARMOR] = GALAK_SHIELD_HEALTH; + NPCInfo->investigateDebounceTime = 0; + NPC->flags |= FL_SHIELDED;//reflect normal shots + NPC->fx_time = level.time; + gi.G2API_SetSurfaceOnOff( &NPC->ghoul2[NPC->playerModel], "torso_shield_off", TURN_ON ); + } + } + } + if ( NPC->client->ps.stats[STAT_ARMOR] > 0 ) + {//armor present + NPC->client->ps.powerups[PW_GALAK_SHIELD] = Q3_INFINITE;//temp, for effect + gi.G2API_SetSurfaceOnOff( &NPC->ghoul2[NPC->playerModel], "torso_shield_off", TURN_ON ); + } + else + { + gi.G2API_SetSurfaceOnOff( &NPC->ghoul2[NPC->playerModel], "torso_shield_off", TURN_OFF ); + } + + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSGM_Patrol(); + } + else //if ( NPC->enemy ) + {//have an enemy + NPC_BSGM_Attack(); + } +} diff --git a/code/game/CMakeLists.txt b/code/game/CMakeLists.txt index cf1ad54a65..ccd875c9e6 100644 --- a/code/game/CMakeLists.txt +++ b/code/game/CMakeLists.txt @@ -42,6 +42,7 @@ set(SPGameGameFiles "${SPDir}/game/AI_Civilian.cpp" "${SPDir}/game/AI_Default.cpp" "${SPDir}/game/AI_Droid.cpp" + "${SPDir}/game/AI_GalakBoss.cpp" "${SPDir}/game/AI_GalakMech.cpp" "${SPDir}/game/AI_Grenadier.cpp" "${SPDir}/game/AI_HazardTrooper.cpp" diff --git a/code/game/NPC.cpp b/code/game/NPC.cpp index d1c794e0e0..dab21270bb 100644 --- a/code/game/NPC.cpp +++ b/code/game/NPC.cpp @@ -48,6 +48,7 @@ extern void Mark1_dying( gentity_t *self ); extern void NPC_BSCinematic( void ); extern int GetTime ( int lastTime ); extern void G_CheckCharmed( gentity_t *self ); +extern void NPC_BSGM_Default( void ); extern qboolean Boba_Flying( gentity_t *self ); extern qboolean RT_Flying( gentity_t *self ); extern qboolean Jedi_CultistDestroyer( gentity_t *self ); @@ -83,6 +84,7 @@ visibility_t enemyVisibility; void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); static bState_t G_CurrentBState( gNPC_t *gNPC ); +extern void GM_Dying( gentity_t *self ); extern int eventClearTime; @@ -93,6 +95,11 @@ void CorpsePhysics( gentity_t *self ) ClientThink( self->s.number, &ucmd ); VectorCopy( self->s.origin, self->s.origin2 ); + if ( self->client->NPC_class == CLASS_GALAKMECH ) + { + GM_Dying( self ); + } + //FIXME: match my pitch and roll for the slope of my groundPlane if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !(self->flags&FL_DISINTEGRATED) ) {//on the ground @@ -2025,6 +2032,9 @@ void NPC_RunBehavior( int team, int bState ) case CLASS_MARK2: NPC_BehaviorSet_Mark2( bState ); return; + case CLASS_GALAKMECH: + NPC_BSGM_Default(); + return; default: break; } diff --git a/code/game/NPC_spawn.cpp b/code/game/NPC_spawn.cpp index 7e3f820a89..9c773f83cd 100644 --- a/code/game/NPC_spawn.cpp +++ b/code/game/NPC_spawn.cpp @@ -43,6 +43,8 @@ extern void Q3_SetParm (int entID, int parmNum, const char *parmValue); extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); +extern void NPC_GalakMech_Init( gentity_t *ent ); + extern int WP_SaberInitBladeData( gentity_t *ent ); extern void ST_ClearTimers( gentity_t *ent ); extern void Jedi_ClearTimers( gentity_t *ent ); @@ -617,6 +619,10 @@ void NPC_SetMiscDefaultData( gentity_t *ent ) } break; } + if ( !Q_stricmp( "galak_mech", ent->NPC_type ) ) + {//starts with armor + NPC_GalakMech_Init( ent ); + } } } break; @@ -2349,6 +2355,16 @@ SHY - Spawner is shy */ void SP_NPC_Galak( gentity_t *self) { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "Galak_Mech"; + } + else + { + self->NPC_type = "Galak"; + } + + SP_NPC_spawner( self ); } /*QUAKED NPC_Desann(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY diff --git a/code/game/NPC_stats.cpp b/code/game/NPC_stats.cpp index 1e3ea43cbb..ebbe579e45 100644 --- a/code/game/NPC_stats.cpp +++ b/code/game/NPC_stats.cpp @@ -1418,6 +1418,7 @@ extern void NPC_ATST_Precache(void); extern void NPC_Sentry_Precache(void); extern void NPC_Mark1_Precache(void); extern void NPC_Mark2_Precache(void); +extern void NPC_GalakMech_Precache( void ); extern void NPC_Protocol_Precache( void ); extern void Boba_Precache( void ); extern void RT_Precache( void ); @@ -1525,6 +1526,10 @@ void NPC_PrecacheByClassName( const char* type ) { NPC_Protocol_Precache(); } + else if ( !Q_stricmp( "galak_mech", type )) + { + NPC_GalakMech_Precache(); + } else if ( !Q_stricmp( "boba_fett", type )) { Boba_Precache(); diff --git a/code/game/bg_pmove.cpp b/code/game/bg_pmove.cpp index 46643fd41f..1c3ab8aabb 100644 --- a/code/game/bg_pmove.cpp +++ b/code/game/bg_pmove.cpp @@ -6710,6 +6710,17 @@ qboolean PM_InRoll( playerState_t *ps ) return qfalse; } +qboolean PM_RestAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_MEDITATE: // default taunt + return qtrue; + break; + } + return qfalse; +} + qboolean PM_CrouchAnim( int anim ) { switch ( anim ) @@ -8916,7 +8927,7 @@ static void PM_BeginWeaponChange( int weapon ) { // eezstreet edit: also ignore if we change to WP_NONE..sorta hacky fix for binoculars using WP_SABER if ( pm->ps->clientNum == 0 && cg.weaponSelect != WP_NONE ) { - if ( cg.zoomMode > 0 && cg.zoomMode < 3 ) + if ( (cg.zoomMode > 0 && cg.zoomMode < 3) || (cg.zoomMode == 3 && cg.weaponSelect == WP_SABER) ) { cg.zoomMode = 0; cg.zoomTime = cg.time; @@ -9034,7 +9045,7 @@ static void PM_FinishWeaponChange( void ) { if ( pm->gent ) { WP_SaberInitBladeData( pm->gent ); - if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) ) + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) /*&& cg_saberAutoThird.integer*/ ) { gi.cvar_set( "cg_thirdperson", "1" ); } diff --git a/code/game/g_active.cpp b/code/game/g_active.cpp index e8b19398bc..9f846bd3d9 100644 --- a/code/game/g_active.cpp +++ b/code/game/g_active.cpp @@ -84,6 +84,7 @@ extern void PM_CmdForRoll( playerState_t *ps, usercmd_t *pCmd ); extern qboolean PM_InAttackRoll( int anim ); extern qboolean PM_CrouchAnim( int anim ); extern qboolean PM_FlippingAnim( int anim ); +extern qboolean PM_RestAnim( int anim ); extern qboolean PM_InCartwheel( int anim ); extern qboolean PM_StandingAnim( int anim ); extern qboolean PM_InForceGetUp( playerState_t *ps ); @@ -1726,6 +1727,26 @@ void ClientTimerActions( gentity_t *ent, int msec ) { ent->flags &= ~FL_OVERCHARGED_HEALTH; } } + if ( (ent->health > 0 && ent->health < ent->client->ps.stats[STAT_MAX_HEALTH]/4) && + (ent->client->ps.forcePowerLevel[FP_SEE] >= FORCE_LEVEL_1) && + (ent->painDebounceTime < level.time) ) + {//gradually increase health back to 25% of max if force sight >= 1 + ent->health++; + ent->client->ps.stats[STAT_HEALTH] = ent->health; + } + if ( PM_RestAnim( ent->client->ps.legsAnim ) && + (ent->health > 0 && ent->health < ent->client->ps.stats[STAT_MAX_HEALTH]) && + (ent->client->ps.forcePower >= ent->client->ps.forcePowerMax) && + (ent->client->ps.forcePowerLevel[FP_SEE] >= FORCE_LEVEL_1) && + (ent->painDebounceTime < level.time) ) + {//regen health to max when force is max (force sight must be >= 1) + ent->health++; + + if ( ent->health >= ent->client->ps.stats[STAT_MAX_HEALTH]/3 ) + { + gi.G2API_ClearSkinGore(ent->ghoul2); + } + } } } @@ -3912,7 +3933,7 @@ qboolean G_CheckClampUcmd( gentity_t *ent, usercmd_t *ucmd ) { if ( ent->client->ps.torsoAnimTimer < 100 ) { - ent->client->ps.legsAnimTimer = 100; + ent->client->ps.torsoAnimTimer = 100; } } if ( ent->client->ps.legsAnimTimer > 0 || ent->client->ps.torsoAnimTimer > 0 ) @@ -4908,14 +4929,14 @@ extern cvar_t *g_skippingcin; ucmd->upmove = 0; PM_AdjustAnglesToGripper( ent, ucmd ); } - if ( ent->client->ps.leanofs ) + /*if ( ent->client->ps.leanofs ) {//no shooting while leaning ucmd->buttons &= ~BUTTON_ATTACK; if ( ent->client->ps.weapon != WP_DISRUPTOR ) {//can still zoom around corners ucmd->buttons &= ~BUTTON_ALT_ATTACK; } - } + }*/ } else { diff --git a/code/game/g_client.cpp b/code/game/g_client.cpp index 053a8f10d9..2376da77f2 100644 --- a/code/game/g_client.cpp +++ b/code/game/g_client.cpp @@ -1290,6 +1290,16 @@ qboolean G_SetG2PlayerModelInfo( gentity_t *ent, const char *modelName, const ch { ent->handRBolt = ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_f1"); } + else if ( !Q_stricmp( "galak_mech", modelName )) + { + ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*antenna_effect"); + ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_eyes"); + ent->torsoBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "torso"); + ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "hips"); + ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flasha"); + ent->genericBolt3 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flashb"); + ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flashc"); + } else if ( !Q_stricmp( "rancor", modelName ) || !Q_stricmp( "mutant_rancor", modelName )) { diff --git a/code/game/g_emplaced.cpp b/code/game/g_emplaced.cpp index 9ef53cf6e3..682f9f510f 100644 --- a/code/game/g_emplaced.cpp +++ b/code/game/g_emplaced.cpp @@ -1041,7 +1041,7 @@ extern void CG_ChangeWeapon( int num ); if ( ent->s.number < MAX_CLIENTS ) { - if ( ent->client->ps.weapon == WP_SABER ) + if ( ent->client->ps.weapon == WP_SABER /*&& cg_saberAutoThird.integer*/ ) { gi.cvar_set( "cg_thirdperson", "1" ); } diff --git a/code/game/g_functions.cpp b/code/game/g_functions.cpp index 822f4413e5..69e7cb06e3 100644 --- a/code/game/g_functions.cpp +++ b/code/game/g_functions.cpp @@ -364,6 +364,7 @@ void GEntity_PainFunc(gentity_t *self, gentity_t *inflictor, gentity_t *attacker PAINCASE( NPC_Remote_Pain ) PAINCASE( emplaced_gun_pain ) PAINCASE( NPC_Mark1_Pain ) + PAINCASE( NPC_GM_Pain ) PAINCASE( NPC_Sentry_Pain ) PAINCASE( NPC_Mark2_Pain ) PAINCASE( PlayerPain ) diff --git a/code/game/g_functions.h b/code/game/g_functions.h index 07a4d24bba..67318548c9 100644 --- a/code/game/g_functions.h +++ b/code/game/g_functions.h @@ -572,6 +572,7 @@ extern void NPC_Seeker_Pain (gentity_t *self, gentity_t *inflictor, gentity_t extern void NPC_Remote_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); extern void emplaced_gun_pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); extern void NPC_Mark1_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_GM_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); extern void NPC_Sentry_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); extern void NPC_Mark2_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); extern void PlayerPain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); diff --git a/code/game/g_main.cpp b/code/game/g_main.cpp index dceac508eb..352211f037 100644 --- a/code/game/g_main.cpp +++ b/code/game/g_main.cpp @@ -660,7 +660,7 @@ void G_InitCvars( void ) { com_buildScript = gi.cvar ("com_buildscript", "0", 0); g_saberAutoBlocking = gi.cvar( "g_saberAutoBlocking", "1", CVAR_CHEAT );//must press +block button to do any blocking - g_saberRealisticCombat = gi.cvar( "g_saberMoreRealistic", "0", CVAR_ARCHIVE );//makes collision more precise, increases damage + g_saberRealisticCombat = gi.cvar( "g_saberMoreRealistic", "2", CVAR_ARCHIVE );//makes collision more precise, increases damage debug_subdivision = gi.cvar( "debug_subdivision", "0", CVAR_ARCHIVE );//debug for dismemberment g_dismemberProbabilities = gi.cvar ( "g_dismemberProbabilities", "1", CVAR_ARCHIVE );//0 = ignore probabilities, 1 = use probabilities g_saberDamageCapping = gi.cvar( "g_saberDamageCapping", "1", CVAR_CHEAT );//caps damage of sabers vs players and NPC who use sabers diff --git a/code/game/game.vcproj b/code/game/game.vcproj index bf5598bce4..452349dba4 100644 --- a/code/game/game.vcproj +++ b/code/game/game.vcproj @@ -450,6 +450,10 @@ RelativePath=".\AI_Droid.cpp" > + + diff --git a/code/game/wp_saber.cpp b/code/game/wp_saber.cpp index ae6985decf..53d5251065 100644 --- a/code/game/wp_saber.cpp +++ b/code/game/wp_saber.cpp @@ -103,6 +103,7 @@ extern saberMoveName_t PM_BrokenParryForAttack( int move ); extern saberMoveName_t PM_KnockawayForParry( int move ); extern qboolean PM_FlippingAnim( int anim ); extern qboolean PM_RollingAnim( int anim ); +extern qboolean PM_RestAnim( int anim ); extern qboolean PM_CrouchAnim( int anim ); extern qboolean PM_SaberInIdle( int move ); extern qboolean PM_SaberInReflect( int move ); @@ -281,15 +282,15 @@ float forceSpeedRangeMod[NUM_FORCE_POWER_LEVELS] = float forceSpeedFOVMod[NUM_FORCE_POWER_LEVELS] = { 0.0f,//none - 20.0f, - 30.0f, - 40.0f + 0.0f,//20.0f, + 0.0f,//30.0f, + 0.0f//40.0f }; int forceGripDamage[NUM_FORCE_POWER_LEVELS] = { 0,//none - 0, + 3,//0, 6, 9 }; @@ -7041,6 +7042,8 @@ void WP_SaberThrow( gentity_t *self, usercmd_t *ucmd ) } //need to recalc this because we just moved it VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff ); + //remember when it launched so it can return automagically + saberent->aimDebounceTime = level.time; } else {//couldn't throw it @@ -7071,6 +7074,12 @@ void WP_SaberThrow( gentity_t *self, usercmd_t *ucmd ) } return; } + if ( (!self->s.number && level.time - saberent->aimDebounceTime > 15000) + || (self->s.number && level.time - saberent->aimDebounceTime > 5000) ) + {//(only for player) been missing for 15 seconds, automagicially return + WP_SaberCatch( self, saberent, qfalse ); + return; + } } if ( saberent->s.pos.trType != TR_STATIONARY ) @@ -7087,6 +7096,12 @@ void WP_SaberThrow( gentity_t *self, usercmd_t *ucmd ) return; } } + if ( (!self->s.number && level.time - saberent->aimDebounceTime > 15000) + || (self->s.number && level.time - saberent->aimDebounceTime > 5000) ) + {//(only for player) been missing for 15 seconds, automagicially return + WP_SaberCatch( self, saberent, qfalse ); + return; + } WP_RunSaber( self, saberent ); } else @@ -12583,6 +12598,11 @@ int WP_AbsorbConversion(gentity_t *attacked, int atdAbsLevel, gentity_t *attacke return -1; } + if (Q_irand( 0, atPowerLevel ) > Q_irand( 1, atdAbsLevel )) + { //chance of attacker's high level power breaking through entirely + return -1; + } + //Subtract absorb power level from the offensive force power getLevel = atPowerLevel; getLevel -= atdAbsLevel; @@ -14375,6 +14395,16 @@ void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd ) { WP_ForcePowerRegenerate( self, self->client->ps.forcePowerRegenAmount ); self->client->ps.forcePowerRegenDebounceTime = level.time + self->client->ps.forcePowerRegenRate; + + if ( PM_CrouchAnim( self->client->ps.legsAnim ) ) + {//regen force much faster when crouched + WP_ForcePowerRegenerate( self, 2 ); + } + else if ( PM_RestAnim( self->client->ps.legsAnim ) ) + {//regen force extremly fast when meditating + WP_ForcePowerRegenerate( self, 4 ); + } + if ( self->client->ps.forceRageRecoveryTime >= level.time ) {//regen half as fast self->client->ps.forcePowerRegenDebounceTime += self->client->ps.forcePowerRegenRate; diff --git a/code/qcommon/common.cpp b/code/qcommon/common.cpp index 0e64a80b97..3b9296fb38 100644 --- a/code/qcommon/common.cpp +++ b/code/qcommon/common.cpp @@ -1094,12 +1094,12 @@ void Com_Init( char *commandLine ) { //Swap_Init (); Cbuf_Init (); - Com_InitZoneMemoryVars(); - Cmd_Init (); - // override anything from the config files with command line args Com_StartupVariable( NULL ); + Com_InitZoneMemoryVars(); + Cmd_Init (); + // done early so bind command exists CL_InitKeyCommands(); @@ -1196,6 +1196,7 @@ void Com_Init( char *commandLine ) { Cbuf_AddText ("cinematic openinglogos\n"); } } + CL_StartHunkUsers(); com_fullyInitialized = qtrue; Com_Printf ("--- Common Initialization Complete ---\n"); diff --git a/code/ui/ui_atoms.cpp b/code/ui/ui_atoms.cpp index 21488388bb..c9fe8879ea 100644 --- a/code/ui/ui_atoms.cpp +++ b/code/ui/ui_atoms.cpp @@ -294,6 +294,7 @@ void UI_Init( int apiVersion, uiimport_t *uiimport, qboolean inGameLoad ) ui.Cvar_Create( "ui_prisonerobj_mintotal", "0", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART); ui.Cvar_Create( "g_dismemberment", "3", CVAR_ARCHIVE );//0 = none, 1 = arms and hands, 2 = legs, 3 = waist and head + // ui.Cvar_Create( "cg_saberAutoThird", "1", CVAR_ARCHIVE ); ui.Cvar_Create( "cg_gunAutoFirst", "1", CVAR_ARCHIVE ); ui.Cvar_Create( "cg_crosshairIdentifyTarget", "1", CVAR_ARCHIVE ); ui.Cvar_Create( "g_subtitles", "0", CVAR_ARCHIVE ); diff --git a/code/ui/ui_shared.cpp b/code/ui/ui_shared.cpp index 2f5cbdf877..3d8bbe3ce0 100644 --- a/code/ui/ui_shared.cpp +++ b/code/ui/ui_shared.cpp @@ -5504,10 +5504,12 @@ static const char *g_bindCommands[] = { "+strafe", "+use", "+useforce", + "bow", //add bow "centerview", "cg_thirdperson !", "datapad", "exitview", + "flourish", //add flourish #ifndef JK2_MODE "force_absorb", #endif @@ -5525,6 +5527,7 @@ static const char *g_bindCommands[] = { "force_throw", "forcenext", "forceprev", + "gloat", //add gloat "invnext", "invprev", "invuse", @@ -5534,6 +5537,7 @@ static const char *g_bindCommands[] = { #else "load quick", #endif + "meditate", //add meditate "saberAttackCycle", #ifdef JK2_MODE "save quik*", diff --git a/codemp/cgame/cg_xcvar.h b/codemp/cgame/cg_xcvar.h index ceb9bd8d39..4070a8050d 100644 --- a/codemp/cgame/cg_xcvar.h +++ b/codemp/cgame/cg_xcvar.h @@ -79,14 +79,14 @@ XCVAR_DEF( cg_debugSaber, "0", NULL, CVAR_CHEAT ) XCVAR_DEF( cg_debugPosition, "0", NULL, CVAR_CHEAT ) XCVAR_DEF( cg_debugEvents, "0", NULL, CVAR_CHEAT ) XCVAR_DEF( cg_duelHeadAngles, "0", NULL, CVAR_NONE ) -XCVAR_DEF( cg_dismember, "0", NULL, CVAR_ARCHIVE ) +XCVAR_DEF( cg_dismember, "2", NULL, CVAR_ARCHIVE ) XCVAR_DEF( cg_deferPlayers, "1", NULL, CVAR_ARCHIVE ) XCVAR_DEF( cg_errorDecay, "100", NULL, CVAR_NONE ) XCVAR_DEF( cg_fallingBob, "1", NULL, CVAR_ARCHIVE ) XCVAR_DEF( cg_footsteps, "3", NULL, CVAR_ARCHIVE ) XCVAR_DEF( cg_forceModel, "0", CG_ForceModelChange, CVAR_ARCHIVE ) XCVAR_DEF( cg_fov, "80", NULL, CVAR_ARCHIVE ) -XCVAR_DEF( cg_fovAspectAdjust, "0", NULL, CVAR_ARCHIVE ) +XCVAR_DEF( cg_fovAspectAdjust, "1", NULL, CVAR_ARCHIVE ) XCVAR_DEF( cg_fovViewmodel, "0", NULL, CVAR_ARCHIVE ) XCVAR_DEF( cg_fovViewmodelAdjust, "1", NULL, CVAR_ARCHIVE ) #if 0 diff --git a/codemp/client/snd_dma.cpp b/codemp/client/snd_dma.cpp index 736203492a..36369d10dc 100644 --- a/codemp/client/snd_dma.cpp +++ b/codemp/client/snd_dma.cpp @@ -4805,14 +4805,14 @@ static qboolean S_UpdateBackgroundTrack_Actual( MusicInfo_t *pMusicInfo, qboolea // or if it's a dynamic music specifier (which can't literally exist), in which case it should set // a return flag then exit... // - char sTestName[MAX_QPATH*2];// *2 so COM_DefaultExtension doesn't do an ERR_DROP if there was no space + char sTestName[MAX_QPATH/**2*/];// *2 so COM_DefaultExtension doesn't do an ERR_DROP if there was no space // for an extension, since this is a "soft" test - Q_strncpyz( sTestName, sMusic_BackgroundLoop, sizeof(sTestName)); + Q_strncpyz( sTestName, sMusic_BackgroundLoop, sizeof(sTestName) - 4 ); COM_DefaultExtension(sTestName, sizeof(sTestName), ".mp3"); if (S_FileExists( sTestName )) { - S_StartBackgroundTrack_Actual( pMusicInfo, qfalse, sMusic_BackgroundLoop, sMusic_BackgroundLoop ); + S_StartBackgroundTrack_Actual( pMusicInfo, qfalse, sTestName, sTestName ); } else { diff --git a/codemp/game/bg_misc.c b/codemp/game/bg_misc.c index b283dcc446..5f97d08456 100644 --- a/codemp/game/bg_misc.c +++ b/codemp/game/bg_misc.c @@ -164,36 +164,36 @@ char *forceMasteryLevels[NUM_FORCE_MASTERY_LEVELS] = int forceMasteryPoints[NUM_FORCE_MASTERY_LEVELS] = { - 0, // FORCE_MASTERY_UNINITIATED, - 5, // FORCE_MASTERY_INITIATE, - 10, // FORCE_MASTERY_PADAWAN, - 20, // FORCE_MASTERY_JEDI, - 30, // FORCE_MASTERY_JEDI_GUARDIAN, - 50, // FORCE_MASTERY_JEDI_ADEPT, - 75, // FORCE_MASTERY_JEDI_KNIGHT, - 100 // FORCE_MASTERY_JEDI_MASTER, + 0, // FORCE_MASTERY_UNINITIATED, //0 + 10, // FORCE_MASTERY_INITIATE, //5 + 15, // FORCE_MASTERY_PADAWAN, //10 + 30, // FORCE_MASTERY_JEDI, //20 + 45, // FORCE_MASTERY_JEDI_GUARDIAN, //30 + 75, // FORCE_MASTERY_JEDI_ADEPT, //50 + 110, // FORCE_MASTERY_JEDI_KNIGHT, //75 + 150 // FORCE_MASTERY_JEDI_MASTER, //100 }; int bgForcePowerCost[NUM_FORCE_POWERS][NUM_FORCE_POWER_LEVELS] = //0 == neutral { - { 0, 2, 4, 6 }, // Heal // FP_HEAL - { 0, 0, 2, 6 }, // Jump //FP_LEVITATION,//hold/duration - { 0, 2, 4, 6 }, // Speed //FP_SPEED,//duration - { 0, 1, 3, 6 }, // Push //FP_PUSH,//hold/duration - { 0, 1, 3, 6 }, // Pull //FP_PULL,//hold/duration - { 0, 4, 6, 8 }, // Mind Trick //FP_TELEPATHY,//instant - { 0, 1, 3, 6 }, // Grip //FP_GRIP,//hold/duration - { 0, 2, 5, 8 }, // Lightning //FP_LIGHTNING,//hold/duration - { 0, 4, 6, 8 }, // Dark Rage //FP_RAGE,//duration - { 0, 2, 5, 8 }, // Protection //FP_PROTECT,//duration - { 0, 1, 3, 6 }, // Absorb //FP_ABSORB,//duration - { 0, 1, 3, 6 }, // Team Heal //FP_TEAM_HEAL,//instant - { 0, 1, 3, 6 }, // Team Force //FP_TEAM_FORCE,//instant - { 0, 2, 4, 6 }, // Drain //FP_DRAIN,//hold/duration - { 0, 2, 5, 8 }, // Sight //FP_SEE,//duration - { 0, 1, 5, 8 }, // Saber Attack //FP_SABER_OFFENSE, - { 0, 1, 5, 8 }, // Saber Defend //FP_SABER_DEFENSE, - { 0, 4, 6, 8 } // Saber Throw //FP_SABERTHROW, + { 0, 2, 4, 6 }, // Heal // FP_HEAL //0246 + { 0, 0, 2, 6 }, // Jump //FP_LEVITATION,//hold/duration //0026 + { 0, 2, 4, 6 }, // Speed //FP_SPEED,//duration //0246 + { 0, 1, 3, 6 }, // Push //FP_PUSH,//hold/duration //0136 + { 0, 1, 3, 6 }, // Pull //FP_PULL,//hold/duration //0136 + { 0, 4, 6, 8 }, // Mind Trick //FP_TELEPATHY,//instant //0468 + { 0, 1, 3, 6 }, // Grip //FP_GRIP,//hold/duration //0136 + { 0, 2, 5, 8 }, // Lightning //FP_LIGHTNING,//hold/duration //0258 + { 0, 4, 6, 8 }, // Dark Rage //FP_RAGE,//duration //0468 + { 0, 2, 5, 8 }, // Protection //FP_PROTECT,//duration //0258 + { 0, 1, 3, 6 }, // Absorb //FP_ABSORB,//duration //0136 + { 0, 1, 3, 6 }, // Team Heal //FP_TEAM_HEAL,//instant //0136 + { 0, 1, 3, 6 }, // Team Force //FP_TEAM_FORCE,//instant //0136 + { 0, 2, 4, 6 }, // Drain //FP_DRAIN,//hold/duration //0246 + { 0, 2, 5, 8 }, // Sight //FP_SEE,//duration //0258 + { 0, 1, 5, 8 }, // Saber Attack //FP_SABER_OFFENSE, //0158 + { 0, 1, 5, 8 }, // Saber Defend //FP_SABER_DEFENSE, //0158 + { 0, 4, 6, 8 } // Saber Throw //FP_SABERTHROW, //0468 //NUM_FORCE_POWERS }; @@ -495,7 +495,8 @@ qboolean BG_LegalizedForcePowers(char *powerOut, size_t powerOutSize, int maxRan { //if this power doesn't match the side we're on, then 0 it now. if (final_Powers[i] && forcePowerDarkLight[i] && - forcePowerDarkLight[i] != final_Side) + forcePowerDarkLight[i] != final_Side && + teamForce) { final_Powers[i] = 0; //This is only likely to happen with g_forceBasedTeams. Let it slide. diff --git a/codemp/game/bg_panimate.c b/codemp/game/bg_panimate.c index fa65f9d724..83b700b5fa 100644 --- a/codemp/game/bg_panimate.c +++ b/codemp/game/bg_panimate.c @@ -65,6 +65,17 @@ qboolean BG_SaberStanceAnim( int anim ) return qfalse; } +qboolean BG_RestAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_MEDITATE: // default taunt + return qtrue; + break; + } + return qfalse; +} + qboolean BG_CrouchAnim( int anim ) { switch ( anim ) diff --git a/codemp/game/bg_pmove.c b/codemp/game/bg_pmove.c index 894867f1c4..b086fffc04 100644 --- a/codemp/game/bg_pmove.c +++ b/codemp/game/bg_pmove.c @@ -10352,7 +10352,7 @@ void PmoveSingle (pmove_t *pmove) { { if ( pm->ps->torsoTimer < 100 ) { - pm->ps->legsTimer = 100; + pm->ps->torsoTimer = 100; } pm->ps->forceHandExtend = HANDEXTEND_TAUNT; pm->ps->forceHandExtendTime = pm->cmd.serverTime + 100; diff --git a/codemp/game/g_active.c b/codemp/game/g_active.c index 5f1f90c44e..b326f64c2f 100644 --- a/codemp/game/g_active.c +++ b/codemp/game/g_active.c @@ -28,6 +28,8 @@ along with this program; if not, see . extern void Jedi_Cloak( gentity_t *self ); extern void Jedi_Decloak( gentity_t *self ); +extern qboolean BG_RestAnim( int anim ); + qboolean PM_SaberInTransition( int move ); qboolean PM_SaberInStart( int move ); qboolean PM_SaberInReturn( int move ); @@ -820,6 +822,21 @@ void ClientTimerActions( gentity_t *ent, int msec ) { if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) { client->ps.stats[STAT_ARMOR]--; } + + //gradually increase health back to 25% of max if force sight >= 1 + if ( (ent->health > 0 && ent->health < client->ps.stats[STAT_MAX_HEALTH]/4) && + (client->ps.fd.forcePowerLevel[FP_SEE] >= FORCE_LEVEL_1) && + (ent->painDebounceTime < level.time) ) { + ent->health++; + } + + //regen health to max when force is max (force sight must be >= 1) + if ( BG_RestAnim( client->ps.legsAnim ) && + (ent->health > 0 && ent->health < client->ps.stats[STAT_MAX_HEALTH]) && + (client->ps.fd.forcePower >= client->ps.fd.forcePowerMax) && + (client->ps.fd.forcePowerLevel[FP_SEE] >= FORCE_LEVEL_1) ) { + ent->health++; + } } } @@ -1603,13 +1620,13 @@ void G_SetTauntAnim( gentity_t *ent, int taunt ) { //hack, don't do while moving return; } - if ( taunt != TAUNT_TAUNT ) + /*if ( taunt != TAUNT_TAUNT ) //always allow all taunts {//normal taunt always allowed if ( level.gametype != GT_DUEL && level.gametype != GT_POWERDUEL ) {//no taunts unless in Duel return; } - } + }*/ // fix: rocket lock bug BG_ClearRocketLock(&ent->client->ps); @@ -1698,16 +1715,19 @@ void G_SetTauntAnim( gentity_t *ent, int taunt ) { anim = BOTH_BOW; } - if ( ent->client->ps.saberHolstered == 1 - && ent->client->saber[1].model[0] ) - {//turn off second saber - G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOff ); - } - else if ( ent->client->ps.saberHolstered == 0 ) - {//turn off first - G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOff ); + if ( ent->client->ps.weapon == WP_SABER ) + { + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model[0] ) + {//turn off second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOff ); + } + else if ( ent->client->ps.saberHolstered == 0 ) + {//turn off first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOff ); + } + ent->client->ps.saberHolstered = 2; } - ent->client->ps.saberHolstered = 2; break; case TAUNT_MEDITATE: if ( ent->client->saber[0].meditateAnim != -1 ) @@ -1723,16 +1743,19 @@ void G_SetTauntAnim( gentity_t *ent, int taunt ) { anim = BOTH_MEDITATE; } - if ( ent->client->ps.saberHolstered == 1 - && ent->client->saber[1].model[0] ) - {//turn off second saber - G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOff ); - } - else if ( ent->client->ps.saberHolstered == 0 ) - {//turn off first - G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOff ); + if ( ent->client->ps.weapon == WP_SABER ) + { + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model[0] ) + {//turn off second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOff ); + } + else if ( ent->client->ps.saberHolstered == 0 ) + {//turn off first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOff ); + } + ent->client->ps.saberHolstered = 2; } - ent->client->ps.saberHolstered = 2; break; case TAUNT_FLOURISH: if ( ent->client->ps.weapon == WP_SABER ) @@ -1782,56 +1805,59 @@ void G_SetTauntAnim( gentity_t *ent, int taunt ) } break; case TAUNT_GLOAT: - if ( ent->client->saber[0].gloatAnim != -1 ) - { - anim = ent->client->saber[0].gloatAnim; - } - else if ( ent->client->saber[1].model[0] - && ent->client->saber[1].gloatAnim != -1 ) - { - anim = ent->client->saber[1].gloatAnim; - } - else + if ( ent->client->ps.weapon == WP_SABER ) { - switch ( ent->client->ps.fd.saberAnimLevel ) + if ( ent->client->saber[0].gloatAnim != -1 ) { - case SS_FAST: - case SS_TAVION: - anim = BOTH_VICTORY_FAST; - break; - case SS_MEDIUM: - anim = BOTH_VICTORY_MEDIUM; - break; - case SS_STRONG: - case SS_DESANN: - if ( ent->client->ps.saberHolstered ) - {//turn on first - G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); - } - ent->client->ps.saberHolstered = 0; - anim = BOTH_VICTORY_STRONG; - break; - case SS_DUAL: - if ( ent->client->ps.saberHolstered == 1 - && ent->client->saber[1].model[0] ) - {//turn on second saber - G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOn ); - } - else if ( ent->client->ps.saberHolstered == 2 ) - {//turn on first - G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); - } - ent->client->ps.saberHolstered = 0; - anim = BOTH_VICTORY_DUAL; - break; - case SS_STAFF: - if ( ent->client->ps.saberHolstered ) - {//turn on first - G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + anim = ent->client->saber[0].gloatAnim; + } + else if ( ent->client->saber[1].model[0] + && ent->client->saber[1].gloatAnim != -1 ) + { + anim = ent->client->saber[1].gloatAnim; + } + else + { + switch ( ent->client->ps.fd.saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + anim = BOTH_VICTORY_FAST; + break; + case SS_MEDIUM: + anim = BOTH_VICTORY_MEDIUM; + break; + case SS_STRONG: + case SS_DESANN: + if ( ent->client->ps.saberHolstered ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_VICTORY_STRONG; + break; + case SS_DUAL: + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model[0] ) + {//turn on second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOn ); + } + else if ( ent->client->ps.saberHolstered == 2 ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_VICTORY_DUAL; + break; + case SS_STAFF: + if ( ent->client->ps.saberHolstered ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_VICTORY_STAFF; + break; } - ent->client->ps.saberHolstered = 0; - anim = BOTH_VICTORY_STAFF; - break; } } break; diff --git a/codemp/game/g_xcvar.h b/codemp/game/g_xcvar.h index df9ee8094a..f35a4fd665 100644 --- a/codemp/game/g_xcvar.h +++ b/codemp/game/g_xcvar.h @@ -87,7 +87,7 @@ XCVAR_DEF( g_debugServerSkel, "0", NULL, CVAR_CHEAT, qfalse ) #ifdef _DEBUG XCVAR_DEF( g_disableServerG2, "0", NULL, CVAR_NONE, qtrue ) #endif -XCVAR_DEF( g_dismember, "0", NULL, CVAR_ARCHIVE, qtrue ) +XCVAR_DEF( g_dismember, "100", NULL, CVAR_ARCHIVE, qtrue ) XCVAR_DEF( g_doWarmup, "0", NULL, CVAR_NONE, qtrue ) //XCVAR_DEF( g_engineModifications, "1", NULL, CVAR_ARCHIVE, qfalse ) XCVAR_DEF( g_ff_objectives, "0", NULL, CVAR_CHEAT|CVAR_NORESTART, qtrue ) diff --git a/codemp/game/w_force.c b/codemp/game/w_force.c index 034df35b9c..024b6e5571 100644 --- a/codemp/game/w_force.c +++ b/codemp/game/w_force.c @@ -35,6 +35,8 @@ extern void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenD extern void Jedi_Decloak( gentity_t *self ); extern qboolean BG_FullBodyTauntAnim( int anim ); +extern qboolean BG_RestAnim( int anim ); +extern qboolean BG_CrouchAnim( int anim ); extern bot_state_t *botstates[MAX_CLIENTS]; @@ -813,6 +815,11 @@ int WP_AbsorbConversion(gentity_t *attacked, int atdAbsLevel, gentity_t *attacke return -1; } + if (Q_irand( 0, atPowerLevel ) > Q_irand( 1, atdAbsLevel )) + { //chance of attacker's high level power breaking through entirely + return -1; + } + //Subtract absorb power level from the offensive force power getLevel = atPowerLevel; getLevel -= atdAbsLevel; @@ -1103,7 +1110,7 @@ void ForceHeal( gentity_t *self ) if (self->client->ps.fd.forcePowerLevel[FP_HEAL] == FORCE_LEVEL_3) { - self->health += 25; //This was 50, but that angered the Balance God. + self->health += 50; //This was 50, but that angered the Balance God. //25; if (self->health > self->client->ps.stats[STAT_MAX_HEALTH]) { @@ -1113,7 +1120,7 @@ void ForceHeal( gentity_t *self ) } else if (self->client->ps.fd.forcePowerLevel[FP_HEAL] == FORCE_LEVEL_2) { - self->health += 10; + self->health += 30; //10; if (self->health > self->client->ps.stats[STAT_MAX_HEALTH]) { @@ -1123,7 +1130,7 @@ void ForceHeal( gentity_t *self ) } else { - self->health += 5; + self->health += 15; //5; if (self->health > self->client->ps.stats[STAT_MAX_HEALTH]) { @@ -1691,7 +1698,7 @@ void ForceLightningDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec } if (ForcePowerUsableOn(self, traceEnt, FP_LIGHTNING)) { - int dmg = Q_irand(1,2); //Q_irand( 1, 3 ); + int dmg = Q_irand( 1, 3 ); //Q_irand( 1, 2 ); int modPowerLevel = -1; @@ -1714,7 +1721,7 @@ void ForceLightningDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec } else if (modPowerLevel == 2) { - dmg = 1; + dmg = 2; traceEnt->client->noLightningTime = level.time + 100; } } @@ -1965,7 +1972,7 @@ void ForceDrainDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec3_t dmg = 2; } } - //G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_FORCE_DARK ); + G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_FORCE_DARK ); if (dmg) { @@ -1991,7 +1998,6 @@ void ForceDrainDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec3_t //Drain the standard amount since we just drained someone else - /* if (self->client->ps.fd.forcePowerLevel[FP_DRAIN] == FORCE_LEVEL_1) { BG_ForcePowerDrain( &self->client->ps, FP_DRAIN, 0 ); @@ -2014,13 +2020,12 @@ void ForceDrainDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec3_t { if ( !Q_irand( 0, 2 ) ) { - //G_Sound( traceEnt, CHAN_BODY, G_SoundIndex( "sound/weapons/force/lightninghit.wav" ) ); + G_Sound( traceEnt, CHAN_BODY, G_SoundIndex( va("sound/weapons/force/lightninghit%i", Q_irand(1, 3) )) ); } - // traceEnt->s.powerups |= ( 1 << PW_DISINT_1 ); + traceEnt->s.powerups |= ( 1 << PW_DISINT_4 ); - // traceEnt->client->ps.powerups[PW_DISINT_1] = level.time + 500; + traceEnt->client->ps.powerups[PW_DISINT_4] = level.time + 500; } - */ if (traceEnt->client->forcePowerSoundDebounce < level.time) { @@ -2161,7 +2166,7 @@ int ForceShootDrain( gentity_t *self ) self->client->ps.activeForcePass = self->client->ps.fd.forcePowerLevel[FP_DRAIN] + FORCE_LEVEL_3; - BG_ForcePowerDrain( &self->client->ps, FP_DRAIN, 5 ); //used to be 1, but this did, too, anger the God of Balance. + BG_ForcePowerDrain( &self->client->ps, FP_DRAIN, 1 ); //used to be 1, but this did, too, anger the God of Balance. //5 self->client->ps.fd.forcePowerRegenDebounceTime = level.time + 500; @@ -5427,7 +5432,11 @@ void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd ) if ( self->client->ps.powerups[PW_FORCE_BOON] ) WP_ForcePowerRegenerate( self, 6 ); else if ( self->client->ps.isJediMaster && level.gametype == GT_JEDIMASTER ) - WP_ForcePowerRegenerate( self, 4 ); //jedi master regenerates 4 times as fast + WP_ForcePowerRegenerate( self, 4 ); //jedi master regenerates 4 times as fast + else if ( BG_CrouchAnim( self->client->ps.legsAnim ) ) + WP_ForcePowerRegenerate( self, 2 ); //regen force much faster when crouched + else if ( BG_RestAnim( self->client->ps.legsAnim ) ) + WP_ForcePowerRegenerate( self, 4 ); //regen force extremly fast when meditating else WP_ForcePowerRegenerate( self, 0 ); } diff --git a/codemp/game/w_saber.h b/codemp/game/w_saber.h index f501a8fb55..b9b8e76789 100644 --- a/codemp/game/w_saber.h +++ b/codemp/game/w_saber.h @@ -49,11 +49,11 @@ along with this program; if not, see . #define SABER_REFLECT_MISSILE_CONE 0.2f #define FORCE_POWER_MAX 100 -#define MAX_GRIP_DISTANCE 256 +#define MAX_GRIP_DISTANCE 512//256 #define MAX_TRICK_DISTANCE 512 #define FORCE_JUMP_CHARGE_TIME 6400//3000.0f #define GRIP_DRAIN_AMOUNT 30 -#define FORCE_LIGHTNING_RADIUS 300 +#define FORCE_LIGHTNING_RADIUS 512//300 #define MAX_DRAIN_DISTANCE 512 typedef enum forceJump_e diff --git a/codemp/rd-rend2/tr_init.cpp b/codemp/rd-rend2/tr_init.cpp index 183fbcae90..fc9bc63568 100644 --- a/codemp/rd-rend2/tr_init.cpp +++ b/codemp/rd-rend2/tr_init.cpp @@ -1436,7 +1436,7 @@ void R_Register( void ) r_ext_multi_draw_arrays = ri.Cvar_Get( "r_ext_multi_draw_arrays", "1", CVAR_ARCHIVE | CVAR_LATCH, "Unused" ); r_ext_texture_float = ri.Cvar_Get( "r_ext_texture_float", "1", CVAR_ARCHIVE | CVAR_LATCH, "Disable/enable floating-point textures" ); r_arb_half_float_pixel = ri.Cvar_Get( "r_arb_half_float_pixel", "1", CVAR_ARCHIVE | CVAR_LATCH, "Disable/enable ARB_half_float GL extension" ); - r_ext_framebuffer_multisample = ri.Cvar_Get( "r_ext_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH, "Disable/enable framebuffer MSAA" ); + r_ext_framebuffer_multisample = ri.Cvar_Get( "r_ext_multisample", "8", CVAR_ARCHIVE | CVAR_LATCH, "Disable/enable framebuffer MSAA" ); r_arb_seamless_cube_map = ri.Cvar_Get( "r_arb_seamless_cube_map", "0", CVAR_ARCHIVE | CVAR_LATCH, "Disable/enable seamless cube map filtering GL extension" ); r_arb_vertex_type_2_10_10_10_rev = ri.Cvar_Get( "r_arb_vertex_type_2_10_10_10_rev", "1", CVAR_ARCHIVE | CVAR_LATCH, "Disable/enable 1010102 UI data type" ); r_arb_buffer_storage = ri.Cvar_Get( "r_arb_buffer_storage", "0", CVAR_ARCHIVE | CVAR_LATCH, "Disable/enable buffer storage GL extension" ); diff --git a/codemp/ui/ui_force.c b/codemp/ui/ui_force.c index 898ad36880..b5376d9d2c 100644 --- a/codemp/ui/ui_force.c +++ b/codemp/ui/ui_force.c @@ -494,6 +494,7 @@ void UI_ReadLegalForce(void) int forcePowerRank = 0; int currank = 0; int forceTeam = 0; + int forceBasedTeams = atoi( Info_ValueForKey( info, "g_forceBasedTeams" ) ); qboolean updateForceLater = qfalse; //First, stick them into a string. @@ -514,7 +515,7 @@ void UI_ReadLegalForce(void) info[0] = '\0'; trap->GetConfigString(CS_SERVERINFO, info, sizeof(info)); - if (atoi( Info_ValueForKey( info, "g_forceBasedTeams" ) )) + if (forceBasedTeams) { switch((int)(trap->Cvar_VariableValue("ui_myteam"))) { @@ -604,7 +605,7 @@ void UI_ReadLegalForce(void) continue; // skip this power } - if (uiForcePowerDarkLight[c] && uiForcePowerDarkLight[c] != uiForceSide) + if (uiForcePowerDarkLight[c] && uiForcePowerDarkLight[c] != uiForceSide && forceBasedTeams) { //Apparently the user has crafted a force config that has powers that don't fit with the config's side. continue; // skip this power } @@ -868,14 +869,14 @@ qboolean UI_ForceSide_HandleKey(int flags, float *special, int key, int num, int uiForceSide = num; // Resetting power ranks based on if light or dark side is chosen - while (x < NUM_FORCE_POWERS) + /*while (x < NUM_FORCE_POWERS) { if (uiForcePowerDarkLight[x] && uiForceSide != uiForcePowerDarkLight[x]) { uiForcePowersRank[x] = 0; } x++; - } + }*/ UpdateForceUsed(); @@ -1022,11 +1023,11 @@ qboolean UI_ForcePowerRank_HandleKey(int flags, float *special, int key, int num } // If we are not on the same side as a power, or if we are not of any rank at all. - if (uiForcePowerDarkLight[forcepower] && uiForceSide != uiForcePowerDarkLight[forcepower]) + /*if (uiForcePowerDarkLight[forcepower] && uiForceSide != uiForcePowerDarkLight[forcepower]) { return qtrue; } - else if (forcepower == FP_SABER_DEFENSE || forcepower == FP_SABERTHROW) + else*/ if (forcepower == FP_SABER_DEFENSE || forcepower == FP_SABERTHROW) { // Saberdefend and saberthrow can't be bought if there is no saberattack if (uiForcePowersRank[FP_SABER_OFFENSE] < 1) { @@ -1136,6 +1137,7 @@ void UI_ForceConfigHandle( int oldindex, int newindex ) char singleBuf[64]; char info[MAX_INFO_VALUE]; int forceTeam = 0; + int forceBasedTeams = atoi( Info_ValueForKey( info, "g_forceBasedTeams" ) ); if (oldindex == 0) { //switching out from custom config, so first shove the current values into the custom storage @@ -1215,7 +1217,7 @@ void UI_ForceConfigHandle( int oldindex, int newindex ) info[0] = '\0'; trap->GetConfigString(CS_SERVERINFO, info, sizeof(info)); - if (atoi( Info_ValueForKey( info, "g_forceBasedTeams" ) )) + if (forceBasedTeams) { switch((int)(trap->Cvar_VariableValue("ui_myteam"))) { @@ -1321,7 +1323,7 @@ void UI_ForceConfigHandle( int oldindex, int newindex ) continue; // skip this power } - if (uiForcePowerDarkLight[c] && uiForcePowerDarkLight[c] != uiForceSide) + if (uiForcePowerDarkLight[c] && uiForcePowerDarkLight[c] != uiForceSide && forceBasedTeams) { //Apparently the user has crafted a force config that has powers that don't fit with the config's side. continue; // skip this power } diff --git a/pk3/assets_fpls.pk3 b/pk3/assets_fpls.pk3 new file mode 100644 index 0000000000..c21976fc3b Binary files /dev/null and b/pk3/assets_fpls.pk3 differ diff --git a/shared/sdl/sdl_window.cpp b/shared/sdl/sdl_window.cpp index 8ff66a8e4b..6bae4ca16d 100644 --- a/shared/sdl/sdl_window.cpp +++ b/shared/sdl/sdl_window.cpp @@ -748,7 +748,7 @@ window_t WIN_Init( const windowDesc_t *windowDesc, glconfig_t *glConfig ) r_depthbits = Cvar_Get( "r_depthbits", "0", CVAR_ARCHIVE_ND|CVAR_LATCH ); r_colorbits = Cvar_Get( "r_colorbits", "0", CVAR_ARCHIVE_ND|CVAR_LATCH ); r_ignorehwgamma = Cvar_Get( "r_ignorehwgamma", "0", CVAR_ARCHIVE_ND|CVAR_LATCH ); - r_ext_multisample = Cvar_Get( "r_ext_multisample", "0", CVAR_ARCHIVE_ND|CVAR_LATCH ); + r_ext_multisample = Cvar_Get( "r_ext_multisample", "8", CVAR_ARCHIVE_ND|CVAR_LATCH ); Cvar_Get( "r_availableModes", "", CVAR_ROM ); // Create the window and set up the context