void FX_CreateLight(const FXSegment_t *segment, vec3_t origin) { FXLightSegment_t *light; fxLight_t *effect; if(!segment) { // NULL segment pointer? return; } if(!segment->SegmentData.FXLightSegment) { // No particle segment data? return; } if(segment->segmentType != EFXS_LIGHT) { // Not a particle? return; } light = segment->SegmentData.FXLightSegment; effect = (fxLight_t *)malloc(sizeof(fxLight_t)); FX_LightInit((fxPrimitive_t *)effect); effect->culldist = flrand(light->cullrange[0], light->cullrange[1]); effect->culldist *= effect->culldist; // allows for VLSquared effect->startTime = fx_time + Q_irand(light->delay[0], light->delay[1]); effect->endTime = effect->startTime + Q_irand(light->life[0], light->life[1]); VectorCopy(origin, effect->origin); if((segment->spawnflags & FXSFLAG_CHEAPORIGINCALC)) { vec3_t offset; vecrandom(light->origin[0], light->origin[1], offset); VectorAdd(effect->origin, offset, effect->origin); } vecrandom(light->rgb.start.sv[0], light->rgb.start.sv[1], effect->startRGB); if(light->rgb.flags != 0 || !(light->rgb.flags & FXTLF_CONSTANT)) { vecrandom(light->rgb.end.ev[0], light->rgb.end.ev[1], effect->endRGB); } else { VectorCopy(effect->startRGB, effect->endRGB); } //effect->RGBflags = light->rgb.flags; VectorCopy(effect->startRGB, effect->RGB); effect->startsize = effect->size = flrand(light->size.start.sf[0], light->size.start.sf[1]); if(light->size.flags != 0 || !(light->size.flags & FXTLF_CONSTANT)) { // TODO: make the distinction between wave, clamp, nonlinear, etc effect->endsize = flrand(light->size.end.ef[0], light->size.end.ef[1]); } else { effect->endsize = effect->startsize; } //effect->sizeFlags = light->size.flags; FX_AddPrimitive((fxPrimitive_t *)effect, effect->startTime, effect->endTime); }
qboolean NPC_FacePosition( vec3_t position, qboolean doPitch ) { vec3_t muzzle; vec3_t angles; float yawDelta; qboolean facing = qtrue; //Get the positions if ( NPC->client && (NPC->client->NPC_class == CLASS_RANCOR || NPC->client->NPC_class == CLASS_WAMPA) )// || NPC->client->NPC_class == CLASS_SAND_CREATURE) ) { CalcEntitySpot( NPC, SPOT_ORIGIN, muzzle ); muzzle[2] += NPC->r.maxs[2] * 0.75f; } else if ( NPC->client && NPC->client->NPC_class == CLASS_GALAKMECH ) { CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); } else { CalcEntitySpot( NPC, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD } //Find the desired angles GetAnglesForDirection( muzzle, position, angles ); NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); if ( NPC->enemy && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_ATST ) { // FIXME: this is kind of dumb, but it was the easiest way to get it to look sort of ok NPCInfo->desiredYaw += flrand( -5, 5 ) + sin( level.time * 0.004f ) * 7; NPCInfo->desiredPitch += flrand( -2, 2 ); } //Face that yaw NPC_UpdateAngles( qtrue, qtrue ); //Find the delta between our goal and our current facing yawDelta = AngleNormalize360( NPCInfo->desiredYaw - ( SHORT2ANGLE( ucmd.angles[YAW] + client->ps.delta_angles[YAW] ) ) ); //See if we are facing properly if ( fabs( yawDelta ) > VALID_ATTACK_CONE ) facing = qfalse; if ( doPitch ) { //Find the delta between our goal and our current facing float currentAngles = ( SHORT2ANGLE( ucmd.angles[PITCH] + client->ps.delta_angles[PITCH] ) ); float pitchDelta = NPCInfo->desiredPitch - currentAngles; //See if we are facing properly if ( fabs( pitchDelta ) > VALID_ATTACK_CONE ) facing = qfalse; } return facing; }
void NPC_AimWiggle( vec3_t enemy_org ) { //shoot for somewhere between the head and torso //NOTE: yes, I know this looks weird, but it works if ( NPCInfo->aimErrorDebounceTime < level.time ) { NPCInfo->aimOfs[0] = 0.3*flrand(NPC->enemy->r.mins[0], NPC->enemy->r.maxs[0]); NPCInfo->aimOfs[1] = 0.3*flrand(NPC->enemy->r.mins[1], NPC->enemy->r.maxs[1]); if ( NPC->enemy->r.maxs[2] > 0 ) { NPCInfo->aimOfs[2] = NPC->enemy->r.maxs[2]*flrand(0.0f, -1.0f); } } VectorAdd( enemy_org, NPCInfo->aimOfs, enemy_org ); }
//lightning strike trigger lightning strike event void Do_Strike(gentity_t *ent) { trace_t localTrace; vec3_t strikeFrom; vec3_t strikePoint; vec3_t fxAng; //maybe allow custom fx direction at some point? VectorSet(fxAng, 90.0f, 0.0f, 0.0f); //choose a random point to strike within the bounds of the trigger strikePoint[0] = flrand(ent->r.absmin[0], ent->r.absmax[0]); strikePoint[1] = flrand(ent->r.absmin[1], ent->r.absmax[1]); //consider the bottom mins the ground level strikePoint[2] = ent->r.absmin[2]; //set the from point strikeFrom[0] = strikePoint[0]; strikeFrom[1] = strikePoint[1]; strikeFrom[2] = ent->r.absmax[2]-4.0f; //now trace for damaging stuff, and do the effect trap->Trace(&localTrace, strikeFrom, NULL, NULL, strikePoint, ent->s.number, MASK_PLAYERSOLID, 0, 0, 0); VectorCopy(localTrace.endpos, strikePoint); if (localTrace.startsolid || localTrace.allsolid) { //got a bad spot, think again next frame to try another strike ent->nextthink = level.time; return; } if (ent->radius) { //do a radius damage at the end pos G_RadiusDamage(strikePoint, ent, ent->damage, ent->radius, ent, NULL, MOD_SUICIDE); } else { //only damage individuals gentity_t *trHit = &g_entities[localTrace.entityNum]; if (trHit->inuse && trHit->takedamage) { //damage it then G_Damage(trHit, ent, ent, NULL, trHit->r.currentOrigin, ent->damage, 0, MOD_SUICIDE); } } G_PlayEffectID(ent->genericValue2, strikeFrom, fxAng); }
/* =============== BG_CalculateBulletEndpoint Calculates the end point of a bullet based on the given inaccuracy and range =============== */ void BG_CalculateBulletEndpoint ( vec3_t muzzlePoint, vec3_t fireAngs, float inaccuracy, float range, vec3_t end, int *seed ) { float fGaussianX = 0; float fGaussianY = 0; vec3_t dir; vec3_t fwd; vec3_t up; vec3_t right; AngleVectors ( fireAngs, fwd, right, up ); // No inaccuracy so just extend it forward by the range if ( inaccuracy <= 0.0f ) { VectorMA (muzzlePoint, range, fwd, end); return; } // Gaussian spread should keep it a bit less random looking while ( 1 ) { float fGaussian; float f1; float f2; f1 = (float)flrand(0, 15000) / 15000.0f; f2 = (float)flrand(0, 15000) / 15000.0f; fGaussianX = (f1-0.5f) + (f2-0.5f); f1 = (float)flrand(0, 15000) / 15000.0f; f2 = (float)flrand(0, 15000) / 15000.0f; fGaussianY = (f1-0.5f) + (f2-0.5f); fGaussian = fGaussianX * fGaussianX + fGaussianY * fGaussianY; if ( fGaussian < 1 ) { break; } } VectorMA ( fwd, 0.05f * inaccuracy * fGaussianX, right, dir ); VectorMA ( dir, 0.05f * inaccuracy * fGaussianY, up, dir ); VectorMA (muzzlePoint, range, dir, end); }
void LayerNet::an1 ( TrainingSet *tptr , struct LearnParams *lptr ) { int itry, user_quit ; long seed ; double best_err ; char msg[80] ; LayerNet *worknet, *bestnet ; /* Allocate scratch memory */ MEMTEXT ( "AN1::learn new worknet, bestnet" ) ; worknet = new LayerNet ( model , outmod , outlin , nin , nhid1 , nhid2 , nout , 0 , 0 ) ; bestnet = new LayerNet ( model , outmod , outlin , nin , nhid1 , nhid2 , nout , 0 , 1 ) ; if ((worknet == NULL) || (! worknet->ok) || (bestnet == NULL) || (! bestnet->ok)) { memory_message ( "to learn" ) ; if (worknet != NULL) delete worknet ; if (bestnet != NULL) delete bestnet ; errtype = 0 ; return ; } best_err = 1.e30 ; for (itry=1 ; itry<=lptr->retries+1 ; itry++) { user_quit = anneal1 ( tptr , lptr , worknet , 1 , itry ) ; if (neterr < best_err) { best_err = neterr ; copy_weights ( bestnet , this ) ; } sprintf ( msg , "Try %d err=%lf best=%lf", itry, neterr, best_err ) ; normal_message ( msg ) ; if (user_quit || (neterr < lptr->quit_err)) break ; seed = flrand() - (long) (itry * 97) ; // Insure new seed for anneal sflrand ( seed ) ; zero_weights () ; // Retry random } copy_weights ( this , bestnet ) ; neterr = best_err ; MEMTEXT ( "AN1::learn delete worknet, bestnet" ) ; delete worknet ; delete bestnet ; return ; }
/* ------------------------- FX_BlasterWeaponHitPlayer ------------------------- */ void FX_BlasterWeaponHitPlayer( gentity_t *hit, vec3_t origin, vec3_t normal, qboolean humanoid ) { //temporary? just testing out the damage skin stuff -rww if ( hit && hit->client && hit->ghoul2.size() ) { CG_AddGhoul2Mark(cgs.media.bdecal_burnmark1, flrand(3.5, 4.0), origin, normal, hit->s.number, hit->client->ps.origin, hit->client->renderInfo.legsYaw, hit->ghoul2, hit->s.modelScale, Q_irand(10000, 13000)); } theFxScheduler.PlayEffect( cgs.effects.blasterFleshImpactEffect, origin, normal ); }
static void vecrandom(const vec3_t v1, const vec3_t v2, vec3_t result) { int i; if(!result) { return; } for(i = 0; i < 3; i++) { result[i] = flrand(v1[i], v2[i]); } }
void Sniper_UpdateEnemyPos( void ) { int index; int i; for ( i = MAX_ENEMY_POS_LAG - ENEMY_POS_LAG_INTERVAL; i >= 0; i -= ENEMY_POS_LAG_INTERVAL ) { index = i / ENEMY_POS_LAG_INTERVAL; if ( !index ) { CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, &NPCInfo->enemyLaggedPos[index] ); NPCInfo->enemyLaggedPos[index].z -= flrand( 2, 16 ); } else { VectorCopy( &NPCInfo->enemyLaggedPos[index - 1], &NPCInfo->enemyLaggedPos[index] ); } } }
void NPC_GetFlyToLeader( vec3_t moveDir ) { vec3_t dir, target, t_angs, t_dir; float dist, groundDist = G_GroundDistance( NPC ); const float minyaw = 35 /*sina(96/176)*/, maxyaw = 75 /*90-15*/; float speed, maxSpeed; const float speedD = 8; VectorSubtract( NPC->client->leader->r.currentOrigin, NPC->r.currentOrigin, dir ); dist = VectorLength( dir ); if ( dist > 96 && dist < 256 && groundDist > 96 ) { return; } GetAnglesForDirection( NPC->client->leader->r.currentOrigin, NPC->r.currentOrigin, t_angs ); t_angs[PITCH] = flrand( minyaw, maxyaw ); //FIXME? AngleVectors( t_angs, t_dir, NULL, NULL ); VectorMA( NPC->client->leader->r.currentOrigin, 176, t_dir, target ); VectorSubtract( target, NPC->r.currentOrigin, moveDir ); dist = VectorLength( moveDir ); VectorNormalize( moveDir ); maxSpeed = (dist>384)?NPCInfo->stats.runSpeed:NPCInfo->stats.walkSpeed; speed = NPCInfo->flySpeed + NPCInfo->stats.acceleration; speed *= sqrt(dist) / speedD; if ( speed > maxSpeed ) speed = maxSpeed; else if ( speed < 0 ) speed = 0; NPCInfo->flySpeed = DotProduct( moveDir, NPCInfo->lastFlyDir ) * speed; if ( NPCInfo->flySpeed < 0 ) NPCInfo->flySpeed = 0; if ( DotProduct( NPC->client->ps.velocity, dir ) > dist ) speed = -speed; VectorCopy( moveDir, NPCInfo->lastFlyDir ); VectorScale( moveDir, speed, moveDir ); }
/* ------------------------- Remote_Attack ------------------------- */ void Remote_Attack( void ) { float distance; qboolean visible; float idealDist; qboolean advance; qboolean retreat; if ( TIMER_Done(NPC,"spin") ) { TIMER_Set( NPC, "spin", Q_irand( 250, 1500 ) ); NPCInfo->desiredYaw += Q_irand( -200, 200 ); } // Always keep a good height off the ground Remote_MaintainHeight(); // If we don't have an enemy, just idle if ( NPC_CheckEnemyExt(qfalse) == qfalse ) { Remote_Idle(); return; } // Rate our distance to the target, and our visibilty distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); visible = NPC_ClearLOS4( NPC->enemy ); idealDist = MIN_DISTANCE_SQR+(MIN_DISTANCE_SQR*flrand( 0, 1 )); advance = (qboolean)(distance > idealDist*1.25); retreat = (qboolean)(distance < idealDist*0.75); // If we cannot see our target, move to see it if ( visible == qfalse ) { if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) { Remote_Hunt( visible, advance, retreat ); return; } } Remote_Ranged( visible, advance, retreat ); }
void NPC_GetFlyToGoal( vec3_t moveDir ) { #if 0 vec3_t dir, a, b; float dot, dist; float acceleration = NPCInfo->stats.acceleration, speed, maxSpeed = ((NPCInfo->scriptFlags&SCF_WALKING)?NPCInfo->stats.walkSpeed:NPCInfo->stats.runSpeed); if ( !NPCInfo->goalEntity ) return; VectorSubtract( NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir ); dist = VectorLengthSquared( dir ); if ( VectorLengthSquared( dir ) < 4096 ) { VectorClear( moveDir ); return; } VectorNormalize( dir ); acceleration *= 5.0f; maxSpeed *= 5.0f; dot = DotProduct( NPC->client->ps.velocity, dir ); speed = maxSpeed - dot; if ( speed > acceleration ) speed = acceleration; else if ( speed < -acceleration ) speed = -acceleration; VectorMA( NPC->r.currentOrigin, dist * 2.0f, dir, a ); VectorMA( NPC->r.currentOrigin, 0.5f, NPC->client->ps.velocity, b ); //FIXME? VectorSubtract( a, b, moveDir ); VectorNormalize( moveDir ); VectorScale( moveDir, acceleration, moveDir ); #else vec3_t vel, dir; float dist; float lMin, lMax; const float lMinD = -(0.015625f*0.015625f), lMaxD = 0.0234375f*0.0234375f; /*const */float speedD = 16; float speed, maxSpeed = ((ucmd.buttons&BUTTON_WALKING)?NPCInfo->stats.walkSpeed:NPCInfo->stats.runSpeed); if ( !NPCInfo->goalEntity ) { if ( VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) { VectorClear( moveDir ); } else { //on décélère speed = VectorLength( NPC->client->ps.velocity ); if ( speed > maxSpeed ) speed = maxSpeed; VectorNormalize2( NPC->client->ps.velocity, moveDir ); VectorScale( moveDir, -speed, moveDir ); } return; } VectorSubtract( NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, moveDir ); VectorNormalize2( moveDir, dir ); dist = VectorLength( moveDir ); lMax = sqrt(dist); lMin = lMax * lMinD; //-0.015625f lMax *= lMaxD; //0.0234375f if ( VectorLengthSquared( NPC->client->ps.velocity ) <= 1 ) { vel[0] = flrand(-1.0f,1.0f); vel[1] = flrand(-1.0f,1.0f); vel[2] = flrand(-1.0f,1.0f); } else { VectorCopy( NPC->client->ps.velocity, vel ); } #if 1 //du gro hack ki pu if ( (NPC->r.currentOrigin[2] - NPCInfo->goalEntity->r.currentOrigin[2]) > 0 && NPCInfo->goalEntity == NPC->client->leader ) { lMin = lMax = VectorLength( NPC->client->ps.velocity ); } if ( NPCInfo->behaviorState == BS_FOLLOW_LEADER ) { speedD = 8; } #endif VectorNormalize( vel ); VectorScale( vel, flrand( lMin, lMax ), vel ); VectorSubtract( moveDir, vel, moveDir ); VectorNormalize( moveDir ); speed = NPCInfo->flySpeed + NPCInfo->stats.acceleration; speed *= sqrt(dist) / speedD; if ( speed > maxSpeed ) speed = maxSpeed; else if ( speed < -maxSpeed ) speed = -maxSpeed; NPCInfo->flySpeed = DotProduct( moveDir, NPCInfo->lastFlyDir ) * speed; if ( NPCInfo->flySpeed < 0 ) NPCInfo->flySpeed = 0; if ( DotProduct( NPC->client->ps.velocity, dir ) > dist ) speed = -speed; VectorCopy( moveDir, NPCInfo->lastFlyDir ); VectorScale( moveDir, speed, moveDir ); #endif }
/* qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) Includes aim when determining angles - so they don't always hit... */ qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) { float error, diff; float decay; float targetPitch = 0; float targetYaw = 0; qboolean exact = qtrue; // if angle changes are locked; just keep the current angles if ( level.time < NPCInfo->aimTime ) { if(doPitch) targetPitch = NPCInfo->lockedDesiredPitch; if(doYaw) targetYaw = NPCInfo->lockedDesiredYaw; } else { if(doPitch) targetPitch = NPCInfo->desiredPitch; if(doYaw) targetYaw = NPCInfo->desiredYaw; // NPCInfo->aimTime = level.time + 250; if(doPitch) NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; if(doYaw) NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; } if ( NPCInfo->aimErrorDebounceTime < level.time ) { if ( Q_irand(0, 1 ) ) { NPCInfo->lastAimErrorYaw = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); } if ( Q_irand(0, 1 ) ) { NPCInfo->lastAimErrorPitch = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); } NPCInfo->aimErrorDebounceTime = level.time + Q_irand(250, 2000); } if(doYaw) { // decay yaw diff diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); if ( diff) { exact = qfalse; decay = 60.0 + 80.0; decay *= 50.0f / 1000.0f;//msec if ( diff < 0.0 ) { diff += decay; if ( diff > 0.0 ) { diff = 0.0; } } else { diff -= decay; if ( diff < 0.0 ) { diff = 0.0; } } } // add yaw error based on NPCInfo->aim value error = NPCInfo->lastAimErrorYaw; /* if(Q_irand(0, 1)) { error *= -1; } */ ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; } if(doPitch) { // decay pitch diff diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( diff) { exact = qfalse; decay = 60.0 + 80.0; decay *= 50.0f / 1000.0f;//msec if ( diff < 0.0 ) { diff += decay; if ( diff > 0.0 ) { diff = 0.0; } } else { diff -= decay; if ( diff < 0.0 ) { diff = 0.0; } } } error = NPCInfo->lastAimErrorPitch; ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; return exact; }
void asteroid_field_think(gentity_t *self) { int numAsteroids = asteroid_count_num_asteroids( self ); self->nextthink = level.time + 500; if ( numAsteroids < self->count ) { //need to spawn a new asteroid gentity_t *newAsteroid = G_Spawn(); if ( newAsteroid ) { vec3_t startSpot, endSpot, startAngles; float dist, speed = flrand( self->speed * 0.25f, self->speed * 2.0f ); int capAxis, axis, time = 0; gentity_t *copyAsteroid = asteroid_pick_random_asteroid( self ); if ( copyAsteroid ) { newAsteroid->model = copyAsteroid->model; newAsteroid->model2 = copyAsteroid->model2; newAsteroid->health = copyAsteroid->health; newAsteroid->spawnflags = copyAsteroid->spawnflags; newAsteroid->mass = copyAsteroid->mass; newAsteroid->damage = copyAsteroid->damage; newAsteroid->speed = copyAsteroid->speed; G_SetOrigin( newAsteroid, copyAsteroid->s.origin ); G_SetAngles( newAsteroid, copyAsteroid->s.angles ); newAsteroid->classname = "func_rotating"; SP_func_rotating( newAsteroid ); newAsteroid->genericValue15 = copyAsteroid->genericValue15; newAsteroid->s.iModelScale = copyAsteroid->s.iModelScale; newAsteroid->maxHealth = newAsteroid->health; G_ScaleNetHealth(newAsteroid); newAsteroid->radius = copyAsteroid->radius; newAsteroid->material = copyAsteroid->material; //CacheChunkEffects( self->material ); //keep track of it newAsteroid->r.ownerNum = self->s.number; //move it capAxis = Q_irand( 0, 2 ); for ( axis = 0; axis < 3; axis++ ) { if ( axis == capAxis ) { if ( Q_irand( 0, 1 ) ) { startSpot[axis] = self->r.mins[axis]; endSpot[axis] = self->r.maxs[axis]; } else { startSpot[axis] = self->r.maxs[axis]; endSpot[axis] = self->r.mins[axis]; } } else { startSpot[axis] = self->r.mins[axis]+(flrand(0,1.0f)*(self->r.maxs[axis]-self->r.mins[axis])); endSpot[axis] = self->r.mins[axis]+(flrand(0,1.0f)*(self->r.maxs[axis]-self->r.mins[axis])); } } //FIXME: maybe trace from start to end to make sure nothing is in the way? How big of a trace? G_SetOrigin( newAsteroid, startSpot ); dist = Distance( endSpot, startSpot ); time = ceil(dist/speed)*1000; Q3_Lerp2Origin( -1, newAsteroid->s.number, endSpot, time ); //spin it startAngles[0] = flrand( -360, 360 ); startAngles[1] = flrand( -360, 360 ); startAngles[2] = flrand( -360, 360 ); G_SetAngles( newAsteroid, startAngles ); newAsteroid->s.apos.trDelta[0] = flrand( -100, 100 ); newAsteroid->s.apos.trDelta[1] = flrand( -100, 100 ); newAsteroid->s.apos.trDelta[2] = flrand( -100, 100 ); newAsteroid->s.apos.trTime = level.time; newAsteroid->s.apos.trType = TR_LINEAR; //remove itself when done newAsteroid->think = G_FreeEntity; newAsteroid->nextthink = level.time+time; //think again sooner if need even more if ( numAsteroids+1 < self->count ) {//still need at least one more //spawn it in 100ms self->nextthink = level.time + 100; } } } } }
void LayerNet::ssg ( TrainingSet *tptr , // Training set to use struct LearnParams *lptr , // User's general learning parameters int use_grad // SS if zero, else SSG ) { int itry, user_quit, n, n_grad ; long seed ; double best_err, *work1, *work2, *grad, *avg_grad ; char msg[80] ; LayerNet *worknet1, *worknet2, *bestnet ; /* Allocate network scratch memory */ MEMTEXT ( "SSG::new 2 worknets, bestnet" ) ; worknet1 = new LayerNet ( model , outmod , outlin , nin , nhid1 , nhid2 , nout , 0 , 0 ) ; worknet2 = new LayerNet ( model , outmod , outlin , nin , nhid1 , nhid2 , nout , 0 , 0 ) ; bestnet = new LayerNet ( model , outmod , outlin , nin , nhid1 , nhid2 , nout , 0 , 1 ) ; if ((worknet1 == NULL) || (! worknet1->ok) || (worknet2 == NULL) || (! worknet2->ok) || (bestnet == NULL) || (! bestnet->ok)) { memory_message ( "to learn" ) ; if (worknet1 != NULL) delete worknet1 ; if (worknet2 != NULL) delete worknet2 ; if (bestnet != NULL) delete bestnet ; errtype = 0 ; return ; } /* Allocate gradient work memory. Work1 is used for hidden layer 2 deltas in REAL model, and output activation partial derivatives and deltas in all COMPLEX models. Work2 is output deltas in REAL model, error difference in COMPLEX models. */ if (use_grad) { if (nhid2) // Must be REAL model if this is true n = nhid2 ; else if (model == NETMOD_COMPLEX_INPUT) n = nhid1 ? nout * 2 + nhid1 * 2 : nout * 2 ; else if (model == NETMOD_COMPLEX_HIDDEN) n = nout * 4 + nhid1 * 4 ; else if (model == NETMOD_COMPLEX) n = nhid1 ? nout * 6 + nhid1 * 4 : nout * 4 ; else n = 0 ; if (n) { MEMTEXT ( "SSG::work1" ) ; work1 = (double *) MALLOC ( n * sizeof(double) ) ; if (work1 == NULL) { memory_message ( "to learn" ) ; delete worknet1 ; delete worknet2 ; delete bestnet ; errtype = 0 ; return ; } } else work1 = NULL ; if (nhid1 == 0) // No hidden layer n_grad = nout * nin_n ; else if (nhid2 == 0) // One hidden layer n_grad = nhid1 * nin_n + nout * nhid1_n ; else // Two hidden layers n_grad = nhid1 * nin_n + nhid2 * nhid1_n + nout * nhid2_n ; MEMTEXT ( "SSG::3 work vectors" ) ; work2 = (double *) MALLOC ( nout_n * sizeof(double) ) ; grad = (double *) MALLOC ( n_grad * sizeof(double) ) ; avg_grad = (double *) MALLOC ( n_grad * sizeof(double) ) ; if ((work2 == NULL) || (grad == NULL) || (avg_grad == NULL)) { if (work1 != NULL) FREE ( work1 ) ; if (work2 != NULL) FREE ( work2 ) ; if (grad != NULL) FREE ( grad ) ; if (avg_grad != NULL) FREE ( avg_grad ) ; memory_message ( "to learn" ) ; delete worknet1 ; delete worknet2 ; delete bestnet ; errtype = 0 ; return ; } } else work1 = work2 = grad = avg_grad = NULL ; best_err = 1.e30 ; for (itry=1 ; itry<=lptr->retries+1 ; itry++) { user_quit = ssg_core ( tptr , lptr , worknet1 , worknet2 , work1 , work2 , grad , avg_grad , n_grad ) ; if (neterr < best_err) { best_err = neterr ; copy_weights ( bestnet , this ) ; } sprintf ( msg , "Try %d err=%lf best=%lf", itry, neterr, best_err ) ; normal_message ( msg ) ; if (user_quit || (neterr < lptr->quit_err)) break ; seed = flrand() - (long) (itry * 97) ; // Insure new seed for anneal sflrand ( seed ) ; zero_weights () ; // Retry random } copy_weights ( this , bestnet ) ; neterr = best_err ; MEMTEXT ( "AN1::learn delete 2 worknets, bestnet" ) ; delete worknet1 ; delete worknet2 ; delete bestnet ; if (use_grad) { if (work1 != NULL) { MEMTEXT ( "SSG::work1" ) ; FREE ( work1 ) ; } MEMTEXT ( "SSG::3 work vectors" ) ; FREE ( work2 ) ; FREE ( grad ) ; FREE ( avg_grad) ; } return ; }
void Sniper_FaceEnemy( void ) { //FIXME: the ones behind kill holes are facing some arbitrary direction and not firing //FIXME: If actually trying to hit enemy, don't fire unless enemy is at least in front of me? //FIXME: need to give designers option to make them not miss first few shots if ( NPC->enemy ) { vec3_t muzzle, target, angles, forward, right, up; //Get the positions AngleVectors( NPC->client->ps.viewangles, forward, right, up ); CalcMuzzlePoint( NPC, forward, right, up, muzzle ); //CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); CalcEntitySpot( NPC->enemy, SPOT_ORIGIN, target ); if ( enemyDist2 > 65536 && NPCInfo->stats.aim < 5 )//is 256 squared, was 16384 (128*128) { if ( NPC->count < (5-NPCInfo->stats.aim) ) {//miss a few times first if ( shoot2 && TIMER_Done( NPC, "attackDelay" ) && level.time >= NPCInfo->shotTime ) {//ready to fire again qboolean aimError = qfalse; qboolean hit = qtrue; int tryMissCount = 0; trace_t trace; GetAnglesForDirection( muzzle, target, angles ); AngleVectors( angles, forward, right, up ); while ( hit && tryMissCount < 10 ) { tryMissCount++; if ( !Q_irand( 0, 1 ) ) { aimError = qtrue; if ( !Q_irand( 0, 1 ) ) { VectorMA( target, NPC->enemy->r.maxs[2]*flrand(1.5, 4), right, target ); } else { VectorMA( target, NPC->enemy->r.mins[2]*flrand(1.5, 4), right, target ); } } if ( !aimError || !Q_irand( 0, 1 ) ) { if ( !Q_irand( 0, 1 ) ) { VectorMA( target, NPC->enemy->r.maxs[2]*flrand(1.5, 4), up, target ); } else { VectorMA( target, NPC->enemy->r.mins[2]*flrand(1.5, 4), up, target ); } } trap_Trace( &trace, muzzle, vec3_origin, vec3_origin, target, NPC->s.number, MASK_SHOT ); hit = Sniper_EvaluateShot( trace.entityNum ); } NPC->count++; } else { if ( !enemyLOS2 ) { NPC_UpdateAngles( qtrue, qtrue ); return; } } } else {//based on distance, aim value, difficulty and enemy movement, miss //FIXME: incorporate distance as a factor? int missFactor = 8-(NPCInfo->stats.aim+g_spskill.integer) * 3; if ( missFactor > ENEMY_POS_LAG_STEPS ) { missFactor = ENEMY_POS_LAG_STEPS; } else if ( missFactor < 0 ) {//??? missFactor = 0 ; } VectorCopy( NPCInfo->enemyLaggedPos[missFactor], target ); } GetAnglesForDirection( muzzle, target, angles ); } else { target[2] += flrand( 0, NPC->enemy->r.maxs[2] ); //CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, target ); GetAnglesForDirection( muzzle, target, angles ); } NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); } NPC_UpdateAngles( qtrue, qtrue ); }
void Wampa_Slash( int boltIndex, qboolean backhand ) { int radiusEntNums[128]; int numEnts; const float radius = 88; const float radiusSquared = (radius*radius); int i; vec3_t boltOrg; int damage = (backhand)?Q_irand(10,15):Q_irand(20,30); numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, boltIndex, boltOrg ); for ( i = 0; i < numEnts; i++ ) { gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; if ( !radiusEnt->inuse ) { continue; } if ( radiusEnt == NPC ) {//Skip the wampa ent continue; } if ( radiusEnt->client == NULL ) {//must be a client continue; } if ( DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ) <= radiusSquared ) { //smack G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, damage, ((backhand)?DAMAGE_NO_ARMOR:(DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK)), MOD_MELEE ); if ( backhand ) { //actually push the enemy vec3_t pushDir; vec3_t angs; VectorCopy( NPC->client->ps.viewangles, angs ); angs[YAW] += flrand( 25, 50 ); angs[PITCH] = flrand( -25, -15 ); AngleVectors( angs, pushDir, NULL, NULL ); if ( radiusEnt->client->NPC_class != CLASS_WAMPA && radiusEnt->client->NPC_class != CLASS_RANCOR && radiusEnt->client->NPC_class != CLASS_ATST ) { G_Throw( radiusEnt, pushDir, 65 ); if ( BG_KnockDownable(&radiusEnt->client->ps) && radiusEnt->health > 0 && Q_irand( 0, 1 ) ) {//do pain on enemy radiusEnt->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; radiusEnt->client->ps.forceDodgeAnim = 0; radiusEnt->client->ps.forceHandExtendTime = level.time + 1100; radiusEnt->client->ps.quickerGetup = qfalse; } } } else if ( radiusEnt->health <= 0 && radiusEnt->client ) {//killed them, chance of dismembering if ( !Q_irand( 0, 1 ) ) {//bite something off int hitLoc = Q_irand( G2_MODELPART_HEAD, G2_MODELPART_RLEG ); if ( hitLoc == G2_MODELPART_HEAD ) { NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATH17, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } else if ( hitLoc == G2_MODELPART_WAIST ) { NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATHBACKWARD2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } G_Dismember( radiusEnt, NPC, radiusEnt->r.currentOrigin, hitLoc, 90, 0, radiusEnt->client->ps.torsoAnim, qtrue); } } else if ( !Q_irand( 0, 3 ) && radiusEnt->health > 0 ) {//one out of every 4 normal hits does a knockdown, too vec3_t pushDir; vec3_t angs; VectorCopy( NPC->client->ps.viewangles, angs ); angs[YAW] += flrand( 25, 50 ); angs[PITCH] = flrand( -25, -15 ); AngleVectors( angs, pushDir, NULL, NULL ); //[KnockdownSys] //ported multi-direction knockdowns from SP. G_Knockdown( radiusEnt, NPC, pushDir, 35, qtrue ); //G_Knockdown( radiusEnt ); //[/KnockdownSys] } G_Sound( radiusEnt, CHAN_WEAPON, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); } } }
void NPC_BSGM_Attack( void ) { //Don't do anything if we're hurt if ( NPCS.NPC->painDebounceTime > level.time ) { NPC_UpdateAngles( qtrue, qtrue ); return; } #if 0 //FIXME: if killed enemy, use victory anim if ( NPC->enemy && NPC->enemy->health <= 0 && !NPC->enemy->s.number ) {//my enemy is dead if ( NPC->client->ps.torsoAnim == BOTH_STAND2TO1 ) { if ( NPC->client->ps.torsoTimer <= 500 ) { G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsTimer += 500; NPC->client->ps.torsoTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1START ) { if ( NPC->client->ps.torsoTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STARTGESTURE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsTimer += 500; NPC->client->ps.torsoTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STARTGESTURE ) { if ( NPC->client->ps.torsoTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsTimer += 500; NPC->client->ps.torsoTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STOP ) { if ( NPC->client->ps.torsoTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsTimer = -1; NPC->client->ps.torsoTimer = -1; } } else if ( NPC->wait ) { if ( TIMER_Done( NPC, "gloatTime" ) ) { GM_StartGloat(); } else if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->r.currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared { NPCInfo->goalEntity = NPC->enemy; GM_Move(); } else {//got there GM_StartGloat(); } } NPC_FaceEnemy( qtrue ); NPC_UpdateAngles( qtrue, qtrue ); return; } #endif //If we don't have an enemy, just idle if ( NPC_CheckEnemyExt(qfalse) == qfalse || !NPCS.NPC->enemy ) { NPCS.NPC->enemy = NULL; NPC_BSGM_Patrol(); return; } enemyLOS4 = enemyCS4 = qfalse; move4 = qtrue; faceEnemy4 = qfalse; shoot4 = qfalse; hitAlly4 = qfalse; VectorClear( impactPos4 ); enemyDist4 = DistanceSquared( NPCS.NPC->r.currentOrigin, NPCS.NPC->enemy->r.currentOrigin ); //if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 || // NPC->client->ps.torsoAnim == BOTH_ATTACK5 ) if (0) { shoot4 = qfalse; if ( TIMER_Done( NPCS.NPC, "smackTime" ) && !NPCS.NPCInfo->blockedDebounceTime ) {//time to smack //recheck enemyDist4 and InFront if ( enemyDist4 < MELEE_DIST_SQUARED && InFront( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->r.currentOrigin, NPCS.NPC->client->ps.viewangles, 0.3f ) ) { vec3_t smackDir; VectorSubtract( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->r.currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); //hurt them G_Sound( NPCS.NPC->enemy, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/skewerhit.wav" ) ); G_Damage( NPCS.NPC->enemy, NPCS.NPC, NPCS.NPC, smackDir, NPCS.NPC->r.currentOrigin, (g_npcspskill.integer+1)*Q_irand( 5, 10), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); if ( NPCS.NPC->client->ps.torsoAnim == BOTH_ATTACK4 ) {//smackdown int knockAnim = BOTH_KNOCKDOWN1; if ( BG_CrouchAnim( NPCS.NPC->enemy->client->ps.legsAnim ) ) {//knockdown from crouch knockAnim = BOTH_KNOCKDOWN4; } //throw them smackDir[2] = 1; VectorNormalize( smackDir ); G_Throw( NPCS.NPC->enemy, smackDir, 50 ); NPC_SetAnim( NPCS.NPC->enemy, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } else {//uppercut //throw them G_Throw( NPCS.NPC->enemy, smackDir, 100 ); //make them backflip NPC_SetAnim( NPCS.NPC->enemy, SETANIM_BOTH, BOTH_KNOCKDOWN5, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } //done with the damage NPCS.NPCInfo->blockedDebounceTime = 1; } } } else if ( NPCS.NPC->lockCount ) //already shooting laser {//sometimes use the laser beam attack, but only after he's taken down our generator shoot4 = qfalse; if ( NPCS.NPC->lockCount == 1 ) {//charging up if ( TIMER_Done( NPCS.NPC, "beamDelay" ) ) {//time to start the beam int laserAnim; //if ( Q_irand( 0, 1 ) ) if (1) { laserAnim = BOTH_ATTACK2; } /* else { laserAnim = BOTH_ATTACK7; } */ NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, laserAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPCS.NPC, "attackDelay", NPCS.NPC->client->ps.torsoTimer + Q_irand( 1000, 3000 ) ); //turn on beam effect NPCS.NPC->lockCount = 2; G_PlayEffectID( G_EffectIndex("galak/trace_beam"), NPCS.NPC->r.currentOrigin, vec3_origin ); NPCS.NPC->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); if ( !NPCS.NPCInfo->coverTarg ) {//for moving looping sound at end of trace NPCS.NPCInfo->coverTarg = G_Spawn(); if ( NPCS.NPCInfo->coverTarg ) { G_SetOrigin( NPCS.NPCInfo->coverTarg, NPCS.NPC->client->renderInfo.muzzlePoint ); NPCS.NPCInfo->coverTarg->r.svFlags |= SVF_BROADCAST; NPCS.NPCInfo->coverTarg->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); } } } } else {//in the actual attack now if ( NPCS.NPC->client->ps.torsoTimer <= 0 ) {//attack done! NPCS.NPC->lockCount = 0; G_FreeEntity( NPCS.NPCInfo->coverTarg ); NPCS.NPC->s.loopSound = 0; #if 0 NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_DROPWEAP2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); #endif TIMER_Set( NPCS.NPC, "attackDelay", NPCS.NPC->client->ps.torsoTimer ); } else {//attack still going //do the trace and damage trace_t trace; vec3_t end, mins={-3,-3,-3}, maxs={3,3,3}; VectorMA( NPCS.NPC->client->renderInfo.muzzlePoint, 1024, NPCS.NPC->client->renderInfo.muzzleDir, end ); trap->Trace( &trace, NPCS.NPC->client->renderInfo.muzzlePoint, mins, maxs, end, NPCS.NPC->s.number, MASK_SHOT, qfalse, 0, 0 ); if ( trace.allsolid || trace.startsolid ) {//oops, in a wall if ( NPCS.NPCInfo->coverTarg ) { G_SetOrigin( NPCS.NPCInfo->coverTarg, NPCS.NPC->client->renderInfo.muzzlePoint ); } } else {//clear if ( trace.fraction < 1.0f ) {//hit something gentity_t *traceEnt = &g_entities[trace.entityNum]; if ( traceEnt && traceEnt->takedamage ) {//damage it G_SoundAtLoc( trace.endpos, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); G_Damage( traceEnt, NPCS.NPC, NPCS.NPC, NPCS.NPC->client->renderInfo.muzzleDir, trace.endpos, 10, 0, MOD_UNKNOWN ); } } if ( NPCS.NPCInfo->coverTarg ) { G_SetOrigin( NPCS.NPCInfo->coverTarg, trace.endpos ); } if ( !Q_irand( 0, 5 ) ) { G_SoundAtLoc( trace.endpos, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); } } } } } else {//Okay, we're not in a special attack, see if we should switch weapons or start a special attack /* if ( NPC->s.weapon == WP_REPEATER && !(NPCInfo->scriptFlags & SCF_ALT_FIRE)//using rapid-fire && NPC->enemy->s.weapon == WP_SABER //enemy using saber && NPC->client && (NPC->client->ps.saberEventFlags&SEF_DEFLECTED) && !Q_irand( 0, 50 ) ) {//he's deflecting my shots, switch to the laser or the lob fire for a while TIMER_Set( NPC, "noRapid", Q_irand( 2000, 6000 ) ); NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && (Q_irand( 0, 1 )||enemyDist4 < MAX_LOB_DIST_SQUARED) ) {//shield down, use laser NPC_GM_StartLaser(); } } else*/ if (// !NPC->client->ps.powerups[PW_GALAK_SHIELD] 1 //rwwFIXMEFIXME: just act like the shield is down til the effects and stuff are done && enemyDist4 < MELEE_DIST_SQUARED && InFront( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->r.currentOrigin, NPCS.NPC->client->ps.viewangles, 0.3f ) && NPCS.NPC->enemy->localAnimIndex <= 1 )//within 80 and in front {//our shield is down, and enemy within 80, if very close, use melee attack to slap away if ( TIMER_Done( NPCS.NPC, "attackDelay" ) ) { //animate me int swingAnim = BOTH_ATTACK1; #if 0 if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH ) {//generator down, use random melee swingAnim = Q_irand( BOTH_ATTACK4, BOTH_ATTACK5 );//smackdown or uppercut } else {//always knock-away swingAnim = BOTH_ATTACK5;//uppercut } #endif //FIXME: swing sound NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, swingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPCS.NPC, "attackDelay", NPCS.NPC->client->ps.torsoTimer + Q_irand( 1000, 3000 ) ); //delay the hurt until the proper point in the anim TIMER_Set( NPCS.NPC, "smackTime", 600 ); NPCS.NPCInfo->blockedDebounceTime = 0; //FIXME: say something? } } else if ( !NPCS.NPC->lockCount && NPCS.NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && TIMER_Done( NPCS.NPC, "attackDelay" ) && InFront( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->r.currentOrigin, NPCS.NPC->client->ps.viewangles, 0.3f ) && ((!Q_irand( 0, 10*(2-g_npcspskill.integer))&& enemyDist4 > MIN_LOB_DIST_SQUARED&& enemyDist4 < MAX_LOB_DIST_SQUARED) ||(!TIMER_Done( NPCS.NPC, "noLob" )&&!TIMER_Done( NPCS.NPC, "noRapid" ))) && NPCS.NPC->enemy->s.weapon != WP_TURRET ) {//sometimes use the laser beam attack, but only after he's taken down our generator shoot4 = qfalse; NPC_GM_StartLaser(); } else if ( enemyDist4 < MIN_LOB_DIST_SQUARED && (NPCS.NPC->enemy->s.weapon != WP_TURRET || Q_stricmp( "PAS", NPCS.NPC->enemy->classname )) && TIMER_Done( NPCS.NPC, "noRapid" ) )//256 {//enemy within 256 if ( (NPCS.NPC->client->ps.weapon == WP_REPEATER) && (NPCS.NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//shooting an explosive, but enemy too close, switch to primary fire NPCS.NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPCS.NPC->alt_fire = qfalse; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } else if ( (enemyDist4 > MAX_LOB_DIST_SQUARED || (NPCS.NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPCS.NPC->enemy->classname ))) && TIMER_Done( NPCS.NPC, "noLob" ) )//448 {//enemy more than 448 away and we are ready to try lob fire again if ( (NPCS.NPC->client->ps.weapon == WP_REPEATER) && !(NPCS.NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//enemy far enough away to use lobby explosives NPCS.NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPCS.NPC->alt_fire = qtrue; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } } //can we see our target? if ( NPC_ClearLOS4( NPCS.NPC->enemy ) ) { NPCS.NPCInfo->enemyLastSeenTime = level.time;//used here for aim debouncing, not always a clear LOS enemyLOS4 = qtrue; if ( NPCS.NPC->client->ps.weapon == WP_NONE ) { enemyCS4 = qfalse;//not true, but should stop us from firing NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon } else {//can we shoot our target? if ( ((NPCS.NPC->client->ps.weapon == WP_REPEATER && (NPCS.NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist4 < MIN_LOB_DIST_SQUARED )//256 { enemyCS4 = qfalse;//not true, but should stop us from firing hitAlly4 = qtrue;//us! //FIXME: if too close, run away! } else { int hit = NPC_ShotEntity( NPCS.NPC->enemy, impactPos4 ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPCS.NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPCS.NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS4 = qtrue; NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy VectorCopy( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPCInfo->enemyLastSeenLocation ); } else {//Hmm, have to get around this bastard NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPCS.NPC->client->playerTeam ) {//would hit an ally, don't fire!!! hitAlly4 = qtrue; } else {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire } } } } } else if ( trap->InPVS( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->r.currentOrigin ) ) { int hit; gentity_t *hitEnt; if ( TIMER_Done( NPCS.NPC, "talkDebounce" ) && !Q_irand( 0, 10 ) ) { if ( NPCS.NPCInfo->enemyCheckDebounceTime < 8 ) { int speech = -1; switch( NPCS.NPCInfo->enemyCheckDebounceTime ) { case 0: case 1: case 2: speech = EV_CHASE1 + NPCS.NPCInfo->enemyCheckDebounceTime; break; case 3: case 4: case 5: speech = EV_COVER1 + NPCS.NPCInfo->enemyCheckDebounceTime-3; break; case 6: case 7: speech = EV_ESCAPING1 + NPCS.NPCInfo->enemyCheckDebounceTime-6; break; } NPCS.NPCInfo->enemyCheckDebounceTime++; if ( speech != -1 ) { G_AddVoiceEvent( NPCS.NPC, speech, Q_irand( 3000, 5000 ) ); TIMER_Set( NPCS.NPC, "talkDebounce", Q_irand( 5000, 7000 ) ); } } } NPCS.NPCInfo->enemyLastSeenTime = level.time; hit = NPC_ShotEntity( NPCS.NPC->enemy, impactPos4 ); hitEnt = &g_entities[hit]; if ( hit == NPCS.NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPCS.NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS4 = qtrue; } else { faceEnemy4 = qtrue; NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy } } if ( enemyLOS4 ) { faceEnemy4 = qtrue; } else { if ( !NPCS.NPCInfo->goalEntity ) { NPCS.NPCInfo->goalEntity = NPCS.NPC->enemy; } if ( NPCS.NPCInfo->goalEntity == NPCS.NPC->enemy ) {//for now, always chase the enemy move4 = qtrue; } } if ( enemyCS4 ) { shoot4 = qtrue; //NPCInfo->enemyCheckDebounceTime = level.time;//actually used here as a last actual LOS } else { if ( !NPCS.NPCInfo->goalEntity ) { NPCS.NPCInfo->goalEntity = NPCS.NPC->enemy; } if ( NPCS.NPCInfo->goalEntity == NPCS.NPC->enemy ) {//for now, always chase the enemy move4 = qtrue; } } //Check for movement to take care of GM_CheckMoveState(); //See if we should override shooting decision with any special considerations GM_CheckFireState(); if ( NPCS.NPC->client->ps.weapon == WP_REPEATER && (NPCS.NPCInfo->scriptFlags&SCF_ALT_FIRE) && shoot4 && TIMER_Done( NPCS.NPC, "attackDelay" ) ) { vec3_t muzzle; vec3_t angles; vec3_t target; vec3_t velocity = {0,0,0}; vec3_t mins = {-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE}, maxs = {REPEATER_ALT_SIZE,REPEATER_ALT_SIZE,REPEATER_ALT_SIZE}; qboolean clearshot; CalcEntitySpot( NPCS.NPC, SPOT_WEAPON, muzzle ); VectorCopy( NPCS.NPC->enemy->r.currentOrigin, target ); target[0] += flrand( -5, 5 )+(Q_flrand(-1.0f, 1.0f)*(6-NPCS.NPCInfo->currentAim)*2); target[1] += flrand( -5, 5 )+(Q_flrand(-1.0f, 1.0f)*(6-NPCS.NPCInfo->currentAim)*2); target[2] += flrand( -5, 5 )+(Q_flrand(-1.0f, 1.0f)*(6-NPCS.NPCInfo->currentAim)*2); //Find the desired angles clearshot = WP_LobFire( NPCS.NPC, muzzle, target, mins, maxs, MASK_SHOT|CONTENTS_LIGHTSABER, velocity, qtrue, NPCS.NPC->s.number, NPCS.NPC->enemy->s.number, 300, 1100, 1500, qtrue ); if ( VectorCompare( vec3_origin, velocity ) || (!clearshot&&enemyLOS4&&enemyCS4) ) {//no clear lob shot and no lob shot that will hit something breakable if ( enemyLOS4 && enemyCS4 && TIMER_Done( NPCS.NPC, "noRapid" ) ) {//have a clear straight shot, so switch to primary NPCS.NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPCS.NPC->alt_fire = qfalse; NPC_ChangeWeapon( WP_REPEATER ); //keep this weap for a bit TIMER_Set( NPCS.NPC, "noLob", Q_irand( 500, 1000 ) ); } else { shoot4 = qfalse; } } else { vectoangles( velocity, angles ); NPCS.NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); NPCS.NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); VectorCopy( velocity, NPCS.NPC->client->hiddenDir ); NPCS.NPC->client->hiddenDist = VectorNormalize ( NPCS.NPC->client->hiddenDir ); } } else if ( faceEnemy4 ) {//face the enemy NPC_FaceEnemy( qtrue ); } if ( !TIMER_Done( NPCS.NPC, "standTime" ) ) { move4 = qfalse; } if ( !(NPCS.NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) {//not supposed to chase my enemies if ( NPCS.NPCInfo->goalEntity == NPCS.NPC->enemy ) {//goal is my entity, so don't move move4 = qfalse; } } if ( move4 && !NPCS.NPC->lockCount ) {//move toward goal if ( NPCS.NPCInfo->goalEntity /*&& NPC->client->ps.legsAnim != BOTH_ALERT1 && NPC->client->ps.legsAnim != BOTH_ATTACK2 && NPC->client->ps.legsAnim != BOTH_ATTACK4 && NPC->client->ps.legsAnim != BOTH_ATTACK5 && NPC->client->ps.legsAnim != BOTH_ATTACK7*/ ) { move4 = GM_Move(); } else { move4 = qfalse; } } if ( !TIMER_Done( NPCS.NPC, "flee" ) ) {//running away faceEnemy4 = qfalse; } //FIXME: check scf_face_move_dir here? if ( !faceEnemy4 ) {//we want to face in the dir we're running if ( !move4 ) {//if we haven't moved, we should look in the direction we last looked? VectorCopy( NPCS.NPC->client->ps.viewangles, NPCS.NPCInfo->lastPathAngles ); } if ( move4 ) {//don't run away and shoot NPCS.NPCInfo->desiredYaw = NPCS.NPCInfo->lastPathAngles[YAW]; NPCS.NPCInfo->desiredPitch = 0; shoot4 = qfalse; } } NPC_UpdateAngles( qtrue, qtrue ); if ( NPCS.NPCInfo->scriptFlags & SCF_DONT_FIRE ) { shoot4 = qfalse; } if ( NPCS.NPC->enemy && NPCS.NPC->enemy->enemy ) { if ( NPCS.NPC->enemy->s.weapon == WP_SABER && NPCS.NPC->enemy->enemy->s.weapon == WP_SABER ) {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) shoot4 = qfalse; } } //FIXME: don't shoot right away! if ( shoot4 ) {//try to shoot if it's time if ( TIMER_Done( NPCS.NPC, "attackDelay" ) ) { if( !(NPCS.NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here { WeaponThink( qtrue ); } } } //also: if ( NPCS.NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPCS.NPC->enemy->classname ) ) {//crush turrets if ( G_BoundsOverlap( NPCS.NPC->r.absmin, NPCS.NPC->r.absmax, NPCS.NPC->enemy->r.absmin, NPCS.NPC->enemy->r.absmax ) ) {//have to do this test because placed turrets are not solid to NPCs (so they don't obstruct navigation) //if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) if (0) { #ifdef BASE_COMPAT NPCS.NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; #endif G_Damage( NPCS.NPC->enemy, NPCS.NPC, NPCS.NPC, NULL, NPCS.NPC->r.currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN ); } else { G_Damage( NPCS.NPC->enemy, NPCS.NPC, NPCS.NPC, NULL, NPCS.NPC->r.currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); } } } else if ( NPCS.NPCInfo->touchedByPlayer != NULL && NPCS.NPCInfo->touchedByPlayer == NPCS.NPC->enemy ) {//touched enemy //if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) if (0) {//zap him! vec3_t smackDir; //animate me #if 0 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK6, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); #endif TIMER_Set( NPCS.NPC, "attackDelay", NPCS.NPC->client->ps.torsoTimer ); TIMER_Set( NPCS.NPC, "standTime", NPCS.NPC->client->ps.legsTimer ); //FIXME: debounce this? NPCS.NPCInfo->touchedByPlayer = NULL; //FIXME: some shield effect? #ifdef BASE_COMPAT NPCS.NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; #endif VectorSubtract( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->r.currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); G_Damage( NPCS.NPC->enemy, NPCS.NPC, NPCS.NPC, smackDir, NPCS.NPC->r.currentOrigin, (g_npcspskill.integer+1)*Q_irand( 5, 10), DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN ); //throw them G_Throw( NPCS.NPC->enemy, smackDir, 100 ); //NPC->enemy->s.powerups |= ( 1 << PW_SHOCKED ); if ( NPCS.NPC->enemy->client ) { // NPC->enemy->client->ps.powerups[PW_SHOCKED] = level.time + 1000; NPCS.NPC->enemy->client->ps.electrifyTime = level.time + 1000; } //stop any attacks NPCS.ucmd.buttons = 0; } } if ( NPCS.NPCInfo->movementSpeech < 3 && NPCS.NPCInfo->blockedSpeechDebounceTime <= level.time ) { if ( NPCS.NPC->enemy && NPCS.NPC->enemy->health > 0 && NPCS.NPC->enemy->painDebounceTime > level.time ) { if ( NPCS.NPC->enemy->health < 50 && NPCS.NPCInfo->movementSpeech == 2 ) { G_AddVoiceEvent( NPCS.NPC, EV_ANGER2, Q_irand( 2000, 4000 ) ); NPCS.NPCInfo->movementSpeech = 3; } else if ( NPCS.NPC->enemy->health < 75 && NPCS.NPCInfo->movementSpeech == 1 ) { G_AddVoiceEvent( NPCS.NPC, EV_ANGER1, Q_irand( 2000, 4000 ) ); NPCS.NPCInfo->movementSpeech = 2; } else if ( NPCS.NPC->enemy->health < 100 && NPCS.NPCInfo->movementSpeech == 0 ) { G_AddVoiceEvent( NPCS.NPC, EV_ANGER3, Q_irand( 2000, 4000 ) ); NPCS.NPCInfo->movementSpeech = 1; } } } }
void LayerNet::anx_dd ( TrainingSet *tptr , struct LearnParams *lptr ) { int itry, n_escape, n_retry, bad_count, new_record, refined ; long seed ; double err, prev_err, best_err, start_of_loop_error, best_inner_error ; double initial_accuracy, final_accuracy ; char msg[80] ; LayerNet *worknet, *worknet2, *bestnet ; n_escape = n_retry = 0 ; /* Allocate scratch memory */ MEMTEXT ( "ANX_DD::learn new worknet, bestnet" ) ; worknet = new LayerNet ( model , outmod , outlin , nin , nhid1 , nhid2 , nout , 0 , 0 ) ; bestnet = new LayerNet ( model , outmod , outlin , nin , nhid1 , nhid2 , nout , 0 , 1 ) ; if ((worknet == NULL) || (! worknet->ok) || (bestnet == NULL) || (! bestnet->ok)) { memory_message ( "to learn" ) ; if (worknet != NULL) delete worknet ; if (bestnet != NULL) delete bestnet ; errtype = 0 ; return ; } if ((lptr->method == METHOD_AN2_CJ) || (lptr->method == METHOD_AN2_LM)) { worknet2 = new LayerNet ( model , outmod , outlin , nin , nhid1 , nhid2 , nout , 0 , 0 ) ; if ((worknet2 == NULL) || (! worknet2->ok)) { if (worknet2 != NULL) delete worknet2 ; delete worknet ; delete bestnet ; memory_message ( "to learn" ) ; errtype = 0 ; return ; } } else worknet2 = NULL ; /* Start by annealing around the starting weights. These will be zero if the net was just created. If it was restored or partially trained already, they will be meaningful. Anneal1 guarantees that it will not return all zero weights if there is at least one hidden layer, even if that means that the error exceeds the amount that could be attained by all zeros. */ best_err = best_inner_error = 1.e30 ; if ((lptr->method == METHOD_AN1_CJ) || (lptr->method == METHOD_AN1_LM)) anneal1 ( tptr , lptr , worknet , 1 , 0 ) ; else if ((lptr->method == METHOD_AN2_CJ) || (lptr->method == METHOD_AN2_LM)) anneal2 ( tptr , lptr , worknet , worknet2 , 1 ) ; /* Do direct descent optimization, finding local minimum. Then anneal to break out of it. If successful, loop back up to do direct descent again. Otherwise restart totally random. */ bad_count = 0 ; // Handles flat local mins refined = 0 ; // Did we ever refine to high resolution? Not yet. new_record = 0 ; // Refine every time a new inner error record set initial_accuracy = pow ( 10.0 , -lptr->cj_acc ) ; final_accuracy = initial_accuracy * pow ( 10.0 , -lptr->cj_refine ) ; for (itry=1 ; ; itry++) { if (neterr < best_err) { // Keep track of best copy_weights ( bestnet , this ) ; best_err = neterr ; } sprintf ( msg , "Try %d (best=%lf):", itry, best_err ) ; normal_message ( msg ) ; if (neterr <= lptr->quit_err) break ; start_of_loop_error = neterr ; if ((lptr->method == METHOD_AN1_CJ) || (lptr->method == METHOD_AN2_CJ)) err = conjgrad ( tptr , 32767 , initial_accuracy , lptr->quit_err , lptr->cj_progress ) ; else if ((lptr->method==METHOD_AN1_LM) || (lptr->method==METHOD_AN2_LM)) err = lev_marq ( tptr , 32767 , initial_accuracy , lptr->quit_err , lptr->cj_progress ) ; neterr = fabs ( err ) ; // err<0 if user pressed ESCape sprintf ( msg , " Gradient err=%lf", neterr ) ; progress_message ( msg ) ; if (neterr < best_err) { // Keep track of best copy_weights ( bestnet , this ) ; best_err = neterr ; } if (err <= lptr->quit_err) { // err<0 if user pressed ESCape if (err < -1.e29) // or insufficient memory printf ( "\nInsufficient memory for gradient learning." ) ; break ; } seed = flrand() - (long) (itry * 97) ; // Insure new seed for anneal sflrand ( seed ) ; prev_err = neterr ; // So we can see if anneal helped if ((lptr->method == METHOD_AN1_CJ) || (lptr->method == METHOD_AN1_LM)) anneal1 ( tptr , lptr , worknet , 0 , itry ) ; else if ((lptr->method==METHOD_AN2_CJ) || (lptr->method==METHOD_AN2_LM)) anneal2 ( tptr , lptr , worknet , worknet2 , 0 ) ; sprintf ( msg , " Anneal err=%lf", neterr ) ; progress_message ( msg ) ; if (neterr < best_err) { // Keep track of best copy_weights ( bestnet , this ) ; best_err = neterr ; } if (best_err <= lptr->quit_err) break ; if (neterr < best_inner_error) { // Keep track of best inner for refine best_inner_error = neterr ; new_record = 1 ; // Tells us to refine } if ((prev_err - neterr) > 1.e-7) { // Did we break out of local min? if ((start_of_loop_error - neterr) < 1.e-3) ++bad_count ; // Avoid many unprofitable iters else bad_count = 0 ; if (bad_count < 4) { ++n_escape ; // Count escapes from local min continue ; // Escaped, so gradient learn again } } /* After first few tries, and after each inprovement thereafter, refine to high resolution */ if ((itry-n_escape >= lptr->cj_pretries) && (new_record || ! refined)) { if (! refined) { // If refining the best of the pretries copy_weights ( this , bestnet ) ; // Get that net neterr = best_err ; } refined = 1 ; // Only force refine once new_record = 0 ; // Reset new inner error record flag progress_message ( " REFINING" ) ; if ((lptr->method == METHOD_AN1_CJ) || (lptr->method == METHOD_AN2_CJ)) err = conjgrad ( tptr , 0 , final_accuracy , lptr->quit_err , lptr->cj_progress ) ; else if ((lptr->method==METHOD_AN1_LM)|| (lptr->method==METHOD_AN2_LM)) err = lev_marq ( tptr , 0 , final_accuracy , lptr->quit_err , lptr->cj_progress ) ; neterr = fabs ( err ) ; // err<0 if user pressed ESCape sprintf ( msg , " Attained err=%lf", neterr ) ; progress_message ( msg ) ; if (neterr < best_err) { // Keep track of best copy_weights ( bestnet , this ) ; best_err = neterr ; } } if (++n_retry > lptr->retries) break ; progress_message ( " RESTART" ) ; zero_weights () ; // Failed to break out, so retry random seed = flrand() - (long) (itry * 773) ; // Insure new seed for anneal sflrand ( seed ) ; if ((lptr->method == METHOD_AN1_CJ) || (lptr->method == METHOD_AN1_LM)) anneal1 ( tptr , lptr , worknet , 1 , itry ) ; else if ((lptr->method==METHOD_AN2_CJ) || (lptr->method==METHOD_AN2_LM)) anneal2 ( tptr , lptr , worknet , worknet2 , 1 ) ; } FINISH: copy_weights ( this , bestnet ) ; neterr = best_err ; MEMTEXT ( "AN1_DD::learn delete worknet, bestnet" ) ; delete worknet ; delete bestnet ; sprintf ( msg , "%d successful escapes, %d retries", n_escape, n_retry ) ; normal_message ( msg ) ; return ; }
//------------------------------------ void Seeker_MaintainHeight( void ) { float dif; // Update our angles regardless NPC_UpdateAngles( qtrue, qtrue ); // If we have an enemy, we should try to hover at or a little below enemy eye level if ( NPCS.NPC->enemy ) { if (TIMER_Done( NPCS.NPC, "heightChange" )) { float difFactor; TIMER_Set( NPCS.NPC,"heightChange",Q_irand( 1000, 3000 )); // Find the height difference dif = (NPCS.NPC->enemy->r.currentOrigin[2] + flrand( NPCS.NPC->enemy->r.maxs[2]/2, NPCS.NPC->enemy->r.maxs[2]+8 )) - NPCS.NPC->r.currentOrigin[2]; difFactor = 1.0f; if ( NPCS.NPC->client->NPC_class == CLASS_BOBAFETT ) { if ( TIMER_Done( NPCS.NPC, "flameTime" ) ) { difFactor = 10.0f; } } // cap to prevent dramatic height shifts if ( fabs( dif ) > 2*difFactor ) { if ( fabs( dif ) > 24*difFactor ) { dif = ( dif < 0 ? -24*difFactor : 24*difFactor ); } NPCS.NPC->client->ps.velocity[2] = (NPCS.NPC->client->ps.velocity[2]+dif)/2; } if ( NPCS.NPC->client->NPC_class == CLASS_BOBAFETT ) { NPCS.NPC->client->ps.velocity[2] *= flrand( 0.85f, 3.0f ); } } } else { gentity_t *goal = NULL; if ( NPCS.NPCInfo->goalEntity ) // Is there a goal? { goal = NPCS.NPCInfo->goalEntity; } else { goal = NPCS.NPCInfo->lastGoalEntity; } if ( goal ) { dif = goal->r.currentOrigin[2] - NPCS.NPC->r.currentOrigin[2]; if ( fabs( dif ) > 24 ) { NPCS.ucmd.upmove = ( NPCS.ucmd.upmove < 0 ? -4 : 4 ); } else { if ( NPCS.NPC->client->ps.velocity[2] ) { NPCS.NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPCS.NPC->client->ps.velocity[2] ) < 2 ) { NPCS.NPC->client->ps.velocity[2] = 0; } } } } } // Apply friction if ( NPCS.NPC->client->ps.velocity[0] ) { NPCS.NPC->client->ps.velocity[0] *= VELOCITY_DECAY; if ( fabs( NPCS.NPC->client->ps.velocity[0] ) < 1 ) { NPCS.NPC->client->ps.velocity[0] = 0; } } if ( NPCS.NPC->client->ps.velocity[1] ) { NPCS.NPC->client->ps.velocity[1] *= VELOCITY_DECAY; if ( fabs( NPCS.NPC->client->ps.velocity[1] ) < 1 ) { NPCS.NPC->client->ps.velocity[1] = 0; } } }
void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs, float speed, int numChunks, material_t chunkType, int customChunk, float baseScale ) { localEntity_t *le; refEntity_t *re; vec3_t dir; int i, j, k; int chunkModel = 0; leBounceSoundType_t bounce = LEBS_NONE; float r, speedMod = 1.0f; qboolean chunk = qfalse; if ( chunkType == MAT_NONE ) { // Well, we should do nothing return; } // Set up our chunk sound info...breaking sounds are done here so they are done once on breaking..some return instantly because the chunks are done with effects instead of models switch( chunkType ) { default: break; case MAT_GLASS: trap->S_StartSound( NULL, owner, CHAN_BODY, cgs.media.glassChunkSound ); return; break; case MAT_GRATE1: trap->S_StartSound( NULL, owner, CHAN_BODY, cgs.media.grateSound ); return; break; case MAT_ELECTRICAL:// (sparks) trap->S_StartSound( NULL, owner, CHAN_BODY, trap->S_RegisterSound (va("sound/ambience/spark%d.wav", Q_irand(1, 6))) ); return; break; case MAT_DRK_STONE: case MAT_LT_STONE: case MAT_GREY_STONE: case MAT_WHITE_METAL: // not quite sure what this stuff is supposed to be...it's for Stu case MAT_SNOWY_ROCK: trap->S_StartSound( NULL, owner, CHAN_BODY, cgs.media.rockBreakSound ); bounce = LEBS_ROCK; speedMod = 0.5f; // rock blows up less break; case MAT_GLASS_METAL: trap->S_StartSound( NULL, owner, CHAN_BODY, cgs.media.glassChunkSound ); // FIXME: should probably have a custom sound bounce = LEBS_METAL; break; case MAT_CRATE1: case MAT_CRATE2: trap->S_StartSound( NULL, owner, CHAN_BODY, cgs.media.crateBreakSound[Q_irand(0,1)] ); break; case MAT_METAL: case MAT_METAL2: case MAT_METAL3: case MAT_ELEC_METAL:// FIXME: maybe have its own sound? trap->S_StartSound( NULL, owner, CHAN_BODY, cgs.media.chunkSound ); bounce = LEBS_METAL; speedMod = 0.8f; // metal blows up a bit more break; case MAT_ROPE: // trap->S_StartSound( NULL, owner, CHAN_BODY, cgi_S_RegisterSound( "" )); FIXME: needs a sound return; break; } if ( baseScale <= 0.0f ) { baseScale = 1.0f; } // Chunks for( i = 0; i < numChunks; i++ ) { if ( customChunk > 0 ) { // Try to use a custom chunk. if ( cgs.gameModels[customChunk] ) { chunk = qtrue; chunkModel = cgs.gameModels[customChunk]; } } if ( !chunk ) { // No custom chunk. Pick a random chunk type at run-time so we don't get the same chunks switch( chunkType ) { default: break; case MAT_METAL2: //bluegrey chunkModel = cgs.media.chunkModels[CHUNK_METAL2][Q_irand(0, 3)]; break; case MAT_GREY_STONE://gray chunkModel = cgs.media.chunkModels[CHUNK_ROCK1][Q_irand(0, 3)]; break; case MAT_LT_STONE: //tan chunkModel = cgs.media.chunkModels[CHUNK_ROCK2][Q_irand(0, 3)]; break; case MAT_DRK_STONE://brown chunkModel = cgs.media.chunkModels[CHUNK_ROCK3][Q_irand(0, 3)]; break; case MAT_SNOWY_ROCK://gray & brown if ( Q_irand( 0, 1 ) ) { chunkModel = cgs.media.chunkModels[CHUNK_ROCK1][Q_irand(0, 3)]; } else { chunkModel = cgs.media.chunkModels[CHUNK_ROCK3][Q_irand(0, 3)]; } break; case MAT_WHITE_METAL: chunkModel = cgs.media.chunkModels[CHUNK_WHITE_METAL][Q_irand(0, 3)]; break; case MAT_CRATE1://yellow multi-colored crate chunks chunkModel = cgs.media.chunkModels[CHUNK_CRATE1][Q_irand(0, 3)]; break; case MAT_CRATE2://red multi-colored crate chunks chunkModel = cgs.media.chunkModels[CHUNK_CRATE2][Q_irand(0, 3)]; break; case MAT_ELEC_METAL: case MAT_GLASS_METAL: case MAT_METAL://grey chunkModel = cgs.media.chunkModels[CHUNK_METAL1][Q_irand(0, 3)]; break; case MAT_METAL3: if ( rand() & 1 ) { chunkModel = cgs.media.chunkModels[CHUNK_METAL1][Q_irand(0, 3)]; } else { chunkModel = cgs.media.chunkModels[CHUNK_METAL2][Q_irand(0, 3)]; } break; } } // It wouldn't look good to throw a bunch of RGB axis models...so make sure we have something to work with. if ( chunkModel ) { le = CG_AllocLocalEntity(); re = &le->refEntity; re->hModel = chunkModel; le->leType = LE_FRAGMENT; le->endTime = cg.time + 1300 + random() * 900; // spawn chunk roughly in the bbox of the thing...bias towards center in case thing blowing up doesn't complete fill its bbox. for( j = 0; j < 3; j++ ) { r = random() * 0.8f + 0.1f; re->origin[j] = ( r * mins[j] + ( 1 - r ) * maxs[j] ); } VectorCopy( re->origin, le->pos.trBase ); // Move out from center of thing, otherwise you can end up things moving across the brush in an undesirable direction. Visually looks wrong VectorSubtract( re->origin, origin, dir ); VectorNormalize( dir ); VectorScale( dir, flrand( speed * 0.5f, speed * 1.25f ) * speedMod, le->pos.trDelta ); // Angular Velocity VectorSet( le->angles.trBase, random() * 360, random() * 360, random() * 360 ); le->angles.trDelta[0] = crandom(); le->angles.trDelta[1] = crandom(); le->angles.trDelta[2] = 0; // don't do roll VectorScale( le->angles.trDelta, random() * 600.0f + 200.0f, le->angles.trDelta ); le->pos.trType = TR_GRAVITY; le->angles.trType = TR_LINEAR; le->pos.trTime = le->angles.trTime = cg.time; le->bounceFactor = 0.2f + random() * 0.2f; le->leFlags |= LEF_TUMBLE; //le->ownerGentNum = owner; le->leBounceSoundType = bounce; // Make sure that we have the desired start size set le->radius = flrand( baseScale * 0.75f, baseScale * 1.25f ); re->nonNormalizedAxes = qtrue; AxisCopy( axisDefault, re->axis ); // could do an angles to axis, but this is cheaper and works ok for( k = 0; k < 3; k++ ) { re->modelScale[k] = le->radius; } ScaleModelAxis(re); /* for( k = 0; k < 3; k++ ) { VectorScale( re->axis[k], le->radius, re->axis[k] ); } */ } } }
void Rancor_Swing( qboolean tryGrab ) { int radiusEntNums[128]; int numEnts; const float radius = 88; const float radiusSquared = (radius*radius); int i; vec3_t boltOrg; numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPCS.NPC->client->renderInfo.handRBolt, boltOrg ); for ( i = 0; i < numEnts; i++ ) { gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; if ( !radiusEnt->inuse ) { continue; } if ( radiusEnt == NPCS.NPC ) {//Skip the rancor ent continue; } if ( radiusEnt->client == NULL ) {//must be a client continue; } if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) {//can't be one already being held continue; } if ( DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ) <= radiusSquared ) { if ( tryGrab && NPCS.NPC->count != 1 //don't have one in hand or in mouth already - FIXME: allow one in hand and any number in mouth! && radiusEnt->client->NPC_class != CLASS_RANCOR && radiusEnt->client->NPC_class != CLASS_GALAKMECH && radiusEnt->client->NPC_class != CLASS_ATST && radiusEnt->client->NPC_class != CLASS_GONK && radiusEnt->client->NPC_class != CLASS_R2D2 && radiusEnt->client->NPC_class != CLASS_R5D2 && radiusEnt->client->NPC_class != CLASS_MARK1 && radiusEnt->client->NPC_class != CLASS_MARK2 && radiusEnt->client->NPC_class != CLASS_MOUSE && radiusEnt->client->NPC_class != CLASS_PROBE && radiusEnt->client->NPC_class != CLASS_SEEKER && radiusEnt->client->NPC_class != CLASS_REMOTE && radiusEnt->client->NPC_class != CLASS_SENTRY && radiusEnt->client->NPC_class != CLASS_INTERROGATOR && radiusEnt->client->NPC_class != CLASS_VEHICLE ) {//grab if ( NPCS.NPC->count == 2 ) {//have one in my mouth, remove him TIMER_Remove( NPCS.NPC, "clearGrabbed" ); Rancor_DropVictim( NPCS.NPC ); } NPCS.NPC->enemy = radiusEnt;//make him my new best friend radiusEnt->client->ps.eFlags2 |= EF2_HELD_BY_MONSTER; //FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something radiusEnt->client->ps.hasLookTarget = qtrue; radiusEnt->client->ps.lookTarget = NPCS.NPC->s.number; NPCS.NPC->activator = radiusEnt;//remember him NPCS.NPC->count = 1;//in my hand //wait to attack TIMER_Set( NPCS.NPC, "attacking", NPCS.NPC->client->ps.legsTimer + Q_irand(500, 2500) ); if ( radiusEnt->health > 0 && radiusEnt->pain ) {//do pain on enemy radiusEnt->pain( radiusEnt, NPCS.NPC, 100 ); //GEntity_PainFunc( radiusEnt, NPC, NPC, radiusEnt->r.currentOrigin, 0, MOD_CRUSH ); } else if ( radiusEnt->client ) { radiusEnt->client->ps.forceHandExtend = HANDEXTEND_NONE; radiusEnt->client->ps.forceHandExtendTime = 0; NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } } else {//smack vec3_t pushDir; vec3_t angs; G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); //actually push the enemy /* //VectorSubtract( radiusEnt->r.currentOrigin, boltOrg, pushDir ); VectorSubtract( radiusEnt->r.currentOrigin, NPC->r.currentOrigin, pushDir ); pushDir[2] = Q_flrand( 100, 200 ); VectorNormalize( pushDir ); */ VectorCopy( NPCS.NPC->client->ps.viewangles, angs ); angs[YAW] += flrand( 25, 50 ); angs[PITCH] = flrand( -25, -15 ); AngleVectors( angs, pushDir, NULL, NULL ); if ( radiusEnt->client->NPC_class != CLASS_RANCOR && radiusEnt->client->NPC_class != CLASS_ATST ) { G_Damage( radiusEnt, NPCS.NPC, NPCS.NPC, vec3_origin, radiusEnt->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); G_Throw( radiusEnt, pushDir, 250 ); if ( radiusEnt->health > 0 ) {//do pain on enemy G_Knockdown( radiusEnt );//, NPC, pushDir, 100, qtrue ); } } } } } }
//----------------------------------------------------- static void turret_aim( gentity_t *self ) //----------------------------------------------------- { vec3_t enemyDir, org, org2; vec3_t desiredAngles, setAngle; float diffYaw = 0.0f, diffPitch = 0.0f, turnSpeed; const float pitchCap = 40.0f; gentity_t *top = &g_entities[self->r.ownerNum]; if ( !top ) { return; } // move our gun base yaw to where we should be at this time.... BG_EvaluateTrajectory( &top->s.apos, level.time, top->r.currentAngles ); top->r.currentAngles[YAW] = AngleNormalize180( top->r.currentAngles[YAW] ); top->r.currentAngles[PITCH] = AngleNormalize180( top->r.currentAngles[PITCH] ); turnSpeed = top->speed; if ( self->painDebounceTime > level.time ) { desiredAngles[YAW] = top->r.currentAngles[YAW]+flrand(-45,45); desiredAngles[PITCH] = top->r.currentAngles[PITCH]+flrand(-10,10); if (desiredAngles[PITCH] < -pitchCap) { desiredAngles[PITCH] = -pitchCap; } else if (desiredAngles[PITCH] > pitchCap) { desiredAngles[PITCH] = pitchCap; } diffYaw = AngleSubtract( desiredAngles[YAW], top->r.currentAngles[YAW] ); diffPitch = AngleSubtract( desiredAngles[PITCH], top->r.currentAngles[PITCH] ); turnSpeed = flrand( -5, 5 ); } else if ( self->enemy ) { // ...then we'll calculate what new aim adjustments we should attempt to make this frame // Aim at enemy VectorCopy( self->enemy->r.currentOrigin, org ); org[2]+=self->enemy->r.maxs[2]*0.5f; if (self->enemy->s.eType == ET_NPC && self->enemy->s.NPC_class == CLASS_VEHICLE && self->enemy->m_pVehicle && self->enemy->m_pVehicle->m_pVehicleInfo->type == VH_WALKER) { //hack! org[2] += 32.0f; } /* mdxaBone_t boltMatrix; // Getting the "eye" here gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, self->torsoBolt, &boltMatrix, self->r.currentAngles, self->s.origin, (cg.time?cg.time:level.time), NULL, self->s.modelScale ); gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 ); */ VectorCopy( top->r.currentOrigin, org2 ); VectorSubtract( org, org2, enemyDir ); vectoangles( enemyDir, desiredAngles ); desiredAngles[PITCH] = AngleNormalize180(desiredAngles[PITCH]); if (desiredAngles[PITCH] < -pitchCap) { desiredAngles[PITCH] = -pitchCap; } else if (desiredAngles[PITCH] > pitchCap) { desiredAngles[PITCH] = pitchCap; } diffYaw = AngleSubtract( desiredAngles[YAW], top->r.currentAngles[YAW] ); diffPitch = AngleSubtract( desiredAngles[PITCH], top->r.currentAngles[PITCH] ); } else {//FIXME: Pan back and forth in original facing // no enemy, so make us slowly sweep back and forth as if searching for a new one desiredAngles[YAW] = sin( level.time * 0.0001f + top->count ); desiredAngles[YAW] *= 60.0f; desiredAngles[YAW] += self->s.angles[YAW]; desiredAngles[YAW] = AngleNormalize180( desiredAngles[YAW] ); diffYaw = AngleSubtract( desiredAngles[YAW], top->r.currentAngles[YAW] ); diffPitch = AngleSubtract( 0, top->r.currentAngles[PITCH] ); turnSpeed = 1.0f; } if ( diffYaw ) { // cap max speed.... if ( fabs(diffYaw) > turnSpeed ) { diffYaw = ( diffYaw >= 0 ? turnSpeed : -turnSpeed ); } } if ( diffPitch ) { if ( fabs(diffPitch) > turnSpeed ) { // cap max speed diffPitch = (diffPitch > 0.0f ? turnSpeed : -turnSpeed ); } } // ...then set up our desired yaw VectorSet( setAngle, diffPitch, diffYaw, 0 ); VectorCopy( top->r.currentAngles, top->s.apos.trBase ); VectorScale( setAngle, (1000/FRAMETIME), top->s.apos.trDelta ); top->s.apos.trTime = level.time; top->s.apos.trType = TR_LINEAR_STOP; top->s.apos.trDuration = FRAMETIME; if ( diffYaw || diffPitch ) { top->s.loopSound = G_SoundIndex( "sound/vehicles/weapons/hoth_turret/turn.wav" ); } else { top->s.loopSound = 0; } }
void G_KickSomeMofos(gentity_t *ent) { vec3_t kickDir, kickEnd, fwdAngs; float animLength = BG_AnimLength( ent->localAnimIndex, (animNumber_t)ent->client->ps.legsAnim ); float elapsedTime = (float)(animLength-ent->client->ps.legsTimer); float remainingTime = (animLength-elapsedTime); float kickDist = (ent->r.maxs[0]*1.5f)+STAFF_KICK_RANGE+8.0f;//fudge factor of 8 int kickDamage = Q_irand(10, 15);//Q_irand( 3, 8 ); //since it can only hit a guy once now int kickPush = flrand( 50.0f, 100.0f ); qboolean doKick = qfalse; renderInfo_t *ri = &ent->client->renderInfo; VectorSet(kickDir, 0.0f, 0.0f, 0.0f); VectorSet(kickEnd, 0.0f, 0.0f, 0.0f); VectorSet(fwdAngs, 0.0f, ent->client->ps.viewangles[YAW], 0.0f); //HMM... or maybe trace from origin to footRBolt/footLBolt? Which one? G2 trace? Will do hitLoc, if so... if ( ent->client->ps.torsoAnim == BOTH_A7_HILT ) { if ( elapsedTime >= 250 && remainingTime >= 250 ) {//front doKick = qtrue; if ( ri->handRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ri->handRBolt, kickEnd, 0 ); VectorSubtract( kickEnd, ent->client->ps.origin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } } else { switch ( ent->client->ps.legsAnim ) { case BOTH_GETUP_BROLL_B: case BOTH_GETUP_BROLL_F: case BOTH_GETUP_FROLL_B: case BOTH_GETUP_FROLL_F: if ( elapsedTime >= 250 && remainingTime >= 250 ) {//front doKick = qtrue; if ( ri->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); VectorSubtract( kickEnd, ent->client->ps.origin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } break; case BOTH_A7_KICK_F_AIR: case BOTH_A7_KICK_B_AIR: case BOTH_A7_KICK_R_AIR: case BOTH_A7_KICK_L_AIR: if ( elapsedTime >= 100 && remainingTime >= 250 ) {//air doKick = qtrue; if ( ri->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } break; case BOTH_A7_KICK_F: //FIXME: push forward? if ( elapsedTime >= 250 && remainingTime >= 250 ) {//front doKick = qtrue; if ( ri->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } break; case BOTH_A7_KICK_B: //FIXME: push back? if ( elapsedTime >= 250 && remainingTime >= 250 ) {//back doKick = qtrue; if ( ri->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; case BOTH_A7_KICK_R: //FIXME: push right? if ( elapsedTime >= 250 && remainingTime >= 250 ) {//right doKick = qtrue; if ( ri->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, NULL, kickDir, NULL ); } } break; case BOTH_A7_KICK_L: //FIXME: push left? if ( elapsedTime >= 250 && remainingTime >= 250 ) {//left doKick = qtrue; if ( ri->footLBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ri->footLBolt, kickEnd, 0 ); VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, NULL, kickDir, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; case BOTH_A7_KICK_S: kickPush = flrand( 75.0f, 125.0f ); if ( ri->footRBolt != -1 ) {//actually trace to a bolt if ( elapsedTime >= 550 && elapsedTime <= 1050 ) { doKick = qtrue; G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is VectorMA( kickEnd, 8.0f, kickDir, kickEnd ); } } else {//guess if ( elapsedTime >= 400 && elapsedTime < 500 ) {//front doKick = qtrue; AngleVectors( fwdAngs, kickDir, NULL, NULL ); } else if ( elapsedTime >= 500 && elapsedTime < 600 ) {//front-right? doKick = qtrue; fwdAngs[YAW] += 45; AngleVectors( fwdAngs, kickDir, NULL, NULL ); } else if ( elapsedTime >= 600 && elapsedTime < 700 ) {//right doKick = qtrue; AngleVectors( fwdAngs, NULL, kickDir, NULL ); } else if ( elapsedTime >= 700 && elapsedTime < 800 ) {//back-right? doKick = qtrue; fwdAngs[YAW] += 45; AngleVectors( fwdAngs, NULL, kickDir, NULL ); } else if ( elapsedTime >= 800 && elapsedTime < 900 ) {//back doKick = qtrue; AngleVectors( fwdAngs, kickDir, NULL, NULL ); VectorScale( kickDir, -1, kickDir ); } else if ( elapsedTime >= 900 && elapsedTime < 1000 ) {//back-left? doKick = qtrue; fwdAngs[YAW] += 45; AngleVectors( fwdAngs, kickDir, NULL, NULL ); } else if ( elapsedTime >= 1000 && elapsedTime < 1100 ) {//left doKick = qtrue; AngleVectors( fwdAngs, NULL, kickDir, NULL ); VectorScale( kickDir, -1, kickDir ); } else if ( elapsedTime >= 1100 && elapsedTime < 1200 ) {//front-left? doKick = qtrue; fwdAngs[YAW] += 45; AngleVectors( fwdAngs, NULL, kickDir, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; case BOTH_A7_KICK_BF: kickPush = flrand( 75.0f, 125.0f ); kickDist += 20.0f; if ( elapsedTime < 1500 ) {//auto-aim! // overridAngles = PM_AdjustAnglesForBFKick( ent, ucmd, fwdAngs, qboolean(elapsedTime<850) )?qtrue:overridAngles; //FIXME: if we haven't done the back kick yet and there's no-one there to // kick anymore, go into some anim that returns us to our base stance } if ( ri->footRBolt != -1 ) {//actually trace to a bolt if ( ( elapsedTime >= 750 && elapsedTime < 850 ) || ( elapsedTime >= 1400 && elapsedTime < 1500 ) ) {//right, though either would do doKick = qtrue; G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is VectorMA( kickEnd, 8, kickDir, kickEnd ); } } else {//guess if ( elapsedTime >= 250 && elapsedTime < 350 ) {//front doKick = qtrue; AngleVectors( fwdAngs, kickDir, NULL, NULL ); } else if ( elapsedTime >= 350 && elapsedTime < 450 ) {//back doKick = qtrue; AngleVectors( fwdAngs, kickDir, NULL, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; case BOTH_A7_KICK_RL: kickPush = flrand( 75.0f, 125.0f ); kickDist += 10.0f; //ok, I'm tracing constantly on these things, they NEVER hit otherwise (in MP at least) //FIXME: auto aim at enemies on the side of us? //overridAngles = PM_AdjustAnglesForRLKick( ent, ucmd, fwdAngs, qboolean(elapsedTime<850) )?qtrue:overridAngles; //if ( elapsedTime >= 250 && elapsedTime < 350 ) if (level.framenum&1) {//right doKick = qtrue; if ( ri->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is VectorMA( kickEnd, 8, kickDir, kickEnd ); } else {//guess AngleVectors( fwdAngs, NULL, kickDir, NULL ); } } //else if ( elapsedTime >= 350 && elapsedTime < 450 ) else {//left doKick = qtrue; if ( ri->footLBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ri->footLBolt, kickEnd, 0 ); VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is VectorMA( kickEnd, 8, kickDir, kickEnd ); } else {//guess AngleVectors( fwdAngs, NULL, kickDir, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; } } if ( doKick ) { // G_KickTrace( ent, kickDir, kickDist, kickEnd, kickDamage, kickPush ); G_KickTrace( ent, kickDir, kickDist, NULL, kickDamage, kickPush ); } }
void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) { if ( le->leMarkType == LEMT_BLOOD ) CG_ImpactMark( cgs.media.bloodMarkShader, &trace->endpos, &trace->plane.normal, random()*360, 1, 1, 1, 1, qtrue, 16.0f+flrand( 0, 31 ), qfalse ); else if ( le->leMarkType == LEMT_BURN ) CG_ImpactMark( cgs.media.burnMarkShader, &trace->endpos, &trace->plane.normal, random()*360, 1, 1, 1, 1, qtrue, 8.0f+flrand( 0, 15 ), qfalse ); le->leMarkType = LEMT_NONE; // don't allow a fragment to make multiple marks, or they pile up while settling }
//---------------------------------------------------------------- static void turretG2_fire ( gentity_t *ent, vec3_t start, vec3_t dir ) //---------------------------------------------------------------- { vec3_t org, ang; gentity_t *bolt; if ( (trap_PointContents( start, ent->s.number )&MASK_SHOT) ) { return; } VectorMA( start, -START_DIS, dir, org ); // dumb.... if ( ent->random ) { vectoangles( dir, ang ); ang[PITCH] += flrand( -ent->random, ent->random ); ang[YAW] += flrand( -ent->random, ent->random ); AngleVectors( ang, dir, NULL, NULL ); } vectoangles(dir, ang); if ( (ent->spawnflags&SPF_TURRETG2_TURBO) ) { //muzzle flash G_PlayEffectID( ent->genericValue13, org, ang ); WP_FireTurboLaserMissile( ent, start, dir ); if ( ent->alt_fire ) { TurboLaser_SetBoneAnim( ent, 2, 3 ); } else { TurboLaser_SetBoneAnim( ent, 0, 1 ); } } else { G_PlayEffectID( G_EffectIndex("blaster/muzzle_flash"), org, ang ); bolt = G_Spawn(); bolt->classname = "turret_proj"; bolt->nextthink = level.time + 10000; bolt->think = G_FreeEntity; bolt->s.eType = ET_MISSILE; bolt->s.weapon = WP_BLASTER; bolt->r.ownerNum = ent->s.number; bolt->damage = ent->damage; bolt->alliedTeam = ent->alliedTeam; bolt->teamnodmg = ent->teamnodmg; bolt->dflags = (DAMAGE_NO_KNOCKBACK|DAMAGE_HEAVY_WEAP_CLASS); // Don't push them around, or else we are constantly re-aiming bolt->splashDamage = ent->splashDamage; bolt->splashRadius = ent->splashDamage; bolt->methodOfDeath = MOD_TARGET_LASER;//MOD_ENERGY; bolt->splashMethodOfDeath = MOD_TARGET_LASER;//MOD_ENERGY; bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; //bolt->trigger_formation = qfalse; // don't draw tail on first frame VectorSet( bolt->r.maxs, 1.5, 1.5, 1.5 ); VectorScale( bolt->r.maxs, -1, bolt->r.mins ); bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time; VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, ent->mass, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy( start, bolt->r.currentOrigin); } }
//TODO: When transition to 0 grav, push away from surface you were resting on //TODO: When free-floating in air, apply some friction to your trDelta (based on mass?) void G_RunObject( gentity_t *ent ) { vector3 origin, oldOrg; trace_t tr; gentity_t *traceEnt = NULL; //FIXME: floaters need to stop floating up after a while, even if gravity stays negative? if ( ent->s.pos.trType == TR_STATIONARY )//g_gravity.value <= 0 && { ent->s.pos.trType = TR_GRAVITY; VectorCopy( &ent->r.currentOrigin, &ent->s.pos.trBase ); ent->s.pos.trTime = level.previousTime;//?necc? if ( !g_gravity.value ) { ent->s.pos.trDelta.z += 100; } } ent->nextthink = level.time + FRAMETIME; VectorCopy( &ent->r.currentOrigin, &oldOrg ); // get current position BG_EvaluateTrajectory( &ent->s.pos, level.time, &origin ); //Get current angles? BG_EvaluateTrajectory( &ent->s.apos, level.time, &ent->r.currentAngles ); if ( VectorCompare( &ent->r.currentOrigin, &origin ) ) {//error - didn't move at all! return; } // trace a line from the previous position to the current position, // ignoring interactions with the missile owner trap->Trace( &tr, &ent->r.currentOrigin, &ent->r.mins, &ent->r.maxs, &origin, ent->parent ? ent->parent->s.number : ent->s.number, ent->clipmask, qfalse, 0, 0 ); if ( !tr.startsolid && !tr.allsolid && tr.fraction ) { VectorCopy( &tr.endpos, &ent->r.currentOrigin ); trap->LinkEntity( (sharedEntity_t *)ent ); } else //if ( tr.startsolid ) { tr.fraction = 0; } G_MoverTouchPushTriggers( ent, &oldOrg ); /* if ( !(ent->s.eFlags & EF_TELEPORT_BIT) && !(ent->svFlags & SVF_NO_TELEPORT) ) { G_MoverTouchTeleportTriggers( ent, oldOrg ); if ( ent->s.eFlags & EF_TELEPORT_BIT ) {//was teleported return; } } else { ent->s.eFlags &= ~EF_TELEPORT_BIT; } */ if ( tr.fraction == 1 ) { if ( g_gravity.value <= 0 ) { if ( ent->s.apos.trType == TR_STATIONARY ) { VectorCopy( &ent->r.currentAngles, &ent->s.apos.trBase ); ent->s.apos.trType = TR_LINEAR; ent->s.apos.trDelta.y = flrand( -300, 300 ); ent->s.apos.trDelta.x = flrand( -10, 10 ); ent->s.apos.trDelta.z = flrand( -10, 10 ); ent->s.apos.trTime = level.time; } } //friction in zero-G if ( !g_gravity.value ) { float friction = 0.975f; //friction -= ent->mass/1000.0f; if ( friction < 0.1f ) { friction = 0.1f; } VectorScale( &ent->s.pos.trDelta, friction, &ent->s.pos.trDelta ); VectorCopy( &ent->r.currentOrigin, &ent->s.pos.trBase ); ent->s.pos.trTime = level.time; } return; } //hit something //Do impact damage traceEnt = &g_entities[tr.entityNum]; if ( tr.fraction || (traceEnt && traceEnt->takedamage) ) { if ( !VectorCompare( &ent->r.currentOrigin, &oldOrg ) ) {//moved and impacted if ( (traceEnt && traceEnt->takedamage) ) {//hurt someone // G_Sound( ent, G_SoundIndex( "sound/movers/objects/objectHurt.wav" ) ); } // G_Sound( ent, G_SoundIndex( "sound/movers/objects/objectHit.wav" ) ); } if ( ent->s.weapon != WP_SABER ) { DoImpact( ent, traceEnt, qtrue ); } } if ( !ent || (ent->takedamage&&ent->health <= 0) ) {//been destroyed by impact //chunks? // G_Sound( ent, G_SoundIndex( "sound/movers/objects/objectBreak.wav" ) ); return; } //do impact physics if ( ent->s.pos.trType == TR_GRAVITY )//tr.fraction < 1.0f && {//FIXME: only do this if no trDelta if ( g_gravity.value <= 0 || tr.plane.normal.z < 0.7f ) { if ( ent->flags&(FL_BOUNCE | FL_BOUNCE_HALF) ) { if ( tr.fraction <= 0.0f ) { VectorCopy( &tr.endpos, &ent->r.currentOrigin ); VectorCopy( &tr.endpos, &ent->s.pos.trBase ); VectorClear( &ent->s.pos.trDelta ); ent->s.pos.trTime = level.time; } else { G_BounceObject( ent, &tr ); } } else {//slide down? //FIXME: slide off the slope } } else { ent->s.apos.trType = TR_STATIONARY; pitch_roll_for_slope( ent, &tr.plane.normal ); //ent->r.currentAngles[0] = 0;//FIXME: match to slope //ent->r.currentAngles[2] = 0;//FIXME: match to slope VectorCopy( &ent->r.currentAngles, &ent->s.apos.trBase ); //okay, we hit the floor, might as well stop or prediction will //make us go through the floor! //FIXME: this means we can't fall if something is pulled out from under us... G_StopObjectMoving( ent ); } } else if ( ent->s.weapon != WP_SABER ) { ent->s.apos.trType = TR_STATIONARY; pitch_roll_for_slope( ent, &tr.plane.normal ); //ent->r.currentAngles[0] = 0;//FIXME: match to slope //ent->r.currentAngles[2] = 0;//FIXME: match to slope VectorCopy( &ent->r.currentAngles, &ent->s.apos.trBase ); } //call touch func ent->touch( ent, &g_entities[tr.entityNum], &tr ); }
// Caused by an EV_FIRE_WEAPON event void CG_FireWeapon( centity_t *cent, qboolean altFire ) { entityState_t *ent; int c; weaponInfo_t *weap; ent = ¢->currentState; if ( ent->weapon == WP_NONE ) return; if ( ent->weapon >= WP_NUM_WEAPONS ) { trap->Error( ERR_DROP, "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); return; } weap = &cg_weapons[ent->weapon]; // mark the entity as muzzle flashing, so when it is added it will append the flash to the weapon model cent->muzzleFlashTime = cg.time; if ( cg.predictedPlayerState.clientNum == cent->currentState.number ) { if ( (altFire && (ent->weapon == WP_BRYAR_PISTOL || ent->weapon == WP_BRYAR_OLD || ent->weapon == WP_DEMP2)) || (!altFire && ent->weapon == WP_BOWCASTER) ) { float val = (cg.time - cent->currentState.constantLight) * 0.001f; if ( val > 3 ) val = 3; if ( val < 0.2f ) val = 0.2f; val *= 2; CGCam_Shake( val, 250 ); } else if ( ent->weapon == WP_ROCKET_LAUNCHER || (ent->weapon == WP_REPEATER && altFire) || ent->weapon == WP_FLECHETTE || (ent->weapon == WP_CONCUSSION && !altFire) ) { if ( ent->weapon == WP_CONCUSSION ) { // gives an advantage to being in 3rd person, but would look silly otherwise if ( !cg.renderingThirdPerson ) { // kick the view back cg.kick_angles.pitch = flrand( -10, -15 ); cg.kick_time = cg.time; } } else if ( ent->weapon == WP_ROCKET_LAUNCHER ) CGCam_Shake( flrand( 2, 3 ), 350 ); else if ( ent->weapon == WP_REPEATER ) CGCam_Shake( flrand( 2, 3 ), 350 ); else if ( ent->weapon == WP_FLECHETTE ) { if ( altFire ) CGCam_Shake( flrand( 2, 3 ), 350 ); else CGCam_Shake( 1.5f, 250 ); } } } // play quad sound if needed if ( cent->currentState.powerups & (1 << PW_QUAD) ) { // trap->S_StartSound( NULL, cent->currentState.number, CHAN_ITEM, media.snd.quad ); } // play a sound if ( altFire ) { // play a sound for ( c = 0; c<4; c++ ) { if ( !weap->altFlashSound[c] ) break; } if ( c > 0 ) { c = rand() % c; if ( weap->altFlashSound[c] ) trap->S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->altFlashSound[c] ); } } else { // play a sound for ( c = 0; c<4; c++ ) { if ( !weap->flashSound[c] ) break; } if ( c > 0 ) { c = rand() % c; if ( weap->flashSound[c] ) trap->S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound[c] ); } } }
static void R_SurfaceSpriteFrameUpdate(void) { float dtime, dampfactor; // Time since last update and damping time for wind changes float ratio; vec3_t ang, diff, retwindvec; float targetspeed; vec3_t up={0,0,1}; if (backEnd.refdef.time == lastSSUpdateTime) return; if (backEnd.refdef.time < lastSSUpdateTime) { // Time is BEFORE the last update time, so reset everything. curWindGust = 5; curWindSpeed = r_windSpeed->value; nextGustTime = 0; gustLeft = 0; } // Reset the last entity drawn, since this is a new frame. ssLastEntityDrawn = NULL; // Adjust for an FOV. If things look twice as wide on the screen, pretend the shaders have twice the range. // ASSUMPTION HERE IS THAT "standard" fov is the first one rendered. if (!standardfovinitialized) { // This isn't initialized yet. if (backEnd.refdef.fov_x > 50 && backEnd.refdef.fov_x < 135) // I don't consider anything below 50 or above 135 to be "normal". { standardfovx = backEnd.refdef.fov_x; standardscalex = tan(standardfovx * 0.5 * (M_PI/180.0f)); standardfovinitialized = qtrue; } else { standardfovx = 90; standardscalex = tan(standardfovx * 0.5 * (M_PI/180.0f)); } rangescalefactor = 1.0; // Don't multiply the shader range by anything. } else if (standardfovx == backEnd.refdef.fov_x) { // This is the standard FOV (or higher), don't multiply the shader range. rangescalefactor = 1.0; } else { // We are using a non-standard FOV. We need to multiply the range of the shader by a scale factor. if (backEnd.refdef.fov_x > 135) { rangescalefactor = standardscalex / tan(135.0f * 0.5f * (M_PI/180.0f)); } else { rangescalefactor = standardscalex / tan(backEnd.refdef.fov_x * 0.5 * (M_PI/180.0f)); } } // Create a set of four right vectors so that vertical sprites aren't always facing the same way. // First generate a HORIZONTAL forward vector (important). CrossProduct(ssViewRight, up, ssfwdvector); // Right Zero has a nudge forward (10 degrees). VectorScale(ssViewRight, 0.985f, ssrightvectors[0]); VectorMA(ssrightvectors[0], 0.174f, ssfwdvector, ssrightvectors[0]); // Right One has a big nudge back (30 degrees). VectorScale(ssViewRight, 0.866f, ssrightvectors[1]); VectorMA(ssrightvectors[1], -0.5f, ssfwdvector, ssrightvectors[1]); // Right two has a big nudge forward (30 degrees). VectorScale(ssViewRight, 0.866f, ssrightvectors[2]); VectorMA(ssrightvectors[2], 0.5f, ssfwdvector, ssrightvectors[2]); // Right three has a nudge back (10 degrees). VectorScale(ssViewRight, 0.985f, ssrightvectors[3]); VectorMA(ssrightvectors[3], -0.174f, ssfwdvector, ssrightvectors[3]); // Update the wind. // If it is raining, get the windspeed from the rain system rather than the cvar if (R_IsRaining() || R_IsPuffing()) { curWeatherAmount = 1.0; } else { curWeatherAmount = r_surfaceWeather->value; } if (R_GetWindSpeed(targetspeed)) { // We successfully got a speed from the rain system. // Set the windgust to 5, since that looks pretty good. targetspeed *= 0.3f; if (targetspeed >= 1.0) { curWindGust = 300/targetspeed; } else { curWindGust = 0; } } else { // Use the cvar. targetspeed = r_windSpeed->value; // Minimum gust delay, in seconds. curWindGust = r_windGust->value; } if (targetspeed > 0 && curWindGust) { if (gustLeft > 0) { // We are gusting // Add an amount to the target wind speed targetspeed *= 1.0 + gustLeft; gustLeft -= (float)(backEnd.refdef.time - lastSSUpdateTime)*WIND_GUST_DECAY; if (gustLeft <= 0) { nextGustTime = backEnd.refdef.time + (curWindGust*1000)*flrand(1.0f,4.0f); } } else if (backEnd.refdef.time >= nextGustTime) { // See if there is another right now // Gust next time, mano gustLeft = flrand(0.75f,1.5f); } } // See if there is a weather system that will tell us a windspeed. if (R_GetWindVector(retwindvec)) { retwindvec[2]=0; VectorScale(retwindvec, -1.0f, retwindvec); vectoangles(retwindvec, ang); } else { // Calculate the target wind vector based off cvars ang[YAW] = r_windAngle->value; } ang[PITCH] = -90.0 + targetspeed; if (ang[PITCH]>-45.0) { ang[PITCH] = -45.0; } ang[ROLL] = 0; if (targetspeed>0) { // ang[YAW] += cos(tr.refdef.time*0.01+flrand(-1.0,1.0))*targetspeed*0.5; // ang[PITCH] += sin(tr.refdef.time*0.01+flrand(-1.0,1.0))*targetspeed*0.5; } // Get the grass wind vector first AngleVectors(ang, targetWindGrassDir, NULL, NULL); targetWindGrassDir[2]-=1.0; // VectorScale(targetWindGrassDir, targetspeed, targetWindGrassDir); // Now get the general wind vector (no pitch) ang[PITCH]=0; AngleVectors(ang, targetWindBlowVect, NULL, NULL); // Start calculating a smoothing factor so wind doesn't change abruptly between speeds. dampfactor = 1.0-r_windDampFactor->value; // We must exponent the amount LEFT rather than the amount bled off dtime = (float)(backEnd.refdef.time - lastSSUpdateTime) * (1.0/(float)WIND_DAMP_INTERVAL); // Our dampfactor is geared towards a time interval equal to "1". // Note that since there are a finite number of "practical" delta millisecond values possible, // the ratio should be initialized into a chart ultimately. ratio = pow(dampfactor, dtime); // Apply this ratio to the windspeed... curWindSpeed = targetspeed - (ratio * (targetspeed-curWindSpeed)); // Use the curWindSpeed to calculate the final target wind vector (with speed) VectorScale(targetWindBlowVect, curWindSpeed, targetWindBlowVect); VectorSubtract(targetWindBlowVect, curWindBlowVect, diff); VectorMA(targetWindBlowVect, -ratio, diff, curWindBlowVect); // Update the grass vector now VectorSubtract(targetWindGrassDir, curWindGrassDir, diff); VectorMA(targetWindGrassDir, -ratio, diff, curWindGrassDir); lastSSUpdateTime = backEnd.refdef.time; curWindPointForce = r_windPointForce->value - (ratio * (r_windPointForce->value - curWindPointForce)); if (curWindPointForce < 0.01) { curWindPointActive = qfalse; } else { curWindPointActive = qtrue; curWindPoint[0] = r_windPointX->value; curWindPoint[1] = r_windPointY->value; curWindPoint[2] = 0; } if (r_surfaceSprites->integer >= 2) { ri->Printf( PRINT_ALL, "Surfacesprites Drawn: %d, on %d surfaces\n", totalsurfsprites, sssurfaces); } totalsurfsprites=0; sssurfaces=0; }
void FX_CreateParticle(const FXSegment_t *segment, vec3_t origin, vec3_t dir) { FXParticleSegment_t *particle; fxParticle_t *effect; if(!segment) { // NULL segment pointer? return; } if(!segment->SegmentData.FXParticleSegment) { // No particle segment data? return; } if(segment->segmentType != EFXS_PARTICLE) { // Not a particle? return; } particle = segment->SegmentData.FXParticleSegment; effect = (fxParticle_t *)malloc(sizeof(fxParticle_t)); FX_ParticleInit((fxPrimitive_t *)effect); effect->culldist = flrand(particle->cullrange[0], particle->cullrange[1]); effect->culldist *= effect->culldist; // allows for VLSquared effect->startTime = fx_time + Q_irand(particle->delay[0], particle->delay[1]); effect->endTime = effect->startTime + Q_irand(particle->life[0], particle->life[1]); effect->flags = segment->flags; VectorCopy(origin, effect->origin); //if((segment->spawnflags & FXSFLAG_CHEAPORIGINCALC)) { vec3_t offset; vecrandom(particle->origin[0], particle->origin[1], offset); VectorAdd(effect->origin, offset, effect->origin); } vecrandom(particle->rgb.start.sv[0], particle->rgb.start.sv[1], effect->startRGB); if(particle->rgb.flags != 0 || !(particle->rgb.flags & FXTLF_CONSTANT)) { vecrandom(particle->rgb.end.ev[0], particle->rgb.end.ev[1], effect->endRGB); } else { VectorCopy(effect->startRGB, effect->endRGB); } //effect->RGBflags = light->rgb.flags; VectorCopy(effect->startRGB, effect->RGB); effect->startsize = effect->size = flrand(particle->size.start.sf[0], particle->size.start.sf[1]); if(particle->size.flags != 0 || !(particle->size.flags & FXTLF_CONSTANT)) { // TODO: make the distinction between wave, clamp, nonlinear, etc effect->endsize = flrand(particle->size.end.ef[0], particle->size.end.ef[1]); } else { effect->endsize = effect->startsize; } //effect->sizeFlags = light->size.flags; effect->startalpha = effect->alpha = flrand(particle->alpha.start.sf[0], particle->alpha.start.sf[1]); if(particle->alpha.flags != 0 || !(particle->alpha.flags & FXTLF_CONSTANT)) { // TODO: make the distinction between wave, clamp, nonlinear, etc effect->endalpha = flrand(particle->alpha.end.ef[0], particle->alpha.end.ef[1]); } else { effect->endalpha = effect->startalpha; } //effect->alphaFlags = particle->alpha.flags; effect->startrotation = effect->rotation = flrand(particle->rotation[0], particle->rotation[1]); effect->deltarotation = flrand(particle->rotationDelta[0], particle->rotationDelta[1]); vecrandom(particle->velocity[0], particle->velocity[1], effect->velocity); vecrandom(particle->acceleration[0], particle->acceleration[1], effect->accel); effect->shader = particle->shader.fieldHandles[Q_irand(0, particle->shader.numFields-1)]; FX_AddPrimitive((fxPrimitive_t *)effect, effect->startTime, effect->endTime); }