// TODO: Check if we can just check for HealthComponent.
static AIValue_t goalDead( gentity_t *self, const AIValue_t* )
{
	bool dead = false;
	botTarget_t *goal = &self->botMind->goal;

	if ( !BotTargetIsEntity( *goal ) )
	{
		dead = true;
	}
	else if ( BotGetTargetTeam( *goal ) == TEAM_NONE )
	{
		dead = true;
	}
	else if ( !G_Alive( self->botMind->goal.ent ) )
	{
		dead = true;
	}
	else if ( goal->ent->client && goal->ent->client->sess.spectatorState != SPECTATOR_NOT )
	{
		dead = true;
	}
	else if ( goal->ent->s.eType == entityType_t::ET_BUILDABLE && goal->ent->buildableTeam == self->client->pers.team && !goal->ent->powered )
	{
		dead = true;
	}

	return AIBoxInt( dead );
}
/*
================
Target tracking for the hive missile.
================
*/
static void HiveMissileThink( gentity_t *self )
{
	vec3_t    dir;
	trace_t   tr;
	gentity_t *ent;
	int       i;
	float     d, nearest;

	if ( level.time > self->timestamp ) // swarm lifetime exceeded
	{
		VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
		self->s.pos.trType = TR_STATIONARY;
		self->s.pos.trTime = level.time;

		self->think = G_ExplodeMissile;
		self->nextthink = level.time + 50;
		self->parent->active = false; //allow the parent to start again
		return;
	}

	nearest = DistanceSquared( self->r.currentOrigin, self->target->r.currentOrigin );

	//find the closest human
	for ( i = 0; i < MAX_CLIENTS; i++ )
	{
		ent = &g_entities[ i ];

		if ( !ent->inuse ) continue;
		if ( ent->flags & FL_NOTARGET ) continue;

		if ( ent->client && G_Alive( ent ) && ent->client->pers.team == TEAM_HUMANS &&
		     nearest > ( d = DistanceSquared( ent->r.currentOrigin, self->r.currentOrigin ) ) )
		{
			trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs,
			            ent->r.currentOrigin, self->r.ownerNum, self->clipmask, 0 );

			if ( tr.entityNum != ENTITYNUM_WORLD )
			{
				nearest = d;
				self->target = ent;
			}
		}
	}

	VectorSubtract( self->target->r.currentOrigin, self->r.currentOrigin, dir );
	VectorNormalize( dir );

	//change direction towards the player
	VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta );
	SnapVector( self->s.pos.trDelta );  // save net bandwidth
	VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
	self->s.pos.trTime = level.time;

	self->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD;
}
static void FindZapChainTargets( zap_t *zap )
{
	gentity_t *ent = zap->targets[ 0 ]; // the source
	int       entityList[ MAX_GENTITIES ];
	vec3_t    range;
	vec3_t    mins, maxs;
	int       i, num;
	gentity_t *enemy;
	trace_t   tr;
	float     distance;

	VectorSet(range, LEVEL2_AREAZAP_CHAIN_RANGE, LEVEL2_AREAZAP_CHAIN_RANGE, LEVEL2_AREAZAP_CHAIN_RANGE);

	VectorAdd( ent->s.origin, range, maxs );
	VectorSubtract( ent->s.origin, range, mins );

	num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );

	for ( i = 0; i < num; i++ )
	{
		enemy = &g_entities[ entityList[ i ] ];

		// don't chain to self; noclippers can be listed, don't chain to them either
		if ( enemy == ent || ( enemy->client && enemy->client->noclip ) )
		{
			continue;
		}

		distance = Distance( ent->s.origin, enemy->s.origin );

		if ( ( ( enemy->client &&
		         enemy->client->pers.team == TEAM_HUMANS ) ||
		       ( enemy->s.eType == ET_BUILDABLE &&
		         BG_Buildable( enemy->s.modelindex )->team == TEAM_HUMANS ) ) &&
		     G_Alive( enemy ) &&
		     distance <= LEVEL2_AREAZAP_CHAIN_RANGE )
		{
			// world-LOS check: trace against the world, ignoring other BODY entities
			trap_Trace( &tr, ent->s.origin, nullptr, nullptr,
			            enemy->s.origin, ent->s.number, CONTENTS_SOLID, 0 );

			if ( tr.entityNum == ENTITYNUM_NONE )
			{
				zap->targets[ zap->numTargets ] = enemy;
				zap->distances[ zap->numTargets ] = distance;

				if ( ++zap->numTargets >= LEVEL2_AREAZAP_MAX_TARGETS )
				{
					return;
				}
			}
		}
	}
}
void G_ChargeAttack( gentity_t *self, gentity_t *victim )
{
	int    damage;
	int    i;
	vec3_t forward;

	if ( !self->client || self->client->ps.stats[ STAT_MISC ] <= 0 ||
	     !( self->client->ps.stats[ STAT_STATE ] & SS_CHARGING ) ||
	     self->client->ps.weaponTime )
	{
		return;
	}

	if ( !G_Alive( victim ) )
	{
		return;
	}

	VectorSubtract( victim->s.origin, self->s.origin, forward );
	VectorNormalize( forward );

	// For buildables, track the last MAX_TRAMPLE_BUILDABLES_TRACKED buildables
	//  hit, and do not do damage if the current buildable is in that list
	//  in order to prevent dancing over stuff to kill it very quickly
	if ( !victim->client )
	{
		for ( i = 0; i < MAX_TRAMPLE_BUILDABLES_TRACKED; i++ )
		{
			if ( self->client->trampleBuildablesHit[ i ] == victim - g_entities )
			{
				return;
			}
		}

		self->client->trampleBuildablesHit[
		  self->client->trampleBuildablesHitPos++ % MAX_TRAMPLE_BUILDABLES_TRACKED ] =
		    victim - g_entities;
	}

	damage = LEVEL4_TRAMPLE_DMG * self->client->ps.stats[ STAT_MISC ] / LEVEL4_TRAMPLE_DURATION;

	victim->entity->Damage((float)damage, self, Vec3::Load(victim->s.origin), Vec3::Load(forward),
	                       DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_TRAMPLE);

	SendMeleeHitEvent( self, victim, nullptr );

	self->client->ps.weaponTime += LEVEL4_TRAMPLE_REPEAT;
}
bool G_CheckPounceAttack( gentity_t *self )
{
	trace_t   tr;
	gentity_t *traceEnt;
	int       damage, timeMax, pounceRange, payload;

	if ( self->client->pmext.pouncePayload <= 0 )
	{
		return false;
	}

	// In case the goon lands on his target, he gets one shot after landing
	payload = self->client->pmext.pouncePayload;

	if ( !( self->client->ps.pm_flags & PMF_CHARGE ) )
	{
		self->client->pmext.pouncePayload = 0;
	}

	// Calculate muzzle point
	AngleVectors( self->client->ps.viewangles, forward, right, up );
	G_CalcMuzzlePoint( self, forward, right, up, muzzle );

	// Trace from muzzle to see what we hit
	pounceRange = self->client->ps.weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_RANGE :
	              LEVEL3_POUNCE_UPG_RANGE;
	G_WideTrace( &tr, self, pounceRange, LEVEL3_POUNCE_WIDTH,
	             LEVEL3_POUNCE_WIDTH, &traceEnt );

	if ( !G_Alive( traceEnt ) )
	{
		return false;
	}

	timeMax = self->client->ps.weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_TIME : LEVEL3_POUNCE_TIME_UPG;
	damage  = payload * LEVEL3_POUNCE_DMG / timeMax;

	self->client->pmext.pouncePayload = 0;

	traceEnt->entity->Damage((float)damage, self, Vec3::Load(tr.endpos), Vec3::Load(forward),
	                         DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE);

	SendMeleeHitEvent( self, traceEnt, &tr );

	return true;
}
static void FirePainsaw( gentity_t *self )
{
	trace_t   tr;
	gentity_t *target;

	G_WideTrace( &tr, self, PAINSAW_RANGE, PAINSAW_WIDTH, PAINSAW_HEIGHT, &target );

	if ( !G_Alive( target ) )
	{
		return;
	}

	// not really a "ranged" weapon, but this is still the right call
	SendRangedHitEvent( self, target, &tr );

	target->entity->Damage((float)PAINSAW_DAMAGE, self, Vec3::Load(tr.endpos),
	                       Vec3::Load(forward), 0, (meansOfDeath_t)MOD_PAINSAW);
}
static gentity_t *FireMelee( gentity_t *self, float range, float width, float height,
                             int damage, meansOfDeath_t mod )
{
	trace_t   tr;
	gentity_t *traceEnt;

	G_WideTrace( &tr, self, range, width, height, &traceEnt );

	if ( !G_Alive( traceEnt ) )
	{
		return nullptr;
	}

	traceEnt->entity->Damage((float)damage, self, Vec3::Load(tr.endpos),
		                     Vec3::Load(forward), 0, (meansOfDeath_t)mod);

	SendMeleeHitEvent( self, traceEnt, &tr );

	return traceEnt;
}
bool G_CheckVenomAttack( gentity_t *self )
{
	trace_t   tr;
	gentity_t *traceEnt;

	if ( self->client->ps.weaponTime )
	{
		return false;
	}

	// Calculate muzzle point
	AngleVectors( self->client->ps.viewangles, forward, right, up );
	G_CalcMuzzlePoint( self, forward, right, up, muzzle );

	G_WideTrace( &tr, self, LEVEL0_BITE_RANGE, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH, &traceEnt );

	if ( !G_Alive( traceEnt ) || G_OnSameTeam( self, traceEnt ) )
	{
		return false;
	}

	// only allow bites to work against buildables in construction
	if ( traceEnt->s.eType == ET_BUILDABLE && traceEnt->spawned )
	{
		return false;
	}

	traceEnt->entity->Damage((float)LEVEL0_BITE_DMG, self, Vec3::Load(tr.endpos),
	                         Vec3::Load(forward), 0, (meansOfDeath_t)MOD_LEVEL0_BITE);

	SendMeleeHitEvent( self, traceEnt, &tr );

	self->client->ps.weaponTime += LEVEL0_BITE_REPEAT;

	return true;
}
static void MissileImpact( gentity_t *ent, trace_t *trace )
{
	int       dirAsByte, impactFlags;
	const missileAttributes_t *ma = BG_Missile( ent->s.modelindex );
	gentity_t *hitEnt   = &g_entities[ trace->entityNum ];
	gentity_t *attacker = &g_entities[ ent->r.ownerNum ];

	// Returns whether damage and hit effects should be done and played.
	std::function<int(gentity_t*, trace_t*, gentity_t*)> impactFunc;

	// Check for bounce.
	if ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) &&
	     !HasComponents<HealthComponent>(*hitEnt->entity) )
	{
		BounceMissile( ent, trace );

		if ( !( ent->s.eFlags & EF_NO_BOUNCE_SOUND ) )
		{
			G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 );
		}

		return;
	}

	// Call missile specific impact functions.
	switch( ent->s.modelindex )
	{
		case MIS_GRENADE:      impactFunc = ImpactGrenade;     break;
		case MIS_FIREBOMB:     impactFunc = ImpactGrenade;     break;
		case MIS_FLAMER:       impactFunc = ImpactFlamer;      break;
		case MIS_FIREBOMB_SUB: impactFunc = ImpactFirebombSub; break;
		case MIS_LOCKBLOB:     impactFunc = ImpactLockblock;   break;
		case MIS_SLOWBLOB:     impactFunc = ImpactSlowblob;    break;
		case MIS_HIVE:         impactFunc = ImpactHive;        break;
		default:               impactFunc = DefaultImpactFunc; break;
	}

	impactFlags = impactFunc( ent, trace, hitEnt );

	// Deal impact damage.
	if ( !( impactFlags & MIF_NO_DAMAGE ) )
	{
		if ( ent->damage && G_Alive( hitEnt ) )
		{
			vec3_t dir;

			BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, dir );

			if ( VectorNormalize( dir ) == 0 )
			{
				dir[ 2 ] = 1; // stepped on a grenade
			}

			int dflags = 0;
			if ( !ma->doLocationalDamage ) dflags |= DAMAGE_NO_LOCDAMAGE;
			if ( ma->doKnockback )         dflags |= DAMAGE_KNOCKBACK;

			hitEnt->entity->Damage(ent->damage * MissileTimeDmgMod(ent), attacker,
			                       Vec3::Load(trace->endpos), Vec3::Load(dir), dflags,
			                       (meansOfDeath_t)ent->methodOfDeath);
		}

		// splash damage (doesn't apply to person directly hit)
		if ( ent->splashDamage )
		{
			G_RadiusDamage( trace->endpos, ent->parent,
			                ent->splashDamage * MissileTimeSplashDmgMod( ent ),
			                ent->splashRadius, hitEnt, ( ma->doKnockback ? DAMAGE_KNOCKBACK : 0 ),
			                ent->splashMethodOfDeath );
		}
	}

	// Play hit effects and remove the missile.
	if ( !( impactFlags & MIF_NO_EFFECT ) )
	{
		// Use either the trajectory direction or the surface normal for the hit event.
		if ( ma->impactFlightDirection )
		{
			vec3_t trajDir;
			BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, trajDir );
			VectorNormalize( trajDir );
			dirAsByte = DirToByte( trajDir );
		}
		else
		{
			dirAsByte = DirToByte( trace->plane.normal );
		}

		// Add hit event.
		if ( HasComponents<HealthComponent>(*hitEnt->entity) )
		{
			G_AddEvent( ent, EV_MISSILE_HIT_ENTITY, dirAsByte );

			ent->s.otherEntityNum = hitEnt->s.number;
		}
		else if ( trace->surfaceFlags & SURF_METAL )
		{
			G_AddEvent( ent, EV_MISSILE_HIT_METAL, dirAsByte );
		}
		else
		{
			G_AddEvent( ent, EV_MISSILE_HIT_ENVIRONMENT, dirAsByte );
		}

		ent->freeAfterEvent = true;

		// HACK: Change over to a general entity at the point of impact.
		ent->s.eType = ET_GENERAL;

		// Prevent map models from appearing at impact point.
		ent->s.modelindex = 0;

		// Save net bandwith.
		G_SnapVectorTowards( trace->endpos, ent->s.pos.trBase );

		G_SetOrigin( ent, trace->endpos );

		trap_LinkEntity( ent );
	}
	// If no impact happened, check if we should continue or free ourselves.
	else if ( !( impactFlags & MIF_NO_FREE ) )
	{
		G_FreeEntity( ent );
	}
}
Beispiel #10
0
bool G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage,
                         float radius, gentity_t *ignore, int dflags, int mod, team_t testHit )
{
	float     points, dist;
	gentity_t *ent;
	int       entityList[ MAX_GENTITIES ];
	int       numListedEntities;
	vec3_t    mins, maxs;
	vec3_t    v;
	vec3_t    dir;
	int       i, e;
	bool  hitSomething = false;

	if ( radius < 1 )
	{
		radius = 1;
	}

	for ( i = 0; i < 3; i++ )
	{
		mins[ i ] = origin[ i ] - radius;
		maxs[ i ] = origin[ i ] + radius;
	}

	numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );

	for ( e = 0; e < numListedEntities; e++ )
	{
		ent = &g_entities[ entityList[ e ] ];

		if ( ent == ignore )
		{
			continue;
		}

		// find the distance from the edge of the bounding box
		for ( i = 0; i < 3; i++ )
		{
			if ( origin[ i ] < ent->r.absmin[ i ] )
			{
				v[ i ] = ent->r.absmin[ i ] - origin[ i ];
			}
			else if ( origin[ i ] > ent->r.absmax[ i ] )
			{
				v[ i ] = origin[ i ] - ent->r.absmax[ i ];
			}
			else
			{
				v[ i ] = 0;
			}
		}

		dist = VectorLength( v );

		if ( dist >= radius )
		{
			continue;
		}

		points = damage * ( 1.0 - dist / radius );

		if ( G_CanDamage( ent, origin ) )
		{
			if ( testHit == TEAM_NONE )
			{
				VectorSubtract( ent->r.currentOrigin, origin, dir );
				// push the center of mass higher than the origin so players
				// get knocked into the air more
				dir[ 2 ] += 24;
				VectorNormalize( dir );

				hitSomething = ent->entity->Damage(points, attacker, Vec3::Load(origin), Vec3::Load(dir),
				                                   (DAMAGE_NO_LOCDAMAGE | dflags), (meansOfDeath_t)mod);
			}
			else if ( G_Team( ent ) == testHit && G_Alive( ent ) )
			{
				return true;
			}
		}
	}

	return hitSomething;
}