//------------------------------------ void Seeker_Strafe( void ) { int side; vec3_t end, right, dir; trace_t tr; if ( random() > 0.7f || !NPC->enemy || !NPC->enemy->client ) { // Do a regular style strafe AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); // Pick a random strafe direction, then check to see if doing a strafe would be // reasonably valid side = ( rand() & 1 ) ? -1 : 1; VectorMA( NPC->r.currentOrigin, SEEKER_STRAFE_DIS * side, right, end ); trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); // Close enough if ( tr.fraction > 0.9f ) { float vel = SEEKER_STRAFE_VEL; float upPush = SEEKER_UPWARD_PUSH; if ( NPC->client->NPC_class != CLASS_BOBAFETT ) { G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); } else { vel *= 3.0f; upPush *= 4.0f; } VectorMA( NPC->client->ps.velocity, vel*side, right, NPC->client->ps.velocity ); // Add a slight upward push NPC->client->ps.velocity[2] += upPush; NPCInfo->standTime = level.time + 1000 + random() * 500; } } else { float stDis; // Do a strafe to try and keep on the side of their enemy AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL ); // Pick a random side side = ( rand() & 1 ) ? -1 : 1; stDis = SEEKER_STRAFE_DIS; if ( NPC->client->NPC_class == CLASS_BOBAFETT ) { stDis *= 2.0f; } VectorMA( NPC->enemy->r.currentOrigin, stDis * side, right, end ); // then add a very small bit of random in front of/behind the player action VectorMA( end, crandom() * 25, dir, end ); trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); // Close enough if ( tr.fraction > 0.9f ) { float dis, upPush; VectorSubtract( tr.endpos, NPC->r.currentOrigin, dir ); dir[2] *= 0.25; // do less upward change dis = VectorNormalize( dir ); // Try to move the desired enemy side VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity ); upPush = SEEKER_UPWARD_PUSH; if ( NPC->client->NPC_class != CLASS_BOBAFETT ) { G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); } else { upPush *= 4.0f; } // Add a slight upward push NPC->client->ps.velocity[2] += upPush; NPCInfo->standTime = level.time + 2500 + random() * 500; } } }
/* ============= P_WorldEffects Check for lava / slime contents and drowning ============= */ void P_WorldEffects( gentity_t *ent ) { qboolean envirosuit; int waterlevel; if ( ent->client->noclip ) { ent->client->airOutTime = level.time + 12000; // don't need air return; } waterlevel = ent->waterlevel; envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; // // check for drowning // if ( waterlevel == 3 ) { // envirosuit give air if ( envirosuit ) { ent->client->airOutTime = level.time + 10000; } // if out of air, start drowning if ( ent->client->airOutTime < level.time) { // drown! ent->client->airOutTime += 1000; if ( ent->health > 0 ) { // take more damage the longer underwater ent->damage += 2; if (ent->damage > 15) ent->damage = 15; // play a gurp sound instead of a normal pain sound if (ent->health <= ent->damage) { G_Sound(ent, CHAN_VOICE, G_SoundIndex(/*"*drown.wav"*/"sound/player/gurp1.wav")); } else if (rand()&1) { G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav")); } else { G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav")); } // don't play a normal pain sound ent->pain_debounce_time = level.time + 200; G_Damage (ent, NULL, NULL, NULL, NULL, ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); } } } else { ent->client->airOutTime = level.time + 12000; ent->damage = 2; } // // check for sizzle damage (move to pmove?) // if (waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { if (ent->health > 0 && ent->pain_debounce_time <= level.time ) { if ( envirosuit ) { G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); } else { if (ent->watertype & CONTENTS_LAVA) { G_Damage (ent, NULL, NULL, NULL, NULL, 30*waterlevel, 0, MOD_LAVA); } if (ent->watertype & CONTENTS_SLIME) { G_Damage (ent, NULL, NULL, NULL, NULL, 10*waterlevel, 0, MOD_SLIME); } } } } }
/* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real( gentity_t *ent ) { gclient_t *client; pmove_t pm; int oldEventSequence; int msec; int i; usercmd_t *ucmd; client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { return; } // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; // sanity check the command time to prevent speedup cheating if ( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; // G_Printf("serverTime <<<<<\n" ); } if ( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; // G_Printf("serverTime >>>>>\n" ); } msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { return; } if ( msec > 200 ) { msec = 200; } if ( pmove_msec.integer < 8 ) { trap_Cvar_Set("pmove_msec", "8"); } else if (pmove_msec.integer > 33) { trap_Cvar_Set("pmove_msec", "33"); } if ( pmove_fixed.integer || client->pers.pmoveFixed ) { ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; //if (ucmd->serverTime - client->ps.commandTime <= 0) // return; } // // check for exiting intermission // if ( level.intermissiontime ) { ClientIntermissionThink( client ); return; } // spectators don't do much if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { return; } SpectatorThink( ent, ucmd ); return; } if (ent && ent->client && (ent->client->ps.eFlags & EF_INVULNERABLE)) { if (ent->client->invulnerableTimer <= level.time) { ent->client->ps.eFlags &= ~EF_INVULNERABLE; } } // check for inactivity timer, but never drop the local client of a non-dedicated server if ( !ClientInactivityTimer( client ) ) { return; } // clear the rewards if time if ( level.time > client->rewardTime ) { client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); } if ( client->noclip ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.eFlags & EF_DISINTEGRATION ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { client->ps.pm_type = PM_DEAD; } else { if (client->ps.forceGripChangeMovetype) { client->ps.pm_type = client->ps.forceGripChangeMovetype; } else { client->ps.pm_type = PM_NORMAL; } } client->ps.gravity = g_gravity.value; // set speed client->ps.speed = g_speed.value; client->ps.basespeed = g_speed.value; if (ent->client->ps.duelInProgress) { gentity_t *duelAgainst = &g_entities[ent->client->ps.duelIndex]; //Keep the time updated, so once this duel ends this player can't engage in a duel for another //10 seconds. This will give other people a chance to engage in duels in case this player wants //to engage again right after he's done fighting and someone else is waiting. ent->client->ps.fd.privateDuelTime = level.time + 10000; if (ent->client->ps.duelTime < level.time) { //Bring out the sabers if (ent->client->ps.weapon == WP_SABER && ent->client->ps.saberHolstered && ent->client->ps.duelTime) { if (!saberOffSound || !saberOnSound) { saberOffSound = G_SoundIndex("sound/weapons/saber/saberoffquick.wav"); saberOnSound = G_SoundIndex("sound/weapons/saber/saberon.wav"); } ent->client->ps.saberHolstered = qfalse; G_Sound(ent, CHAN_AUTO, saberOnSound); G_AddEvent(ent, EV_PRIVATE_DUEL, 2); ent->client->ps.duelTime = 0; } if (duelAgainst && duelAgainst->client && duelAgainst->inuse && duelAgainst->client->ps.weapon == WP_SABER && duelAgainst->client->ps.saberHolstered && duelAgainst->client->ps.duelTime) { if (!saberOffSound || !saberOnSound) { saberOffSound = G_SoundIndex("sound/weapons/saber/saberoffquick.wav"); saberOnSound = G_SoundIndex("sound/weapons/saber/saberon.wav"); } duelAgainst->client->ps.saberHolstered = qfalse; G_Sound(duelAgainst, CHAN_AUTO, saberOnSound); G_AddEvent(duelAgainst, EV_PRIVATE_DUEL, 2); duelAgainst->client->ps.duelTime = 0; } } else { client->ps.speed = 0; client->ps.basespeed = 0; ucmd->forwardmove = 0; ucmd->rightmove = 0; ucmd->upmove = 0; } if (!duelAgainst || !duelAgainst->client || !duelAgainst->inuse || duelAgainst->client->ps.duelIndex != ent->s.number) { ent->client->ps.duelInProgress = 0; G_AddEvent(ent, EV_PRIVATE_DUEL, 0); } else if (duelAgainst->health < 1 || duelAgainst->client->ps.stats[STAT_HEALTH] < 1) { ent->client->ps.duelInProgress = 0; duelAgainst->client->ps.duelInProgress = 0; G_AddEvent(ent, EV_PRIVATE_DUEL, 0); G_AddEvent(duelAgainst, EV_PRIVATE_DUEL, 0); //Winner gets full health.. providing he's still alive if (ent->health > 0 && ent->client->ps.stats[STAT_HEALTH] > 0) { if (ent->health < ent->client->ps.stats[STAT_MAX_HEALTH]) { ent->client->ps.stats[STAT_HEALTH] = ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; } if (g_spawnInvulnerability.integer) { ent->client->ps.eFlags |= EF_INVULNERABLE; ent->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer; } } /* trap_SendServerCommand( ent-g_entities, va("print \"%s %s\n\"", ent->client->pers.netname, G_GetStripEdString("SVINGAME", "PLDUELWINNER")) ); trap_SendServerCommand( duelAgainst-g_entities, va("print \"%s %s\n\"", ent->client->pers.netname, G_GetStripEdString("SVINGAME", "PLDUELWINNER")) ); */ //Private duel announcements are now made globally because we only want one duel at a time. if (ent->health > 0 && ent->client->ps.stats[STAT_HEALTH] > 0) { trap_SendServerCommand( -1, va("cp \"%s %s %s!\n\"", ent->client->pers.netname, G_GetStripEdString("SVINGAME", "PLDUELWINNER"), duelAgainst->client->pers.netname) ); } else { //it was a draw, because we both managed to die in the same frame trap_SendServerCommand( -1, va("cp \"%s\n\"", G_GetStripEdString("SVINGAME", "PLDUELTIE")) ); } } else { vec3_t vSub; float subLen = 0; VectorSubtract(ent->client->ps.origin, duelAgainst->client->ps.origin, vSub); subLen = VectorLength(vSub); if (subLen >= 1024) { ent->client->ps.duelInProgress = 0; duelAgainst->client->ps.duelInProgress = 0; G_AddEvent(ent, EV_PRIVATE_DUEL, 0); G_AddEvent(duelAgainst, EV_PRIVATE_DUEL, 0); trap_SendServerCommand( -1, va("print \"%s\n\"", G_GetStripEdString("SVINGAME", "PLDUELSTOP")) ); } } } /* if ( client->ps.powerups[PW_HASTE] ) { client->ps.speed *= 1.3; } */ if (client->ps.usingATST && ent->health > 0) { //we have special shot clip boxes as an ATST ent->r.contents |= CONTENTS_NOSHOT; ATST_ManageDamageBoxes(ent); } else { ent->r.contents &= ~CONTENTS_NOSHOT; client->damageBoxHandle_Head = 0; client->damageBoxHandle_RLeg = 0; client->damageBoxHandle_LLeg = 0; } //rww - moved this stuff into the pmove code so that it's predicted properly //BG_AdjustClientSpeed(&client->ps, &client->pers.cmd, level.time); // set up for pmove oldEventSequence = client->ps.eventSequence; memset (&pm, 0, sizeof(pm)); if ( ent->flags & FL_FORCE_GESTURE ) { ent->flags &= ~FL_FORCE_GESTURE; ent->client->pers.cmd.buttons |= BUTTON_GESTURE; } if (ent->client && ent->client->ps.fallingToDeath && (level.time - FALL_FADE_TIME) > ent->client->ps.fallingToDeath) { //die! player_die(ent, ent, ent, 100000, MOD_FALLING); respawn(ent); ent->client->ps.fallingToDeath = 0; G_MuteSound(ent->s.number, CHAN_VOICE); //stop screaming, because you are dead! } if (ent->client->ps.otherKillerTime > level.time && ent->client->ps.groundEntityNum != ENTITYNUM_NONE && ent->client->ps.otherKillerDebounceTime < level.time) { ent->client->ps.otherKillerTime = 0; ent->client->ps.otherKiller = ENTITYNUM_NONE; } else if (ent->client->ps.otherKillerTime > level.time && ent->client->ps.groundEntityNum == ENTITYNUM_NONE) { if (ent->client->ps.otherKillerDebounceTime < (level.time + 100)) { ent->client->ps.otherKillerDebounceTime = level.time + 100; } } // WP_ForcePowersUpdate( ent, msec, ucmd); //update any active force powers // WP_SaberPositionUpdate(ent, ucmd); //check the server-side saber point, do apprioriate server-side actions (effects are cs-only) if ((ent->client->pers.cmd.buttons & BUTTON_USE) && ent->client->ps.useDelay < level.time) { TryUse(ent); ent->client->ps.useDelay = level.time + 100; } pm.ps = &client->ps; pm.cmd = *ucmd; if ( pm.ps->pm_type == PM_DEAD ) { pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; } else if ( ent->r.svFlags & SVF_BOT ) { pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP; } else { pm.tracemask = MASK_PLAYERSOLID; } pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; pm.animations = bgGlobalAnimations;//NULL; pm.gametype = g_gametype.integer; VectorCopy( client->ps.origin, client->oldOrigin ); if (level.intermissionQueued != 0 && g_singlePlayer.integer) { if ( level.time - level.intermissionQueued >= 1000 ) { pm.cmd.buttons = 0; pm.cmd.forwardmove = 0; pm.cmd.rightmove = 0; pm.cmd.upmove = 0; if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) { trap_SendConsoleCommand( EXEC_APPEND, "centerview\n"); } ent->client->ps.pm_type = PM_SPINTERMISSION; } } for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { if (g_entities[i].inuse && g_entities[i].client) { pm.bgClients[i] = &g_entities[i].client->ps; } } if (ent->client->ps.saberLockTime > level.time) { gentity_t *blockOpp = &g_entities[ent->client->ps.saberLockEnemy]; if (blockOpp && blockOpp->inuse && blockOpp->client) { vec3_t lockDir, lockAng; //VectorClear( ent->client->ps.velocity ); VectorSubtract( blockOpp->r.currentOrigin, ent->r.currentOrigin, lockDir ); //lockAng[YAW] = vectoyaw( defDir ); vectoangles(lockDir, lockAng); SetClientViewAngle( ent, lockAng ); } if ( ( ent->client->buttons & BUTTON_ATTACK ) && ! ( ent->client->oldbuttons & BUTTON_ATTACK ) ) { ent->client->ps.saberLockHits++; } if (ent->client->ps.saberLockHits > 2) { if (!ent->client->ps.saberLockAdvance) { ent->client->ps.saberLockHits -= 3; } ent->client->ps.saberLockAdvance = qtrue; } } else { ent->client->ps.saberLockFrame = 0; } Pmove (&pm); switch(pm.cmd.generic_cmd) { case 0: break; case GENCMD_SABERSWITCH: Cmd_ToggleSaber_f(ent); break; case GENCMD_ENGAGE_DUEL: Cmd_EngageDuel_f(ent); break; case GENCMD_FORCE_HEAL: ForceHeal(ent); break; case GENCMD_FORCE_SPEED: ForceSpeed(ent, 0); break; case GENCMD_FORCE_THROW: ForceThrow(ent, qfalse); break; case GENCMD_FORCE_PULL: ForceThrow(ent, qtrue); break; case GENCMD_FORCE_DISTRACT: ForceTelepathy(ent); break; case GENCMD_FORCE_RAGE: ForceRage(ent); break; case GENCMD_FORCE_PROTECT: ForceProtect(ent); break; case GENCMD_FORCE_ABSORB: ForceAbsorb(ent); break; case GENCMD_FORCE_HEALOTHER: ForceTeamHeal(ent); break; case GENCMD_FORCE_FORCEPOWEROTHER: ForceTeamForceReplenish(ent); break; case GENCMD_FORCE_SEEING: ForceSeeing(ent); break; case GENCMD_USE_SEEKER: if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SEEKER)) && G_ItemUsable(&ent->client->ps, HI_SEEKER) ) { ItemUse_Seeker(ent); G_AddEvent(ent, EV_USE_ITEM0+HI_SEEKER, 0); ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_SEEKER); } break; case GENCMD_USE_FIELD: if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SHIELD)) && G_ItemUsable(&ent->client->ps, HI_SHIELD) ) { ItemUse_Shield(ent); G_AddEvent(ent, EV_USE_ITEM0+HI_SHIELD, 0); ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_SHIELD); } break; case GENCMD_USE_BACTA: if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC)) && G_ItemUsable(&ent->client->ps, HI_MEDPAC) ) { ItemUse_MedPack(ent); G_AddEvent(ent, EV_USE_ITEM0+HI_MEDPAC, 0); ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_MEDPAC); } break; case GENCMD_USE_ELECTROBINOCULARS: if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_BINOCULARS)) && G_ItemUsable(&ent->client->ps, HI_BINOCULARS) ) { ItemUse_Binoculars(ent); if (ent->client->ps.zoomMode == 0) { G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 1); } else { G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 2); } } break; case GENCMD_ZOOM: if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_BINOCULARS)) && G_ItemUsable(&ent->client->ps, HI_BINOCULARS) ) { ItemUse_Binoculars(ent); if (ent->client->ps.zoomMode == 0) { G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 1); } else { G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 2); } } break; case GENCMD_USE_SENTRY: if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SENTRY_GUN)) && G_ItemUsable(&ent->client->ps, HI_SENTRY_GUN) ) { ItemUse_Sentry(ent); G_AddEvent(ent, EV_USE_ITEM0+HI_SENTRY_GUN, 0); ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_SENTRY_GUN); } break; case GENCMD_SABERATTACKCYCLE: Cmd_SaberAttackCycle_f(ent); break; default: break; } // save results of pmove if ( ent->client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; } if (g_smoothClients.integer) { BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); } else { BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); } SendPendingPredictableEvents( &ent->client->ps ); if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { client->fireHeld = qfalse; // for grapple } // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); VectorCopy (pm.mins, ent->r.mins); VectorCopy (pm.maxs, ent->r.maxs); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents( ent, oldEventSequence ); if ( pm.useEvent ) { //TODO: Use // TryUse( ent ); } // link entity now, after any personal teleporters have been used trap_LinkEntity (ent); if ( !ent->client->noclip ) { G_TouchTriggers( ent ); } // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); //test for solid areas in the AAS file // BotTestAAS(ent->r.currentOrigin); // touch other objects ClientImpacts( ent, &pm ); // save results of triggers and client events if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; // Did we kick someone in our pmove sequence? if (client->ps.forceKickFlip) { gentity_t *faceKicked = &g_entities[client->ps.forceKickFlip-1]; if (faceKicked && faceKicked->client && (!OnSameTeam(ent, faceKicked) || g_friendlyFire.integer) && (!faceKicked->client->ps.duelInProgress || faceKicked->client->ps.duelIndex == ent->s.number) && (!ent->client->ps.duelInProgress || ent->client->ps.duelIndex == faceKicked->s.number)) { if ( faceKicked && faceKicked->client && faceKicked->health && faceKicked->takedamage ) {//push them away and do pain vec3_t oppDir; int strength = (int)VectorNormalize2( client->ps.velocity, oppDir ); strength *= 0.05; VectorScale( oppDir, -1, oppDir ); G_Damage( faceKicked, ent, ent, oppDir, client->ps.origin, strength, DAMAGE_NO_ARMOR, MOD_MELEE ); if (faceKicked->health > 0 && faceKicked->client->ps.stats[STAT_HEALTH] > 0 && faceKicked->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN) { if (Q_irand(1, 10) <= 3) { //only actually knock over sometimes, but always do velocity hit faceKicked->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; faceKicked->client->ps.forceHandExtendTime = level.time + 1100; faceKicked->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim } faceKicked->client->ps.otherKiller = ent->s.number; faceKicked->client->ps.otherKillerTime = level.time + 5000; faceKicked->client->ps.otherKillerDebounceTime = level.time + 100; faceKicked->client->ps.velocity[0] = oppDir[0]*(strength*40); faceKicked->client->ps.velocity[1] = oppDir[1]*(strength*40); faceKicked->client->ps.velocity[2] = 200; } G_Sound( faceKicked, CHAN_AUTO, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) ); } } client->ps.forceKickFlip = 0; } // check for respawning if ( client->ps.stats[STAT_HEALTH] <= 0 ) { // wait for the attack button to be pressed if ( level.time > client->respawnTime ) { // forcerespawn is to prevent users from waiting out powerups if ( g_forcerespawn.integer > 0 && ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) { respawn( ent ); return; } // pressing attack or use is the normal respawn method if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { respawn( ent ); } } return; } // perform once-a-second actions ClientTimerActions( ent, msec ); G_UpdateClientBroadcasts ( ent ); }
/** * @brief SP_team_WOLF_checkpoint * @details QUAKED team_WOLF_checkpoint (.9 .3 .9) (-16 -16 0) (16 16 128) SPAWNPOINT CP_HOLD AXIS_ONLY ALLIED_ONLY * This is the flagpole players touch in Capture and Hold game scenarios. * * It will call specific trigger funtions in the map script for this object. * When allies capture, it will call "allied_capture". * When axis capture, it will call "axis_capture". * * if spawnpoint flag is set, think will turn on spawnpoints (specified as targets) * for capture team and turn *off* targeted spawnpoints for opposing team * * @param[in,out] ent */ void SP_team_WOLF_checkpoint(gentity_t *ent) { char *capture_sound; if (!ent->scriptName) { G_Error("team_WOLF_checkpoint must have a \"scriptname\"\n"); } // Make sure the ET_TRAP entity type stays valid ent->s.eType = ET_TRAP; // Model is user assignable, but it will always try and use the animations for flagpole.md3 if (ent->model) { ent->s.modelindex = G_ModelIndex(ent->model); } else { ent->s.modelindex = G_ModelIndex("models/multiplayer/flagpole/flagpole.md3"); } G_SpawnString("noise", "sound/movers/doors/door6_open.wav", &capture_sound); ent->soundPos1 = G_SoundIndex(capture_sound); ent->clipmask = CONTENTS_SOLID; ent->r.contents = CONTENTS_SOLID; VectorSet(ent->r.mins, -8, -8, 0); VectorSet(ent->r.maxs, 8, 8, 128); G_SetOrigin(ent, ent->s.origin); G_SetAngle(ent, ent->s.angles); // s.frame is the animation number ent->s.frame = WCP_ANIM_NOFLAG; // s.teamNum is which set of animations to use ( only 1 right now ) ent->s.teamNum = 1; // Used later to set animations (and delay between captures) ent->nextthink = 0; // Used to time how long it must be "held" to switch ent->health = -1; ent->count2 = -1; // 'count' signifies which team holds the checkpoint ent->count = -1; if (ent->spawnflags & SPAWNPOINT) { ent->touch = checkpoint_spawntouch; } else { if (ent->spawnflags & CP_HOLD) { ent->use = checkpoint_use; } else { ent->touch = checkpoint_touch; } } trap_LinkEntity(ent); }
/* =============== RespawnItem =============== */ void RespawnItem( gentity_t *ent ) { // randomly select from teamed entities if (ent->team) { gentity_t *master; int count; int choice; if ( !ent->teammaster ) { G_Error( "RespawnItem: bad teammaster"); } master = ent->teammaster; for (count = 0, ent = master; ent; ent = ent->teamchain, count++) ; choice = rand() % count; for (count = 0, ent = master; count < choice; ent = ent->teamchain, count++) ; } ent->r.contents = CONTENTS_TRIGGER; ent->s.eFlags &= ~EF_NODRAW; ent->r.svFlags &= ~SVF_NOCLIENT; trap_LinkEntity (ent); if ( ent->item->giType == IT_POWERUP ) { // play powerup spawn sound to all clients gentity_t *te; // if the powerup respawn sound should Not be global if (ent->speed) { te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND ); } else { te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); } te->s.eventParm = G_SoundIndex( "sound/items/poweruprespawn.wav" ); te->r.svFlags |= SVF_BROADCAST; } if ( ent->item->giType == IT_HOLDABLE && ent->item->giTag == HI_KAMIKAZE ) { // play powerup spawn sound to all clients gentity_t *te; // if the powerup respawn sound should Not be global if (ent->speed) { te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND ); } else { te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); } te->s.eventParm = G_SoundIndex( "sound/items/kamikazerespawn.wav" ); te->r.svFlags |= SVF_BROADCAST; } // play the normal respawn sound only to nearby clients G_AddEvent( ent, EV_ITEM_RESPAWN, 0 ); ent->nextthink = 0; }
void eweb_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { if ( !eweb_can_be_used( self, other, activator ) ) { return; } int oldWeapon = activator->s.weapon; if ( oldWeapon == WP_SABER ) { self->alt_fire = activator->client->ps.SaberActive(); } // swap the users weapon with the emplaced gun and add the ammo the gun has to the player activator->client->ps.weapon = self->s.weapon; Add_Ammo( activator, WP_EMPLACED_GUN, self->count ); activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN ); // Allow us to point from one to the other activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; G_RemoveWeaponModels( activator ); extern void ChangeWeapon( gentity_t *ent, int newWeapon ); if ( activator->NPC ) { ChangeWeapon( activator, WP_EMPLACED_GUN ); } else if ( activator->s.number == 0 ) { // we don't want for it to draw the weapon select stuff cg.weaponSelect = WP_EMPLACED_GUN; CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 ); } VectorCopy( activator->currentOrigin, self->pos4 );//keep this around so we know when to make them play the strafe anim // the gun will track which weapon we used to have self->s.weapon = oldWeapon; // Lock the player activator->client->ps.eFlags |= EF_LOCKED_TO_WEAPON; activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; self->delay = level.time; // can't disconnect from the thing for half a second // Let the gun be considered an enemy //Ugh, so much AI code seems to assume enemies are clients, maybe this shouldn't be on, but it's too late in the game to change it now without knowing what side-effects this will have self->svFlags |= SVF_NONNPC_ENEMY; self->noDamageTeam = activator->client->playerTeam; //FIXME: should really wait a bit after spawn and get this just once? self->waypoint = NAV::GetNearestNode(self); #ifdef _DEBUG if ( self->waypoint == -1 ) { gi.Printf( S_COLOR_RED"ERROR: no waypoint for emplaced_gun %s at %s\n", self->targetname, vtos(self->currentOrigin) ); } #endif G_Sound( self, G_SoundIndex( "sound/weapons/eweb/eweb_mount.mp3" )); #ifdef _IMMERSION G_Force( self, G_ForceIndex( "fffx/weapons/emplaced/emplaced_mount", FF_CHANNEL_TOUCH ) ); #endif // _IMMERSION if ( !(self->spawnflags&EMPLACED_PLAYERUSE) || activator->s.number == 0 ) {//player-only usescript or any usescript // Run use script G_ActivateBehavior( self, BSET_USE ); } }
/* ============== G_Script_ScriptParse Parses the script for the given entity ============== */ void G_Script_ScriptParse( gentity_t *ent ) { char *pScript; char *token; qboolean wantName; qboolean inScript; int eventNum; g_script_event_t events[G_MAX_SCRIPT_STACK_ITEMS]; int numEventItems; g_script_event_t *curEvent; // DHM - Nerve :: Some of our multiplayer script commands have longer parameters //char params[MAX_QPATH]; char params[MAX_INFO_STRING]; // dhm - end g_script_stack_action_t *action; int i; int bracketLevel; qboolean buildScript; if (!ent->scriptName) return; if (!level.scriptEntity) return; buildScript = (qboolean)trap_Cvar_VariableIntegerValue( "com_buildScript" ); pScript = level.scriptEntity; wantName = qtrue; inScript = qfalse; COM_BeginParseSession("G_Script_ScriptParse"); bracketLevel = 0; numEventItems = 0; memset( events, 0, sizeof(events) ); while (1) { token = COM_Parse( &pScript ); if ( !token[0] ) { if ( !wantName ) { G_Error( "G_Script_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); } break; } // end of script if ( token[0] == '}' ) { if ( inScript ) { break; } if ( wantName ) { G_Error( "G_Script_ScriptParse(), Error (line %d): '}' found, but not expected.\n", COM_GetCurrentParseLine() ); } wantName = qtrue; } else if ( token[0] == '{' ) { if ( wantName ) { G_Error( "G_Script_ScriptParse(), Error (line %d): '{' found, NAME expected.\n", COM_GetCurrentParseLine() ); } } else if ( wantName ) { if ( !Q_stricmp( token, "bot" ) ) { // a bot, skip this whole entry SkipRestOfLine ( &pScript ); // skip this section SkipBracedSection( &pScript ); // continue; } if( !Q_stricmp( token, "entity" ) ) { // this is an entity, so go back to look for a name continue; } if ( !Q_stricmp( ent->scriptName, token ) ) { inScript = qtrue; numEventItems = 0; } wantName = qfalse; } else if ( inScript ) { Q_strlwr( token ); eventNum = G_Script_EventForString( token ); if (eventNum < 0) { G_Error( "G_Script_ScriptParse(), Error (line %d): unknown event: %s.\n", COM_GetCurrentParseLine(), token ); } if( numEventItems >= G_MAX_SCRIPT_STACK_ITEMS ) { G_Error( "G_Script_ScriptParse(), Error (line %d): G_MAX_SCRIPT_STACK_ITEMS reached (%d)\n", COM_GetCurrentParseLine(), G_MAX_SCRIPT_STACK_ITEMS ); } curEvent = &events[numEventItems]; curEvent->eventNum = eventNum; memset( params, 0, sizeof(params) ); // parse any event params before the start of this event's actions while ((token = COM_Parse( &pScript )) != NULL && (token[0] != '{')) { if( !token[0] ) { G_Error( "G_Script_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); } if(strlen( params )) { // add a space between each param Q_strcat( params, sizeof(params), " " ); } Q_strcat( params, sizeof(params), token ); } if( strlen( params ) ) { // copy the params into the event curEvent->params = (char*)G_Alloc( strlen( params ) + 1 ); Q_strncpyz( curEvent->params, params, strlen(params)+1 ); } // parse the actions for this event while( (token = COM_Parse( &pScript )) != NULL && (token[0] != '}') ) { if( !token[0] ) { G_Error( "G_Script_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); } action = G_Script_ActionForString( token ); if( !action ) { G_Error( "G_Script_ScriptParse(), Error (line %d): unknown action: %s.\n", COM_GetCurrentParseLine(), token ); } curEvent->stack.items[curEvent->stack.numItems].action = action; memset( params, 0, sizeof(params) ); // Ikkyo - Parse for {}'s if this is a set command if( !Q_stricmp( action->actionString, "set" ) || !Q_stricmp( action->actionString, "create" ) || !Q_stricmp( action->actionString, "delete" ) ) { token = COM_Parse( &pScript ); if( token[0] != '{' ) { COM_ParseError( "'{' expected, found: %s.\n", token ); } while( ( token = COM_Parse( &pScript ) ) && ( token[0] != '}') ) { if ( strlen( params ) ) // add a space between each param Q_strcat( params, sizeof( params ), " " ); if ( strrchr( token,' ') ) // need to wrap this param in quotes since it has more than one word Q_strcat( params, sizeof( params ), "\"" ); Q_strcat( params, sizeof( params ), token ); if ( strrchr( token,' ') ) // need to wrap this param in quotes since it has mor Q_strcat( params, sizeof( params ), "\"" ); } } else // hackly precaching of custom characters if( !Q_stricmp(token, "spawnbot") ) { // this is fairly indepth, so I'll move it to a separate function for readability G_Script_ParseSpawnbot( &pScript, params, MAX_INFO_STRING ); } else { token = COM_ParseExt( &pScript, qfalse ); for (i=0; token[0]; i++) { if( strlen( params ) ) // add a space between each param Q_strcat( params, sizeof(params), " " ); if( i == 0 ) { // Special case: playsound's need to be cached on startup to prevent in-game pauses if (!Q_stricmp(action->actionString, "playsound")) { G_SoundIndex(token); } else if (!Q_stricmp(action->actionString, "changemodel")) { G_ModelIndex(token); } else if ( buildScript && ( !Q_stricmp(action->actionString, "mu_start") || !Q_stricmp(action->actionString, "mu_play") || !Q_stricmp(action->actionString, "mu_queue") || !Q_stricmp(action->actionString, "startcam")) ) { if(strlen(token)) // we know there's a [0], but don't know if it's '0' trap_SendServerCommand(-1, va("addToBuild %s\n", token) ); } } if(i == 0 || i == 1) { if (!Q_stricmp(action->actionString, "remapshader")) { G_ShaderIndex(token); } } if (strrchr(token,' ')) // need to wrap this param in quotes since it has more than one word Q_strcat( params, sizeof(params), "\"" ); Q_strcat( params, sizeof(params), token ); if (strrchr(token,' ')) // need to wrap this param in quotes since it has more than one word Q_strcat( params, sizeof(params), "\"" ); token = COM_ParseExt( &pScript, qfalse ); } } if (strlen( params )) { // copy the params into the event curEvent->stack.items[curEvent->stack.numItems].params = (char*)G_Alloc( strlen( params ) + 1 ); Q_strncpyz( curEvent->stack.items[curEvent->stack.numItems].params, params, strlen(params)+1 ); } curEvent->stack.numItems++; if (curEvent->stack.numItems >= G_MAX_SCRIPT_STACK_ITEMS) { G_Error( "G_Script_ScriptParse(): script exceeded G_MAX_SCRIPT_STACK_ITEMS (%d), line %d\n", G_MAX_SCRIPT_STACK_ITEMS, COM_GetCurrentParseLine() ); } } numEventItems++; } else { // skip this character completely // TTimo gcc: suggest parentheses around assignment used as truth value while ( ( token = COM_Parse( &pScript ) ) != NULL ) { if (!token[0]) { G_Error( "G_Script_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); } else if (token[0] == '{') { bracketLevel++; } else if (token[0] == '}') { if (!--bracketLevel) break; } } } } // alloc and copy the events into the gentity_t for this cast if (numEventItems > 0) { ent->scriptEvents = (g_script_event_t*)G_Alloc( sizeof(g_script_event_t) * numEventItems ); memcpy( ent->scriptEvents, events, sizeof(g_script_event_t) * numEventItems ); ent->numScriptEvents = numEventItems; } }
/* ------------------------- Remote_MaintainHeight ------------------------- */ void Remote_MaintainHeight( void ) { float dif; // Update our angles regardless NPC_UpdateAngles( qtrue, qtrue ); if ( NPC->client->ps.velocity[2] ) { NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) { NPC->client->ps.velocity[2] = 0; } } // If we have an enemy, we should try to hover at or a little below enemy eye level if ( NPC->enemy ) { if (TIMER_Done( NPC, "heightChange")) { TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); // Find the height difference dif = (NPC->enemy->r.currentOrigin[2] + Q_irand( 0, NPC->enemy->r.maxs[2]+8 )) - NPC->r.currentOrigin[2]; // cap to prevent dramatic height shifts if ( fabs( dif ) > 2 ) { if ( fabs( dif ) > 24 ) { dif = ( dif < 0 ? -24 : 24 ); } dif *= 10; NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/remote/misc/hiss.wav")); } } } else { gentity_t *goal = NULL; if ( NPCInfo->goalEntity ) // Is there a goal? { goal = NPCInfo->goalEntity; } else { goal = NPCInfo->lastGoalEntity; } if ( goal ) { dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2]; if ( fabs( dif ) > 24 ) { dif = ( dif < 0 ? -24 : 24 ); NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; } } } // Apply friction if ( NPC->client->ps.velocity[0] ) { NPC->client->ps.velocity[0] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) { NPC->client->ps.velocity[0] = 0; } } if ( NPC->client->ps.velocity[1] ) { NPC->client->ps.velocity[1] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) { NPC->client->ps.velocity[1] = 0; } } }
/* ------------------------- NPC_Protocol_Precache ------------------------- */ void NPC_Protocol_Precache( void ) { G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); G_EffectIndex( "env/med_explode"); }
void charge_stick (gentity_t *self, gentity_t *other, trace_t *trace) { gentity_t *tent; if ( other && (other->flags&FL_BBRUSH) && other->s.pos.trType == TR_STATIONARY && other->s.apos.trType == TR_STATIONARY ) {//a perfectly still breakable brush, let us attach directly to it! self->target_ent = other;//remember them when we blow up } else if ( other && other->s.number < ENTITYNUM_WORLD && other->s.eType == ET_MOVER && trace->plane.normal[2] > 0 ) {//stick to it? self->s.groundEntityNum = other->s.number; } else if (other && other->s.number < ENTITYNUM_WORLD && (other->client || !other->s.weapon)) { //hit another entity that is not stickable, "bounce" off //self->target_ent = other; vec3_t vNor, tN; VectorCopy(trace->plane.normal, vNor); VectorNormalize(vNor); VectorNPos(self->s.pos.trDelta, tN); self->s.pos.trDelta[0] += vNor[0]*(tN[0]*(((float)Q_irand(1, 10))*0.1)); self->s.pos.trDelta[1] += vNor[1]*(tN[1]*(((float)Q_irand(1, 10))*0.1)); self->s.pos.trDelta[2] += vNor[1]*(tN[2]*(((float)Q_irand(1, 10))*0.1)); vectoangles(vNor, self->s.angles); vectoangles(vNor, self->s.apos.trBase); self->touch = charge_stick; return; } else if (other && other->s.number < ENTITYNUM_WORLD) { //hit an entity that we just want to explode on (probably another projectile or something) vec3_t v; self->touch = 0; self->think = 0; self->nextthink = 0; self->takedamage = qfalse; VectorClear(self->s.apos.trDelta); self->s.apos.trType = TR_STATIONARY; G_RadiusDamage( self->r.currentOrigin, self->parent, self->splashDamage, self->splashRadius, self, self, MOD_DET_PACK_SPLASH ); VectorCopy(trace->plane.normal, v); VectorCopy(v, self->pos2); self->count = -1; G_PlayEffect(EFFECT_EXPLOSION_DETPACK, self->r.currentOrigin, v); self->think = G_FreeEntity; self->nextthink = level.time; return; } //JAC Bugfix //if we get here I guess we hit hte world so we can stick to it //Raz: This fix requires a bit of explaining.. // When you suicide, all of the detpacks you have placed (either on a wall, or still falling in the air) will // have their ent->think() set to DetPackBlow and ent->nextthink will be between 100 <-> 300 // If your detpacks land on a surface (i.e. charge_stick gets called) within that 100<->300 ms then ent->think() // will be overwritten (set to DetpackBlow) and ent->nextthink will be 30000 // The end result is your detpacks won't explode, but will be stuck to the wall for 30 seconds without // being able to detonate them (or shoot them) // The fix Sil came up with is to check the think() function in charge_stick, and only overwrite it // if they haven't been primed to detonate if ( self->think == G_RunObject ) { self->touch = 0; self->think = DetPackBlow; //[OpenRP - Detpacks last forever] self->nextthink = level.time + Q3_INFINITE; //make them last forever //[/OpenRP - Detpacks last forever] } VectorClear(self->s.apos.trDelta); self->s.apos.trType = TR_STATIONARY; self->s.pos.trType = TR_STATIONARY; VectorCopy( self->r.currentOrigin, self->s.origin ); VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); VectorClear( self->s.pos.trDelta ); VectorClear( self->s.apos.trDelta ); VectorNormalize(trace->plane.normal); vectoangles(trace->plane.normal, self->s.angles); VectorCopy(self->s.angles, self->r.currentAngles ); VectorCopy(self->s.angles, self->s.apos.trBase); VectorCopy(trace->plane.normal, self->pos2); self->count = -1; G_Sound(self, CHAN_WEAPON, G_SoundIndex("sound/weapons/detpack/stick.wav")); tent = G_TempEntity( self->r.currentOrigin, EV_MISSILE_MISS ); tent->s.weapon = 0; tent->parent = self; tent->r.ownerNum = self->s.number; //so that the owner can blow it up with projectiles self->r.svFlags |= SVF_OWNERNOTSHARED; }
void NPC_Remote_Precache(void) { G_SoundIndex("sound/chars/remote/misc/fire.wav"); G_SoundIndex( "sound/chars/remote/misc/hiss.wav"); G_EffectIndex( "env/small_explode"); }
//---------------------------------------------------------- void SP_emplaced_gun( gentity_t *ent ) { char name[] = "models/map_objects/imp_mine/turret_chair.glm"; ent->svFlags |= SVF_PLAYER_USABLE; ent->contents = CONTENTS_BODY;//CONTENTS_SHOTCLIP|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP;//CONTENTS_SOLID; if ( ent->spawnflags & EMPLACED_INACTIVE ) { ent->svFlags |= SVF_INACTIVE; } VectorSet( ent->mins, -30, -30, -5 ); VectorSet( ent->maxs, 30, 30, 60 ); ent->takedamage = qtrue; if ( !( ent->spawnflags & EMPLACED_VULNERABLE )) { ent->flags |= FL_GODMODE; } ent->s.radius = 110; ent->spawnflags |= 4; // deadsolid //ent->e_ThinkFunc = thinkF_NULL; ent->e_PainFunc = painF_emplaced_gun_pain; ent->e_DieFunc = dieF_emplaced_gun_die; G_EffectIndex( "emplaced/explode" ); G_EffectIndex( "emplaced/dead_smoke" ); G_SoundIndex( "sound/weapons/emplaced/emplaced_mount.mp3" ); G_SoundIndex( "sound/weapons/emplaced/emplaced_dismount.mp3" ); G_SoundIndex( "sound/weapons/emplaced/emplaced_move_lp.wav" ); // Set up our defaults and override with custom amounts as necessary G_SpawnInt( "count", "999", &ent->count ); G_SpawnInt( "health", "250", &ent->health ); G_SpawnInt( "splashDamage", "80", &ent->splashDamage ); G_SpawnInt( "splashRadius", "128", &ent->splashRadius ); G_SpawnFloat( "delay", "200", &ent->random ); // NOTE: spawning into a different field!! G_SpawnFloat( "wait", "800", &ent->wait ); ent->max_health = ent->health; ent->dflags |= DAMAGE_CUSTOM_HUD; // dumb, but we draw a custom hud ent->s.modelindex = G_ModelIndex( name ); ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, name, ent->s.modelindex ); // Activate our tags and bones ent->headBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*seat" ); ent->handLBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*flash01" ); ent->handRBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*flash02" ); ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "base_bone", qtrue ); ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "swivel_bone", qtrue ); gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL); RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN )); ent->s.weapon = WP_EMPLACED_GUN; G_SetOrigin( ent, ent->s.origin ); G_SetAngles( ent, ent->s.angles ); VectorCopy( ent->s.angles, ent->lastAngles ); // store base angles for later VectorCopy( ent->s.angles, ent->pos1 ); ent->e_UseFunc = useF_emplaced_gun_use; ent->bounceCount = 0;//to distinguish it from the eweb gi.linkentity (ent); }
//---------------------------------------------------------- void emplaced_gun_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { vec3_t fwd1, fwd2; if ( self->health <= 0 ) { // can't use a dead gun. return; } if ( self->svFlags & SVF_INACTIVE ) { return; // can't use inactive gun } if ( !activator->client ) { return; // only a client can use it. } if ( self->activator ) { // someone is already in the gun. return; } if ( other && other->client && G_IsRidingVehicle( other ) ) {//can't use eweb when on a vehicle return; } if ( activator && activator->client && G_IsRidingVehicle( activator ) ) {//can't use eweb when on a vehicle return; } // We'll just let the designers duke this one out....I mean, as to whether they even want to limit such a thing. if ( self->spawnflags & EMPLACED_FACING ) { // Let's get some direction vectors for the users AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL ); // Get the guns direction vector AngleVectors( self->pos1, fwd2, NULL, NULL ); float dot = DotProduct( fwd1, fwd2 ); // Must be reasonably facing the way the gun points ( 90 degrees or so ), otherwise we don't allow to use it. if ( dot < 0.0f ) { return; } } // don't allow using it again for half a second if ( self->delay + 500 < level.time ) { int oldWeapon = activator->s.weapon; if ( oldWeapon == WP_SABER ) { self->alt_fire = activator->client->ps.SaberActive(); } // swap the users weapon with the emplaced gun and add the ammo the gun has to the player activator->client->ps.weapon = self->s.weapon; Add_Ammo( activator, WP_EMPLACED_GUN, self->count ); activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN ); // Allow us to point from one to the other activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; G_RemoveWeaponModels( activator ); extern void ChangeWeapon( gentity_t *ent, int newWeapon ); if ( activator->NPC ) { ChangeWeapon( activator, WP_EMPLACED_GUN ); } else if ( activator->s.number == 0 ) { // we don't want for it to draw the weapon select stuff cg.weaponSelect = WP_EMPLACED_GUN; CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 ); } // Since we move the activator inside of the gun, we reserve a solid spot where they were standing in order to be able to get back out without being in solid if ( self->nextTrain ) {//you never know G_FreeEntity( self->nextTrain ); } self->nextTrain = G_Spawn(); //self->nextTrain->classname = "emp_placeholder"; self->nextTrain->contents = CONTENTS_MONSTERCLIP|CONTENTS_PLAYERCLIP;//hmm... playerclip too now that we're doing it for NPCs? G_SetOrigin( self->nextTrain, activator->client->ps.origin ); VectorCopy( activator->mins, self->nextTrain->mins ); VectorCopy( activator->maxs, self->nextTrain->maxs ); gi.linkentity( self->nextTrain ); //need to inflate the activator's mins/maxs since the gunsit anim puts them outside of their bbox VectorSet( activator->mins, -24, -24, -24 ); VectorSet( activator->maxs, 24, 24, 40 ); // Move the activator into the center of the gun. For NPC's the only way the can get out of the gun is to die. VectorCopy( self->s.origin, activator->client->ps.origin ); activator->client->ps.origin[2] += 30; // move them up so they aren't standing in the floor gi.linkentity( activator ); // the gun will track which weapon we used to have self->s.weapon = oldWeapon; // Lock the player activator->client->ps.eFlags |= EF_LOCKED_TO_WEAPON; activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; self->delay = level.time; // can't disconnect from the thing for half a second // Let the gun be considered an enemy //Ugh, so much AI code seems to assume enemies are clients, maybe this shouldn't be on, but it's too late in the game to change it now without knowing what side-effects this will have self->svFlags |= SVF_NONNPC_ENEMY; self->noDamageTeam = activator->client->playerTeam; // FIXME: don't do this, we'll try and actually put the player in this beast // move the player to the center of the gun // activator->contents = 0; // VectorCopy( self->currentOrigin, activator->client->ps.origin ); SetClientViewAngle( activator, self->pos1 ); //FIXME: should really wait a bit after spawn and get this just once? self->waypoint = NAV::GetNearestNode(self); #ifdef _DEBUG if ( self->waypoint == -1 ) { gi.Printf( S_COLOR_RED"ERROR: no waypoint for emplaced_gun %s at %s\n", self->targetname, vtos(self->currentOrigin) ); } #endif G_Sound( self, G_SoundIndex( "sound/weapons/emplaced/emplaced_mount.mp3" )); #ifdef _IMMERSION G_Force( self, G_ForceIndex( "fffx/weapons/emplaced/emplaced_mount", FF_CHANNEL_TOUCH ) ); #endif // _IMMERSION if ( !(self->spawnflags&EMPLACED_PLAYERUSE) || activator->s.number == 0 ) {//player-only usescript or any usescript // Run use script G_ActivateBehavior( self, BSET_USE ); } } }
//---------------------------------------------------------- void SP_emplaced_eweb( gentity_t *ent ) { char name[] = "models/map_objects/hoth/eweb_model.glm"; ent->svFlags |= SVF_PLAYER_USABLE; ent->contents = CONTENTS_BODY; if ( ent->spawnflags & EMPLACED_INACTIVE ) { ent->svFlags |= SVF_INACTIVE; } VectorSet( ent->mins, -12, -12, -24 ); VectorSet( ent->maxs, 12, 12, 24 ); ent->takedamage = qtrue; if ( ( ent->spawnflags & EWEB_INVULNERABLE )) { ent->flags |= FL_GODMODE; } ent->s.radius = 80; ent->spawnflags |= 4; // deadsolid //ent->e_ThinkFunc = thinkF_NULL; ent->e_PainFunc = painF_eweb_pain; ent->e_DieFunc = dieF_eweb_die; G_EffectIndex( "emplaced/explode" ); G_EffectIndex( "emplaced/dead_smoke" ); G_SoundIndex( "sound/weapons/eweb/eweb_aim.wav" ); G_SoundIndex( "sound/weapons/eweb/eweb_dismount.mp3" ); //G_SoundIndex( "sound/weapons/eweb/eweb_empty.wav" ); G_SoundIndex( "sound/weapons/eweb/eweb_fire.wav" ); G_SoundIndex( "sound/weapons/eweb/eweb_hitplayer.wav" ); G_SoundIndex( "sound/weapons/eweb/eweb_hitsurface.wav" ); //G_SoundIndex( "sound/weapons/eweb/eweb_load.wav" ); G_SoundIndex( "sound/weapons/eweb/eweb_mount.mp3" ); // Set up our defaults and override with custom amounts as necessary G_SpawnInt( "count", "999", &ent->count ); G_SpawnInt( "health", "250", &ent->health ); G_SpawnInt( "splashDamage", "40", &ent->splashDamage ); G_SpawnInt( "splashRadius", "100", &ent->splashRadius ); G_SpawnFloat( "delay", "200", &ent->random ); // NOTE: spawning into a different field!! G_SpawnFloat( "wait", "800", &ent->wait ); ent->max_health = ent->health; ent->dflags |= DAMAGE_CUSTOM_HUD; // dumb, but we draw a custom hud ent->s.modelindex = G_ModelIndex( name ); ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, name, ent->s.modelindex ); // Activate our tags and bones ent->handLBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*cannonflash" ); //muzzle bolt ent->headBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "cannon_Xrot" ); //for placing the owner relative to rotation ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue ); ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cannon_Yrot", qtrue ); ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cannon_Xrot", qtrue ); gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_X, NEGATIVE_Y, NULL); gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_X, NEGATIVE_Y, NULL); //gi.G2API_SetBoneAngles( &ent->ghoul2[0], "cannon_Yrot", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL); //set the constraints for this guy as an emplaced weapon, and his constraint angles //ent->s.origin2[0] = 60.0f; //60 degrees in either direction RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN )); ent->s.weapon = WP_EMPLACED_GUN; G_SetOrigin( ent, ent->s.origin ); G_SetAngles( ent, ent->s.angles ); VectorCopy( ent->s.angles, ent->lastAngles ); // store base angles for later VectorClear( ent->pos1 ); ent->e_UseFunc = useF_eweb_use; ent->bounceCount = 1;//to distinguish it from the emplaced gun gi.linkentity (ent); }
//------------------------------------ void NPC_Seeker_Precache(void) { G_SoundIndex("sound/chars/seeker/misc/fire.wav"); G_SoundIndex( "sound/chars/seeker/misc/hiss.wav"); G_EffectIndex( "env/small_explode"); }
/* ============ G_InitGame ============ */ void G_InitGame( int levelTime, int randomSeed, int restart ) { int i; G_Printf ("------- Game Initialization -------\n"); G_Printf ("gamename: %s\n", GAMEVERSION); G_Printf ("gamedate: %s\n", __DATE__); srand( randomSeed ); G_RegisterCvars(); G_ProcessIPBans(); G_InitMemory(); // set some level globals memset( &level, 0, sizeof( level ) ); level.time = levelTime; level.startTime = levelTime; level.snd_fry = G_SoundIndex("sound/player/fry.wav"); // FIXME standing in lava / slime if ( g_gametype.integer != GT_SINGLE_PLAYER && g_logfile.string[0] ) { if ( g_logfileSync.integer ) { trap_FS_FOpenFile( g_logfile.string, &level.logFile, FS_APPEND_SYNC ); } else { trap_FS_FOpenFile( g_logfile.string, &level.logFile, FS_APPEND ); } if ( !level.logFile ) { G_Printf( "WARNING: Couldn't open logfile: %s\n", g_logfile.string ); } else { char serverinfo[MAX_INFO_STRING]; trap_GetServerinfo( serverinfo, sizeof( serverinfo ) ); G_LogPrintf("------------------------------------------------------------\n" ); G_LogPrintf("InitGame: %s\n", serverinfo ); } } else { G_Printf( "Not logging to disk.\n" ); } G_InitWorldSession(); // initialize all entities for this game memset( g_entities, 0, MAX_GENTITIES * sizeof(g_entities[0]) ); level.gentities = g_entities; // initialize all clients for this game level.maxclients = g_maxclients.integer; memset( g_clients, 0, MAX_CLIENTS * sizeof(g_clients[0]) ); level.clients = g_clients; // set client fields on player ents for ( i=0 ; i<level.maxclients ; i++ ) { g_entities[i].client = level.clients + i; } // always leave room for the max number of clients, // even if they aren't all used, so numbers inside that // range are NEVER anything but clients level.num_entities = MAX_CLIENTS; for ( i=0 ; i<MAX_CLIENTS ; i++ ) { g_entities[i].classname = "clientslot"; } // let the server system know where the entites are trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ), &level.clients[0].ps, sizeof( level.clients[0] ) ); // reserve some spots for dead player bodies InitBodyQue(); ClearRegisteredItems(); // parse the key/value pairs and spawn gentities G_SpawnEntitiesFromString(); // general initialization G_FindTeams(); // make sure we have flags for CTF, etc if( g_gametype.integer >= GT_TEAM ) { G_CheckTeamItems(); } SaveRegisteredItems(); G_Printf ("-----------------------------------\n"); if( g_gametype.integer == GT_SINGLE_PLAYER || trap_Cvar_VariableIntegerValue( "com_buildScript" ) ) { G_ModelIndex( SP_PODIUM_MODEL ); } if ( trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { BotAISetup( restart ); BotAILoadMap( restart ); G_InitBots( restart ); } G_RemapTeamShaders(); trap_SetConfigstring( CS_INTERMISSION, "" ); }
//[CoOp] //------------------------------------ void Seeker_FollowPlayer( void ) { //hover around the closest player //[SeekerItemNpc] #if 1 vec3_t pt, dir; float dis; float minDistSqr = MIN_DISTANCE_SQR; gentity_t *target; Seeker_MaintainHeight(); if(NPC->activator && NPC->activator->client) { if(NPC->activator->client->remote != NPC || NPC->activator->health <= 0) { //have us fall down and explode. NPC->NPC->aiFlags |= NPCAI_CUSTOM_GRAVITY; return; } target = NPCInfo->goalEntity; if(!target) target = NPC->client->leader; } else { target = FindClosestPlayer(NPC->r.currentOrigin, NPC->client->playerTeam); } if(!target) { //in MP it's actually possible that there's no players on our team at the moment. return; } dis = DistanceHorizontalSquared( NPC->r.currentOrigin, target->r.currentOrigin ); if ( NPC->client->NPC_class == CLASS_BOBAFETT ) { if ( TIMER_Done( NPC, "flameTime" ) ) { minDistSqr = 200*200; } } if ( dis < minDistSqr ) { // generally circle the player closely till we take an enemy..this is our target point if ( NPC->client->NPC_class == CLASS_BOBAFETT ) { pt[0] = target->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 250; pt[1] = target->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 250; if ( NPC->client->jetPackTime < level.time ) { pt[2] = target->r.currentOrigin[2] - 64; } else { pt[2] = target->r.currentOrigin[2] + 200; } } else { pt[0] = target->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 56; pt[1] = target->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 56; pt[2] = target->r.currentOrigin[2] + 40; } VectorSubtract( pt, NPC->r.currentOrigin, dir ); VectorMA( NPC->client->ps.velocity, 0.8f, dir, NPC->client->ps.velocity ); } else { if ( NPC->client->NPC_class != CLASS_BOBAFETT ) { if ( TIMER_Done( NPC, "seekerhiss" )) { TIMER_Set( NPC, "seekerhiss", 1000 + random() * 1000 ); G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); } } // Hey come back! NPCInfo->goalEntity = target; if(target == NPC->enemy) NPCInfo->goalRadius = 60; else NPCInfo->goalRadius = 32; if(!NPC_MoveToGoal(qtrue)) { //cant go there on our first try, abort. //this really isnt the best way... but if it cant reach the point, it will just sit there doing nothing. NPCInfo->goalEntity = NPC->client->leader; //stop chasing the enemy if we were told to, and return to the player NPCInfo->scriptFlags &= ~SCF_CHASE_ENEMIES; } } //call this even if we do have an enemy, for enemy proximity detection if ( /*!NPC->enemy && */ NPCInfo->enemyCheckDebounceTime < level.time ) { // check twice a second to find a new enemy Seeker_FindEnemy(); NPCInfo->enemyCheckDebounceTime = level.time + 500; } //play our proximity beep if(NPC->genericValue3 && NPC->fly_sound_debounce_time > 0 && NPC->fly_sound_debounce_time < level.time) { G_Sound(NPC, CHAN_AUTO, NPC->genericValue3); NPC->fly_sound_debounce_time = -1; } NPC_UpdateAngles( qtrue, qtrue ); #else vec3_t pt, dir; float dis; float minDistSqr = MIN_DISTANCE_SQR; gentity_t *closestPlayer = NULL; Seeker_MaintainHeight(); closestPlayer = FindClosestPlayer(NPC->r.currentOrigin, NPC->client->playerTeam); if(!closestPlayer) { //in MP it's actually possible that there's no players on our team at the moment. return; } dis = DistanceHorizontalSquared( NPC->r.currentOrigin, closestPlayer->r.currentOrigin ); if ( NPC->client->NPC_class == CLASS_BOBAFETT ) { if ( TIMER_Done( NPC, "flameTime" ) ) { minDistSqr = 200*200; } } if ( dis < minDistSqr ) { // generally circle the player closely till we take an enemy..this is our target point if ( NPC->client->NPC_class == CLASS_BOBAFETT ) { pt[0] = closestPlayer->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 250; pt[1] = closestPlayer->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 250; if ( NPC->client->jetPackTime < level.time ) { pt[2] = closestPlayer->r.currentOrigin[2] - 64; } else { pt[2] = closestPlayer->r.currentOrigin[2] + 200; } } else { pt[0] = closestPlayer->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 56; pt[1] = closestPlayer->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 56; pt[2] = closestPlayer->r.currentOrigin[2] + 40; } VectorSubtract( pt, NPC->r.currentOrigin, dir ); VectorMA( NPC->client->ps.velocity, 0.8f, dir, NPC->client->ps.velocity ); } else { if ( NPC->client->NPC_class != CLASS_BOBAFETT ) { if ( TIMER_Done( NPC, "seekerhiss" )) { TIMER_Set( NPC, "seekerhiss", 1000 + random() * 1000 ); G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); } } // Hey come back! NPCInfo->goalEntity = closestPlayer; NPCInfo->goalRadius = 32; NPC_MoveToGoal( qtrue ); NPC->s.owner = closestPlayer->s.number; } if ( NPCInfo->enemyCheckDebounceTime < level.time ) { // check twice a second to find a new enemy Seeker_FindEnemy(); NPCInfo->enemyCheckDebounceTime = level.time + 500; } NPC_UpdateAngles( qtrue, qtrue ); #endif //[/SeekerItemNpc] }
void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd ) { if (( (*ucmd)->buttons & BUTTON_USE || (*ucmd)->forwardmove < 0 || (*ucmd)->upmove > 0 ) && ent->owner && ent->owner->delay + 500 < level.time ) { ent->owner->s.loopSound = 0; if ( ent->owner->e_UseFunc == useF_eweb_use )//yeah, crappy way to check this, but... { G_Sound( ent, G_SoundIndex( "sound/weapons/eweb/eweb_dismount.mp3" )); } else { G_Sound( ent, G_SoundIndex( "sound/weapons/emplaced/emplaced_dismount.mp3" )); } #ifdef _IMMERSION G_Force( ent, G_ForceIndex( "fffx/weapons/emplaced/emplaced_dismount", FF_CHANNEL_TOUCH ) ); #endif // _IMMERSION ExitEmplacedWeapon( ent ); (*ucmd)->buttons &= ~BUTTON_USE; if ( (*ucmd)->upmove > 0 ) {//don't actually jump (*ucmd)->upmove = 0; } } else { // this is a crappy way to put sounds on a moving eweb.... if ( ent->owner && ent->owner->e_UseFunc == useF_eweb_use )//yeah, crappy way to check this, but... { if ( !VectorCompare( ent->client->ps.viewangles, ent->owner->movedir )) { ent->owner->s.loopSound = G_SoundIndex( "sound/weapons/eweb/eweb_aim.wav" ); ent->owner->fly_sound_debounce_time = level.time; } else { if ( ent->owner->fly_sound_debounce_time + 100 <= level.time ) { ent->owner->s.loopSound = 0; } } VectorCopy( ent->client->ps.viewangles, ent->owner->movedir ); } // don't allow movement, weapon switching, and most kinds of button presses (*ucmd)->forwardmove = 0; (*ucmd)->rightmove = 0; (*ucmd)->upmove = 0; (*ucmd)->buttons &= (BUTTON_ATTACK|BUTTON_ALT_ATTACK); (*ucmd)->weapon = ent->client->ps.weapon; //WP_EMPLACED_GUN; if ( ent->health <= 0 ) { ExitEmplacedWeapon( ent ); } } }