// Copyright (C) 1999-2000 Id Software, Inc. // // g_misc.c #include "g_local.h" #include "ai_main.h" //for the g2animents #define HOLOCRON_RESPAWN_TIME 30000 #define MAX_AMMO_GIVE 2 #define STATION_RECHARGE_TIME 3000//800 void HolocronThink(gentity_t *ent); extern vmCvar_t g_MaxHolocronCarry; /*QUAKED func_group (0 0 0) ? Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities. */ /*QUAKED info_camp (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay. */ void SP_info_camp( gentity_t *self ) { G_SetOrigin( self, self->s.origin ); } /*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay. */ void SP_info_null( gentity_t *self ) { G_FreeEntity( self ); } /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional target for in-game calculation, like jumppad targets. target_position does the same thing */ void SP_info_notnull( gentity_t *self ){ G_SetOrigin( self, self->s.origin ); } /*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear Non-displayed light. "light" overrides the default 300 intensity. Linear checbox gives linear falloff instead of inverse square Lights pointed at a target will be spotlights. "radius" overrides the default 64 unit radius of a spotlight at the target point. */ void SP_light( gentity_t *self ) { G_FreeEntity( self ); } /* ================================================================================= TELEPORTERS ================================================================================= */ void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) { gentity_t *tent; // use temp events at source and destination to prevent the effect // from getting dropped by a second player event if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); tent->s.clientNum = player->s.clientNum; tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN ); tent->s.clientNum = player->s.clientNum; } // unlink to make sure it can't possibly interfere with G_KillBox trap_UnlinkEntity (player); VectorCopy ( origin, player->client->ps.origin ); player->client->ps.origin[2] += 1; // spit the player out AngleVectors( angles, player->client->ps.velocity, NULL, NULL ); VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity ); player->client->ps.pm_time = 160; // hold time player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; // toggle the teleport bit so the client knows to not lerp player->client->ps.eFlags ^= EF_TELEPORT_BIT; // set angles SetClientViewAngle( player, angles ); // kill anything at the destination if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { G_KillBox (player); } // save results of pmove BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); // use the precise origin for linking VectorCopy( player->client->ps.origin, player->r.currentOrigin ); if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { trap_LinkEntity (player); } } /*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) Point teleporters at these. Now that we don't have teleport destination pads, this is just an info_notnull */ void SP_misc_teleporter_dest( gentity_t *ent ) { } //=========================================================== /*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) "model" arbitrary .md3 file to display */ void SP_misc_model( gentity_t *ent ) { #if 0 ent->s.modelindex = G_ModelIndex( ent->model ); VectorSet (ent->mins, -16, -16, -16); VectorSet (ent->maxs, 16, 16, 16); trap_LinkEntity (ent); G_SetOrigin( ent, ent->s.origin ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); #else G_FreeEntity( ent ); #endif } /*QUAKED misc_G2model (1 0 0) (-16 -16 -16) (16 16 16) "model" arbitrary .glm file to display */ void SP_misc_G2model( gentity_t *ent ) { #if 0 char name1[200] = "models/players/kyle/modelmp.glm"; trap_G2API_InitGhoul2Model(&ent->s, name1, G_ModelIndex( name1 ), 0, 0, 0, 0); trap_G2API_SetBoneAnim(ent->s.ghoul2, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, level.time, -1, -1); ent->s.radius = 150; // VectorSet (ent->mins, -16, -16, -16); // VectorSet (ent->maxs, 16, 16, 16); trap_LinkEntity (ent); G_SetOrigin( ent, ent->s.origin ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); #else G_FreeEntity( ent ); #endif } //=========================================================== void locateCamera( gentity_t *ent ) { vec3_t dir; gentity_t *target; gentity_t *owner; owner = G_PickTarget( ent->target ); if ( !owner ) { G_Printf( "Couldn't find target for misc_partal_surface\n" ); G_FreeEntity( ent ); return; } ent->r.ownerNum = owner->s.number; // frame holds the rotate speed if ( owner->spawnflags & 1 ) { ent->s.frame = 25; } else if ( owner->spawnflags & 2 ) { ent->s.frame = 75; } // swing camera ? if ( owner->spawnflags & 4 ) { // set to 0 for no rotation at all ent->s.powerups = 0; } else { ent->s.powerups = 1; } // clientNum holds the rotate offset ent->s.clientNum = owner->s.clientNum; VectorCopy( owner->s.origin, ent->s.origin2 ); // see if the portal_camera has a target target = G_PickTarget( owner->target ); if ( target ) { VectorSubtract( target->s.origin, owner->s.origin, dir ); VectorNormalize( dir ); } else { G_SetMovedir( owner->s.angles, dir ); } ent->s.eventParm = DirToByte( dir ); } /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8) The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted. This must be within 64 world units of the surface! */ void SP_misc_portal_surface(gentity_t *ent) { VectorClear( ent->r.mins ); VectorClear( ent->r.maxs ); trap_LinkEntity (ent); ent->r.svFlags = SVF_PORTAL; ent->s.eType = ET_PORTAL; if ( !ent->target ) { VectorCopy( ent->s.origin, ent->s.origin2 ); } else { ent->think = locateCamera; ent->nextthink = level.time + 100; } } /*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing The target for a misc_portal_director. You can set either angles or target another entity to determine the direction of view. "roll" an angle modifier to orient the camera around the target vector; */ void SP_misc_portal_camera(gentity_t *ent) { float roll; VectorClear( ent->r.mins ); VectorClear( ent->r.maxs ); trap_LinkEntity (ent); G_SpawnFloat( "roll", "0", &roll ); ent->s.clientNum = roll/360.0 * 256; } /*QUAKED misc_holocron (0 0 1) (-8 -8 -8) (8 8 8) count Set to type of holocron (based on force power value) HEAL = 0 JUMP = 1 SPEED = 2 PUSH = 3 PULL = 4 TELEPATHY = 5 GRIP = 6 LIGHTNING = 7 RAGE = 8 PROTECT = 9 ABSORB = 10 TEAM HEAL = 11 TEAM FORCE = 12 DRAIN = 13 SEE = 14 SABERATTACK = 15 SABERDEFEND = 16 SABERTHROW = 17 */ /*char *holocronTypeModels[] = { "models/chunks/rock/rock_big.md3",//FP_HEAL, "models/chunks/rock/rock_big.md3",//FP_LEVITATION, "models/chunks/rock/rock_big.md3",//FP_SPEED, "models/chunks/rock/rock_big.md3",//FP_PUSH, "models/chunks/rock/rock_big.md3",//FP_PULL, "models/chunks/rock/rock_big.md3",//FP_TELEPATHY, "models/chunks/rock/rock_big.md3",//FP_GRIP, "models/chunks/rock/rock_big.md3",//FP_LIGHTNING, "models/chunks/rock/rock_big.md3",//FP_RAGE, "models/chunks/rock/rock_big.md3",//FP_PROTECT, "models/chunks/rock/rock_big.md3",//FP_ABSORB, "models/chunks/rock/rock_big.md3",//FP_TEAM_HEAL, "models/chunks/rock/rock_big.md3",//FP_TEAM_FORCE, "models/chunks/rock/rock_big.md3",//FP_DRAIN, "models/chunks/rock/rock_big.md3",//FP_SEE "models/chunks/rock/rock_big.md3",//FP_SABERATTACK "models/chunks/rock/rock_big.md3",//FP_SABERDEFEND "models/chunks/rock/rock_big.md3"//FP_SABERTHROW };*/ void HolocronRespawn(gentity_t *self) { self->s.modelindex = (self->count - 128); } void HolocronPopOut(gentity_t *self) { if (Q_irand(1, 10) < 5) { self->s.pos.trDelta[0] = 150 + Q_irand(1, 100); } else { self->s.pos.trDelta[0] = -150 - Q_irand(1, 100); } if (Q_irand(1, 10) < 5) { self->s.pos.trDelta[1] = 150 + Q_irand(1, 100); } else { self->s.pos.trDelta[1] = -150 - Q_irand(1, 100); } self->s.pos.trDelta[2] = 150 + Q_irand(1, 100); } void HolocronTouch(gentity_t *self, gentity_t *other, trace_t *trace) { int i = 0; int othercarrying = 0; float time_lowest = 0; int index_lowest = -1; int hasall = 1; int forceReselect = WP_NONE; if (trace) { self->s.groundEntityNum = trace->entityNum; } if (!other || !other->client || other->health < 1) { return; } if (!self->s.modelindex) { return; } if (self->enemy) { return; } if (other->client->ps.holocronsCarried[self->count]) { return; } if (other->client->ps.holocronCantTouch == self->s.number && other->client->ps.holocronCantTouchTime > level.time) { return; } while (i < NUM_FORCE_POWERS) { if (other->client->ps.holocronsCarried[i]) { othercarrying++; if (index_lowest == -1 || other->client->ps.holocronsCarried[i] < time_lowest) { index_lowest = i; time_lowest = other->client->ps.holocronsCarried[i]; } } else if (i != self->count) { hasall = 0; } i++; } if (hasall) { //once we pick up this holocron we'll have all of them, so give us super special best prize! //G_Printf("You deserve a pat on the back.\n"); } if (!(other->client->ps.fd.forcePowersActive & (1 << other->client->ps.fd.forcePowerSelected))) { //If the player isn't using his currently selected force power, select this one if (self->count != FP_SABERATTACK && self->count != FP_SABERDEFEND && self->count != FP_SABERTHROW && self->count != FP_LEVITATION) { other->client->ps.fd.forcePowerSelected = self->count; } } if (g_MaxHolocronCarry.integer && othercarrying >= g_MaxHolocronCarry.integer) { //make the oldest holocron carried by the player pop out to make room for this one other->client->ps.holocronsCarried[index_lowest] = 0; /* if (index_lowest == FP_SABERATTACK && !HasSetSaberOnly()) { //you lost your saberattack holocron, so no more saber for you other->client->ps.stats[STAT_WEAPONS] |= (1 << WP_STUN_BATON); other->client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER); if (other->client->ps.weapon == WP_SABER) { forceReselect = WP_SABER; } } */ //NOTE: No longer valid as we are now always giving a force level 1 saber attack level in holocron } //G_Sound(other, CHAN_AUTO, G_SoundIndex("sound/weapons/w_pkup.wav")); G_AddEvent( other, EV_ITEM_PICKUP, self->s.number ); other->client->ps.holocronsCarried[self->count] = level.time; self->s.modelindex = 0; self->enemy = other; self->pos2[0] = 1; self->pos2[1] = level.time + HOLOCRON_RESPAWN_TIME; /* if (self->count == FP_SABERATTACK && !HasSetSaberOnly()) { //player gets a saber other->client->ps.stats[STAT_WEAPONS] |= (1 << WP_SABER); other->client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_STUN_BATON); if (other->client->ps.weapon == WP_STUN_BATON) { forceReselect = WP_STUN_BATON; } } */ if (forceReselect != WP_NONE) { G_AddEvent(other, EV_NOAMMO, forceReselect); } //G_Printf("DON'T TOUCH ME\n"); } void HolocronThink(gentity_t *ent) { if (ent->pos2[0] && (!ent->enemy || !ent->enemy->client || ent->enemy->health < 1)) { if (ent->enemy && ent->enemy->client) { HolocronRespawn(ent); VectorCopy(ent->enemy->client->ps.origin, ent->s.pos.trBase); VectorCopy(ent->enemy->client->ps.origin, ent->s.origin); VectorCopy(ent->enemy->client->ps.origin, ent->r.currentOrigin); //copy to person carrying's origin before popping out of them HolocronPopOut(ent); ent->enemy->client->ps.holocronsCarried[ent->count] = 0; ent->enemy = NULL; goto justthink; } } else if (ent->pos2[0] && ent->enemy && ent->enemy->client) { ent->pos2[1] = level.time + HOLOCRON_RESPAWN_TIME; } if (ent->enemy && ent->enemy->client) { if (!ent->enemy->client->ps.holocronsCarried[ent->count]) { ent->enemy->client->ps.holocronCantTouch = ent->s.number; ent->enemy->client->ps.holocronCantTouchTime = level.time + 5000; HolocronRespawn(ent); VectorCopy(ent->enemy->client->ps.origin, ent->s.pos.trBase); VectorCopy(ent->enemy->client->ps.origin, ent->s.origin); VectorCopy(ent->enemy->client->ps.origin, ent->r.currentOrigin); //copy to person carrying's origin before popping out of them HolocronPopOut(ent); ent->enemy = NULL; goto justthink; } if (!ent->enemy->inuse || (ent->enemy->client && ent->enemy->client->ps.fallingToDeath)) { if (ent->enemy->inuse && ent->enemy->client) { ent->enemy->client->ps.holocronBits &= ~(1 << ent->count); ent->enemy->client->ps.holocronsCarried[ent->count] = 0; } ent->enemy = NULL; HolocronRespawn(ent); VectorCopy(ent->s.origin2, ent->s.pos.trBase); VectorCopy(ent->s.origin2, ent->s.origin); VectorCopy(ent->s.origin2, ent->r.currentOrigin); ent->s.pos.trTime = level.time; ent->pos2[0] = 0; trap_LinkEntity(ent); goto justthink; } } if (ent->pos2[0] && ent->pos2[1] < level.time) { //isn't in original place and has been there for (HOLOCRON_RESPAWN_TIME) seconds without being picked up, so respawn VectorCopy(ent->s.origin2, ent->s.pos.trBase); VectorCopy(ent->s.origin2, ent->s.origin); VectorCopy(ent->s.origin2, ent->r.currentOrigin); ent->s.pos.trTime = level.time; ent->pos2[0] = 0; trap_LinkEntity(ent); } justthink: ent->nextthink = level.time + 50; if (ent->s.pos.trDelta[0] || ent->s.pos.trDelta[1] || ent->s.pos.trDelta[2]) { G_RunObject(ent); } } void SP_misc_holocron(gentity_t *ent) { vec3_t dest; trace_t tr; if (g_gametype.integer != GT_HOLOCRON) { G_FreeEntity(ent); return; } if (HasSetSaberOnly()) { if (ent->count == FP_SABERATTACK || ent->count == FP_SABERDEFEND || ent->count == FP_SABERTHROW) { //having saber holocrons in saber only mode is pointless G_FreeEntity(ent); return; } } ent->s.isJediMaster = qtrue; VectorSet( ent->r.maxs, 8, 8, 8 ); VectorSet( ent->r.mins, -8, -8, -8 ); ent->s.origin[2] += 0.1; ent->r.maxs[2] -= 0.1; VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 ); trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID ); if ( tr.startsolid ) { G_Printf ("SP_misc_holocron: misc_holocron startsolid at %s\n", vtos(ent->s.origin)); G_FreeEntity( ent ); return; } //add the 0.1 back after the trace ent->r.maxs[2] += 0.1; // allow to ride movers // ent->s.groundEntityNum = tr.entityNum; G_SetOrigin( ent, tr.endpos ); if (ent->count < 0) { ent->count = 0; } if (ent->count >= NUM_FORCE_POWERS) { ent->count = NUM_FORCE_POWERS-1; } /* if (g_forcePowerDisable.integer && (g_forcePowerDisable.integer & (1 << ent->count))) { G_FreeEntity(ent); return; } */ //No longer doing this, causing too many complaints about accidentally setting no force powers at all //and starting a holocron game (making it basically just FFA) ent->enemy = NULL; ent->s.eFlags = EF_BOUNCE_HALF; ent->s.modelindex = (ent->count - 128);//G_ModelIndex(holocronTypeModels[ent->count]); ent->s.eType = ET_HOLOCRON; ent->s.pos.trType = TR_GRAVITY; ent->s.pos.trTime = level.time; ent->r.contents = CONTENTS_TRIGGER; ent->clipmask = MASK_SOLID; ent->s.trickedentindex4 = ent->count; if (forcePowerDarkLight[ent->count] == FORCE_DARKSIDE) { ent->s.trickedentindex3 = 1; } else if (forcePowerDarkLight[ent->count] == FORCE_LIGHTSIDE) { ent->s.trickedentindex3 = 2; } else { ent->s.trickedentindex3 = 3; } ent->physicsObject = qtrue; VectorCopy(ent->s.pos.trBase, ent->s.origin2); //remember the spawn spot ent->touch = HolocronTouch; trap_LinkEntity(ent); ent->think = HolocronThink; ent->nextthink = level.time + 50; } /* ====================================================================== SHOOTERS ====================================================================== */ void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator ) { vec3_t dir; float deg; vec3_t up, right; // see if we have a target if ( ent->enemy ) { VectorSubtract( ent->enemy->r.currentOrigin, ent->s.origin, dir ); VectorNormalize( dir ); } else { VectorCopy( ent->movedir, dir ); } // randomize a bit PerpendicularVector( up, dir ); CrossProduct( up, dir, right ); deg = crandom() * ent->random; VectorMA( dir, deg, up, dir ); deg = crandom() * ent->random; VectorMA( dir, deg, right, dir ); VectorNormalize( dir ); switch ( ent->s.weapon ) { case WP_BLASTER: WP_FireBlasterMissile( ent, ent->s.origin, dir, qfalse ); break; } G_AddEvent( ent, EV_FIRE_WEAPON, 0 ); } static void InitShooter_Finish( gentity_t *ent ) { ent->enemy = G_PickTarget( ent->target ); ent->think = 0; ent->nextthink = 0; } void InitShooter( gentity_t *ent, int weapon ) { ent->use = Use_Shooter; ent->s.weapon = weapon; RegisterItem( BG_FindItemForWeapon( weapon ) ); G_SetMovedir( ent->s.angles, ent->movedir ); if ( !ent->random ) { ent->random = 1.0; } ent->random = sin( M_PI * ent->random / 180 ); // target might be a moving object, so we can't set movedir for it if ( ent->target ) { ent->think = InitShooter_Finish; ent->nextthink = level.time + 500; } trap_LinkEntity( ent ); } /*QUAKED shooter_blaster (1 0 0) (-16 -16 -16) (16 16 16) Fires at either the target or the current direction. "random" is the number of degrees of deviance from the taget. (1.0 default) */ void SP_shooter_blaster( gentity_t *ent ) { InitShooter( ent, WP_BLASTER); } void check_recharge(gentity_t *ent) { if (ent->fly_sound_debounce_time < level.time) { ent->s.loopSound = 0; } if (ent->count < ent->boltpoint4) { ent->count++; } ent->nextthink = level.time + ent->bolt_Head; } /* ================ EnergyShieldStationSettings ================ */ void EnergyShieldStationSettings(gentity_t *ent) { G_SpawnInt( "count", "0", &ent->count ); if (!ent->count) { ent->count = 50; } G_SpawnInt("chargerate", "0", &ent->bolt_Head); if (!ent->bolt_Head) { ent->bolt_Head = STATION_RECHARGE_TIME; } } /* ================ shield_power_converter_use ================ */ void shield_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator) { int dif,add; int stop = 1; if (!activator || !activator->client) { return; } if (self->setTime < level.time) { if (!self->s.loopSound) { self->s.loopSound = G_SoundIndex("sound/interface/shieldcon_run.wav"); } self->setTime = level.time + 100; dif = activator->client->ps.stats[STAT_MAX_HEALTH] - activator->client->ps.stats[STAT_ARMOR]; if (dif > 0) // Already at full armor? { if (dif >MAX_AMMO_GIVE) { add = MAX_AMMO_GIVE; } else { add = dif; } if (self->countcount; } self->count -= add; if (self->count <= 0) { self->setTime = 0; } stop = 0; self->fly_sound_debounce_time = level.time + 50; activator->client->ps.stats[STAT_ARMOR] += add; } } if (stop || self->count <= 0) { if (self->s.loopSound && self->setTime < level.time) { G_Sound(self, CHAN_AUTO, G_SoundIndex("sound/interface/shieldcon_done.mp3")); } self->s.loopSound = 0; if (self->setTime < level.time) { self->setTime = level.time + self->bolt_Head+100; } } } //QED comment is in bg_misc //------------------------------------------------------------ void SP_misc_shield_floor_unit( gentity_t *ent ) { vec3_t dest; trace_t tr; if (g_gametype.integer != GT_CTF && g_gametype.integer != GT_CTY) { G_FreeEntity( ent ); return; } VectorSet( ent->r.mins, -16, -16, 0 ); VectorSet( ent->r.maxs, 16, 16, 40 ); ent->s.origin[2] += 0.1; ent->r.maxs[2] -= 0.1; VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 ); trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID ); if ( tr.startsolid ) { G_Printf ("SP_misc_shield_floor_unit: misc_shield_floor_unit startsolid at %s\n", vtos(ent->s.origin)); G_FreeEntity( ent ); return; } //add the 0.1 back after the trace ent->r.maxs[2] += 0.1; // allow to ride movers ent->s.groundEntityNum = tr.entityNum; G_SetOrigin( ent, tr.endpos ); if (!ent->health) { ent->health = 60; } if (!ent->model || !ent->model[0]) { ent->model = "/models/items/a_shield_converter.md3"; } ent->s.modelindex = G_ModelIndex( ent->model ); ent->s.eFlags = 0; ent->r.svFlags |= SVF_PLAYER_USABLE; ent->r.contents = CONTENTS_SOLID; ent->clipmask = MASK_SOLID; EnergyShieldStationSettings(ent); ent->boltpoint4 = ent->count; //initial value ent->think = check_recharge; ent->nextthink = level.time + STATION_RECHARGE_TIME; ent->use = shield_power_converter_use; VectorCopy( ent->s.angles, ent->s.apos.trBase ); trap_LinkEntity (ent); G_SoundIndex("sound/interface/shieldcon_run.wav"); G_SoundIndex("sound/interface/shieldcon_done.mp3"); G_SoundIndex("sound/interface/shieldcon_empty.mp3"); } /*QUAKED misc_model_shield_power_converter (1 0 0) (-16 -16 -16) (16 16 16) #MODELNAME="models/items/psd_big.md3" Gives shield energy when used. "count" - the amount of ammo given when used (default 100) */ //------------------------------------------------------------ void SP_misc_model_shield_power_converter( gentity_t *ent ) { if (!ent->health) { ent->health = 60; } VectorSet (ent->r.mins, -16, -16, -16); VectorSet (ent->r.maxs, 16, 16, 16); ent->s.modelindex = G_ModelIndex( ent->model ); ent->s.eFlags = 0; ent->r.svFlags |= SVF_PLAYER_USABLE; ent->r.contents = CONTENTS_SOLID; ent->clipmask = MASK_SOLID; EnergyShieldStationSettings(ent); ent->boltpoint4 = ent->count; //initial value ent->think = check_recharge; ent->nextthink = level.time + STATION_RECHARGE_TIME; ent->use = shield_power_converter_use; G_SetOrigin( ent, ent->s.origin ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); trap_LinkEntity (ent); G_SoundIndex("sound/movers/objects/useshieldstation.wav"); ent->s.modelindex2 = G_ModelIndex("/models/items/psd_big.md3"); // Precache model } /* ================ EnergyAmmoShieldStationSettings ================ */ void EnergyAmmoStationSettings(gentity_t *ent) { G_SpawnInt( "count", "0", &ent->count ); if (!ent->count) { ent->count = 100; } } /* ================ ammo_power_converter_use ================ */ void ammo_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator) { int add,highest; qboolean overcharge; int difBlaster,difPowerCell,difMetalBolts; int stop = 1; if (!activator || !activator->client) { return; } if (self->setTime < level.time) { overcharge = qfalse; if (!self->s.loopSound) { self->s.loopSound = G_SoundIndex("sound/player/pickupshield.wav"); } self->setTime = level.time + 100; if (self->count) // Has it got any power left? { if (self->count > MAX_AMMO_GIVE) { add = MAX_AMMO_GIVE; } else if (self->count<0) { add = 0; } else { add = self->count; } activator->client->ps.ammo[AMMO_BLASTER] += add; activator->client->ps.ammo[AMMO_POWERCELL] += add; activator->client->ps.ammo[AMMO_METAL_BOLTS] += add; self->count -= add; stop = 0; self->fly_sound_debounce_time = level.time + 50; difBlaster = activator->client->ps.ammo[AMMO_BLASTER] - ammoData[AMMO_BLASTER].max; difPowerCell = activator->client->ps.ammo[AMMO_POWERCELL] - ammoData[AMMO_POWERCELL].max; difMetalBolts = activator->client->ps.ammo[AMMO_METAL_BOLTS] - ammoData[AMMO_METAL_BOLTS].max; // Find the highest one highest = difBlaster; if (difPowerCell>difBlaster) { highest = difPowerCell; } if (difMetalBolts > highest) { highest = difMetalBolts; } } } if (stop) { self->s.loopSound = 0; } } /*QUAKED misc_model_ammo_power_converter (1 0 0) (-16 -16 -16) (16 16 16) #MODELNAME="models/items/power_converter.md3" Gives ammo energy when used. "count" - the amount of ammo given when used (default 100) */ //------------------------------------------------------------ void SP_misc_model_ammo_power_converter( gentity_t *ent ) { if (!ent->health) { ent->health = 60; } VectorSet (ent->r.mins, -16, -16, -16); VectorSet (ent->r.maxs, 16, 16, 16); ent->s.modelindex = G_ModelIndex( ent->model ); ent->s.eFlags = 0; ent->r.svFlags |= SVF_PLAYER_USABLE; ent->r.contents = CONTENTS_SOLID; ent->clipmask = MASK_SOLID; ent->use = ammo_power_converter_use; EnergyAmmoStationSettings(ent); ent->boltpoint4 = ent->count; //initial value ent->think = check_recharge; ent->nextthink = level.time + STATION_RECHARGE_TIME; G_SetOrigin( ent, ent->s.origin ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); trap_LinkEntity (ent); G_SoundIndex("sound/movers/objects/useshieldstation.wav"); } /* ================ EnergyHealthStationSettings ================ */ void EnergyHealthStationSettings(gentity_t *ent) { G_SpawnInt( "count", "0", &ent->count ); if (!ent->count) { ent->count = 100; } } /* ================ health_power_converter_use ================ */ void health_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator) { int dif,add; int stop = 1; if (!activator || !activator->client) { return; } if (self->setTime < level.time) { if (!self->s.loopSound) { self->s.loopSound = G_SoundIndex("sound/player/pickuphealth.wav"); } self->setTime = level.time + 100; dif = activator->client->ps.stats[STAT_MAX_HEALTH] - activator->health; if (dif > 0) // Already at full armor? { if (dif >MAX_AMMO_GIVE) { add = MAX_AMMO_GIVE; } else { add = dif; } if (self->countcount; } self->count -= add; stop = 0; self->fly_sound_debounce_time = level.time + 50; activator->health += add; } } if (stop) { self->s.loopSound = 0; } } /*QUAKED misc_model_health_power_converter (1 0 0) (-16 -16 -16) (16 16 16) #MODELNAME="models/items/power_converter.md3" Gives ammo energy when used. "count" - the amount of ammo given when used (default 100) */ //------------------------------------------------------------ void SP_misc_model_health_power_converter( gentity_t *ent ) { if (!ent->health) { ent->health = 60; } VectorSet (ent->r.mins, -16, -16, -16); VectorSet (ent->r.maxs, 16, 16, 16); ent->s.modelindex = G_ModelIndex( ent->model ); ent->s.eFlags = 0; ent->r.svFlags |= SVF_PLAYER_USABLE; ent->r.contents = CONTENTS_SOLID; ent->clipmask = MASK_SOLID; ent->use = health_power_converter_use; EnergyHealthStationSettings(ent); ent->boltpoint4 = ent->count; //initial value ent->think = check_recharge; ent->nextthink = level.time + STATION_RECHARGE_TIME; G_SetOrigin( ent, ent->s.origin ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); trap_LinkEntity (ent); G_SoundIndex("sound/movers/objects/useshieldstation.wav"); } void DmgBoxHit( gentity_t *self, gentity_t *other, trace_t *trace ) { return; } void DmgBoxUpdateSelf(gentity_t *self) { gentity_t *owner = &g_entities[self->r.ownerNum]; if (!owner || !owner->client || !owner->inuse) { goto killMe; } if (self->damageRedirect == DAMAGEREDIRECT_HEAD && owner->client->damageBoxHandle_Head != self->s.number) { goto killMe; } if (self->damageRedirect == DAMAGEREDIRECT_RLEG && owner->client->damageBoxHandle_RLeg != self->s.number) { goto killMe; } if (self->damageRedirect == DAMAGEREDIRECT_LLEG && owner->client->damageBoxHandle_LLeg != self->s.number) { goto killMe; } if (owner->health < 1) { goto killMe; } //G_TestLine(self->r.currentOrigin, owner->client->ps.origin, 0x0000ff, 100); trap_LinkEntity(self); self->nextthink = level.time; return; killMe: self->think = G_FreeEntity; self->nextthink = level.time; } void DmgBoxAbsorb_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { self->health = 1; } void DmgBoxAbsorb_Pain(gentity_t *self, gentity_t *attacker, int damage) { self->health = 1; } gentity_t *CreateNewDamageBox( gentity_t *ent ) { gentity_t *dmgBox; //We do not want the client to have any real knowledge of the entity whatsoever. It will only //ever be used on the server. dmgBox = G_Spawn(); dmgBox->classname = "dmg_box"; dmgBox->r.svFlags = SVF_USE_CURRENT_ORIGIN; dmgBox->r.ownerNum = ent->s.number; dmgBox->clipmask = 0; dmgBox->r.contents = MASK_PLAYERSOLID; dmgBox->mass = 5000; dmgBox->s.eFlags |= EF_NODRAW; dmgBox->r.svFlags |= SVF_NOCLIENT; dmgBox->touch = DmgBoxHit; dmgBox->takedamage = qtrue; dmgBox->health = 1; dmgBox->pain = DmgBoxAbsorb_Pain; dmgBox->die = DmgBoxAbsorb_Die; dmgBox->think = DmgBoxUpdateSelf; dmgBox->nextthink = level.time + 50; return dmgBox; } void ATST_ManageDamageBoxes(gentity_t *ent) { vec3_t headOrg, lLegOrg, rLegOrg; vec3_t fwd, right, up, flatAngle; if (!ent->client->damageBoxHandle_Head) { gentity_t *dmgBox = CreateNewDamageBox(ent); if (dmgBox) { VectorSet( dmgBox->r.mins, ATST_MINS0, ATST_MINS1, ATST_MINS2 ); VectorSet( dmgBox->r.maxs, ATST_MAXS0, ATST_MAXS1, ATST_HEADSIZE ); ent->client->damageBoxHandle_Head = dmgBox->s.number; dmgBox->damageRedirect = DAMAGEREDIRECT_HEAD; dmgBox->damageRedirectTo = ent->s.number; } } if (!ent->client->damageBoxHandle_RLeg) { gentity_t *dmgBox = CreateNewDamageBox(ent); if (dmgBox) { VectorSet( dmgBox->r.mins, ATST_MINS0/4, ATST_MINS1/4, ATST_MINS2 ); VectorSet( dmgBox->r.maxs, ATST_MAXS0/4, ATST_MAXS1/4, ATST_MAXS2-ATST_HEADSIZE ); ent->client->damageBoxHandle_RLeg = dmgBox->s.number; dmgBox->damageRedirect = DAMAGEREDIRECT_RLEG; dmgBox->damageRedirectTo = ent->s.number; } } if (!ent->client->damageBoxHandle_LLeg) { gentity_t *dmgBox = CreateNewDamageBox(ent); if (dmgBox) { VectorSet( dmgBox->r.mins, ATST_MINS0/4, ATST_MINS1/4, ATST_MINS2 ); VectorSet( dmgBox->r.maxs, ATST_MAXS0/4, ATST_MAXS1/4, ATST_MAXS2-ATST_HEADSIZE ); ent->client->damageBoxHandle_LLeg = dmgBox->s.number; dmgBox->damageRedirect = DAMAGEREDIRECT_LLEG; dmgBox->damageRedirectTo = ent->s.number; } } if (!ent->client->damageBoxHandle_Head || !ent->client->damageBoxHandle_LLeg || !ent->client->damageBoxHandle_RLeg) { return; } VectorCopy(ent->client->ps.origin, headOrg); headOrg[2] += (ATST_MAXS2-ATST_HEADSIZE); VectorCopy(ent->client->ps.viewangles, flatAngle); flatAngle[PITCH] = 0; flatAngle[ROLL] = 0; AngleVectors(flatAngle, fwd, right, up); VectorCopy(ent->client->ps.origin, lLegOrg); VectorCopy(ent->client->ps.origin, rLegOrg); lLegOrg[0] -= right[0]*32; lLegOrg[1] -= right[1]*32; lLegOrg[2] -= right[2]*32; rLegOrg[0] += right[0]*32; rLegOrg[1] += right[1]*32; rLegOrg[2] += right[2]*32; G_SetOrigin(&g_entities[ent->client->damageBoxHandle_Head], headOrg); G_SetOrigin(&g_entities[ent->client->damageBoxHandle_LLeg], lLegOrg); G_SetOrigin(&g_entities[ent->client->damageBoxHandle_RLeg], rLegOrg); } int G_PlayerBecomeATST(gentity_t *ent) { if (!ent || !ent->client) { return 0; } if (ent->client->ps.weaponTime > 0) { return 0; } if (ent->client->ps.forceHandExtend != HANDEXTEND_NONE) { return 0; } if (ent->client->ps.zoomMode) { return 0; } if (ent->client->ps.usingATST) { ent->client->ps.usingATST = qfalse; ent->client->ps.forceHandExtend = HANDEXTEND_WEAPONREADY; } else { ent->client->ps.usingATST = qtrue; } ent->client->ps.weaponTime = 1000; return 1; } /*QUAKED fx_runner (0 0 1) (-8 -8 -8) (8 8 8) STARTOFF ONESHOT STARTOFF - effect starts off, toggles on/off when used ONESHOT - effect fires only when used "angles" - 3-float vector, angle the effect should play (unless fxTarget is supplied) "fxFile" - name of the effect file to play "fxTarget" - aim the effect toward this object, otherwise defaults to up "target" - uses its target when the fx gets triggered "delay" - how often to call the effect, don't over-do this ( default 400 ) note that it has to send an event each time it plays, so don't kill bandwidth or I will cry "random" - random amount of time to add to delay, ( default 0, 200 = 0ms to 200ms ) */ #define FX_RUNNER_RESERVED 0x800000 #define FX_ENT_RADIUS 8 //32 //---------------------------------------------------------- void fx_runner_think( gentity_t *ent ) { // call the effect with the desired position and orientation G_AddEvent( ent, EV_PLAY_EFFECT_ID, ent->bolt_Head ); ent->nextthink = level.time + ent->delay + random() * ent->random; if ( ent->target ) { // let our target know that we have spawned an effect G_UseTargets( ent, ent ); } } //---------------------------------------------------------- void fx_runner_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { if ( self->spawnflags & 2 ) // ONESHOT { // call the effect with the desired position and orientation, as a safety thing, // make sure we aren't thinking at all. fx_runner_think( self ); self->nextthink = -1; if ( self->target ) { // let our target know that we have spawned an effect G_UseTargets( self, self ); } } else { // ensure we are working with the right think function self->think = fx_runner_think; // toggle our state if ( self->nextthink == -1 ) { // NOTE: we fire the effect immediately on use, the fx_runner_think func will set // up the nextthink time. fx_runner_think( self ); } else { // turn off for now self->nextthink = -1; } } } //---------------------------------------------------------- void fx_runner_link( gentity_t *ent ) { vec3_t dir; if ( ent->roffname && ent->roffname[0] ) { // try to use the target to override the orientation gentity_t *target = NULL; target = G_Find( target, FOFS(targetname), ent->roffname ); if ( !target ) { // Bah, no good, dump a warning, but continue on and use the UP vector Com_Printf( "fx_runner_link: target specified but not found: %s\n", ent->roffname ); Com_Printf( " -assuming UP orientation.\n" ); } else { // Our target is valid so let's override the default UP vector VectorSubtract( target->s.origin, ent->s.origin, dir ); VectorNormalize( dir ); vectoangles( dir, ent->s.angles ); } } // don't really do anything with this right now other than do a check to warn the designers if the target is bogus if ( ent->target ) { gentity_t *target = NULL; target = G_Find( target, FOFS(targetname), ent->target ); if ( !target ) { // Target is bogus, but we can still continue Com_Printf( "fx_runner_link: target was specified but is not valid: %s\n", ent->target ); } } G_SetAngles( ent, ent->s.angles ); if ( ent->spawnflags & 1 || ent->spawnflags & 2 ) // STARTOFF || ONESHOT { // We won't even consider thinking until we are used ent->nextthink = -1; } else { // Let's get to work right now! ent->think = fx_runner_think; ent->nextthink = level.time + 100; // wait a small bit, then start working } } //---------------------------------------------------------- void SP_fx_runner( gentity_t *ent ) { char *fxFile; // Get our defaults G_SpawnInt( "delay", "400", &ent->delay ); G_SpawnFloat( "random", "0", &ent->random ); if (!ent->s.angles[0] && !ent->s.angles[1] && !ent->s.angles[2]) { // didn't have angles, so give us the default of up VectorSet( ent->s.angles, -90, 0, 0 ); } // make us useable if we can be targeted if ( ent->targetname ) { ent->use = fx_runner_use; } G_SpawnString( "fxFile", "", &fxFile ); G_SpawnString( "fxTarget", "", &ent->roffname ); if ( !fxFile || !fxFile[0] ) { Com_Printf( S_COLOR_RED"ERROR: fx_runner %s at %s has no fxFile specified\n", ent->targetname, vtos(ent->s.origin) ); G_FreeEntity( ent ); return; } // Try and associate an effect file, unfortunately we won't know if this worked or not // until the CGAME trys to register it... ent->bolt_Head = G_EffectIndex( fxFile ); //It is dirty, yes. But no one likes adding things to the entity structure. // Give us a bit of time to spawn in the other entities, since we may have to target one of 'em ent->think = fx_runner_link; ent->nextthink = level.time + 300; // Save our position and link us up! G_SetOrigin( ent, ent->s.origin ); VectorSet( ent->r.maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS ); VectorScale( ent->r.maxs, -1, ent->r.mins ); trap_LinkEntity( ent ); } //rww - here starts the main example g2animent stuff #define ANIMENT_TYPE_STORMTROOPER 0 #define ANIMENT_TYPE_RODIAN 1 #define ANIMENT_TYPE_JAN 2 #define ANIMENT_TYPE_CUSTOM 3 #define MAX_ANIMENTS 4 #define TROOPER_PAIN_SOUNDS 4 #define TROOPER_DEATH_SOUNDS 3 #define TROOPER_ALERT_SOUNDS 5 int gTrooperSound_Pain[TROOPER_PAIN_SOUNDS]; int gTrooperSound_Death[TROOPER_DEATH_SOUNDS]; int gTrooperSound_Alert[TROOPER_ALERT_SOUNDS]; #define RODIAN_PAIN_SOUNDS 4 #define RODIAN_DEATH_SOUNDS 3 #define RODIAN_ALERT_SOUNDS 5 int gRodianSound_Pain[RODIAN_PAIN_SOUNDS]; int gRodianSound_Death[RODIAN_DEATH_SOUNDS]; int gRodianSound_Alert[RODIAN_ALERT_SOUNDS]; #define JAN_PAIN_SOUNDS 4 #define JAN_DEATH_SOUNDS 3 #define JAN_ALERT_SOUNDS 5 int gJanSound_Pain[JAN_PAIN_SOUNDS]; int gJanSound_Death[JAN_DEATH_SOUNDS]; int gJanSound_Alert[JAN_ALERT_SOUNDS]; int G_PickDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc ); void AnimEntFireWeapon( gentity_t *ent, qboolean altFire ); int GetNearestVisibleWP(vec3_t org, int ignore); int InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles); extern float gBotEdit; #define ANIMENT_ALIGNED_UNKNOWN 0 #define ANIMENT_ALIGNED_BAD 1 #define ANIMENT_ALIGNED_GOOD 2 #define ANIMENT_CUSTOMSOUND_PAIN 0 #define ANIMENT_CUSTOMSOUND_DEATH 1 #define ANIMENT_CUSTOMSOUND_ALERT 2 int gAnimEntTypes = 0; typedef struct animentCustomInfo_s { int aeAlignment; int aeIndex; int aeWeapon; char *modelPath; char *soundPath; void *next; } animentCustomInfo_t; animentCustomInfo_t *animEntRoot = NULL; animentCustomInfo_t *ExampleAnimEntCustomData(gentity_t *self) { animentCustomInfo_t *iter = animEntRoot; int safetyCheck = 0; while (iter && safetyCheck < 30000) { if (iter->aeIndex == self->waterlevel) { return iter; } iter = iter->next; safetyCheck++; } return NULL; } animentCustomInfo_t *ExampleAnimEntCustomDataExists(gentity_t *self, int alignment, int weapon, char *modelname, char *soundpath) { animentCustomInfo_t *iter = animEntRoot; int safetyCheck = 0; while (iter && safetyCheck < 30000) { if (iter->aeAlignment == alignment && iter->aeWeapon == weapon && !Q_stricmp(iter->modelPath, modelname) && !Q_stricmp(iter->soundPath, soundpath)) { return iter; } iter = iter->next; safetyCheck++; } return NULL; } void ExampleAnimEntCustomDataEntry(gentity_t *self, int alignment, int weapon, char *modelname, char *soundpath) { animentCustomInfo_t *find = ExampleAnimEntCustomDataExists(self, alignment, weapon, modelname, soundpath); animentCustomInfo_t *lastValid = NULL; int safetyCheck = 0; if (find) { //data for this guy already exists. Set our waterlevel (aeIndex) to use this. self->waterlevel = find->aeIndex; return; } find = animEntRoot; while (find && safetyCheck < 30000) { //find the next null pointer lastValid = find; find = find->next; safetyCheck++; } if (!find) { find = BG_Alloc(sizeof(animentCustomInfo_t)); if (!find) { //careful not to exceed the BG_Alloc limit! return; } find->aeAlignment = alignment; self->waterlevel = gAnimEntTypes; find->aeIndex = self->waterlevel; find->aeWeapon = weapon; find->next = NULL; find->modelPath = BG_Alloc(strlen(modelname)+1); find->soundPath = BG_Alloc(strlen(soundpath)+1); if (!find->modelPath || !find->soundPath) { find->aeIndex = -1; return; } strcpy(find->modelPath, modelname); strcpy(find->soundPath, soundpath); find->modelPath[strlen(modelname)] = 0; find->soundPath[strlen(modelname)] = 0; if (lastValid) { lastValid->next = find; } if (!animEntRoot) { animEntRoot = find; } gAnimEntTypes++; } } void AnimEntCustomSoundPrecache(animentCustomInfo_t *aeInfo) { if (!aeInfo) { return; } G_SoundIndex(va("%s/pain25", aeInfo->soundPath)); G_SoundIndex(va("%s/pain50", aeInfo->soundPath)); G_SoundIndex(va("%s/pain75", aeInfo->soundPath)); G_SoundIndex(va("%s/pain100", aeInfo->soundPath)); G_SoundIndex(va("%s/death1", aeInfo->soundPath)); G_SoundIndex(va("%s/death2", aeInfo->soundPath)); G_SoundIndex(va("%s/death3", aeInfo->soundPath)); G_SoundIndex(va("%s/detected1", aeInfo->soundPath)); G_SoundIndex(va("%s/detected2", aeInfo->soundPath)); G_SoundIndex(va("%s/detected3", aeInfo->soundPath)); G_SoundIndex(va("%s/detected4", aeInfo->soundPath)); G_SoundIndex(va("%s/detected5", aeInfo->soundPath)); } void ExampleAnimEntCustomSound(gentity_t *self, int soundType) { animentCustomInfo_t *aeInfo = ExampleAnimEntCustomData(self); int customSounds[16]; int numSounds = 0; if (!aeInfo) { return; } if (soundType == ANIMENT_CUSTOMSOUND_PAIN) { customSounds[0] = G_SoundIndex(va("%s/pain25", aeInfo->soundPath)); customSounds[1] = G_SoundIndex(va("%s/pain50", aeInfo->soundPath)); customSounds[2] = G_SoundIndex(va("%s/pain75", aeInfo->soundPath)); customSounds[3] = G_SoundIndex(va("%s/pain100", aeInfo->soundPath)); numSounds = 4; } else if (soundType == ANIMENT_CUSTOMSOUND_DEATH) { customSounds[0] = G_SoundIndex(va("%s/death1", aeInfo->soundPath)); customSounds[1] = G_SoundIndex(va("%s/death2", aeInfo->soundPath)); customSounds[2] = G_SoundIndex(va("%s/death3", aeInfo->soundPath)); numSounds = 3; } else if (soundType == ANIMENT_CUSTOMSOUND_ALERT) { customSounds[0] = G_SoundIndex(va("%s/detected1", aeInfo->soundPath)); customSounds[1] = G_SoundIndex(va("%s/detected2", aeInfo->soundPath)); customSounds[2] = G_SoundIndex(va("%s/detected3", aeInfo->soundPath)); customSounds[3] = G_SoundIndex(va("%s/detected4", aeInfo->soundPath)); customSounds[4] = G_SoundIndex(va("%s/detected5", aeInfo->soundPath)); numSounds = 5; } if (!numSounds) { return; } G_Sound(self, CHAN_AUTO, customSounds[Q_irand(0, numSounds-1)]); } int ExampleAnimEntAlignment(gentity_t *self) { if (self->watertype == ANIMENT_TYPE_STORMTROOPER) { return ANIMENT_ALIGNED_BAD; } if (self->watertype == ANIMENT_TYPE_RODIAN) { return ANIMENT_ALIGNED_BAD; } if (self->watertype == ANIMENT_TYPE_JAN) { return ANIMENT_ALIGNED_GOOD; } if (self->watertype == ANIMENT_TYPE_CUSTOM) { animentCustomInfo_t *aeInfo = ExampleAnimEntCustomData(self); if (aeInfo) { return aeInfo->aeAlignment; } } return ANIMENT_ALIGNED_UNKNOWN; } void ExampleAnimEntAlertOthers(gentity_t *self) { //alert all the other animents in the area int i = 0; while (i < MAX_GENTITIES) { if (g_entities[i].inuse && g_entities[i].s.eType == ET_GRAPPLE && g_entities[i].health > 0) { if (g_entities[i].bolt_Motion == ENTITYNUM_NONE && trap_InPVS(self->r.currentOrigin, g_entities[i].r.currentOrigin) && ExampleAnimEntAlignment(self) == ExampleAnimEntAlignment(&g_entities[i])) { g_entities[i].bolt_Motion = self->bolt_Motion; g_entities[i].speed = level.time + 4000; //4 seconds til we forget about the enemy g_entities[i].bolt_RArm = level.time + Q_irand(500, 1000); } } i++; } } void ExampleAnimEnt_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { self->s.torsoAnim = G_PickDeathAnim(self, self->pos1, damage, mod, HL_NONE); if (self->s.torsoAnim <= 0 || self->s.torsoAnim >= MAX_TOTALANIMATIONS) { //?! (bad) self->s.torsoAnim = BOTH_DEATH1; } self->s.legsAnim = self->s.torsoAnim; self->health = 1; self->s.eFlags |= EF_DEAD; self->takedamage = qfalse; self->r.contents = 0; if (self->watertype == ANIMENT_TYPE_STORMTROOPER) { G_Sound(self, CHAN_AUTO, gTrooperSound_Death[Q_irand(0, TROOPER_DEATH_SOUNDS-1)]); } else if (self->watertype == ANIMENT_TYPE_RODIAN) { G_Sound(self, CHAN_AUTO, gRodianSound_Death[Q_irand(0, RODIAN_DEATH_SOUNDS-1)]); } else if (self->watertype == ANIMENT_TYPE_JAN) { G_Sound(self, CHAN_AUTO, gJanSound_Death[Q_irand(0, JAN_DEATH_SOUNDS-1)]); } else if (self->watertype == ANIMENT_TYPE_CUSTOM) { ExampleAnimEntCustomSound(self, ANIMENT_CUSTOMSOUND_DEATH); } if (mod == MOD_SABER) { //Set the velocity up a bit to make the limb fly up more than it otherwise would. vec3_t preDelta; VectorCopy(self->s.pos.trDelta, preDelta); if (Q_irand(1, 10) < 5) { self->s.pos.trDelta[0] += Q_irand(10, 40); } else { self->s.pos.trDelta[0] -= Q_irand(10, 40); } if (Q_irand(1, 10) < 5) { self->s.pos.trDelta[1] += Q_irand(10, 40); } else { self->s.pos.trDelta[1] -= Q_irand(10, 40); } self->s.pos.trDelta[2] += 100; G_CheckForDismemberment(self, self->pos1, damage, self->s.torsoAnim); VectorCopy(preDelta, self->s.pos.trDelta); } if (self->bolt_Motion == ENTITYNUM_NONE && (attacker->client || attacker->s.eType == ET_GRAPPLE)) { self->bolt_Motion = attacker->s.number; } if (self->bolt_Motion != ENTITYNUM_NONE) { ExampleAnimEntAlertOthers(self); } trap_LinkEntity(self); self->bolt_Head = level.time + 5000; } void ExampleAnimEnt_Pain(gentity_t *self, gentity_t *attacker, int damage) { int painAnim = (BOTH_PAIN1 + Q_irand(0, 3)); int animLen = (bgGlobalAnimations[painAnim].numFrames * fabs(bgGlobalAnimations[painAnim].frameLerp))-50; while (painAnim == self->s.torsoAnim) { painAnim = (BOTH_PAIN1 + Q_irand(0, 3)); } self->s.torsoAnim = painAnim; self->s.legsAnim = painAnim; self->bolt_LArm = level.time + animLen; if (self->s.torsoAnim <= 0 || self->s.torsoAnim >= MAX_TOTALANIMATIONS) { self->s.torsoAnim = self->s.legsAnim = BOTH_PAIN1; } if (self->watertype == ANIMENT_TYPE_STORMTROOPER) { G_Sound(self, CHAN_AUTO, gTrooperSound_Pain[Q_irand(0, TROOPER_PAIN_SOUNDS-1)]); } else if (self->watertype == ANIMENT_TYPE_RODIAN) { G_Sound(self, CHAN_AUTO, gRodianSound_Pain[Q_irand(0, RODIAN_PAIN_SOUNDS-1)]); } else if (self->watertype == ANIMENT_TYPE_JAN) { G_Sound(self, CHAN_AUTO, gJanSound_Pain[Q_irand(0, JAN_PAIN_SOUNDS-1)]); } else if (self->watertype == ANIMENT_TYPE_CUSTOM) { ExampleAnimEntCustomSound(self, ANIMENT_CUSTOMSOUND_PAIN); } if (attacker && (attacker->client || attacker->s.eType == ET_GRAPPLE) && self->bolt_Motion == ENTITYNUM_NONE) { if (attacker->s.number >= MAX_CLIENTS || (ExampleAnimEntAlignment(self) != ANIMENT_ALIGNED_GOOD && !(attacker->r.svFlags & SVF_BOT))) { self->bolt_Motion = attacker->s.number; self->speed = level.time + 4000; //4 seconds til we forget about the enemy ExampleAnimEntAlertOthers(self); self->bolt_RArm = level.time + Q_irand(500, 1000); } } } void ExampleAnimEntTouch(gentity_t *self, gentity_t *other, trace_t *trace) { } //We can use this method of movement without horrible choppiness, because //we are smoothing out the lerpOrigin on the client when rendering this eType. int ExampleAnimEntMove(gentity_t *self, vec3_t moveTo, float stepSize) { trace_t tr; vec3_t stepTo; vec3_t stepSub; vec3_t stepGoal; int didMove = 0; if (self->s.groundEntityNum == ENTITYNUM_NONE) { return 2; } VectorCopy(moveTo, stepTo); stepTo[2] = self->r.currentOrigin[2]; VectorSubtract(stepTo, self->r.currentOrigin, stepSub); if (VectorLength(stepSub) < 32) { return 2; } VectorNormalize(stepSub); stepGoal[0] = self->r.currentOrigin[0] + stepSub[0]*stepSize; stepGoal[1] = self->r.currentOrigin[1] + stepSub[1]*stepSize; stepGoal[2] = self->r.currentOrigin[2] + stepSub[2]*stepSize; trap_Trace(&tr, self->r.currentOrigin, self->r.mins, self->r.maxs, stepGoal, self->s.number, self->clipmask); if (!tr.startsolid && !tr.allsolid && tr.fraction) { vec3_t vecSub; VectorSubtract(self->r.currentOrigin, tr.endpos, vecSub); if (VectorLength(vecSub) > (stepSize/2)) { self->r.currentOrigin[0] = tr.endpos[0]; self->r.currentOrigin[1] = tr.endpos[1]; self->s.pos.trBase[0] = tr.endpos[0]; self->s.pos.trBase[1] = tr.endpos[1]; self->s.origin[0] = tr.endpos[0]; self->s.origin[1] = tr.endpos[1]; trap_LinkEntity(self); didMove = 1; } } if (didMove != 1) { //stair check vec3_t trFrom; vec3_t trTo; vec3_t trDir; vec3_t vecMeasure; VectorCopy(tr.endpos, trFrom); trFrom[2] += 16; VectorSubtract(/*tr.endpos*/stepGoal, self->r.currentOrigin, trDir); VectorNormalize(trDir); trTo[0] = tr.endpos[0] + trDir[0]*2; trTo[1] = tr.endpos[1] + trDir[1]*2; trTo[2] = tr.endpos[2] + trDir[2]*2; trTo[2] += 16; VectorSubtract(trFrom, trTo, vecMeasure); if (VectorLength(vecMeasure) > 1) { trap_Trace(&tr, trFrom, self->r.mins, self->r.maxs, trTo, self->s.number, self->clipmask); if (!tr.startsolid && !tr.allsolid && tr.fraction == 1) { //clear trace here, probably up a step vec3_t trDown; vec3_t trUp; VectorCopy(tr.endpos, trUp); VectorCopy(tr.endpos, trDown); trDown[2] -= 16; trap_Trace(&tr, trFrom, self->r.mins, self->r.maxs, trTo, self->s.number, self->clipmask); if (!tr.startsolid && !tr.allsolid) { //plop us down on the step after moving up VectorCopy(tr.endpos, self->r.currentOrigin); VectorCopy(tr.endpos, self->s.pos.trBase); VectorCopy(tr.endpos, self->s.origin); trap_LinkEntity(self); didMove = 1; } } } } return didMove; } float ExampleAnimEntYaw(gentity_t *self, float idealYaw, float yawSpeed) { float curYaw = 0; float diffYaw = 0; curYaw = AngleNormalize360(self->s.apos.trBase[YAW]); diffYaw = AngleSubtract( curYaw, idealYaw ); if ( fabs(diffYaw) > 0.25f ) { if ( fabs(diffYaw) > yawSpeed ) { // cap max speed curYaw += (diffYaw > 0.0f) ? -yawSpeed : yawSpeed; } else { // small enough curYaw -= diffYaw; } } return curYaw; } void ExampleAnimEntLook(gentity_t *self, vec3_t lookTo) { vec3_t lookSub; VectorSubtract(lookTo, self->r.currentOrigin, lookSub); VectorNormalize(lookSub); vectoangles(lookSub, lookSub); if (lookSub[PITCH] < -180) { lookSub[PITCH] -= 90; } //VectorCopy(lookSub, self->s.apos.trBase); self->s.apos.trBase[PITCH] = lookSub[PITCH]; self->s.apos.trBase[ROLL] = 0; self->s.apos.trBase[YAW] = ExampleAnimEntYaw(self, lookSub[YAW], 20); } qboolean ExampleAnimEntClearLOS(gentity_t *self, vec3_t point) { trace_t tr; trap_Trace(&tr, self->r.currentOrigin, 0, 0, point, self->s.number, self->clipmask); if (ExampleAnimEntAlignment(self) == ANIMENT_ALIGNED_GOOD) { if (tr.fraction == 1 || (g_entities[tr.entityNum].s.eType == ET_GRAPPLE && ExampleAnimEntAlignment(&g_entities[tr.entityNum]) != ANIMENT_ALIGNED_GOOD) || (self->bolt_Motion < MAX_CLIENTS && tr.entityNum == self->bolt_Motion)) { //clear LOS, or would be hitting a bad animent, so fire. return qtrue; } else if (g_entities[tr.entityNum].inuse && g_entities[tr.entityNum].client && (g_entities[tr.entityNum].r.svFlags & SVF_BOT)) { return qtrue; } } else { if (tr.fraction == 1 || tr.entityNum < MAX_CLIENTS || (g_entities[tr.entityNum].s.eType == ET_GRAPPLE && ExampleAnimEntAlignment(&g_entities[tr.entityNum]) != ANIMENT_ALIGNED_BAD)) { //clear LOS, or would be hitting a client (they're all bad!), so fire. return qtrue; } } return qfalse; } void ExampleAnimEntWeaponHandling(gentity_t *self) { if (self->bolt_RArm > level.time) { return; } if (self->boltpoint4) { if (self->s.weapon == WP_DISRUPTOR) { AnimEntFireWeapon(self, qtrue); G_AddEvent(self, EV_FIRE_WEAPON, 1); self->bolt_RArm = level.time + Q_irand(1500, 2500); } else { AnimEntFireWeapon(self, qfalse); G_AddEvent(self, EV_FIRE_WEAPON, 0); if (self->s.weapon == WP_REPEATER) { self->bolt_RArm = level.time + Q_irand(1, 500); } else if (ExampleAnimEntAlignment(self) == ANIMENT_ALIGNED_GOOD) { self->bolt_RArm = level.time + Q_irand(200, 400); } else { self->bolt_RArm = level.time + Q_irand(700, 1000); } } } } qboolean ExampleAnimEntWayValidCheck(gentity_t *self) { wpobject_t *currentWP; trace_t tr; if (self->bolt_Waist < 0 || self->bolt_Waist >= gWPNum) { return qfalse; } if (self->boltpoint1 < level.time) { return qfalse; } if (self->boltpoint2 < level.time) { return qfalse; } currentWP = gWPArray[self->bolt_Waist]; if (!currentWP) { return qfalse; } trap_Trace(&tr, self->r.currentOrigin, 0, 0, currentWP->origin, self->s.number, self->clipmask); if (tr.fraction == 1) { //allow one second for time you cannot see the point. If we go beyond that, kill the connection. self->boltpoint2 = level.time + 1000; } return qtrue; } //Simple nav routine utilizing bot path data //bolt_Waist represents our current indexed waypoint void ExampleAnimEntNavigation(gentity_t *self, vec3_t goalPos) { if (self->bolt_Waist == -1 || !ExampleAnimEntWayValidCheck(self)) { int wpIndex = GetNearestVisibleWP(self->r.currentOrigin, self->s.number); if (wpIndex >= 0 && wpIndex < gWPNum) { self->bolt_Waist = wpIndex; self->boltpoint1 = level.time + 10000; //10 seconds to get to the point self->boltpoint2 = level.time + 1000; //initialize the 1 second allowed visibility } else { self->bolt_Waist = -1; } } if (self->bolt_Waist != -1) { //we have a point to go to wpobject_t *currentWP = gWPArray[self->bolt_Waist]; if (currentWP) { vec3_t subLen; float vecLen = 0; VectorCopy(currentWP->origin, goalPos); VectorSubtract(self->r.currentOrigin, currentWP->origin, subLen); vecLen = VectorLength(subLen); if (vecLen <= 40) { int desiredIndex = -20; if (!self->boltpoint3) { desiredIndex = currentWP->index+1; } else { desiredIndex = currentWP->index-1; } if (desiredIndex != -20) { if (desiredIndex < 0) { self->boltpoint3 = 0; desiredIndex = currentWP->index+1; } if (desiredIndex >= gWPNum) { self->boltpoint3 = 1; desiredIndex = currentWP->index-1; } } if (desiredIndex != -1 && desiredIndex >= 0 && desiredIndex < gWPNum) { currentWP = gWPArray[desiredIndex]; if (currentWP) { self->bolt_Waist = desiredIndex; self->boltpoint1 = level.time + 10000; //every time we grab a new point, set the allowed travel-to time again VectorCopy(currentWP->origin, goalPos); } } } } } else { //We have no place to go. Run toward the origin mindlessly. VectorClear(goalPos); } } void ExampleAnimEntEnemyHandling(gentity_t *self, float enDist) { int i = 0; int bestIndex = -1; float minDist = enDist; if (ExampleAnimEntAlignment(self) == ANIMENT_ALIGNED_GOOD) { while (i < MAX_GENTITIES) { if (g_entities[i].inuse && (g_entities[i].s.eType == ET_GRAPPLE || (g_entities[i].client && (g_entities[i].r.svFlags & SVF_BOT))) && ExampleAnimEntAlignment(&g_entities[i]) != ANIMENT_ALIGNED_GOOD && g_entities[i].health > 0 && !(g_entities[i].s.eFlags & EF_DEAD)) { vec3_t checkLen; float fCheckLen; VectorSubtract(self->r.currentOrigin, g_entities[i].r.currentOrigin, checkLen); fCheckLen = VectorLength(checkLen); if (fCheckLen < (minDist - 128)) { vec3_t enAngles; VectorSubtract(g_entities[i].r.currentOrigin, self->r.currentOrigin, enAngles); vectoangles(enAngles, enAngles); if ((InFieldOfVision(self->s.apos.trBase, 120, enAngles) || self->s.genericenemyindex > level.time) && ExampleAnimEntClearLOS(self, g_entities[i].r.currentOrigin)) { minDist = fCheckLen; bestIndex = i; } } } i++; } } else { while (i < MAX_CLIENTS) { if (g_entities[i].inuse && g_entities[i].client && !(g_entities[i].r.svFlags & SVF_BOT) && g_entities[i].health > 0 && !(g_entities[i].s.eFlags & EF_DEAD) && g_entities[i].client->sess.sessionTeam != TEAM_SPECTATOR) { vec3_t checkLen; float fCheckLen; VectorSubtract(self->r.currentOrigin, g_entities[i].client->ps.origin, checkLen); fCheckLen = VectorLength(checkLen); if (fCheckLen < (minDist - 128)) { vec3_t enAngles; VectorSubtract(g_entities[i].client->ps.origin, self->r.currentOrigin, enAngles); vectoangles(enAngles, enAngles); if ((InFieldOfVision(self->s.apos.trBase, 120, enAngles) || self->s.genericenemyindex > level.time) && ExampleAnimEntClearLOS(self, g_entities[i].client->ps.origin)) { minDist = fCheckLen; bestIndex = i; } } } i++; } if (bestIndex == -1) { i = 0; while (i < MAX_GENTITIES) { if (g_entities[i].inuse && g_entities[i].s.eType == ET_GRAPPLE && ExampleAnimEntAlignment(&g_entities[i]) != ANIMENT_ALIGNED_BAD && g_entities[i].health > 0 && !(g_entities[i].s.eFlags & EF_DEAD)) { vec3_t checkLen; float fCheckLen; VectorSubtract(self->r.currentOrigin, g_entities[i].r.currentOrigin, checkLen); fCheckLen = VectorLength(checkLen); if (fCheckLen < (minDist - 128)) { vec3_t enAngles; VectorSubtract(g_entities[i].r.currentOrigin, self->r.currentOrigin, enAngles); vectoangles(enAngles, enAngles); if ((InFieldOfVision(self->s.apos.trBase, 120, enAngles) || self->s.genericenemyindex > level.time) && ExampleAnimEntClearLOS(self, g_entities[i].r.currentOrigin)) { minDist = fCheckLen; bestIndex = i; } } } i++; } } } if (bestIndex != -1) { self->bolt_Motion = bestIndex; enDist = minDist; self->speed = level.time + 4000; //4 seconds til we forget about the enemy ExampleAnimEntAlertOthers(self); self->bolt_RArm = level.time + Q_irand(500, 1000); if (self->watertype == ANIMENT_TYPE_STORMTROOPER) { G_Sound(self, CHAN_AUTO, gTrooperSound_Alert[Q_irand(0, TROOPER_ALERT_SOUNDS-1)]); } else if (self->watertype == ANIMENT_TYPE_RODIAN) { G_Sound(self, CHAN_AUTO, gRodianSound_Alert[Q_irand(0, RODIAN_ALERT_SOUNDS-1)]); } else if (self->watertype == ANIMENT_TYPE_JAN) { G_Sound(self, CHAN_AUTO, gJanSound_Alert[Q_irand(0, JAN_ALERT_SOUNDS-1)]); } else if (self->watertype == ANIMENT_TYPE_CUSTOM) { ExampleAnimEntCustomSound(self, ANIMENT_CUSTOMSOUND_ALERT); } } } void ExampleAnimEntUpdateSelf(gentity_t *self) { vec3_t preserveAngles; if (gBotEdit || !gWPNum) { if (!(self->s.eFlags & EF_DEAD)) { if (self->bolt_LArm < level.time) { self->s.torsoAnim = BOTH_ATTACK3; self->s.legsAnim = BOTH_STAND3; } } else { if (self->bolt_Head < level.time) { self->think = G_FreeEntity; self->nextthink = level.time; return; } } VectorCopy(self->s.apos.trBase, preserveAngles); G_RunObject(self); VectorCopy(preserveAngles, self->s.apos.trBase); return; } if (!(self->s.eFlags & EF_DEAD)) { if (self->bolt_LArm < level.time) { vec3_t goalPos; int didMove = 0; float enDist = 999999999; float runSpeed = 18; vec3_t enemyOrigin; qboolean hasEnemyLOS = qfalse; int originalEnemyIndex = self->bolt_Motion; if (self->bolt_Motion != ENTITYNUM_NONE && g_entities[self->bolt_Motion].inuse && g_entities[self->bolt_Motion].client) { if (g_entities[self->bolt_Motion].client->sess.sessionTeam == TEAM_SPECTATOR) { self->bolt_Motion = ENTITYNUM_NONE; } } if (self->bolt_Motion < MAX_CLIENTS && (!g_entities[self->bolt_Motion].inuse || !g_entities[self->bolt_Motion].client)) { self->bolt_Motion = ENTITYNUM_NONE; } if (self->bolt_Motion != ENTITYNUM_NONE && g_entities[self->bolt_Motion].inuse && (g_entities[self->bolt_Motion].client || g_entities[self->bolt_Motion].s.eType == ET_GRAPPLE)) { if (g_entities[self->bolt_Motion].health < 1 || (g_entities[self->bolt_Motion].s.eFlags & EF_DEAD)) { self->bolt_Motion = ENTITYNUM_NONE; } } if (gWPNum > 0) { if (self->bolt_Motion != ENTITYNUM_NONE && g_entities[self->bolt_Motion].inuse && g_entities[self->bolt_Motion].client) { vec3_t enSubVec; VectorSubtract(self->r.currentOrigin, g_entities[self->bolt_Motion].client->ps.origin, enSubVec); enDist = VectorLength(enSubVec); VectorCopy(g_entities[self->bolt_Motion].client->ps.origin, enemyOrigin); if (g_entities[self->bolt_Motion].client->pers.cmd.upmove < 0) { enemyOrigin[2] -= 8; } else { enemyOrigin[2] += 8; } hasEnemyLOS = ExampleAnimEntClearLOS(self, enemyOrigin); } else if (self->bolt_Motion != ENTITYNUM_NONE && g_entities[self->bolt_Motion].inuse && g_entities[self->bolt_Motion].s.eType == ET_GRAPPLE) { vec3_t enSubVec; VectorSubtract(self->r.currentOrigin, g_entities[self->bolt_Motion].r.currentOrigin, enSubVec); enDist = VectorLength(enSubVec); VectorCopy(g_entities[self->bolt_Motion].r.currentOrigin, enemyOrigin); hasEnemyLOS = ExampleAnimEntClearLOS(self, enemyOrigin); } if (hasEnemyLOS && enDist < 512 && self->splashRadius < level.time) { if (rand()%10 <= 8) { if (self->splashMethodOfDeath) { self->splashMethodOfDeath = 0; } else { self->splashMethodOfDeath = 1; } } if (self->watertype == ANIMENT_TYPE_RODIAN) { //these guys stand still more often because they are "snipers" if (rand()%10 <= 7) { self->splashMethodOfDeath = 1; } } self->splashRadius = level.time + Q_irand(2000, 5000); } if (hasEnemyLOS && (enDist < 512 || self->watertype == ANIMENT_TYPE_RODIAN) && self->splashMethodOfDeath) { VectorCopy(self->r.currentOrigin, goalPos); } else { ExampleAnimEntNavigation(self, goalPos); } } else { //No path data? Eh. Just run toward the origin mindlessly. VectorClear(goalPos); } if (self->bolt_Motion == ENTITYNUM_NONE) { if (ExampleAnimEntAlignment(self) == ANIMENT_ALIGNED_GOOD) { runSpeed = 18; } else { runSpeed = 6; } } didMove = ExampleAnimEntMove(self, goalPos, runSpeed); if (self->bolt_Motion != ENTITYNUM_NONE && g_entities[self->bolt_Motion].inuse && g_entities[self->bolt_Motion].client) { if (self->speed < level.time || g_entities[self->bolt_Motion].health < 1) { self->bolt_Motion = ENTITYNUM_NONE; } else { if (self->bolt_Motion != originalEnemyIndex) { vec3_t enSubVec; VectorSubtract(self->r.currentOrigin, g_entities[self->bolt_Motion].client->ps.origin, enSubVec); enDist = VectorLength(enSubVec); } } } else if (self->bolt_Motion != ENTITYNUM_NONE && g_entities[self->bolt_Motion].inuse && g_entities[self->bolt_Motion].s.eType == ET_GRAPPLE) { if (self->speed < level.time || g_entities[self->bolt_Motion].health < 1) { self->bolt_Motion = ENTITYNUM_NONE; } else { if (self->bolt_Motion != originalEnemyIndex) { vec3_t enSubVec; VectorSubtract(self->r.currentOrigin, g_entities[self->bolt_Motion].r.currentOrigin, enSubVec); enDist = VectorLength(enSubVec); } } } ExampleAnimEntEnemyHandling(self, enDist); if (self->bolt_Motion != ENTITYNUM_NONE && g_entities[self->bolt_Motion].inuse && (g_entities[self->bolt_Motion].client || g_entities[self->bolt_Motion].s.eType == ET_GRAPPLE)) { vec3_t enOrigin; if (g_entities[self->bolt_Motion].client) { VectorCopy(g_entities[self->bolt_Motion].client->ps.origin, enOrigin); } else { VectorCopy(g_entities[self->bolt_Motion].r.currentOrigin, enOrigin); } if (originalEnemyIndex != self->bolt_Motion) { VectorCopy(enOrigin, enemyOrigin); if (g_entities[self->bolt_Motion].client) { if (g_entities[self->bolt_Motion].client->pers.cmd.upmove < 0) { enemyOrigin[2] -= 8; } else { enemyOrigin[2] += 8; } } hasEnemyLOS = ExampleAnimEntClearLOS(self, enemyOrigin); } if (hasEnemyLOS) { vec3_t enAngles; vec3_t enAimOrg; vec3_t selfAimOrg; vec3_t myZeroPitchAngles; VectorCopy(enOrigin, enAimOrg); VectorCopy(self->r.currentOrigin, selfAimOrg); enAimOrg[2] = selfAimOrg[2]; VectorSubtract(enAimOrg, selfAimOrg, enAngles); vectoangles(enAngles, enAngles); VectorCopy(self->s.apos.trBase, myZeroPitchAngles); myZeroPitchAngles[PITCH] = 0; if (InFieldOfVision(myZeroPitchAngles, 50, enAngles)) { self->boltpoint4 = 1; } self->speed = level.time + 4000; //4 seconds til we forget about the enemy } else { self->boltpoint4 = 0; } ExampleAnimEntLook(self, enemyOrigin); } else { self->boltpoint4 = 0; ExampleAnimEntLook(self, goalPos); } if (!didMove) { //not just didMove 2, this means we're actually probably stuck vec3_t aFwd, aRight; vec3_t newGoalPos; AngleVectors(self->s.apos.trBase, aFwd, aRight, 0); newGoalPos[0] = self->r.currentOrigin[0] + aRight[0]*64 - aFwd[0]*64; newGoalPos[1] = self->r.currentOrigin[1] + aRight[1]*64 - aFwd[1]*64; newGoalPos[2] = self->r.currentOrigin[2] + aRight[2]*64 - aFwd[2]*64; //Try moving to the right of the direction we're looking, to get around stuff didMove = ExampleAnimEntMove(self, newGoalPos, 18); if (!didMove) { //still? Try to the left. newGoalPos[0] = self->r.currentOrigin[0] - aRight[0]*64 - aFwd[0]*64; newGoalPos[1] = self->r.currentOrigin[1] - aRight[1]*64 - aFwd[1]*64; newGoalPos[2] = self->r.currentOrigin[2] - aRight[2]*64 - aFwd[2]*64; didMove = ExampleAnimEntMove(self, newGoalPos, 18); } } if (didMove == 1) { if (self->bolt_Motion == ENTITYNUM_NONE && ExampleAnimEntAlignment(self) != ANIMENT_ALIGNED_GOOD) { //Good guys are always on "alert" self->s.torsoAnim = BOTH_WALK1; self->s.legsAnim = BOTH_WALK1; } else { self->s.torsoAnim = BOTH_ATTACK3; self->s.legsAnim = BOTH_RUN2; } } else { self->s.torsoAnim = BOTH_ATTACK3; self->s.legsAnim = BOTH_STAND3; } ExampleAnimEntWeaponHandling(self); } } else { if (self->bolt_Head < level.time) { self->think = G_FreeEntity; self->nextthink = level.time; return; } } VectorCopy(self->s.apos.trBase, preserveAngles); G_RunObject(self); VectorCopy(preserveAngles, self->s.apos.trBase); } void G_SpawnExampleAnimEnt(vec3_t pos, int aeType, animentCustomInfo_t *aeInfo) { gentity_t *animEnt; vec3_t playerMins; vec3_t playerMaxs; VectorSet(playerMins, -15, -15, DEFAULT_MINS_2); VectorSet(playerMaxs, 15, 15, DEFAULT_MAXS_2); if (aeType == ANIMENT_TYPE_STORMTROOPER) { if (!gTrooperSound_Pain[0]) { gTrooperSound_Pain[0] = G_SoundIndex("sound/chars/st1/misc/pain25"); gTrooperSound_Pain[1] = G_SoundIndex("sound/chars/st1/misc/pain50"); gTrooperSound_Pain[2] = G_SoundIndex("sound/chars/st1/misc/pain75"); gTrooperSound_Pain[3] = G_SoundIndex("sound/chars/st1/misc/pain100"); gTrooperSound_Death[0] = G_SoundIndex("sound/chars/st1/misc/death1"); gTrooperSound_Death[1] = G_SoundIndex("sound/chars/st1/misc/death2"); gTrooperSound_Death[2] = G_SoundIndex("sound/chars/st1/misc/death3"); gTrooperSound_Alert[0] = G_SoundIndex("sound/chars/st1/misc/detected1"); gTrooperSound_Alert[1] = G_SoundIndex("sound/chars/st1/misc/detected2"); gTrooperSound_Alert[2] = G_SoundIndex("sound/chars/st1/misc/detected3"); gTrooperSound_Alert[3] = G_SoundIndex("sound/chars/st1/misc/detected4"); gTrooperSound_Alert[4] = G_SoundIndex("sound/chars/st1/misc/detected5"); } } else if (aeType == ANIMENT_TYPE_RODIAN) { if (!gRodianSound_Pain[0]) { gRodianSound_Pain[0] = G_SoundIndex("sound/chars/rodian1/misc/pain25"); gRodianSound_Pain[1] = G_SoundIndex("sound/chars/rodian1/misc/pain50"); gRodianSound_Pain[2] = G_SoundIndex("sound/chars/rodian1/misc/pain75"); gRodianSound_Pain[3] = G_SoundIndex("sound/chars/rodian1/misc/pain100"); gRodianSound_Death[0] = G_SoundIndex("sound/chars/rodian1/misc/death1"); gRodianSound_Death[1] = G_SoundIndex("sound/chars/rodian1/misc/death2"); gRodianSound_Death[2] = G_SoundIndex("sound/chars/rodian1/misc/death3"); gRodianSound_Alert[0] = G_SoundIndex("sound/chars/rodian1/misc/detected1"); gRodianSound_Alert[1] = G_SoundIndex("sound/chars/rodian1/misc/detected2"); gRodianSound_Alert[2] = G_SoundIndex("sound/chars/rodian1/misc/detected3"); gRodianSound_Alert[3] = G_SoundIndex("sound/chars/rodian1/misc/detected4"); gRodianSound_Alert[4] = G_SoundIndex("sound/chars/rodian1/misc/detected5"); } } else if (aeType == ANIMENT_TYPE_JAN) { if (!gJanSound_Pain[0]) { gJanSound_Pain[0] = G_SoundIndex("sound/chars/jan/misc/pain25"); gJanSound_Pain[1] = G_SoundIndex("sound/chars/jan/misc/pain50"); gJanSound_Pain[2] = G_SoundIndex("sound/chars/jan/misc/pain75"); gJanSound_Pain[3] = G_SoundIndex("sound/chars/jan/misc/pain100"); gJanSound_Death[0] = G_SoundIndex("sound/chars/jan/misc/death1"); gJanSound_Death[1] = G_SoundIndex("sound/chars/jan/misc/death2"); gJanSound_Death[2] = G_SoundIndex("sound/chars/jan/misc/death3"); gJanSound_Alert[0] = G_SoundIndex("sound/chars/jan/misc/detected1"); gJanSound_Alert[1] = G_SoundIndex("sound/chars/jan/misc/detected2"); gJanSound_Alert[2] = G_SoundIndex("sound/chars/jan/misc/detected3"); gJanSound_Alert[3] = G_SoundIndex("sound/chars/jan/misc/detected4"); gJanSound_Alert[4] = G_SoundIndex("sound/chars/jan/misc/detected5"); } } animEnt = G_Spawn(); animEnt->watertype = aeType; //set the animent type if (aeType == ANIMENT_TYPE_CUSTOM && aeInfo) { ExampleAnimEntCustomDataEntry(animEnt, aeInfo->aeAlignment, aeInfo->aeWeapon, aeInfo->modelPath, aeInfo->soundPath); AnimEntCustomSoundPrecache(aeInfo); } animEnt->s.eType = ET_GRAPPLE; //ET_GRAPPLE is the reserved special type for G2 anim ents. if (animEnt->watertype == ANIMENT_TYPE_STORMTROOPER) { animEnt->s.modelindex = G_ModelIndex( "models/players/stormtrooper/model.glm" ); } else if (animEnt->watertype == ANIMENT_TYPE_RODIAN) { animEnt->s.modelindex = G_ModelIndex( "models/players/rodian/model.glm" ); } else if (animEnt->watertype == ANIMENT_TYPE_JAN) { animEnt->s.modelindex = G_ModelIndex( "models/players/jan/model.glm" ); } else if (animEnt->watertype == ANIMENT_TYPE_CUSTOM) { animentCustomInfo_t *aeInfo = ExampleAnimEntCustomData(animEnt); if (aeInfo) { animEnt->s.modelindex = G_ModelIndex(aeInfo->modelPath); } else { animEnt->s.modelindex = G_ModelIndex( "models/players/stormtrooper/model.glm" ); } } else { G_Error("Unknown AnimEnt type!\n"); } animEnt->s.g2radius = 100; if (animEnt->watertype == ANIMENT_TYPE_STORMTROOPER) { animEnt->s.weapon = WP_BLASTER; //This will tell the client to stick a blaster in the model's hands upon model init. } else if (animEnt->watertype == ANIMENT_TYPE_RODIAN) { animEnt->s.weapon = WP_DISRUPTOR; //These guys get disruptors instead of blasters. } else if (animEnt->watertype == ANIMENT_TYPE_JAN) { animEnt->s.weapon = WP_BLASTER; } else if (animEnt->watertype == ANIMENT_TYPE_CUSTOM) { animentCustomInfo_t *aeInfo = ExampleAnimEntCustomData(animEnt); if (aeInfo) { animEnt->s.weapon = aeInfo->aeWeapon; } else { animEnt->s.weapon = WP_BLASTER; } } animEnt->s.modelGhoul2 = 1; //Deal with it like any other ghoul2 ent, as far as killing instances. G_SetOrigin(animEnt, pos); animEnt->classname = "g2animent"; VectorCopy (playerMins, animEnt->r.mins); VectorCopy (playerMaxs, animEnt->r.maxs); animEnt->r.svFlags = SVF_USE_CURRENT_ORIGIN; animEnt->clipmask = MASK_PLAYERSOLID; animEnt->r.contents = MASK_PLAYERSOLID; animEnt->takedamage = qtrue; animEnt->health = 60; animEnt->s.owner = MAX_CLIENTS+1; animEnt->s.shouldtarget = qtrue; animEnt->s.teamowner = 0; trap_LinkEntity(animEnt); animEnt->pain = ExampleAnimEnt_Pain; animEnt->die = ExampleAnimEnt_Die; animEnt->touch = ExampleAnimEntTouch; animEnt->think = ExampleAnimEntUpdateSelf; animEnt->nextthink = level.time + 50; animEnt->s.torsoAnim = BOTH_ATTACK3; animEnt->s.legsAnim = BOTH_STAND3; //initialize the "AI" values animEnt->bolt_Waist = -1; //the waypoint index animEnt->bolt_Motion = ENTITYNUM_NONE; //the enemy index animEnt->splashMethodOfDeath = 0; //don't stand still while you have an enemy animEnt->splashRadius = 0; //timer for randomly deciding to stand still animEnt->boltpoint3 = 0; //running forward on the trail } qboolean gEscaping = qfalse; int gEscapeTime = 0; #ifdef ANIMENT_SPAWNER int AESpawner_CountAnimEnts(gentity_t *spawner, qboolean onlySameType) { int i = 0; int count = 0; while (i < MAX_GENTITIES) { if (g_entities[i].inuse && g_entities[i].s.eType == ET_GRAPPLE) { if (!onlySameType) { count++; } else { if (spawner->watertype == g_entities[i].watertype) { if (spawner->watertype == ANIMENT_TYPE_CUSTOM) { if (spawner->waterlevel == g_entities[i].waterlevel) { //only count it if it's the same custom type template, indicated by equal "waterlevel" value. count++; } } else { count++; } } } } i++; } return count; } qboolean AESpawner_NoClientInPVS(gentity_t *ent) { int i = 0; while (i < MAX_CLIENTS) { if (g_entities[i].inuse && g_entities[i].client && trap_InPVS(ent->s.origin, g_entities[i].client->ps.origin)) { return qfalse; } i++; } return qtrue; } qboolean AESpawner_PassAnimEntPVSCheck(gentity_t *ent) { int count = 0; int i = 0; if (!ent->bolt_LArm) { //unlimited return qtrue; } while (i < MAX_GENTITIES) { if (g_entities[i].inuse && g_entities[i].s.eType == ET_GRAPPLE && trap_InPVS(ent->r.currentOrigin, g_entities[i].r.currentOrigin)) { count++; } if (count >= ent->bolt_LArm) { //too many in this pvs.. return qfalse; } i++; } return qtrue; } void AESpawner_Think(gentity_t *ent) { int animEntCount; animentCustomInfo_t *aeInfo = NULL; if (gBotEdit) { ent->nextthink = level.time + 1000; return; } if (!ent->bolt_LLeg) { animEntCount = -1; } else { qboolean onlySameType = qfalse; if (ent->bolt_RLeg) { onlySameType = qtrue; } animEntCount = AESpawner_CountAnimEnts(ent, onlySameType); } if (animEntCount < ent->bolt_LLeg) { trace_t tr; vec3_t playerMins; vec3_t playerMaxs; VectorSet(playerMins, -15, -15, DEFAULT_MINS_2); VectorSet(playerMaxs, 15, 15, DEFAULT_MAXS_2); trap_Trace(&tr, ent->s.origin, playerMins, playerMaxs, ent->s.origin, ent->s.number, MASK_PLAYERSOLID); if (tr.fraction == 1) { if (ent->bolt_Head || AESpawner_NoClientInPVS(ent)) { if (AESpawner_PassAnimEntPVSCheck(ent)) { if (ent->watertype == ANIMENT_TYPE_CUSTOM) { aeInfo = ExampleAnimEntCustomData(ent); //we can get this info from the spawner, because it has its waterlevel set too. } G_SpawnExampleAnimEnt(ent->s.origin, ent->watertype, aeInfo); } } } } ent->nextthink = level.time + 1000; } void SP_misc_animent_spawner(gentity_t *ent) { if (g_gametype.integer != GT_SINGLE_PLAYER) { G_FreeEntity(ent); return; } G_SpawnInt( "spawninpvs", "0", &ent->bolt_Head ); //If this is non-0, the spawner will spawn even if a client is in the PVS G_SpawnInt( "othersinpvs", "3", &ent->bolt_LArm); //Don't spawn more than this many animents in the PVS of this spawner. //If 0, the amount is unlimited. G_SpawnInt( "totalspawn", "12", &ent->bolt_LLeg); //Only spawn if less than or equal to this many ents active globally. //0 is unlimited, but that could cause horrible disaster. G_SpawnInt( "spawntype", "0", &ent->watertype); //Spawn type. 0 is stormtrooper, 1 is rodian. G_SpawnInt( "sametype", "1", &ent->bolt_RLeg); //If 1, only counts other animates of the same type for deciding whether or not to spawn (as opposed to all types). //Default is 1. //Just precache the assets now if (ent->watertype == ANIMENT_TYPE_STORMTROOPER) { gTrooperSound_Pain[0] = G_SoundIndex("sound/chars/st1/misc/pain25"); gTrooperSound_Pain[1] = G_SoundIndex("sound/chars/st1/misc/pain50"); gTrooperSound_Pain[2] = G_SoundIndex("sound/chars/st1/misc/pain75"); gTrooperSound_Pain[3] = G_SoundIndex("sound/chars/st1/misc/pain100"); gTrooperSound_Death[0] = G_SoundIndex("sound/chars/st1/misc/death1"); gTrooperSound_Death[1] = G_SoundIndex("sound/chars/st1/misc/death2"); gTrooperSound_Death[2] = G_SoundIndex("sound/chars/st1/misc/death3"); gTrooperSound_Alert[0] = G_SoundIndex("sound/chars/st1/misc/detected1"); gTrooperSound_Alert[1] = G_SoundIndex("sound/chars/st1/misc/detected2"); gTrooperSound_Alert[2] = G_SoundIndex("sound/chars/st1/misc/detected3"); gTrooperSound_Alert[3] = G_SoundIndex("sound/chars/st1/misc/detected4"); gTrooperSound_Alert[4] = G_SoundIndex("sound/chars/st1/misc/detected5"); G_ModelIndex( "models/players/stormtrooper/model.glm" ); } else if (ent->watertype == ANIMENT_TYPE_RODIAN) { gRodianSound_Pain[0] = G_SoundIndex("sound/chars/rodian1/misc/pain25"); gRodianSound_Pain[1] = G_SoundIndex("sound/chars/rodian1/misc/pain50"); gRodianSound_Pain[2] = G_SoundIndex("sound/chars/rodian1/misc/pain75"); gRodianSound_Pain[3] = G_SoundIndex("sound/chars/rodian1/misc/pain100"); gRodianSound_Death[0] = G_SoundIndex("sound/chars/rodian1/misc/death1"); gRodianSound_Death[1] = G_SoundIndex("sound/chars/rodian1/misc/death2"); gRodianSound_Death[2] = G_SoundIndex("sound/chars/rodian1/misc/death3"); gRodianSound_Alert[0] = G_SoundIndex("sound/chars/rodian1/misc/detected1"); gRodianSound_Alert[1] = G_SoundIndex("sound/chars/rodian1/misc/detected2"); gRodianSound_Alert[2] = G_SoundIndex("sound/chars/rodian1/misc/detected3"); gRodianSound_Alert[3] = G_SoundIndex("sound/chars/rodian1/misc/detected4"); gRodianSound_Alert[4] = G_SoundIndex("sound/chars/rodian1/misc/detected5"); G_ModelIndex( "models/players/rodian/model.glm" ); } else if (ent->watertype == ANIMENT_TYPE_JAN) { gJanSound_Pain[0] = G_SoundIndex("sound/chars/jan/misc/pain25"); gJanSound_Pain[1] = G_SoundIndex("sound/chars/jan/misc/pain50"); gJanSound_Pain[2] = G_SoundIndex("sound/chars/jan/misc/pain75"); gJanSound_Pain[3] = G_SoundIndex("sound/chars/jan/misc/pain100"); gJanSound_Death[0] = G_SoundIndex("sound/chars/jan/misc/death1"); gJanSound_Death[1] = G_SoundIndex("sound/chars/jan/misc/death2"); gJanSound_Death[2] = G_SoundIndex("sound/chars/jan/misc/death3"); gJanSound_Alert[0] = G_SoundIndex("sound/chars/jan/misc/detected1"); gJanSound_Alert[1] = G_SoundIndex("sound/chars/jan/misc/detected2"); gJanSound_Alert[2] = G_SoundIndex("sound/chars/jan/misc/detected3"); gJanSound_Alert[3] = G_SoundIndex("sound/chars/jan/misc/detected4"); gJanSound_Alert[4] = G_SoundIndex("sound/chars/jan/misc/detected5"); G_ModelIndex( "models/players/jan/model.glm" ); } else if (ent->watertype == ANIMENT_TYPE_CUSTOM) { int alignment = 1; int weapon = 3; char *model; char *soundpath; animentCustomInfo_t *aeInfo; G_SpawnInt( "ae_aligned", "1", &alignment ); //Alignedment - 1 is bad, 2 is good. G_SpawnInt( "ae_weapon", "3", &weapon); //Weapon - Same values as normal weapons. G_SpawnString( "ae_model", "models/players/stormtrooper/model.glm", &model); //Model to use G_SpawnString( "ae_soundpath", "sound/chars/jan/misc", &soundpath); //Sound path to use ExampleAnimEntCustomDataEntry(ent, alignment, weapon, model, soundpath); aeInfo = ExampleAnimEntCustomData(ent); if (aeInfo) { AnimEntCustomSoundPrecache(aeInfo); G_ModelIndex( aeInfo->modelPath ); } } ent->think = AESpawner_Think; ent->nextthink = level.time + Q_irand(50, 500); trap_LinkEntity(ent); } void Use_Target_Screenshake( gentity_t *ent, gentity_t *other, gentity_t *activator ) { qboolean bGlobal = qfalse; if (ent->bolt_LArm) { bGlobal = qtrue; } G_ScreenShake(ent->s.origin, NULL, ent->speed, ent->bolt_Head, bGlobal); } void SP_target_screenshake(gentity_t *ent) { if (g_gametype.integer != GT_SINGLE_PLAYER) { G_FreeEntity(ent); return; } G_SpawnFloat( "intensity", "10", &ent->speed ); //intensity of the shake G_SpawnInt( "duration", "800", &ent->bolt_Head ); //duration of the shake G_SpawnInt( "globalshake", "1", &ent->bolt_LArm ); //non-0 if shake should be global (all clients). Otherwise, only in the PVS. ent->use = Use_Target_Screenshake; } void LogExit( const char *string ); void Use_Target_Escapetrig( gentity_t *ent, gentity_t *other, gentity_t *activator ) { if (!ent->bolt_LArm) { gEscaping = qtrue; gEscapeTime = level.time + ent->bolt_Head; } else if (gEscaping) { int i = 0; gEscaping = qfalse; while (i < MAX_CLIENTS) { //all of the survivors get 100 points! if (g_entities[i].inuse && g_entities[i].client && g_entities[i].health > 0 && g_entities[i].client->sess.sessionTeam != TEAM_SPECTATOR && !(g_entities[i].client->ps.pm_flags & PMF_FOLLOW)) { AddScore(&g_entities[i], g_entities[i].client->ps.origin, 100); } i++; } if (activator && activator->inuse && activator->client) { //the one who escaped gets 500 AddScore(activator, activator->client->ps.origin, 500); } LogExit("Escaped!"); } } void SP_target_escapetrig(gentity_t *ent) { if (g_gametype.integer != GT_SINGLE_PLAYER) { G_FreeEntity(ent); return; } G_SpawnInt( "escapetime", "60000", &ent->bolt_Head); //time given (in ms) for the escape G_SpawnInt( "escapegoal", "0", &ent->bolt_LArm); //if non-0, when used, will end an ongoing escape instead of start it ent->use = Use_Target_Escapetrig; } #endif void G_CreateExampleAnimEnt(gentity_t *ent) { vec3_t fwd, fwdPos; animentCustomInfo_t aeInfo; char arg[MAX_STRING_CHARS]; int iArg = 0; int argNum = trap_Argc(); memset(&aeInfo, 0, sizeof(aeInfo)); if (argNum > 1) { trap_Argv( 1, arg, sizeof( arg ) ); iArg = atoi(arg); if (iArg < 0) { iArg = 0; } if (iArg >= MAX_ANIMENTS) { iArg = MAX_ANIMENTS-1; } } AngleVectors(ent->client->ps.viewangles, fwd, 0, 0); fwdPos[0] = ent->client->ps.origin[0] + fwd[0]*128; fwdPos[1] = ent->client->ps.origin[1] + fwd[1]*128; fwdPos[2] = ent->client->ps.origin[2] + fwd[2]*128; if (iArg == ANIMENT_TYPE_CUSTOM) { char arg2[MAX_STRING_CHARS]; if (argNum > 2) { trap_Argv( 2, arg, sizeof( arg ) ); aeInfo.aeAlignment = atoi(arg); } else { aeInfo.aeAlignment = ANIMENT_ALIGNED_BAD; } if (argNum > 3) { trap_Argv( 3, arg, sizeof( arg ) ); aeInfo.aeWeapon = atoi(arg); } else { aeInfo.aeWeapon = WP_BRYAR_PISTOL; } if (argNum > 4) { trap_Argv( 4, arg, sizeof( arg ) ); aeInfo.modelPath = arg; } else { aeInfo.modelPath = "models/players/stormtrooper/model.glm"; } if (argNum > 5) { trap_Argv( 5, arg2, sizeof( arg2 ) ); aeInfo.soundPath = arg2; } else { aeInfo.soundPath = "sound/chars/jan/misc"; } } G_SpawnExampleAnimEnt(fwdPos, iArg, &aeInfo); } //rww - here ends the main example g2animent stuff