/* ============ AIChar_AIScript_AlertEntity triggered spawning, called from AI scripting ============ */ void AIChar_AIScript_AlertEntity( gentity_t *ent ) { vec3_t mins, maxs; int numTouch, touch[10], i; cast_state_t *cs; if ( !ent->aiInactive ) { return; } cs = AICast_GetCastState( ent->s.number ); // if the current bounding box is invalid, then wait VectorAdd( ent->r.currentOrigin, ent->r.mins, mins ); VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs ); trap_UnlinkEntity( ent ); numTouch = trap_EntitiesInBox( mins, maxs, touch, 10 ); // check that another client isn't inside us if ( numTouch ) { for ( i = 0; i < numTouch; i++ ) { // RF, note we should only check against clients since zombies need to spawn inside func_explosive (so they dont clip into view after it explodes) if ( g_entities[touch[i]].client && g_entities[touch[i]].r.contents == CONTENTS_BODY ) { //if (g_entities[touch[i]].r.contents & MASK_PLAYERSOLID) break; } } if ( i == numTouch ) { numTouch = 0; } } if ( numTouch ) { // invalid location cs->aiFlags |= AIFL_WAITINGTOSPAWN; return; } // RF, has to disable this so I could test some maps which have erroneously placed alertentity calls //ent->AIScript_AlertEntity = NULL; cs->aiFlags &= ~AIFL_WAITINGTOSPAWN; ent->aiInactive = qfalse; trap_LinkEntity( ent ); // trigger a spawn script event AICast_ScriptEvent( AICast_GetCastState( ent->s.number ), "spawn", "" ); // make it think so we update animations/angles AICast_Think( ent->s.number, (float)FRAMETIME / 1000 ); cs->lastThink = level.time; AICast_UpdateInput( cs, FRAMETIME ); trap_BotUserCommand( cs->bs->client, &( cs->lastucmd ) ); }
// the trigger was just activated // ent->activator should be set to the activator so it can be held through a delay // so wait for the delay time before firing void AICast_trigger_trigger(gentity_t *ent, gentity_t *activator) { if(ent->nextthink) { return; // can't retrigger until the wait is over } ent->activator = AICast_FindEntityForName(ent->aiName); if(ent->activator) // they might be dead { // trigger the script event AICast_ScriptEvent(AICast_GetCastState(ent->activator->s.number), "trigger", ent->target); } if(ent->wait > 0) { ent->think = AICast_trigger_wait; ent->nextthink = level.time + (ent->wait + ent->random * crandom()) * 1000; } else { // we can't just remove (self) here, because this is a touch function // called while looping through area links... ent->touch = 0; ent->nextthink = level.time + FRAMETIME; ent->think = G_FreeEntity; } }
/* ============== AICast_SetupClient ============== */ int AICast_SetupClient(int client) { cast_state_t *cs; bot_state_t *bs; if (!botstates[client]) { botstates[client] = G_Alloc(sizeof(bot_state_t)); memset( botstates[client], 0, sizeof(bot_state_t) ); } bs = botstates[client]; if (bs->inuse) { BotAI_Print(PRT_FATAL, "client %d already setup\n", client); return qfalse; } cs = AICast_GetCastState(client); cs->bs = bs; //allocate a goal state bs->gs = trap_BotAllocGoalState(client); bs->inuse = qtrue; bs->client = client; bs->entitynum = client; bs->setupcount = qtrue; bs->entergame_time = trap_AAS_Time(); bs->ms = trap_BotAllocMoveState(); return qtrue; }
/* ============== AICast_UpdateNonVisibility ============== */ void AICast_UpdateNonVisibility( gentity_t *srcent, gentity_t *destent, qboolean directview ) { cast_visibility_t *vis; cast_state_t *cs; cs = AICast_GetCastState( srcent->s.number ); vis = &cs->vislist[destent->s.number]; // update the values vis->lastcheck_timestamp = level.time; vis->notvisible_timestamp = level.time; if ( directview ) { vis->real_update_timestamp = level.time; vis->real_notvisible_timestamp = level.time; } // if enough time has passed, and still within chase period, drop a marker if ( vis->chase_marker_count < MAX_CHASE_MARKERS ) { if ( ( level.time - vis->visible_timestamp ) > ( vis->chase_marker_count + 1 ) * CHASE_MARKER_INTERVAL ) { VectorCopy( destent->client->ps.origin, vis->chase_marker[vis->chase_marker_count] ); vis->chase_marker_count++; } } }
/* ============== AICast_ShutdownClient ============== */ int AICast_ShutdownClient(int client) { cast_state_t *cs; bot_state_t *bs; if (!(bs = botstates[client])) { return BLERR_NOERROR; } if (!bs->inuse) { BotAI_Print(PRT_ERROR, "client %d already shutdown\n", client); return BLERR_AICLIENTALREADYSHUTDOWN; } cs = AICast_GetCastState( client ); // memset( cs, 0, sizeof(cast_state_t) ); numcast--; // now do the other bot stuff #ifdef DEBUG // botai_import.DebugLineDelete(bs->debugline); #endif //DEBUG trap_BotFreeMoveState(bs->ms); //free the goal state trap_BotFreeGoalState(bs->gs); // //clear the bot state memset(bs, 0, sizeof(bot_state_t)); //set the inuse flag to qfalse bs->inuse = qfalse; //everything went ok return BLERR_NOERROR; }
/* ============== AICast_QueryThink ============== */ void AICast_QueryThink( cast_state_t *cs ) { gentity_t *ent; qboolean visible; cast_state_t *ocs; vec3_t vec; ent = &g_entities[cs->entityNum]; ocs = AICast_GetCastState( cs->bs->enemy ); // never crouch while in this state (by choice anyway) cs->bs->attackcrouch_time = 0; // look at where we last (thought we) saw them VectorSubtract( cs->vislist[cs->bs->enemy].visible_pos, cs->bs->origin, vec ); VectorNormalize( vec ); vectoangles( vec, cs->bs->ideal_viewangles ); // are they visible now? visible = AICast_VisibleFromPos( cs->bs->origin, cs->entityNum, g_entities[cs->bs->enemy].r.currentOrigin, cs->bs->enemy, qfalse ); // make sure we dont process the sighting of this enemy by going into query mode again, without them being visible again after we leave here cs->vislist[cs->bs->enemy].flags &= ~AIVIS_PROCESS_SIGHTING; // look towards where we last saw them AICast_AimAtEnemy( cs ); // if visible and alert time has expired, go POSTAL if ( ( cs->queryAlertSightTime < 0 ) || ( ( cs->queryAlertSightTime < level.time ) && visible ) ) { if ( !cs->queryAlertSightTime ) { // set the "short reaction" condition BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qtrue, qfalse ); } AICast_StateChange( cs, AISTATE_COMBAT ); BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qfalse, qfalse ); AIFunc_BattleStart( cs ); return; } // if they've fired since the start of the query mode, go POSTAL if ( ocs->lastWeaponFired > cs->queryStartTime ) { // set the "short reaction" condition BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qtrue, qfalse ); AICast_StateChange( cs, AISTATE_COMBAT ); BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qfalse, qfalse ); AIFunc_BattleStart( cs ); return; } // if not visible, then kill the Lock On timer if ( ( cs->queryAlertSightTime > 0 ) && !visible ) { cs->queryAlertSightTime = 0; } // if the query has expired, go back to relaxed if ( !ent->client->ps.legsTimer ) { AICast_StateChange( cs, AISTATE_RELAXED ); } }
/* =============== AICast_Activate =============== */ void AICast_Activate( int activatorNum, int entNum ) { cast_state_t *cs; cs = AICast_GetCastState( entNum ); if (cs->activate) { cs->activate( entNum, activatorNum ); } AICast_Printf( AICAST_PRT_DEBUG, "activated entity # %i\n", entNum ); }
/* =============== AICast_AgePlayTime =============== */ void AICast_AgePlayTime( int entnum ) { cast_state_t *cs = AICast_GetCastState(entnum); // if ((level.time - cs->lastLoadTime) > 100) { if ((level.time - cs->lastLoadTime) < 1000) { cs->totalPlayTime += level.time - cs->lastLoadTime; } // cs->lastLoadTime = level.time; } }
/* ================ AICast_NoFlameDamage ================ */ qboolean AICast_NoFlameDamage( int entNum ) { cast_state_t *cs; if (entNum >= MAX_CLIENTS) return qfalse; // DHM - Nerve :: Not in multiplayer if ( g_gametype.integer != GT_SINGLE_PLAYER ) return qfalse; cs = AICast_GetCastState( entNum ); return ((cs->aiFlags & AIFL_NO_FLAME_DAMAGE) != 0); }
/* ============ AICast_Sight ============ */ void AICast_Sight( gentity_t *ent, gentity_t *other, int lastSight ) { cast_state_t *cs, *ocs; cs = AICast_GetCastState( ent->s.number ); ocs = AICast_GetCastState( other->s.number ); // // call the sightfunc for this cast, so we can play associated sounds, or do any character-specific things // if ( cs->sightfunc ) { // factor in the reaction time if ( AICast_EntityVisible( cs, other->s.number, qfalse ) ) { cs->sightfunc( ent, other, lastSight ); } } if ( other->aiName && other->health <= 0 ) { // they died since we last saw them if ( ocs->deathTime > lastSight ) { if ( !AICast_SameTeam( cs, other->s.number ) ) { AICast_ScriptEvent( cs, "enemysightcorpse", other->aiName ); } else if ( !( cs->castScriptStatus.scriptFlags & SFL_FRIENDLYSIGHTCORPSE_TRIGGERED ) ) { cs->castScriptStatus.scriptFlags |= SFL_FRIENDLYSIGHTCORPSE_TRIGGERED; AICast_ScriptEvent( cs, "friendlysightcorpse", "" ); } } // if this is the first time, call the sight script event } else if ( !lastSight && other->aiName ) { if ( !AICast_SameTeam( cs, other->s.number ) ) { // disabled.. triggered when entering combat mode //AICast_ScriptEvent( cs, "enemysight", other->aiName ); } else { AICast_ScriptEvent( cs, "sight", other->aiName ); } } }
/* ============ AICast_Pain ============ */ void AICast_Pain( gentity_t *targ, gentity_t *attacker, int damage, vec3_t point ) { cast_state_t *cs; cs = AICast_GetCastState( targ->s.number ); // print debugging message if ( aicast_debug.integer == 2 && attacker->s.number == 0 ) { G_Printf( "hit %s %i\n", targ->aiName, targ->health ); } // if we are below alert mode, then go there immediately if ( cs->aiState < AISTATE_ALERT ) { AICast_StateChange( cs, AISTATE_ALERT ); } if ( cs->aiFlags & AIFL_NOPAIN ) { return; } // process the event (turn to face the attacking direction? go into hide/retreat state?) // need to weigh up the situation, but foremost, an inactive AI cast should always react in some way to being hurt cs->lastPain = level.time; // record the sighting (FIXME: silent weapons shouldn't do this, but the AI should react in some way) if ( attacker->client ) { AICast_UpdateVisibility( targ, attacker, qtrue, qtrue ); } // if either of us are neutral, then we are now enemies if ( targ->aiTeam == AITEAM_NEUTRAL || attacker->aiTeam == AITEAM_NEUTRAL ) { cs->vislist[attacker->s.number].flags |= AIVIS_ENEMY; } AICast_ScriptEvent( cs, "pain", va( "%d %d", targ->health, targ->health + damage ) ); if ( cs->aiFlags & AIFL_DENYACTION ) { // dont play any sounds return; } // // call the painfunc for this cast, so we can play associated sounds, or do any character-specific things // if ( cs->painfunc ) { cs->painfunc( targ, attacker, damage, point ); } }
/*QUAKED target_script_trigger (1 .7 .2) (-8 -8 -8) (8 8 8) must have an aiName must have a target when used it will fire its targets */ void target_script_trigger_use (gentity_t *ent, gentity_t *other, gentity_t *activator ) { gentity_t *player; if (ent->aiName) { player = AICast_FindEntityForName("player"); if (player) AICast_ScriptEvent( AICast_GetCastState(player->s.number), "trigger", ent->target ); } // DHM - Nerve :: In multiplayer, we use the brush scripting only if ( g_gametype.integer >= GT_WOLF && ent->scriptName ) { G_Script_ScriptEvent( ent, "trigger", ent->target ); } G_UseTargets ( ent, other); }
/* =============== AICast_AgePlayTime =============== */ void AICast_AgePlayTime( int entnum ) { cast_state_t *cs = AICast_GetCastState( entnum ); // if ( saveGamePending ) { return; } // if (reloading) if ( g_reloading.integer ) { return; } // if ( ( level.time - cs->lastLoadTime ) > 1000 ) { if ( /*(level.time - cs->lastLoadTime) < 2000 &&*/ ( level.time - cs->lastLoadTime ) > 0 ) { cs->totalPlayTime += level.time - cs->lastLoadTime; trap_Cvar_Set( "g_totalPlayTime", va( "%i", cs->totalPlayTime ) ); } // cs->lastLoadTime = level.time; } }
/* ================ AICast_SetFlameDamage ================ */ void AICast_SetFlameDamage( int entNum, qboolean status ) { cast_state_t *cs; if ( entNum >= MAX_CLIENTS ) { return; } // DHM - Nerve :: Not in multiplayer if ( g_gametype.integer != GT_SINGLE_PLAYER ) { return; } cs = AICast_GetCastState( entNum ); if ( status ) { cs->aiFlags |= AIFL_NO_FLAME_DAMAGE; } else { cs->aiFlags &= ~AIFL_NO_FLAME_DAMAGE; } }
/* ================= G_ScriptAction_Trigger syntax: trigger <aiName/scriptName> <trigger> Calls the specified trigger for the given ai character or script entity ================= */ qboolean G_ScriptAction_Trigger( gentity_t *ent, const char* params ) { gentity_t *trent; const char* pString; char* token; char name[MAX_QPATH]; char trigger[MAX_QPATH]; int oldId; // get the cast name pString = params; token = COM_ParseExt( &pString, qfalse ); Q_strncpyz( name, token, sizeof( name ) ); if ( !name[0] ) { G_Error( "G_Scripting: trigger must have a name and an identifier\n" ); } token = COM_ParseExt( &pString, qfalse ); Q_strncpyz( trigger, token, sizeof( trigger ) ); if ( !trigger[0] ) { G_Error( "G_Scripting: trigger must have a name and an identifier\n" ); } trent = AICast_FindEntityForName( name ); if ( trent ) { // we are triggering an AI //oldId = trent->scriptStatus.scriptId; AICast_ScriptEvent( AICast_GetCastState( trent->s.number ), "trigger", trigger ); return qtrue; } // look for an entity trent = G_Find( &g_entities[MAX_CLIENTS], FOFS( scriptName ), name ); if ( trent ) { oldId = trent->scriptStatus.scriptId; G_Script_ScriptEvent( trent, "trigger", trigger ); // if the script changed, return false so we don't muck with it's variables return ( ( trent != ent ) || ( oldId == trent->scriptStatus.scriptId ) ); } G_Error( "G_Scripting: trigger has unknown name: %s\n", name ); return qfalse; // shutup the compiler }
/* ============ AICast_CreateCharacter returns 0 if unable to create the character ============ */ gentity_t *AICast_CreateCharacter( gentity_t *ent, float *attributes, cast_weapon_info_t *weaponInfo, char *castname, char *model, char *head, char *sex, char *color, char *handicap ) { gentity_t *newent; gclient_t *client; cast_state_t *cs; char **ppStr; int j; if (g_gametype.integer != GT_SINGLE_PLAYER) { // no cast AI in multiplayer return NULL; } // are bots enabled? if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { G_Printf( S_COLOR_RED "ERROR: Unable to spawn %s, 'bot_enable' is not set\n", ent->classname ); return NULL; } // // make sure we have a free slot for them // if (level.numPlayingClients+1 > aicast_maxclients) { G_Error( "Exceeded sv_maxclients (%d), unable to create %s\n", aicast_maxclients, ent->classname ); return NULL; } // // add it to the list (only do this if everything else passed) // newent = AICast_AddCastToGame( ent, castname, model, head, sex, color, handicap ); if (!newent) { return NULL; } client = newent->client; // // setup the character.. // cs = AICast_GetCastState( newent->s.number ); // // setup the attributes memcpy( cs->attributes, attributes, sizeof(cs->attributes) ); ppStr = &ent->aiAttributes; AICast_CheckLevelAttributes( cs, ent, ppStr ); // AICast_SetAASIndex( cs ); // make sure they face the right direction VectorCopy( ent->s.angles, cs->bs->ideal_viewangles ); // factor in the delta_angles for (j = 0; j < 3; j++) { cs->bs->viewangles[j] = AngleMod(newent->s.angles[j] - SHORT2ANGLE(newent->client->ps.delta_angles[j])); } VectorCopy( ent->s.angles, newent->s.angles ); VectorCopy( ent->s.origin, cs->startOrigin ); // cs->lastEnemy = -1; cs->bs->enemy = -1; cs->leaderNum = -1; cs->castScriptStatus.scriptGotoEnt = -1; cs->aiCharacter = ent->aiCharacter; // newent->aiName = ent->aiName; newent->aiTeam = ent->aiTeam; newent->targetname = ent->targetname; // newent->AIScript_AlertEntity = ent->AIScript_AlertEntity; newent->aiInactive = ent->aiInactive; newent->aiCharacter = cs->aiCharacter; // // parse the AI script for this character (if applicable) cs->aiFlags |= AIFL_CORPSESIGHTING; // this is on by default for all characters, disabled if they have a "friendlysightcorpse" script event AICast_ScriptParse( cs ); // // setup bounding boxes //VectorCopy( mins, client->ps.mins ); //VectorCopy( maxs, client->ps.maxs ); AIChar_SetBBox( newent, cs ); client->ps.friction = cs->attributes[RUNNING_SPEED]/300.0; // // clear weapons/ammo client->ps.weapon = 0; memcpy( client->ps.weapons, weaponInfo->startingWeapons, sizeof(weaponInfo->startingWeapons) ); memcpy( client->ps.ammo, weaponInfo->startingAmmo, sizeof(client->ps.ammo) ); // // starting health if (ent->health) { newent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = ent->health; } else { newent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = cs->attributes[STARTING_HEALTH]; } // cs->weaponInfo = weaponInfo; // cs->lastThink = level.time; // newent->pain = AICast_Pain; newent->die = AICast_Die; // //update the attack inventory values AICast_UpdateBattleInventory(cs, cs->bs->enemy); //----(SA) make sure all clips are loaded so we don't hear everyone loading up // (we don't want to do this inside AICast_UpdateBattleInventory(), only on spawn or giveweapon) for (j=0; j<MAX_WEAPONS; j++) { Fill_Clip (&client->ps, j); } //----(SA) end // select a weapon AICast_ChooseWeapon( cs, qfalse ); // // set the default function, overwrite if necessary cs->aiFlags |= AIFL_JUST_SPAWNED; AIFunc_DefaultStart( cs ); // numcast++; // return newent; }
/* ============== AICast_PlayTime ============== */ int AICast_PlayTime( int entnum ) { cast_state_t *cs = AICast_GetCastState( entnum ); return ( cs->totalPlayTime ); }
const char *AIFunc_Heinrich_SwordSideSlash( cast_state_t *cs ) { gentity_t *ent = &g_entities[cs->entityNum]; trace_t *tr; vec3_t right, left; float enemyDist; aicast_predictmove_t move; vec3_t vec; cast_state_t *ecs; cs->aiFlags |= AIFL_SPECIAL_FUNC; if ( cs->enemyNum < 0 ) { if ( ent->client->ps.torsoTimer ) { return NULL; } return AIFunc_DefaultStart( cs ); } ecs = AICast_GetCastState( cs->enemyNum ); if ( ent->client->ps.torsoTimer < 500 ) { if ( !ent->client->ps.legsTimer ) { trap_EA_MoveForward( cs->entityNum ); } ent->client->ps.legsTimer = 0; ent->client->ps.torsoTimer = 0; cs->castScriptStatus.scriptNoMoveTime = 0; AICast_Heinrich_Taunt( cs ); return AIFunc_BattleChaseStart( cs ); } // time for the melee? if ( cs->enemyNum >= 0 && !( cs->aiFlags & AIFL_MISCFLAG1 ) ) { // face them AICast_AimAtEnemy( cs ); // keep checking for impact status tr = CheckMeleeAttack( ent, HEINRICH_SLASH_RANGE, qfalse ); // ready for damage? if ( cs->thinkFuncChangeTime < level.time - HEINRICH_SLASH_DELAY ) { cs->aiFlags |= AIFL_MISCFLAG1; // do melee damage if ( tr && ( tr->entityNum == cs->enemyNum ) ) { AngleVectors( cs->viewangles, NULL, right, NULL ); VectorNegate( right, left ); G_Damage( &g_entities[tr->entityNum], ent, ent, left, tr->endpos, HEINRICH_SLASH_DAMAGE, 0, MOD_GAUNTLET ); // sound G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_SWORDIMPACT] ); // throw them in direction of impact left[2] = 0.5; VectorMA( g_entities[cs->enemyNum].client->ps.velocity, 400, left, g_entities[cs->enemyNum].client->ps.velocity ); } } } // if they are outside range, move forward AICast_PredictMovement( ecs, 2, 0.3, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 ); VectorSubtract( move.endpos, cs->bs->origin, vec ); vec[2] = 0; enemyDist = VectorLength( vec ); enemyDist -= g_entities[cs->enemyNum].r.maxs[0]; enemyDist -= ent->r.maxs[0]; if ( enemyDist > 30 ) { // we can get closer if ( ent->client->ps.legsTimer ) { cs->castScriptStatus.scriptNoMoveTime = level.time + 100; ent->client->ps.legsTimer = 0; // allow legs to move us } if ( cs->castScriptStatus.scriptNoMoveTime < level.time ) { trap_EA_MoveForward( cs->entityNum ); } } return NULL; }
/* ================ AIFunc_Helga_Melee ================ */ const char *AIFunc_Helga_Melee( cast_state_t *cs ) { gentity_t *ent = &g_entities[cs->entityNum]; gentity_t *enemy; cast_state_t *ecs; int hitDelay = -1, anim; trace_t tr; float enemyDist; aicast_predictmove_t move; vec3_t vec; cs->aiFlags |= AIFL_SPECIAL_FUNC; if ( !ent->client->ps.torsoTimer || !ent->client->ps.legsTimer ) { cs->aiFlags &= ~AIFL_SPECIAL_FUNC; return AIFunc_DefaultStart( cs ); } if ( cs->enemyNum < 0 ) { ent->client->ps.legsTimer = 0; // allow legs us to move ent->client->ps.torsoTimer = 0; // allow legs us to move cs->aiFlags &= ~AIFL_SPECIAL_FUNC; return AIFunc_DefaultStart( cs ); } ecs = AICast_GetCastState( cs->enemyNum ); enemy = &g_entities[cs->enemyNum]; anim = ( ent->client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) - BG_AnimationIndexForString( "attack3", cs->entityNum ); if ( anim < 0 || anim >= NUM_HELGA_ANIMS ) { // animation interupted cs->aiFlags &= ~AIFL_SPECIAL_FUNC; return AIFunc_DefaultStart( cs ); //G_Error( "AIFunc_HelgaZombieMelee: helgaBoss using invalid or unknown attack anim" ); } if ( cs->animHitCount < MAX_HELGA_IMPACTS && helgaHitTimes[anim][cs->animHitCount] >= 0 ) { // face them VectorCopy( cs->bs->origin, vec ); vec[2] += ent->client->ps.viewheight; VectorSubtract( enemy->client->ps.origin, vec, vec ); VectorNormalize( vec ); vectoangles( vec, cs->ideal_viewangles ); cs->ideal_viewangles[PITCH] = AngleNormalize180( cs->ideal_viewangles[PITCH] ); // get hitDelay if ( !cs->animHitCount ) { hitDelay = helgaHitTimes[anim][cs->animHitCount]; } else { hitDelay = helgaHitTimes[anim][cs->animHitCount] - helgaHitTimes[anim][cs->animHitCount - 1]; } // check for inflicting damage if ( level.time - cs->weaponFireTimes[cs->weaponNum] > hitDelay ) { // do melee damage enemyDist = VectorDistance( enemy->r.currentOrigin, ent->r.currentOrigin ); enemyDist -= g_entities[cs->enemyNum].r.maxs[0]; enemyDist -= ent->r.maxs[0]; if ( enemyDist < 10 + AICast_WeaponRange( cs, cs->weaponNum ) ) { trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, enemy->r.currentOrigin, ent->s.number, MASK_SHOT ); if ( tr.entityNum == cs->enemyNum ) { G_Damage( &g_entities[tr.entityNum], ent, ent, vec3_origin, tr.endpos, helgaHitDamage[anim], 0, MOD_GAUNTLET ); G_AddEvent( enemy, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[STAYSOUNDSCRIPT] ) ); } } cs->weaponFireTimes[cs->weaponNum] = level.time; cs->animHitCount++; } } // if they are outside range, move forward AICast_PredictMovement( ecs, 2, 0.3, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 ); VectorSubtract( move.endpos, cs->bs->origin, vec ); vec[2] = 0; enemyDist = VectorLength( vec ); enemyDist -= g_entities[cs->enemyNum].r.maxs[0]; enemyDist -= ent->r.maxs[0]; if ( enemyDist > 8 ) { // we can get closer //if (!ent->client->ps.legsTimer) { // cs->castScriptStatus.scriptNoMoveTime = 0; trap_EA_MoveForward( cs->entityNum ); //} //ent->client->ps.legsTimer = 0; // allow legs us to move } return NULL; }
void AICast_StartFrame( int time ) { int i, elapsed, count, clCount; cast_state_t *cs; int castcount; static int lasttime; static vmCvar_t aicast_disable; gentity_t *ent; if ( trap_Cvar_VariableIntegerValue( "savegame_loading" ) ) { return; } if ( saveGamePending ) { return; } // if waiting at intermission, don't think if ( strlen( g_missionStats.string ) > 1 ) { return; } if ( !aicast_disable.handle ) { trap_Cvar_Register( &aicast_disable, "aicast_disable", "0", CVAR_CHEAT ); } else { trap_Cvar_Update( &aicast_disable ); if ( aicast_disable.integer ) { return; } } trap_Cvar_Update( &aicast_debug ); trap_Cvar_Update( &aicast_debugname ); trap_Cvar_Update( &aicast_scripts ); // no need to think during the intermission if ( level.intermissiontime ) { return; } // // make sure the AAS gets updated trap_BotLibStartFrame( (float) time / 1000 ); // // elapsed = time - lasttime; if ( elapsed == 0 ) { return; // no time has elapsed } //G_Printf( "AI startframe: %i\n", time ); if ( elapsed < 0 ) { elapsed = 0; lasttime = time; } // don't let the framerate drop below 10 if ( elapsed > 100 ) { elapsed = 100; } //AICast_SightUpdate( (int)((float)SIGHT_PER_SEC * ((float)elapsed / 1000)) ); // count = 0; castcount = 0; clCount = 0; ent = g_entities; // //update the AI characters // TTimo gcc: left-hand operand of comma expression has no effect // initial line was: for (i = 0; i < aicast_maxclients, clCount < level.numPlayingClients; i++, ent++) for ( i = 0; ( i < aicast_maxclients ) && ( clCount < level.numPlayingClients ) ; i++, ent++ ) { if ( ent->client ) { clCount++; } // cs = AICast_GetCastState( i ); // is this a cast AI? if ( cs->bs ) { if ( ent->inuse ) { if ( ent->aiInactive == qfalse ) { // elapsed = time - cs->lastThink; // // if they're moving/firing think every frame if ( ( elapsed >= 50 ) && ( ( ( ( !VectorCompare( ent->client->ps.velocity, vec3_origin ) ) || ( ent->client->buttons ) || ( elapsed >= aicast_thinktime ) ) && ( count <= aicast_maxthink ) ) || ( elapsed >= aicast_thinktime * 2 ) ) ) { // make it think now AICast_Think( i, (float)elapsed / 1000 ); cs->lastThink = time; // count++; } // check for any debug info updates AICast_DebugFrame( cs ); } else if ( cs->aiFlags & AIFL_WAITINGTOSPAWN ) { // check f the space is clear yet ent->AIScript_AlertEntity( ent ); } } else { trap_UnlinkEntity( ent ); } // // see if we've checked all cast AI's if ( ++castcount >= numcast ) { break; } } } // lasttime = time; }
/* ============ AICast_StartServerFrame Do movements, sighting, etc ============ */ void AICast_StartServerFrame( int time ) { int i, elapsed, count, clCount; cast_state_t *cs; int castcount; static int lasttime; static vmCvar_t aicast_disable; gentity_t *ent; cast_state_t *pcs; // int oldLegsTimer; if ( trap_Cvar_VariableIntegerValue( "savegame_loading" ) ) { return; } if ( g_gametype.integer != GT_SINGLE_PLAYER ) { return; } if ( saveGamePending ) { return; } // if waiting at intermission, don't think if ( strlen( g_missionStats.string ) > 1 ) { return; } if ( !aicast_disable.handle ) { trap_Cvar_Register( &aicast_disable, "aicast_disable", "0", CVAR_CHEAT ); } else { trap_Cvar_Update( &aicast_disable ); if ( aicast_disable.integer ) { return; } } trap_Cvar_Update( &aicast_debug ); // no need to think during the intermission if ( level.intermissiontime ) { return; } // // make sure the AAS gets updated trap_BotLibStartFrame( (float) time / 1000 ); // // elapsed = time - lasttime; if ( elapsed == 0 ) { return; // no time has elapsed } pcs = AICast_GetCastState( 0 ); //G_Printf( "AI startserverframe: %i\n", time ); if ( elapsed < 0 ) { elapsed = 0; lasttime = time; } // don't let the framerate drop below 10 if ( elapsed > 100 ) { elapsed = 100; } // // process player's current script if it exists AICast_ScriptRun( AICast_GetCastState( 0 ), qfalse ); // AICast_SightUpdate( (int)( (float)SIGHT_PER_SEC * ( (float)elapsed / 1000 ) ) ); // count = 0; castcount = 0; clCount = 0; ent = g_entities; // //update the AI characters // TTimo gcc: left-hand operand of comma expression has no effect // initial line: for (i = 0; i < aicast_maxclients, clCount < level.numPlayingClients; i++, ent++) for ( i = 0; ( i < aicast_maxclients ) && ( clCount < level.numPlayingClients ) ; i++, ent++ ) { if ( ent->client ) { clCount++; } // cs = AICast_GetCastState( i ); // is this a cast AI? if ( cs->bs ) { if ( ent->aiInactive == qfalse && ent->inuse ) { // elapsed = level.time - cs->lastMoveThink; // // optimization, if they're not in the player's PVS, and they aren't trying to move, then don't bother thinking if ( ( ( ent->health > 0 ) && ( elapsed > 300 ) ) || ( g_entities[0].client && g_entities[0].client->cameraPortal ) || ( cs->vislist[0].visible_timestamp == cs->vislist[0].lastcheck_timestamp ) || ( pcs->vislist[cs->entityNum].visible_timestamp == pcs->vislist[cs->entityNum].lastcheck_timestamp ) || ( VectorLength( ent->client->ps.velocity ) > 0 ) || ( cs->bs->lastucmd.forwardmove || cs->bs->lastucmd.rightmove || cs->bs->lastucmd.upmove > 0 || cs->bs->lastucmd.buttons || cs->bs->lastucmd.wbuttons ) || ( trap_InPVS( cs->bs->origin, g_entities[0].s.pos.trBase ) ) ) { // do pvs check last, since it's the most expensive to call // oldLegsTimer = ent->client->ps.legsTimer; // // send it's movement commands // serverTime = time; AICast_UpdateInput( cs, elapsed ); trap_BotUserCommand( cs->bs->client, &( cs->bs->lastucmd ) ); cs->lastMoveThink = level.time; // // check for anim changes that may require us to stay still // /* if (oldLegsTimer != ent->client->ps.legsTimer) { // dont move until they are finished if (cs->castScriptStatus.scriptNoMoveTime < level.time + ent->client->ps.legsTimer) { cs->castScriptStatus.scriptNoMoveTime = level.time + ent->client->ps.legsTimer; } } */ } } else { trap_UnlinkEntity( ent ); } // // see if we've checked all cast AI's if ( ++castcount >= numcast ) { break; } } } // lasttime = time; }
/* ============ AICast_Think entry point for all cast AI ============ */ void AICast_Think( int client, float thinktime ) { gentity_t *ent; cast_state_t *cs; int i; int animIndex; animation_t *anim; // if (saveGamePending || (strlen( g_missionStats.string ) > 2 )) { // return; // } // // get the cast ready for processing // cs = AICast_GetCastState( client ); ent = &g_entities[client]; // // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data) trap_AAS_SetCurrentWorld( cs->aasWorldIndex ); // // make sure we have a valid navigation system // if ( !trap_AAS_Initialized() ) { return; } // trap_EA_ResetInput( client, NULL ); cs->aiFlags &= ~AIFL_VIEWLOCKED; //cs->bs->weaponnum = ent->client->ps.weapon; // // turn off flags that are set each frame if needed ent->client->ps.eFlags &= ~( EF_NOSWINGANGLES | EF_MONSTER_EFFECT | EF_MONSTER_EFFECT2 | EF_MONSTER_EFFECT3 ); // conditional flags if ( ent->aiCharacter == AICHAR_ZOMBIE ) { if ( COM_BitCheck( ent->client->ps.weapons, WP_MONSTER_ATTACK1 ) ) { cs->aiFlags |= AIFL_NO_FLAME_DAMAGE; SET_FLAMING_ZOMBIE( ent->s, 1 ); } else { SET_FLAMING_ZOMBIE( ent->s, 0 ); } } // // if we're dead, do special stuff only if ( ent->health <= 0 || cs->revivingTime || cs->rebirthTime ) { // if ( cs->revivingTime && cs->revivingTime < level.time ) { // start us thinking again ent->client->ps.pm_type = PM_NORMAL; cs->revivingTime = 0; } // if ( cs->rebirthTime && cs->rebirthTime < level.time ) { vec3_t mins, maxs; int touch[10], numTouch; float oldmaxZ; oldmaxZ = ent->r.maxs[2]; // make sure the area is clear AIChar_SetBBox( ent, cs ); VectorAdd( ent->r.currentOrigin, ent->r.mins, mins ); VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs ); trap_UnlinkEntity( ent ); numTouch = trap_EntitiesInBox( mins, maxs, touch, 10 ); if ( numTouch ) { for ( i = 0; i < numTouch; i++ ) { //if (!g_entities[touch[i]].client || g_entities[touch[i]].r.contents == CONTENTS_BODY) if ( g_entities[touch[i]].r.contents & MASK_PLAYERSOLID ) { break; } } if ( i == numTouch ) { numTouch = 0; } } if ( numTouch == 0 ) { // ok to spawn // give them health when they start reviving, so we won't gib after // just a couple shots while reviving ent->health = ent->client->ps.stats[STAT_HEALTH] = ent->client->ps.stats[STAT_MAX_HEALTH] = ( ( cs->attributes[STARTING_HEALTH] - 50 ) > 30 ? ( cs->attributes[STARTING_HEALTH] - 50 ) : 30 ); ent->r.contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; ent->takedamage = qtrue; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; ent->die = AICast_Die; ent->client->ps.eFlags &= ~EF_DEAD; ent->s.eFlags &= ~EF_DEAD; cs->rebirthTime = 0; cs->deathTime = 0; // play the revive animation cs->revivingTime = level.time + BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_REVIVE, qfalse, qtrue );; } else { // can't spawn yet, so set bbox back, and wait ent->r.maxs[2] = oldmaxZ; ent->client->ps.maxs[2] = ent->r.maxs[2]; } trap_LinkEntity( ent ); } // ZOMBIE should set effect flag if really dead if ( cs->aiCharacter == AICHAR_ZOMBIE && !ent->r.contents ) { ent->client->ps.eFlags |= EF_MONSTER_EFFECT2; } // if ( ent->health > GIB_HEALTH && cs->deathTime && cs->deathTime < ( level.time - 3000 ) ) { /* // been dead for long enough, set our animation to the end frame switch ( ent->s.legsAnim & ~ANIM_TOGGLEBIT ) { case BOTH_DEATH1: case BOTH_DEAD1: anim = BOTH_DEAD1; break; case BOTH_DEATH2: case BOTH_DEAD2: anim = BOTH_DEAD2; break; case BOTH_DEATH3: case BOTH_DEAD3: anim = BOTH_DEAD3; break; default: G_Error( "%s has unknown death animation\n", ent->classname); } ent->client->ps.torsoAnim = ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; ent->client->ps.legsAnim = ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; */ cs->deathTime = 0; ent->r.svFlags &= ~SVF_BROADCAST; } // // no more thinking required return; } // // set some anim conditions if ( cs->secondDeadTime ) { BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SECONDLIFE, qtrue, qfalse ); } else { BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SECONDLIFE, qfalse, qfalse ); } // set health value if ( ent->health <= 0.25 * cs->attributes[STARTING_HEALTH] ) { BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 3, qfalse ); } else if ( ent->health <= 0.5 * cs->attributes[STARTING_HEALTH] ) { BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 2, qfalse ); } else { BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 1, qfalse ); } // cs->speedScale = 1.0; // reset each frame, set if required cs->actionFlags = 0; // FIXME: move this to a Cast AI movement init function! //retrieve the current client state BotAI_GetClientState( client, &( cs->bs->cur_ps ) ); // // setup movement speeds for the given state // walking animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_WALK ); if ( animIndex >= 0 ) { anim = BG_GetAnimationForIndex( cs->entityNum, animIndex ); cs->attributes[WALKING_SPEED] = anim->moveSpeed; } // crouching animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_WALKCR ); if ( animIndex >= 0 ) { anim = BG_GetAnimationForIndex( cs->entityNum, animIndex ); cs->attributes[CROUCHING_SPEED] = anim->moveSpeed; } // running animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_RUN ); if ( animIndex >= 0 ) { anim = BG_GetAnimationForIndex( cs->entityNum, animIndex ); cs->attributes[RUNNING_SPEED] = anim->moveSpeed; } // update crouch speed scale ent->client->ps.crouchSpeedScale = cs->attributes[CROUCHING_SPEED] / cs->attributes[RUNNING_SPEED]; // // only enable headlook if we want to this frame ent->client->ps.eFlags &= ~EF_HEADLOOK; if ( cs->bs->enemy >= 0 ) { ent->client->ps.eFlags &= ~EF_STAND_IDLE2; // never use alt idle if fighting } // // check for dead leader if ( cs->leaderNum >= 0 && g_entities[cs->leaderNum].health <= 0 ) { cs->leaderNum = -1; } // #if 0 // HACK for village2, if they are stuck, find a good position (there is a friendly guy placed inside a table) { trace_t tr; vec3_t org; trap_Trace( &tr, cs->bs->cur_ps.origin, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, cs->bs->cur_ps.origin, cs->entityNum, CONTENTS_SOLID ); while ( tr.startsolid ) { VectorCopy( cs->bs->cur_ps.origin, org ); org[0] += 96 * crandom(); org[1] += 96 * crandom(); org[2] += 16 * crandom(); trap_Trace( &tr, org, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, org, cs->entityNum, CONTENTS_SOLID ); G_SetOrigin( &g_entities[cs->entityNum], org ); VectorCopy( org, g_entities[cs->entityNum].client->ps.origin ); } } #endif //add the delta angles to the cast's current view angles for ( i = 0; i < 3; i++ ) { cs->bs->viewangles[i] = AngleMod( cs->bs->viewangles[i] + SHORT2ANGLE( cs->bs->cur_ps.delta_angles[i] ) ); } // //increase the local time of the cast cs->bs->ltime += thinktime; // cs->bs->thinktime = thinktime; //origin of the cast VectorCopy( cs->bs->cur_ps.origin, cs->bs->origin ); //eye coordinates of the cast VectorCopy( cs->bs->cur_ps.origin, cs->bs->eye ); cs->bs->eye[2] += cs->bs->cur_ps.viewheight; //get the area the cast is in cs->bs->areanum = BotPointAreaNum( cs->bs->origin ); // clear flags each frame cs->bs->flags = 0; // // check enemy health if ( cs->bs->enemy >= 0 && g_entities[cs->bs->enemy].health <= 0 ) { cs->bs->enemy = -1; } // // if the previous movetype was temporary, set it back if ( cs->movestateType == MSTYPE_TEMPORARY ) { cs->movestate = MS_DEFAULT; cs->movestateType = MSTYPE_NONE; } // crouching? if ( ( cs->bs->attackcrouch_time > trap_AAS_Time() ) && ( ( cs->lastAttackCrouch > level.time - 500 ) || ( cs->thinkFuncChangeTime < level.time - 1000 ) ) ) { // if we are not moving, and we are firing, always stand, unless we are allowed to crouch + fire if ( VectorLength( cs->bs->cur_ps.velocity ) || ( cs->lastWeaponFired < level.time - 2000 ) || ( cs->aiFlags & AIFL_ATTACK_CROUCH ) ) { cs->lastAttackCrouch = level.time; trap_EA_Crouch( cs->bs->client ); } } // //if (cs->bs->enemy >= 0) { //update the attack inventory values AICast_UpdateBattleInventory( cs, cs->bs->enemy ); //} // // if we don't have ammo for the current weapon, get rid of it if ( !( COM_BitCheck( cs->bs->cur_ps.weapons, cs->bs->weaponnum ) ) || !AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { // select a weapon AICast_ChooseWeapon( cs, qfalse ); // if still no ammo, select a blank weapon //if (!AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum )) { // cs->bs->weaponnum = WP_NONE; //} } // // in query mode, we do special handling (pause scripting, check for transition to alert/combat, etc) if ( cs->aiState == AISTATE_QUERY ) { AICast_QueryThink( cs ); } else if ( cs->pauseTime < level.time ) { // do the thinking AICast_ProcessAIFunctions( cs, thinktime ); // // make sure the correct weapon is selected trap_EA_SelectWeapon( cs->bs->client, cs->bs->weaponnum ); // // process current script if it exists cs->castScriptStatusCurrent = cs->castScriptStatus; AICast_ScriptRun( cs, qfalse ); } // // set special movestate if necessary if ( cs->movestateType != MSTYPE_NONE ) { switch ( cs->movestate ) { case MS_WALK: cs->actionFlags |= CASTACTION_WALK; break; case MS_CROUCH: trap_EA_Crouch( cs->entityNum ); break; default: break; // TTimo gcc: MS_DEFAULT MS_RUN not handled in switch } } // //subtract the delta angles for ( i = 0; i < 3; i++ ) { cs->bs->viewangles[i] = AngleMod( cs->bs->viewangles[i] - SHORT2ANGLE( cs->bs->cur_ps.delta_angles[i] ) ); } }
const char *AIFunc_Heinrich_Earthquake( cast_state_t *cs ) { gentity_t *ent = &g_entities[cs->entityNum]; gentity_t *enemy; cast_state_t *ecs; vec3_t enemyVec; float enemyDist, scale; trace_t *tr; cs->aiFlags |= AIFL_SPECIAL_FUNC; if ( cs->enemyNum < 0 ) { if ( !ent->client->ps.torsoTimer ) { return AIFunc_DefaultStart( cs ); } return NULL; } enemy = &g_entities[cs->enemyNum]; ecs = AICast_GetCastState( cs->enemyNum ); VectorMA( enemy->r.currentOrigin, HEINRICH_STOMP_DELAY, enemy->client->ps.velocity, enemyVec ); enemyDist = VectorDistance( ent->r.currentOrigin, enemyVec ); if ( ent->client->ps.torsoTimer < 500 ) { int rnd; aicast_predictmove_t move; vec3_t vec; AICast_PredictMovement( ecs, 2, 0.5, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 ); VectorSubtract( move.endpos, cs->bs->origin, vec ); vec[2] = 0; enemyDist = VectorLength( vec ); enemyDist -= g_entities[cs->enemyNum].r.maxs[0]; enemyDist -= ent->r.maxs[0]; // if ( enemyDist < 140 ) { // combo attack rnd = rand() % 3; switch ( rnd ) { case 0: return AIFunc_Heinrich_SwordSideSlashStart( cs ); case 1: return AIFunc_Heinrich_SwordKnockbackStart( cs ); case 2: return AIFunc_Heinrich_SwordLungeStart( cs ); } } else { // back to roaming ent->client->ps.legsTimer = 0; ent->client->ps.torsoTimer = 0; cs->castScriptStatus.scriptNoMoveTime = 0; AICast_Heinrich_Taunt( cs ); return AIFunc_DefaultStart( cs ); } } // time for the thump? if ( !( cs->aiFlags & AIFL_MISCFLAG1 ) ) { // face them AICast_AimAtEnemy( cs ); // ready for damage? if ( cs->thinkFuncChangeTime < level.time - HEINRICH_STOMP_DELAY ) { cs->aiFlags |= AIFL_MISCFLAG1; // play the stomp sound G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[ORDERSDENYSOUNDSCRIPT] ) ); // check for striking the player tr = CheckMeleeAttack( ent, 70, qfalse ); // do melee damage if ( tr && ( tr->entityNum == cs->enemyNum ) ) { G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos, HEINRICH_STOMP_DAMAGE, 0, MOD_GAUNTLET ); } // call the debris trigger AICast_ScriptEvent( cs, "trigger", "quake" ); } } enemyDist = Distance( enemy->s.pos.trBase, ent->s.pos.trBase ); // do the earthquake effects if ( cs->thinkFuncChangeTime < level.time - HEINRICH_STOMP_DELAY ) { // throw the player into the air, if they are on the ground if ( ( enemy->s.groundEntityNum != ENTITYNUM_NONE ) && enemyDist < HEINRICH_STOMP_RANGE ) { scale = 0.5 + 0.5 * ( (float)ent->client->ps.torsoTimer / 1000.0 ); if ( scale > 1.0 ) { scale = 1.0; } VectorSubtract( ent->s.pos.trBase, enemy->s.pos.trBase, enemyVec ); VectorScale( enemyVec, 2.0 * ( 0.6 + 0.5 * random() ) * scale * ( 0.6 + 0.6 * ( 1.0 - ( enemyDist / HEINRICH_STOMP_RANGE ) ) ), enemyVec ); enemyVec[2] = scale * HEINRICH_STOMP_VELOCITY_Z * ( 1.0 - 0.5 * ( enemyDist / HEINRICH_STOMP_RANGE ) ); // bounce the player using this velocity VectorAdd( enemy->client->ps.velocity, enemyVec, enemy->client->ps.velocity ); } } return NULL; }
/* ================ AICast_EvaluatePmove Avoidance after the event (leaders instruct AI's to get out the way, AI's instruct other non-moving AI's to get out the way) ================ */ void AICast_EvaluatePmove( int clientnum, pmove_t *pm ) { cast_state_t *cs, *ocs; int i, ent; bot_goal_t ogoal; //vec3_t pos, dir; cs = AICast_GetCastState( clientnum ); // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data) trap_AAS_SetCurrentWorld( cs->aasWorldIndex ); // NOTE: this is only enabled for real clients, so their followers get out of their way //if (cs->bs) // return; // look through the touchent's to see if we've bumped into something we should avoid, or react to for ( i = 0; i < pm->numtouch; i++ ) { // mark the time, so they can deal with the obstruction in their own think functions cs->blockedTime = level.time; if ( pm->touchents[i] == pm->ps->groundEntityNum ) { continue; } // if they are an AI Cast, inform them of our disposition, and hope that they are reasonable // enough to assist us in our desire to move beyond our current position if ( pm->touchents[i] < aicast_maxclients ) { if ( !AICast_EntityVisible( cs, pm->touchents[i], qtrue ) ) { continue; } // if we are inspecting the body, abort if we touch anything if ( cs->bs && cs->bs->enemy >= 0 && g_entities[cs->bs->enemy].health <= 0 ) { cs->bs->enemy = -1; } // anything we touch, should see us AICast_UpdateVisibility( &g_entities[pm->touchents[i]], &g_entities[cs->entityNum], qfalse, qtrue ); ocs = AICast_GetCastState( pm->touchents[i] ); if ( ( ocs->bs ) && ( !( ocs->aiFlags & AIFL_NOAVOID ) ) && ( ( ocs->leaderNum == cs->entityNum ) || ( VectorLength( ocs->bs->velocity ) < 5 ) ) && ( ocs->obstructingTime < ( level.time + 100 ) ) ) { // if they are moving away from us already, let them go if ( VectorLength( ocs->bs->cur_ps.velocity ) > 10 ) { vec3_t v1, v2; VectorSubtract( ocs->bs->origin, g_entities[clientnum].client->ps.velocity, v2 ); VectorNormalize( v2 ); VectorNormalize2( ocs->bs->cur_ps.velocity, v1 ); if ( DotProduct( v1, v2 ) > 0.0 ) { continue; } } if ( ocs->leaderNum >= 0 ) { VectorCopy( g_entities[ocs->leaderNum].r.currentOrigin, ogoal.origin ); ogoal.areanum = BotPointAreaNum( ogoal.origin ); ogoal.entitynum = ocs->leaderNum; if ( ocs->bs && AICast_GetAvoid( ocs, &ogoal, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else ocs->obstructingTime = level.time + 1000; } } else { if ( ocs->bs && AICast_GetAvoid( ocs, NULL, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else ocs->obstructingTime = level.time + 1000; } } } } else if ( cs->bs ) { // if we are blocked by a brush entity, see if we can activate it ent = pm->touchents[i]; if ( g_entities[ent].s.modelindex > 0 && g_entities[ent].s.eType == ET_MOVER ) { //find the bsp entity which should be activated in order to remove //the blocking entity if ( !g_entities[ent].isProp && Q_stricmp( g_entities[ent].classname, "func_static" ) && Q_stricmp( g_entities[ent].classname, "func_button" ) && Q_stricmp( g_entities[ent].classname, "func_tram" ) ) { G_Activate( &g_entities[ent], &g_entities[cs->entityNum] ); } } } } }
/* ============ AICast_Blocked ============ */ void AICast_Blocked( cast_state_t *cs, bot_moveresult_t *moveresult, int activate, bot_goal_t *goal ) { vec3_t pos, dir; aicast_predictmove_t move; usercmd_t ucmd; bot_input_t bi; cast_state_t *ocs; int i, blockEnt = -1; bot_goal_t ogoal; if ( cs->blockedAvoidTime < level.time ) { if ( cs->blockedAvoidTime < level.time - 300 ) { if ( VectorCompare( cs->bs->cur_ps.velocity, vec3_origin ) && !cs->bs->lastucmd.forwardmove && !cs->bs->lastucmd.rightmove ) { // not moving, don't bother checking cs->blockedAvoidTime = level.time - 1; return; } // are we going to hit someone soon? trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi ); AICast_InputToUserCommand( cs, &bi, &ucmd, cs->bs->cur_ps.delta_angles ); AICast_PredictMovement( cs, 1, 0.6, &move, &ucmd, ( goal && goal->entitynum > -1 ) ? goal->entitynum : cs->entityNum ); // blocked if we hit a client (or non-stationary mover) other than our enemy or goal if ( move.stopevent != PREDICTSTOP_HITCLIENT ) { // not blocked cs->blockedAvoidTime = level.time - 1; return; } // if we stopped passed our goal, ignore it if ( goal ) { if ( VectorDistance( cs->bs->origin, goal->origin ) < VectorDistance( cs->bs->origin, move.endpos ) ) { vec3_t v1, v2; VectorSubtract( goal->origin, cs->bs->origin, v1 ); VectorSubtract( goal->origin, move.endpos, v2 ); VectorNormalize( v1 ); VectorNormalize( v2 ); if ( DotProduct( v1, v2 ) < 0 ) { // we went passed the goal, so assume we can reach it cs->blockedAvoidTime = level.time - 1; return; } } } // try and get them to move, in case we can't get around them blockEnt = -1; for ( i = 0; i < move.numtouch; i++ ) { if ( move.touchents[i] >= MAX_CLIENTS ) { if ( !Q_stricmp( g_entities[move.touchents[i]].classname, "script_mover" ) ) { // avoid script_mover's blockEnt = move.touchents[i]; } // if we are close to the impact point, then avoid this entity else if ( VectorDistance( cs->bs->origin, move.endpos ) < 10 ) { //G_Printf("AI (%s) avoiding %s\n", g_entities[cs->entityNum].aiName, g_entities[move.touchents[i]].classname ); blockEnt = move.touchents[i]; } continue; } // ocs = AICast_GetCastState( move.touchents[i] ); if ( !ocs->bs ) { blockEnt = move.touchents[i]; } // reject this blocker if we are following or going to them else if ( cs->followEntity != ocs->entityNum ) { // if they are moving away from us already, let them go if ( VectorLength( ocs->bs->cur_ps.velocity ) > 10 ) { vec3_t v1, v2; VectorSubtract( ocs->bs->origin, cs->bs->origin, v2 ); VectorNormalize( v2 ); VectorNormalize2( ocs->bs->cur_ps.velocity, v1 ); if ( DotProduct( v1, v2 ) > 0.0 ) { continue; } } // // if they recently were asked to avoid us, then they're probably not listening if ( ocs->obstructingTime > level.time - 500 ) { blockEnt = move.touchents[i]; } // // if they are not avoiding, ignore if ( !( ocs->aiFlags & AIFL_NOAVOID ) ) { continue; } // // they should avoid us if ( ocs->leaderNum >= 0 ) { ogoal.entitynum = ocs->leaderNum; VectorCopy( g_entities[ocs->leaderNum].r.currentOrigin, ogoal.origin ); if ( AICast_GetAvoid( ocs, &ogoal, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else ocs->obstructingTime = level.time + 1000; } else { // make sure they don't call GetAvoid() for another few frames to let others avoid also ocs->obstructingTime = level.time - 1; blockEnt = move.touchents[i]; } } else { if ( AICast_GetAvoid( ocs, NULL, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else ocs->obstructingTime = level.time + 1000; } else { // make sure they don't call GetAvoid() for another few frames to let others avoid also ocs->obstructingTime = level.time - 1; blockEnt = move.touchents[i]; } } } } } else { return; } if ( blockEnt < 0 ) { // nothing found to be worth avoding cs->blockedAvoidTime = level.time - 1; return; } // something is blocking our path if ( g_entities[blockEnt].aiName && g_entities[blockEnt].client ) { int oldId = cs->castScriptStatus.scriptId; AICast_ScriptEvent( cs, "blocked", g_entities[blockEnt].aiName ); if ( oldId != cs->castScriptStatus.scriptId ) { // the script has changed, so assume the scripting is handling the avoidance return; } } // avoid geometry and props, but assume clients will get out the way if ( /*blockEnt > MAX_CLIENTS &&*/ AICast_GetAvoid( cs, goal, pos, qfalse, blockEnt ) ) { VectorSubtract( pos, cs->bs->cur_ps.origin, dir ); VectorNormalize( dir ); cs->blockedAvoidYaw = vectoyaw( dir ); if ( blockEnt >= MAX_CLIENTS ) { cs->blockedAvoidTime = level.time + 100 + rand() % 200; } else { cs->blockedAvoidTime = level.time + 300 + rand() % 400; } } else { cs->blockedAvoidTime = level.time - 1; // don't look again for another few frames return; } } VectorClear( pos ); pos[YAW] = cs->blockedAvoidYaw; AngleVectors( pos, dir, NULL, NULL ); if ( moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE ) { trap_EA_Jump( cs->bs->entitynum ); } trap_EA_Move( cs->bs->entitynum, dir, 200 ); //400); vectoangles( dir, cs->bs->ideal_viewangles ); cs->bs->ideal_viewangles[2] *= 0.5; }
/* ================== AICast_CheckLoadGame at the start of a level, the game is either saved, or loaded we must wait for all AI to spawn themselves, and a real client to connect ================== */ void AICast_CheckLoadGame( void ) { char loading[4]; gentity_t *ent = NULL; // TTimo: VC6 'may be used without having been init' qboolean ready; cast_state_t *pcs; // have we already done the save or load? if ( !saveGamePending ) { return; } // tell the cgame NOT to render the scene while we are waiting for things to settle trap_Cvar_Set( "cg_norender", "1" ); trap_Cvar_VariableStringBuffer( "savegame_loading", loading, sizeof( loading ) ); // reloading = qtrue; trap_Cvar_Set( "g_reloading", "1" ); if ( strlen( loading ) > 0 && atoi( loading ) != 0 ) { // screen should be black if we are at this stage trap_SetConfigstring( CS_SCREENFADE, va( "1 %i 1", level.time - 10 ) ); // if (!reloading && atoi(loading) == 2) { if ( !( g_reloading.integer ) && atoi( loading ) == 2 ) { // (SA) hmm, this seems redundant when it sets it above... // reloading = qtrue; // this gets reset at the Map_Restart() since the server unloads the game dll trap_Cvar_Set( "g_reloading", "1" ); } ready = qtrue; if ( numSpawningCast != numcast ) { ready = qfalse; } else if ( !( ent = AICast_FindEntityForName( "player" ) ) ) { ready = qfalse; } else if ( !ent->client || ent->client->pers.connected != CON_CONNECTED ) { ready = qfalse; } if ( ready ) { trap_Cvar_Set( "savegame_loading", "0" ); // in-case it aborts saveGamePending = qfalse; G_LoadGame( NULL ); // always load the "current" savegame // RF, spawn a thinker that will enable rendering after the client has had time to process the entities and setup the display //trap_Cvar_Set( "cg_norender", "0" ); ent = G_Spawn(); ent->nextthink = level.time + 200; ent->think = AICast_EnableRenderingThink; // wait for the clients to return from faded screen //trap_SetConfigstring( CS_SCREENFADE, va("0 %i 1500", level.time + 500) ); trap_SetConfigstring( CS_SCREENFADE, va( "0 %i 750", level.time + 500 ) ); level.reloadPauseTime = level.time + 1100; // make sure sound fades up trap_SendServerCommand( -1, va( "snd_fade 1 %d", 2000 ) ); //----(SA) added AICast_CastScriptThink(); } } else { ready = qtrue; if ( numSpawningCast != numcast ) { ready = qfalse; } else if ( !( ent = AICast_FindEntityForName( "player" ) ) ) { ready = qfalse; } else if ( !ent->client || ent->client->pers.connected != CON_CONNECTED ) { ready = qfalse; } // not loading a game, we must be in a new level, so look for some persistant data to read in, then save the game if ( ready ) { G_LoadPersistant(); // make sure we save the game after we have brought across the items trap_Cvar_Set( "g_totalPlayTime", "0" ); // reset play time trap_Cvar_Set( "g_attempts", "0" ); pcs = AICast_GetCastState( ent->s.number ); pcs->totalPlayTime = 0; pcs->lastLoadTime = 0; pcs->attempts = 0; // RF, disabled, since the pregame menu turns this off after the button is pressed, this isn't // required here // RF, spawn a thinker that will enable rendering after the client has had time to process the entities and setup the display //trap_Cvar_Set( "cg_norender", "0" ); //ent = G_Spawn(); //ent->nextthink = level.time + 200; //ent->think = AICast_EnableRenderingThink; saveGamePending = qfalse; // wait for the clients to return from faded screen // trap_SetConfigstring( CS_SCREENFADE, va("0 %i 1500", level.time + 500) ); // trap_SetConfigstring( CS_SCREENFADE, va("0 %i 750", level.time + 500) ); // (SA) send a command that will be interpreted for both the screenfade and any other effects (music cues, pregame menu, etc) // briefing menu will handle transition, just set a cvar for it to check for drawing the 'continue' button trap_SendServerCommand( -1, "rockandroll\n" ); level.reloadPauseTime = level.time + 1100; AICast_CastScriptThink(); } } }
/* =============== AICast_AdjustIdealYawForMover =============== */ void AICast_AdjustIdealYawForMover( int entnum, float yaw ) { cast_state_t *cs = AICast_GetCastState(entnum); // cs->bs->ideal_viewangles[YAW] += yaw; }
/* ============== AICast_NumAttempts ============== */ int AICast_NumAttempts( int entnum ) { cast_state_t *cs = AICast_GetCastState( entnum ); return ( cs->attempts ); }
/* =============== AICast_NoReload =============== */ int AICast_NoReload( int entnum ) { cast_state_t *cs = AICast_GetCastState(entnum); // return ((cs->aiFlags & AIFL_NO_RELOAD) != 0); }
void AICast_RegisterPain( int entnum ) { cast_state_t *cs = AICast_GetCastState( entnum ); if ( cs ) { cs->lastPain = level.time; } }