If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle
See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// ge_air.c -- airbound entities
#include "game.h"
/*
==============================================================================
Enemy Type Management
==============================================================================
*/
airunit_type_t *airtypes = 0;
/*
==============
Air_FreeType
Frees a given airtype
==============
*/
void Air_FreeType(airunit_type_t *type)
{
lassert(type->used > 0);
type->used--;
if (type->used)
return;
if (type->sprite)
SPR_Free(type->sprite);
if (type->next)
type->next->pprev = type->pprev;
*type->pprev = type->next;
L_Free(type);
}
/*
==============
AttackTypeForString
Returns the appropriate EW constant
==============
*/
static int AttackTypeForString(const char *sztype, LParser *p)
{
if (!strcmp(sztype, "shot"))
return EW_SHOT;
if (!strcmp(sztype, "homing-shot"))
return EW_HOMINGSHOT;
if (!strcmp(sztype, "left-shot"))
return EW_LEFTSHOT;
if (!strcmp(sztype, "right-shot"))
return EW_RIGHTSHOT;
if (!strcmp(sztype, "missile"))
return EW_MISSILE;
if (!strcmp(sztype, "fireball"))
return EW_FIREBALL;
if (p)
throw p->Error("unknown attack type %s", sztype);
else
throw LError("unknown attack type %s", sztype);
}
/*
==============
ParseAttack
Parse an attack definition
==============
*/
static void ParseAttack(const char *name, au_attack_t *attack, LParser *p)
{
p->NextToken();
attack->wait = 0;
attack->repeat = 1;
attack->delay = 0;
if (p->tok.type == TOK_STRING || p->tok.type == TOK_QUOTED)
{
attack->type = AttackTypeForString(p->tok.string, p);
attack->attach = p->Integer();
}
else if (p->tok.type == '{')
{
// fire { "xxx" attach wait [ * repeat delay ] }
attack->type = AttackTypeForString(p->AnyString(), p);
attack->attach = p->Integer();
attack->wait = p->Float();
p->NextToken();
if (p->tok.type == '*')
{
attack->repeat = p->Integer();
attack->delay = p->Float();
p->NextToken();
}
if (p->tok.type != '}')
throw p->Error("'}' expected after fire definition");
}
else
throw p->Error("syntax error in fire definition");
}
/*
==============
Air_GetType
Parse an enemy type definition
==============
*/
airunit_type_t *Air_GetType(const char *name)
{
airunit_type_t *type;
char path[MAX_OSPATH];
// Check if it's already loaded
for(type = airtypes; type; type = type->next) {
if (!strcmp(type->name, name)) {
type->used++;
return type;
}
}
// Load the description file
type = 0;
try
{
LParser p;
const char *keyword;
const char *parm;
snprintf(path, sizeof(path), "air/%s.txt", name);
p.AddFile(path);
type = (airunit_type_t *)L_Malloc(sizeof(airunit_type_t), TAG_UNIT);
memset(type, 0, sizeof(airunit_type_t));
type->used = 1;
xstrcpy(type->name, sizeof(type->name), name);
for(;;) {
keyword = p.TryString();
if (!keyword)
break;
if (!strcmp(keyword, "boss"))
{
type->bBoss = true;
}
else if (!strcmp(keyword, "sprite"))
{
char buf[MAX_OSPATH];
parm = p.AnyString();
p.RelativePath(buf, sizeof(buf), parm);
type->sprite = SPR_Get(buf);
}
else if (!strcmp(keyword, "movement"))
{
keyword = p.AnyString();
if (!strcmp(keyword, "dumb"))
type->iMovement = AIRMOVE_DUMB;
else if (!strcmp(keyword, "chase"))
type->iMovement = AIRMOVE_CHASE;
else
throw p.Error("unknown movement type %s", keyword);
}
else if (!strcmp(keyword, "mparm1"))
{
type->flMParm1 = p.Float();
}
else if (!strcmp(keyword, "mparm2"))
{
type->flMParm2 = p.Float();
}
else if (!strcmp(keyword, "speed"))
{
type->flSpeed = p.Float();
}
else if (!strcmp(keyword, "health"))
{
type->flHealth = p.Float();
}
else if (!strcmp(keyword, "money"))
{
type->iMoney = p.Integer();
}
else if (!strcmp(keyword, "attackcycle"))
{
type->flAttackCycle = p.Float();
}
else if (!strcmp(keyword, "fire"))
{
if (type->numattacks >= MAX_ATTACKS)
throw p.Error("too many attacks");
ParseAttack(name, &type->attacks[type->numattacks], &p);
type->numattacks++;
}
else if (!strcmp(keyword, "trail"))
{
type->iTrailAttach = p.Integer();;
xstrcpy(type->szTrailSprite, sizeof(type->szTrailSprite), p.AnyString());
type->iTrailTime = p.Integer();
}
else
throw p.Error("unknown keyword %s", keyword);
}
if (!type->sprite)
throw LError("%s: no sprite given", name);
}
catch(...)
{
if (type)
L_Free(type);
throw;
}
type->pprev = &airtypes;
type->next = airtypes;
if (type->next)
type->next->pprev = &type->next;
airtypes = type;
return type;
}
/*
==============================================================================
AirSprite IMPLEMENTATION
==============================================================================
*/
class AirSprite : public AirBase {
public:
int m_iDieTime;
public:
AirSprite(const char *pszName, int iLoops, float x, float y, int iAnimTime = 0);
AirSprite(sprite_t *pSprite, int iLoops, float x, float y, int iAnimTime = 0);
virtual void Think();
virtual bool IsGhost() { return true; }
private:
void Init(int iLoops);
};
/*
==============
AirSprite::AirSprite
Load the requested sprite
==============
*/
AirSprite::AirSprite(const char *pszName, int iLoops, float x, float y, int iAnimTime)
: AirBase(LYR_SPRITE, pszName, x, y)
{
m_iAnimTime = iAnimTime;
Init(iLoops);
}
AirSprite::AirSprite(sprite_t *pSprite, int iLoops, float x, float y, int iAnimTime)
: AirBase(LYR_SPRITE, pSprite, x, y)
{
m_iAnimTime = iAnimTime;
Init(iLoops);
}
/*
==============
AirSprite::Init
Set the death timer
==============
*/
void AirSprite::Init(int iLoops)
{
if (iLoops <= 0)
m_iDieTime = -1;
else {
float time;
time = (float)m_pSprite->base.numframes / (float)m_pSprite->base.speed;
time *= iLoops;
m_iDieTime = (int)(time * 1000);
}
}
/*
==============
AirSprite::Think
Advance the animation, kill the sprite after too many loops
==============
*/
void AirSprite::Think()
{
m_iAnimTime += LOGICTIME;
if (m_iDieTime >= 0 && m_iAnimTime >= m_iDieTime)
m_bRemove = true;
}
/*
==============================================================================
MISSILES
==============================================================================
*/
#define MM_NORMAL 0
// fly straight down or up
#define MM_TARGET 1
// target an enemy
#define MM_DFIRE 2
// dumbfire missile
#define MAX_MISS_SPRITES 6
typedef struct missile_type_s {
struct missile_type_s **pprev, *next;
int used;
char name[64];
int layer;
int numsprites;
sprite_t *sprites[MAX_MISS_SPRITES];
int movement;
float speed;
float damage;
int numtrails;
sprite_t *trails[MAX_MISS_SPRITES];
int trailtime;
} missile_type_t;
missile_type_t *missile_types = 0;
/*
==============
Air_DoFreeMissileType
Actually free a missile
==============
*/
void Air_DoFreeMissileType(missile_type_t *type)
{
int i;
if (type->next)
type->next->pprev = type->pprev;
if (type->pprev)
*type->pprev = type->next;
for(i = 0; i < type->numsprites; i++)
SPR_Free(type->sprites[i]);
for(i = 0; i < type->numtrails; i++)
SPR_Free(type->trails[i]);
L_Free(type);
}
/*
==============
Air_GetMissileType
Find the missile data and sprites and load them
==============
*/
static void ParseSpriteList(int *pnumsprites, sprite_t **sprites, int maxsprites,
LParser *p)
{
p->Expect("[");
for(;;) {
p->NextToken();
if (p->tok.type == ']')
break;
if (p->tok.type != TOK_STRING && p->tok.type != TOK_QUOTED)
throw p->Error("] or sprite name expected");
if (*pnumsprites >= maxsprites)
throw p->Error("too many sprites");
sprites[*pnumsprites] = SPR_Get(p->tok.string);
(*pnumsprites)++;
}
}
missile_type_t *Air_GetMissileType(const char *name)
{
missile_type_t *type;
char path[MAX_OSPATH];
// Lookup the missile
for(type = missile_types; type; type = type->next) {
if (!strcmp(type->name, name)) {
type->used++;
return type;
}
}
// Allocate a new missile type
type = 0;
try
{
LParser p;
const char *keyword;
snprintf(path, sizeof(path), "missiles/%s.txt", name);
p.AddFile(path);
type = (missile_type_t *)L_Malloc(sizeof(missile_type_t), TAG_UNIT);
memset(type, 0, sizeof(missile_type_t));
type->used = 1;
type->layer = LYR_MISS;
xstrcpy(type->name, sizeof(type->name), name);
for(;;) {
keyword = p.TryString();
if (!keyword)
break;
if (!strcmp(keyword, "sprite"))
{
if (type->numsprites)
throw p.Error("duplicate sprite");
ParseSpriteList(&type->numsprites, type->sprites, MAX_MISS_SPRITES, &p);
}
else if (!strcmp(keyword, "speed"))
{
type->speed = p.Float();
}
else if (!strcmp(keyword, "damage"))
{
type->damage = p.Float();
}
else if (!strcmp(keyword, "low"))
{
type->layer = LYR_BOMB;
}
else if (!strcmp(keyword, "trail"))
{
if (type->numtrails)
throw p.Error("duplicate trail");
ParseSpriteList(&type->numtrails, type->trails, MAX_MISS_SPRITES, &p);
type->trailtime = p.Integer();
}
else if (!strcmp(keyword, "move"))
{
keyword = p.AnyString();
if (!strcmp(keyword, "normal"))
type->movement = MM_NORMAL;
else if (!strcmp(keyword, "target"))
type->movement = MM_TARGET;
else if (!strcmp(keyword, "dfire"))
type->movement = MM_DFIRE;
else
throw p.Error("unknown movement type %s", keyword);
}
else
throw p.Error("unknown keyword %s", keyword);
}
if (!type->numsprites)
throw LError("%s: no sprite(s) defined", path);
}
catch(...)
{
if (type)
Air_DoFreeMissileType(type);
throw;
}
type->pprev = &missile_types;
type->next = missile_types;
if (type->next)
type->next->pprev = &type->next;
missile_types = type;
return type;
}
/*
==============
Air_FreeMissileType
Reduce the missile use counter - don't actually free though,
to avoid reloading missile types all the time
==============
*/
void Air_FreeMissileType(missile_type_t *type)
{
type->used--;
}
class AirMissile : public AirBase {
public:
missile_type_t *m_pType;
bool m_bGoodGuy;
int m_iCycle;
int m_iFuse;
sprite_t *m_pTrail;
int m_iTrailTimer;
int m_iLogicTime; // logic time to be used by the missile
public:
virtual ~AirMissile();
virtual void Think();
virtual bool IsGoodGuy() { return m_bGoodGuy; }
virtual void Collide(AirBase *pOther);
static AirMissile *Spawn(bool bGoodGuy, const char *name, int iCycle,
float x, float y, bool bOverride, float vx, float vy, AirBase *pEnemy);
private:
AirMissile(bool bGoodGuy, missile_type_t *pType, sprite_t *pSprite, sprite_t *pTrail,
float x, float y, bool bOverride, float vx, float vy, AirBase *pEnemy);
void Init(bool bOverride, float vx, float vy, AirBase *pEnemy);
};
/*
==============
AirMissile::AirMissile
Missile constructor
==============
*/
AirMissile::AirMissile(bool bGoodGuy, missile_type_t *pType, sprite_t *pSprite, sprite_t *pTrail,
float x, float y, bool bOverride, float vx, float vy, AirBase *pEnemy)
: AirBase(pType->layer, pSprite, x, y)
{
m_bGoodGuy = bGoodGuy;
m_pType = pType;
m_pType->used++;
m_pTrail = pTrail;
if (m_pTrail)
m_pTrail->used++;
m_iTrailTimer = pType->trailtime;
m_iFuse = m_iCycle = 0;
Init(bOverride, vx, vy, pEnemy);
}
/*
==============
AirMissile::Init
Initialize velocity etc..
==============
*/
void AirMissile::Init(bool bOverride, float vx, float vy, AirBase *pEnemy)
{
AirBase *pOther;
float f;
if (m_bGoodGuy)
m_iLogicTime = ge->m_iFrameTime;
else
m_iLogicTime = LOGICTIME; // badguy missiles accelerate with level
if (bOverride)
{
m_vel[0] = vx;
m_vel[1] = vy;
f = sqrt(m_vel[0]*m_vel[0] + m_vel[1]*m_vel[1]);
if (!f) f = 1;
f = m_pType->speed / f;
m_vel[0] *= f;
m_vel[1] *= f;
}
else
{
switch(m_pType->movement) {
default:
case MM_NORMAL:
m_vel[0] = 0;
if (m_bGoodGuy)
m_vel[1] = -m_pType->speed;
else
m_vel[1] = m_pType->speed;
m_vel[0] += vx;
m_vel[1] += vy;
break;
case MM_TARGET:
if (pEnemy)
pOther = pEnemy;
else
pOther = ge->Air_FindNearest(m_x, m_y, !m_bGoodGuy, 0, 0);
if (pOther)
{
float ox, oy;
ox = pOther->m_x + 24.0 * (2.0*RandFloat() - 1.0);
oy = pOther->m_y + 24.0 * (2.0*RandFloat() - 1.0);
m_vel[0] = ox - m_x;
m_vel[1] = oy - m_y;
f = sqrt(m_vel[0]*m_vel[0] + m_vel[1]*m_vel[1]);
if (!f) f = 1;
f = m_pType->speed / f;
m_vel[0] *= f;
m_vel[1] *= f;
}
else
m_bRemove = true;
m_vel[0] += vx;
m_vel[1] += vy;
break;
case MM_DFIRE:
// dumbfire missiles are special: they're launched horizontally up to
// a certain distance, and then start off vertically later
m_vel[0] = vx * 100.0 * (0.3 + 0.7 * RandFloat());
m_vel[1] = vy;
m_iFuse = 500;
break;
}
}
}
/*
==============
AirMissile::~AirMissile
Clean up allocated resources
==============
*/
AirMissile::~AirMissile()
{
Air_FreeMissileType(m_pType);
if (m_pTrail)
SPR_Free(m_pTrail);
}
/*
==============
AirMissile::Think
Moves the missile; remove if out of screen
==============
*/
void AirMissile::Think()
{
float dx, dy;
float t;
float x, y;
bool bTrail;
if (m_pType->movement == MM_DFIRE && !m_iCycle)
{
bTrail = false;
t = m_vel[0] * -0.9;
m_vel[0] += t * m_iLogicTime / 1000.0;
// if (m_x <= 12 || m_x >= 628)
// m_vel[0] = 0;
m_iFuse -= m_iLogicTime;
if (m_iFuse <= 0) {
m_vel[0] = 0;
if (m_bGoodGuy)
m_vel[1] = -m_pType->speed;
else
m_vel[1] = m_pType->speed;
bTrail = true;
m_iCycle = 1;
}
}
else
bTrail = true;
if (bTrail && m_pTrail) {
m_iTrailTimer -= m_iLogicTime;
while(m_iTrailTimer <= 0) {
t = (float)-m_iTrailTimer / m_iLogicTime;
x = m_lastx * t + m_x * (1.0 - t);
y = m_lasty * t + m_y * (1.0 - t);
new AirSprite(m_pTrail, 1, m_x, m_y, -m_iTrailTimer);
m_iTrailTimer += m_pType->trailtime;
}
}
m_iAnimTime += m_iLogicTime;
dx = m_vel[0] * m_iLogicTime / 1000.0;
dy = m_vel[1] * m_iLogicTime / 1000.0;
Move(dx, dy);
if (SHADOWY(m_y)+m_pSprite->maxs[1] < -2 || SHADOWY(m_y)+m_pSprite->mins[1] > 482)
m_bRemove = true;
}
/*
==============
AirMissile::Collide
Collided with something: kill self, deal damage and show hit sprite.
Note that the latter is _not_ done if we're out of the screen
==============
*/
void AirMissile::Collide(AirBase *pOther)
{
if (m_y+m_pSprite->maxs[1] >= 0 && m_y+m_pSprite->mins[1] <= 480) {
pOther->Damage(this, m_pType->damage);
if (m_bGoodGuy)
new AirSprite("effects/hit0", 1, m_x, m_y);
else
new AirSprite("effects/exp1", 1, m_x, m_y);
}
m_bRemove = true;
}
/*
==============
AirMissile::Spawn
Create a missile out of thin air.
If bOverride is true, vx/vy are used as velocity. Otherwise, the
velocity is determined automatically,
pEnemy overrides the target for targeting missiles.
==============
*/
AirMissile *AirMissile::Spawn(bool bGoodGuy, const char *name, int iCycle,
float x, float y, bool bOverride, float vx, float vy,
AirBase *pEnemy)
{
AirMissile *pMiss;
missile_type_t *type;
sprite_t *sprite;
sprite_t *trail;
type = Air_GetMissileType(name);
sprite = type->sprites[iCycle % type->numsprites];
if (!type->numtrails)
trail = 0;
else
trail = type->trails[iCycle % type->numtrails];
pMiss = new AirMissile(bGoodGuy, type, sprite, trail, x, y, bOverride, vx, vy,
pEnemy);
Air_FreeMissileType(type);
return pMiss;
}
/*
==============================================================================
AirUnit IMPLEMENTATION
==============================================================================
*/
/*
==============
AirUnit::AirUnit
Initialize using default values
==============
*/
AirUnit::AirUnit(const char *pszSprite, float flSpeed, float x, float y, float health)
: AirBase(LYR_UNIT, pszSprite, x, y)
{
m_flHealth = health;
m_flLowHealth = health / 20;
m_iDeathTime = 0;
m_flDmgExplosions = 10;
m_iDmgExplosionTimer = 0;
m_flSpeed = flSpeed;
m_flDeathMoveAngle = 2*M_PI*RandFloat();
m_flDeathAngleSpeed = 10.0 + 10.0*RandFloat();
if (rand() % 2)
m_flDeathAngleSpeed = -m_flDeathAngleSpeed;
}
AirUnit::AirUnit(sprite_t *sprite, float flSpeed, float x, float y, float health)
: AirBase(LYR_UNIT, sprite, x, y)
{
m_flHealth = health;
m_flLowHealth = health / 20;
m_iDeathTime = 0;
m_flDmgExplosions = 10;
m_iDmgExplosionTimer = 0;
m_flSpeed = flSpeed;
m_flDeathMoveAngle = 2*M_PI*RandFloat();
m_flDeathAngleSpeed = 10.0 + 10.0*RandFloat();
if (rand() % 2)
m_flDeathAngleSpeed = -m_flDeathAngleSpeed;
}
/*
==============
AirUnit::Think
Thinking hub; don't override
==============
*/
void AirUnit::Think()
{
float fract;
int frametime = IsGoodGuy() ? ge->m_iFrameTime : LOGICTIME;
if (m_flHealth <= 0) {
float mx, my;
m_flDeathMoveAngle += m_flDeathAngleSpeed * frametime / 1000.0;
m_flDeathMoveAngle *= 0.96;
m_vel[0] = cos(m_flDeathMoveAngle) * m_flSpeed * 2;
m_vel[1] = sin(m_flDeathMoveAngle) * m_flSpeed * 2;
mx = m_vel[0] * frametime / 1000.0;
my = m_vel[1] * frametime / 1000.0;
Move(mx, my);
m_iDeathTime -= frametime;
if (m_iDeathTime <= 0) {
FinalDeath();
m_bRemove = true;
new AirSprite("effects/exp0", 1, m_x, m_y); // final explosion
}
} else
Run();
// lowhealth explosions
if (m_flLowHealth > 0 && m_flHealth < m_flLowHealth)
{
m_iDmgExplosionTimer -= frametime;
if (m_iDmgExplosionTimer <= 0) {
float freq, x, y, t;
fract = m_flHealth;
if (fract < 0)
fract = 0;
fract /= m_flLowHealth;
freq = m_flDmgExplosions * (1.0 - (fract*0.6));
if (m_flHealth <= 0)
freq *= 3;
do {
t = RandFloat();
x = m_pSprite->mins[0] * t + m_pSprite->maxs[0] * (1.0-t);
t = RandFloat();
y = m_pSprite->mins[1] * t + m_pSprite->maxs[1] * (1.0-t);
x += m_x;
y += m_y;
new AirSprite("effects/exp1", 1, x, y);
m_iDmgExplosionTimer += (int)(1000.0 / freq);
} while(m_iDmgExplosionTimer <= 0);
}
}
}
/*
==============
AirUnit::Damage
Apply damage
==============
*/
void AirUnit::Damage(AirBase *pInflictor, float flAmt)
{
float flOldHealth;
if (ge->m_iState == ge_win || ge->m_iState == ge_abort)
return;
flOldHealth = m_flHealth;
m_flHealth -= flAmt;
DamageNotice(flAmt);
}
/*
==============================================================================
AirEnemy IMPLEMENTATION
==============================================================================
*/
typedef struct {
float x, y;
} mnode_t;
class AirEnemy : public AirUnit {
public:
airunit_type_t *m_pType;
int m_iAttackTime; // time the current attack lasts
int m_iAttackRepeats[MAX_ATTACKS];
int m_iMovement;
bool m_bDieIfOffscreen;
bool m_bFollowNodes;
int m_iNumNodes;
mnode_t *m_pNodes;
int m_iNodeIdx;
int m_iChaseState; // 0 = innocent, 1 = chasing
int m_iChaseDelay; // ms until chase
int m_iTrailTimer;
public:
AirEnemy(airjob_t *aj);
virtual ~AirEnemy();
virtual void FinalDeath();
virtual void Draw();
virtual void MoveAI();
virtual void Run();
virtual void Collide(AirBase *pOther);
void Fire(int type, float x, float y);
void BounceX();
void FollowNodes();
void InitMoveLogic();
void MoveChase();
};
/*
==============
AirEnemy::AirEnemy
Initialize the enemy data. Note that pType ownership is transferred.
==============
*/
AirEnemy::AirEnemy(airjob_t *aj)
: AirUnit(aj->type->sprite, aj->type->flSpeed, aj->x,
FIRSTSHADOWY(aj->type->sprite->maxs[1]),
aj->type->flHealth * ge->m_flHealthFactor)
{
int i;
float x, y;
m_pType = aj->type;
m_pType->used++;
m_iAttackTime = -99999;
memset(m_iAttackRepeats, 0, sizeof(m_iAttackRepeats));
m_iMovement = m_pType->iMovement;
m_bDieIfOffscreen = false;
m_bFollowNodes = true;
m_iNodeIdx = 0;
m_iNumNodes = aj->nodes.size();
if (m_iNumNodes) {
m_pNodes = (mnode_t *)L_Malloc(sizeof(mnode_t)*m_iNumNodes, TAG_UNIT);
x = m_x;
y = m_y;
for(i = 0; i < m_iNumNodes; i++) {
x += aj->nodes[i].dx;
y += aj->nodes[i].dy;
m_pNodes[i].x = x;
m_pNodes[i].y = y;
}
} else
m_pNodes = 0;
FollowNodes();
m_iTrailTimer = m_pType->iTrailTime;
ge->m_player.stats.moneymax += m_pType->iMoney;
}
/*
==============
AirEnemy::~AirEnemy
Free associated data
==============
*/
AirEnemy::~AirEnemy()
{
if (m_pNodes)
L_Free(m_pNodes);
Air_FreeType(m_pType);
}
/*
==============
AirEnemy::Draw
Special draw (engine)
==============
*/
void AirEnemy::Draw()
{
float x, y;
float time;
x = LERP(m_lastx, m_x);
y = LERP(m_lasty, m_y);
time = LERP(m_iAnimTime-LOGICTIME, m_iAnimTime);
SPR_Draw(m_pSprite, x, y, -1, time);
SPR_Draw(m_pSprite, x, y, 1001, time); // engine
}
/*
==============
AirEnemy::FinalDeath
Called just before the remove flag is set
==============
*/
void AirEnemy::FinalDeath()
{
ge->m_player.data.money += m_pType->iMoney;
ge->m_player.stats.money += m_pType->iMoney;
}
/*
==============
AirEnemy::Fire
Fire a single bullet or rocket
==============
*/
void AirEnemy::Fire(int type, float x, float y)
{
switch(type) {
case EW_SHOT:
AirMissile::Spawn(false, "shot", 0, x, y, false, 0, 0, 0);
break;
case EW_HOMINGSHOT:
AirMissile::Spawn(false, "homingshot", 0, x, y, false, 0, 0, 0);
break;
case EW_LEFTSHOT:
AirMissile::Spawn(false, "shot", 0, x, y, true, -1, 1, 0);
break;
case EW_RIGHTSHOT:
AirMissile::Spawn(false, "shot", 0, x, y, true, 1, 1, 0);
break;
case EW_MISSILE:
AirMissile::Spawn(false, "missile", 0, x, y, false, 0, 0, 0);
break;
case EW_FIREBALL:
AirMissile::Spawn(false, "fireball", 0, x, y, false, 0, 0, 0);
break;
}
}
/*
==============
AirEnemy::BounceX
Bounce off the screen boundaries
==============
*/
void AirEnemy::BounceX()
{
if ((m_x < 12-m_pSprite->mins[0] && m_vel[0] < 0) ||
(m_x > 628-m_pSprite->maxs[0] && m_vel[0] > 0))
m_vel[0] = -m_vel[0];
}
/*
==============
AirEnemy::FollowNodes
Logic for following nodes. Every enemy first follows the nodes set by the
level designer. Once the last node is reached, the autonomous move logic
begins to act.
==============
*/
void AirEnemy::FollowNodes()
{
if (!m_bFollowNodes)
return;
restart:
if (m_iNodeIdx < m_iNumNodes)
{
float dx, dy;
dx = m_pNodes[m_iNodeIdx].x - m_x;
dy = m_pNodes[m_iNodeIdx].y - m_y;
if (dx < 0.1 && dy < 0.1)
{
m_iNodeIdx++;
goto restart;
}
if (fabs(dx) > fabs(dy))
{
m_vel[0] = m_pType->flSpeed * dx / fabs(dx);
m_vel[1] = m_pType->flSpeed * dy / fabs(dx);
}
else
{
m_vel[1] = m_pType->flSpeed * dy / fabs(dy);
m_vel[0] = m_pType->flSpeed * dx / fabs(dy);
}
}
else
{
m_bFollowNodes = false;
InitMoveLogic();
}
}
/*
===============
AirEnemy::InitMoveLogic
Initialize the autonomous move logic once the last node has been reached.
===============
*/
void AirEnemy::InitMoveLogic()
{
if (m_iMovement == AIRMOVE_CHASE)
{
float dx = (float)rand() * (0.2/RAND_MAX); // 0..0.2
m_vel[0] = m_pType->flSpeed * (dx - 0.1);
m_vel[1] = m_pType->flSpeed;
m_iChaseState = 0;
m_iChaseDelay = rand() % (int)(m_pType->flMParm1 * 1000);
}
else
{
m_vel[0] = 0;
m_vel[1] = m_pType->flSpeed;
}
}
/*
===============
AirEnemy::MoveChase
Move logic for chase movement type
===============
*/
void AirEnemy::MoveChase()
{
if (m_iChaseState == 0)
{
m_iChaseDelay -= LOGICTIME;
if (m_iChaseDelay < 0)
{
AirBase* pOther;
pOther = ge->Air_FindNearest(m_x, m_y, true, 0, 0);
if (!pOther)
return;
float speed = (1.0 + RandFloat() * (m_pType->flMParm2-1.0)) * m_pType->flSpeed;
float ox, oy;
float f;
ox = pOther->m_x + 24.0 * (2.0*RandFloat() - 1.0);
oy = pOther->m_y + 24.0 * (2.0*RandFloat() - 1.0);
m_vel[0] = ox - m_x;
m_vel[1] = oy - m_y;
f = sqrt(m_vel[0]*m_vel[0] + m_vel[1]*m_vel[1]);
if (!f) f = 1;
f = speed / f;
m_vel[0] *= f;
m_vel[1] *= f;
m_iChaseState = 1;
}
}
}
/*
==============
AirEnemy::MoveAI
Move "intelligently"
==============
*/
void AirEnemy::MoveAI()
{
float dx, dy;
dx = m_vel[0] * LOGICTIME / 1000.0;
dy = m_vel[1] * LOGICTIME / 1000.0;
Move(dx, dy);
if (m_bFollowNodes)
{
if (dx > dy)
{
if ((dx > 0 && m_x >= m_pNodes[m_iNodeIdx].x) ||
(dx < 0 && m_x <= m_pNodes[m_iNodeIdx].x))
{
m_iNodeIdx++;
FollowNodes();
}
}
else
{
if ((dy > 0 && m_y >= m_pNodes[m_iNodeIdx].y) ||
(dy < 0 && m_y <= m_pNodes[m_iNodeIdx].y))
{
m_iNodeIdx++;
FollowNodes();
}
}
}
else
{
if (m_iMovement == AIRMOVE_CHASE)
{
MoveChase();
}
else
{
// dumb movement, simply move downwards
if (m_y > 0)
m_bDieIfOffscreen = true;
}
// remove when out of screen
bool onscreen = IsInScreen();
if (onscreen)
m_bDieIfOffscreen = true;
else if (m_bDieIfOffscreen)
m_bRemove = true;
}
}
/*
==============
AirEnemy::Run
Move the enemy, run AI, ...
==============
*/
void AirEnemy::Run()
{
au_attack_t *attack;
bool finished;
int i;
// Trails
if (m_pType->szTrailSprite[0]) {
spr_attach_t *attach;
float t, x, y;
m_iTrailTimer -= LOGICTIME;
if (m_iTrailTimer <= 0) {
attach = SPR_GetAttach(m_pSprite, m_pType->iTrailAttach);
do {
t = (float)-m_iTrailTimer / LOGICTIME;
//t = 0;
x = m_lastx * t + m_x * (1.0 - t) + attach->offset[0];
y = m_lasty * t + m_y * (1.0 - t) + attach->offset[1];
new AirSprite(m_pType->szTrailSprite, 1, x, y, -m_iTrailTimer);
m_iTrailTimer += m_pType->iTrailTime;
} while(m_iTrailTimer <= 0);
}
}
// Attack
if (m_y > 0) {
if (m_iAttackTime <= -99999) {
m_iAttackTime = (int)(- m_pType->flAttackCycle * (1000 / 4));
if (m_iAttackTime < -500)
m_iAttackTime = -500;
}
m_iAttackTime += LOGICTIME;
if (m_iAttackTime >= 0)
{
finished = true;
for(i = 0, attack = m_pType->attacks; i < m_pType->numattacks; i++, attack++)
{
spr_attach_t *attach;
float ax, ay;
float t;
while(m_iAttackRepeats[i] < attack->repeat) {
finished = false;
t = attack->wait + m_iAttackRepeats[i] * attack->delay;
if (m_iAttackTime < t*1000)
break;
attach = SPR_GetAttach(m_pSprite, m_pType->attacks[i].attach);
ax = m_x + attach->offset[0];
ay = m_y + attach->offset[1];
Fire(attack->type, ax, ay);
m_iAttackRepeats[i]++;
}
}
if (finished) {
m_iAttackTime -= (int)(m_pType->flAttackCycle * 1000);
memset(m_iAttackRepeats, 0, sizeof(m_iAttackRepeats));
}
}
}
// Animation and movement
m_iAnimTime += LOGICTIME;
MoveAI();
}
/*
==============
AirEnemy::Collide
Deal some damage
==============
*/
void AirEnemy::Collide(AirBase *pOther)
{
pOther->Damage(this, m_pType->flHealth);
}
/*
==============================================================================
AirBoss IMPLEMENTATION
==============================================================================
*/
class AirBoss : public AirEnemy {
public:
static AirBoss *m_pBossList;
AirBoss **m_ppPrev;
AirBoss *m_pNext;
int m_iDirChangeDelay;
public:
AirBoss(airjob_t *aj);
virtual ~AirBoss();
virtual void MoveAI();
};
AirBoss *AirBoss::m_pBossList = 0;
/*
==============
AirBoss::AirBoss
Create the boss unit
==============
*/
AirBoss::AirBoss(airjob_t *aj)
: AirEnemy(aj)
{
m_ppPrev = &m_pBossList;
m_pNext = m_pBossList;
if (m_pNext)
m_pNext->m_ppPrev = &m_pNext;
m_pBossList = this;
m_vel[0] = 0.3;
m_vel[1] = 0.9;
m_iDirChangeDelay = 4000;
m_flLowHealth = m_flHealth * 0.2;
m_iDeathTime = 500;
m_flDmgExplosions *= 2.0; // bigger surface -> more explosions
}
/*
==============
AirBoss::~AirBoss
Remove the boss; if we died and were the last finalboss, set gamestate
to bossdestroyed
==============
*/
AirBoss::~AirBoss()
{
// first unlink self
if (m_pNext)
m_pNext->m_ppPrev = m_ppPrev;
*m_ppPrev = m_pNext;
// did we die?
ge->m_iBossDestroyed++;
}
/*
==============
AirBoss::MoveAI
Bosses move differently
==============
*/
void AirBoss::MoveAI()
{
float dx, dy;
int i;
float f;
m_iDirChangeDelay -= LOGICTIME;
if (m_iDirChangeDelay < 0) {
m_iDirChangeDelay += 4500 + rand() % 1000;
i = rand() % 2;
m_vel[i] = -m_vel[i];
m_vel[i] += 0.3 * (2*RandFloat() - 1.0);
if (m_vel[i] < -1.0)
m_vel[i]= -1.0;
else if (m_vel[i] > 1.0)
m_vel[i] = 1.0;
f = sqrt(m_vel[0]*m_vel[0] + m_vel[1]*m_vel[1]);
if (!f)
m_vel[0] = m_vel[1] = 0.2;
else {
m_vel[0] /= f;
m_vel[1] /= f;
}
}
dx = m_pType->flSpeed * m_vel[0] * LOGICTIME / 1000.0;
dy = m_pType->flSpeed * m_vel[1] * LOGICTIME / 1000.0;
Move(dx, dy);
if ((m_x < 12-m_pSprite->mins[0] && m_vel[0] < 0) ||
(m_x > 628-m_pSprite->maxs[0] && m_vel[0] > 0))
m_vel[0] = -m_vel[0];
if ((m_y < 12-m_pSprite->mins[1] && m_vel[1] < 0) ||
(m_y > 240-m_pSprite->maxs[1] && m_vel[1] > 0))
m_vel[1] = -m_vel[1];
}
/*
==============================================================================
AirPlayer IMPLEMENTATION
==============================================================================
*/
/*
==============
AirPlayer::AirPlayer
Spawn the player ship
==============
*/
AirPlayer::AirPlayer(eplayer_t *pPlayer, float x, float y)
: AirUnit("ship", 320, x, y, pPlayer->data.health)
{
m_pPlayer = pPlayer;
m_pPlayer->ship = this;
m_flLowHealth = 10;
m_iDeathTime = 2000;
m_iFireMGDelay = 0;
m_iFirePrimaryDelay = 0;
m_iMGCycle = 0;
m_iPrimaryCycle = 0;
m_bInFire = false;
SelectBestWeapon();
}
/*
==============
AirPlayer::~AirPlayer
==============
*/
AirPlayer::~AirPlayer()
{
m_flHealth = (int)m_flHealth;
if (m_flHealth <= 0)
{
ge->m_iState = ge_loose;
}
else if (ge->m_iPrewinTime < 0 && m_y < 0)
{
ge->m_iState = ge_win;
}
m_pPlayer->data.health = (int)m_flHealth;
m_pPlayer->ship = 0;
}
/*
==============
AirPlayer::Draw
Special draw for the player (muzzleflashes)
==============
*/
void AirPlayer::Draw()
{
float x, y;
float time;
x = LERP(m_lastx, m_x);
y = LERP(m_lasty, m_y);
time = LERP(m_iAnimTime-ge->m_iFrameTime, m_iAnimTime);
SPR_Draw(m_pSprite, x, y, -1, time);
SPR_Draw(m_pSprite, x, y, 1001, time); // engine
if (m_bInFire)
SPR_Draw(m_pSprite, x, y, 1, time);
}
/*
==============
AirPlayer::RunWeapons
Shoot if it's time
==============
*/
void AirPlayer::RunWeapons()
{
AirBase *pOther;
spr_attach_t *attach;
int att1, att2;
float x, y;
float vx;
if (m_iFireMGDelay < 0)
m_iFireMGDelay = 0;
else
m_iFireMGDelay -= ge->m_iFrameTime;
if (m_iFirePrimaryDelay < 0)
m_iFirePrimaryDelay = 0;
else
m_iFirePrimaryDelay -= ge->m_iFrameTime;
if (!m_bInFire)
return;
if (m_iFireMGDelay <= 0) {
m_iFireMGDelay += 120; // 1 shot / X ms
m_iMGCycle++;
attach = SPR_GetAttach(m_pSprite, 1);
x = m_x + attach->offset[0];
y = m_y + attach->offset[1];
AirMissile::Spawn(true, "mg", m_iMGCycle, x, y, false, 0, 0, 0);
attach = SPR_GetAttach(m_pSprite, 2);
x = m_x + attach->offset[0];
y = m_y + attach->offset[1];
AirMissile::Spawn(true, "mg", m_iMGCycle, x, y, false, 0, 0, 0);
}
if (m_iFirePrimaryDelay <= 0) {
switch(m_iCurWeapon) {
case WEAP_ROCKET:
m_iFirePrimaryDelay += 300;
m_iPrimaryCycle++;
if (m_iPrimaryCycle % 2) {
att1 = 3;
att2 = 4;
} else {
att1 = 1;
att2 = 2;
}
attach = SPR_GetAttach(m_pSprite, att1);
x = m_x + attach->offset[0] + 2;
y = m_y + attach->offset[1];
AirMissile::Spawn(true, "rocket", m_iPrimaryCycle, x, y, false, 0, 0, 0);
attach = SPR_GetAttach(m_pSprite, att2);
x = m_x + attach->offset[0] - 2;
y = m_y + attach->offset[1];
AirMissile::Spawn(true, "rocket", m_iPrimaryCycle, x, y, false, 0, 0, 0);
break;
case WEAP_DUMBFIRE:
m_iFirePrimaryDelay += 200;
m_iPrimaryCycle++;
if (m_iPrimaryCycle % 2) {
attach = SPR_GetAttach(m_pSprite, 3);
vx = -1;
} else {
attach = SPR_GetAttach(m_pSprite, 4);
vx = 1;
}
x = m_x + attach->offset[0];
y = m_y + attach->offset[1];
AirMissile::Spawn(true, "dfire", m_iPrimaryCycle, x, y, false, vx, 0, 0);
break;
case WEAP_ATG:
m_iFirePrimaryDelay += 120;
m_iPrimaryCycle++;
attach = SPR_GetAttach(m_pSprite, 3);
x = m_x + attach->offset[0];
y = m_y + attach->offset[1];
pOther = ge->Air_FindNearest(x, y, false, -1, -1);
if (pOther)
AirMissile::Spawn(true, "atg", m_iPrimaryCycle, m_x, m_y,
false, m_vel[0] / 10, m_vel[1] / 10, pOther);
attach = SPR_GetAttach(m_pSprite, 4);
x = m_x + attach->offset[0];
y = m_y + attach->offset[1];
pOther = ge->Air_FindNearest(x, y, false, 1, -1);
if (pOther)
AirMissile::Spawn(true, "atg", m_iPrimaryCycle, m_x, m_y,
false, m_vel[0] / 10, m_vel[1] / 10, pOther);
break;
case WEAP_SMLASER:
m_iFirePrimaryDelay += 220;
// right laser
attach = SPR_GetAttach(m_pSprite, 4);
x = m_x + attach->offset[0];
y = m_y + attach->offset[1];
AirMissile::Spawn(true, "smlaser", m_iPrimaryCycle, x, y,
false, 0,0,0);
// left laser
attach = SPR_GetAttach(m_pSprite, 3);
x = m_x + attach->offset[0];
y = m_y + attach->offset[1];
AirMissile::Spawn(true, "smlaser", m_iPrimaryCycle, x, y,
false, 0,0,0);
break;
}
}
}
/*
==============
AirPlayer::RunMovement
Perform movement through input
==============
*/
void AirPlayer::RunMovement(byte *keystate)
{
float vx, vy;
float dx, dy;
float t;
vx = vy = 0;
if (m_pPlayer->ctrl.up && keystate[m_pPlayer->ctrl.up])
vy -= 1.0;
if (m_pPlayer->ctrl.down && keystate[m_pPlayer->ctrl.down])
vy += 1.0;
if (m_pPlayer->ctrl.right && keystate[m_pPlayer->ctrl.right])
vx += 1.0;
if (m_pPlayer->ctrl.left && keystate[m_pPlayer->ctrl.left])
vx -= 1.0;
vx *= m_pPlayer->ctrl.speed;
vy *= m_pPlayer->ctrl.speed;
dx = vx * ge->m_iFrameTime / 1000.0;
dy = vy * ge->m_iFrameTime / 1000.0;
Move(dx, dy);
if (!ge->m_iBossDestroyed || ge->m_iBossDestroyed < ge->m_pLevel->m_iNumBosses) {
if (m_x < 5-m_pSprite->mins[0])
m_x = 5-m_pSprite->mins[0];
else if (m_x > 623-m_pSprite->maxs[0])
m_x = 623-m_pSprite->maxs[0];
if (m_y < 14-m_pSprite->mins[1])
m_y = 14-m_pSprite->mins[1];
else if (m_y > 466-m_pSprite->maxs[1])
m_y = 466-m_pSprite->maxs[1];
}
// average the velocity (which is only used in the end sequence)
if (vx || vy)
t = 0.5;
else
t = 0.2;
m_vel[0] = (1.0-t) * m_vel[0] + t * vx;
m_vel[1] = (1.0-t) * m_vel[1] + t * vy;
}
/*
==============
AirPlayer::Run
Mainly input processing
==============
*/
void AirPlayer::Run()
{
byte *keystate = hal->GetKeyState();
if (m_flHealth > 0)
{
m_bInFire = false;
if (m_pPlayer->ctrl.fire && keystate[m_pPlayer->ctrl.fire])
m_bInFire = true;
RunWeapons();
m_iAnimTime += ge->m_iFrameTime;
if (ge->m_iPrewinTime < 0) {
float dx, dy, mx, my;
float f;
// accelerate towards imaginary exitpoint
dx = 320 - m_x;
dy = -100;
f = sqrt(dx*dx + dy*dy);
f = 300 / f;
dx *= f;
dy *= f;
m_vel[0] += dx;
m_vel[1] += dy;
f = sqrt(m_vel[0]*m_vel[0] + m_vel[1]*m_vel[1]);
if (f > 900) {
f = 900 / f;
m_vel[0] *= f;
m_vel[1] *= f;
}
mx = m_vel[0] * ge->m_iFrameTime / 1000.0;
my = m_vel[1] * ge->m_iFrameTime / 1000.0;
m_x += mx;
m_y += my;
if (m_y < -32)
m_bRemove = true;
} else
RunMovement(keystate);
}
}
/*
==============
AirPlayer::Input
Process single-hit keys (e.g. weapon switch)
==============
*/
bool AirPlayer::Input(int code, int c, bool down)
{
if (!down)
return false;
if (code == m_pPlayer->ctrl.weaponswitch) {
NextWeapon();
return true;
}
return false;
}
/*
==============
AirPlayer::Collide
Deal damage to the other guy
==============
*/
void AirPlayer::Collide(AirBase *pOther)
{
pOther->Damage(this, 50);
}
/*
==============
AirPlayer::SelectBestWeapon
==============
*/
void AirPlayer::SelectBestWeapon()
{
m_iCurWeapon = NUM_WEAP-1;
while(m_iCurWeapon >= 0) {
if (m_pPlayer->data.weapons[m_iCurWeapon])
return;
m_iCurWeapon--;
}
m_iFirePrimaryDelay += 100;
}
/*
==============
AirPlayer::NextWeapon
Cycle to the next weapon
==============
*/
void AirPlayer::NextWeapon()
{
int idx;
if (m_iCurWeapon < 0 || m_iCurWeapon >= NUM_WEAP)
m_iCurWeapon = 0;
idx = m_iCurWeapon;
do {
idx++;
if (idx >= NUM_WEAP)
idx = 0;
if (m_pPlayer->data.weapons[idx]) {
m_iCurWeapon = idx;
return;
}
} while(idx != m_iCurWeapon);
m_iCurWeapon = -1;
m_iFirePrimaryDelay += 100;
}
/*
==============
AirPlayer::DamageNotice
Add to stats
==============
*/
void AirPlayer::DamageNotice(float amt)
{
m_pPlayer->stats.shieldslost += amt;
}
/*
==============================================================================
AirBase IMPLEMENTATION
==============================================================================
*/
/*
==============
AirBase::AirBase
Create an air unit
==============
*/
AirBase::AirBase(int iLayer, const char *pszSprite, float x, float y)
{
m_pSprite = SPR_Get(pszSprite);
m_iLayer = iLayer;
m_x = x;
m_y = y;
Init();
}
/*
==============
AirBase::AirBase
Create an air unit with the given sprite (ownership is not transferred)
==============
*/
AirBase::AirBase(int iLayer, sprite_t *pSprite, float x, float y)
{
m_pSprite = pSprite;
m_pSprite->used++;
m_iLayer = iLayer;
m_x = x;
m_y = y;
Init();
}
/*
==============
AirBase::Init
Set member variables to default, link into list
==============
*/
void AirBase::Init()
{
m_bRemove = false;
m_iAnimTime = 0;
m_lastx = m_x;
m_lasty = m_y;
m_vel[0] = m_vel[1] = 0;
m_ppPrev = &ge->units_air[m_iLayer];
m_pNext = *m_ppPrev;
if (m_pNext)
m_pNext->m_ppPrev = &m_pNext;
*m_ppPrev = this;
}
/*
==============
AirBase::~AirBase
Free an air unit structure; you should normally set remove to true
instead of calling this directly
==============
*/
AirBase::~AirBase()
{
SPR_Free(m_pSprite);
if (m_pNext)
m_pNext->m_ppPrev = m_ppPrev;
*m_ppPrev = m_pNext;
}
/*
===============
AirBase::IsInScreen
Return true if this air entity is visible on screen.
===============
*/
bool AirBase::IsInScreen()
{
if (m_x < -2*m_pSprite->maxs[0] ||
m_x > 640-(2*m_pSprite->mins[0]) ||
m_y < -2*m_pSprite->maxs[1] ||
m_y > 480-(2*m_pSprite->mins[1]))
return false;
else
return true;
}
/*
==============
AirBase::Move -- PIXEL PRECISE --
Move the given distance, colliding with other units as appropriate
==============
*/
#define MAX_HIT 4
void AirBase::Move(float movex, float movey)
{
spr_frame_t *plthis, *plother;
int thismins[2], thismaxs[2];
int omins[2], omaxs[2];
bool bGoodGuy = IsGoodGuy();
bool bTangible = IsTangible();
AirBase *pOther;
AirBase *pHit;
int iPrevHit;
AirBase *pPrevHit[MAX_HIT];
float flFract;
float dx, dy, dt, x, y, t;
float x0, x1, y0, y1;
float f;
int layer;
int i;
if (IsGhost()) {
m_x += movex;
m_y += movey;
return;
}
iPrevHit = 0;
plthis = &m_pSprite->base.frames[0];
thismins[0] = (int)plthis->offsets[0];
thismins[1] = (int)plthis->offsets[1];
thismaxs[0] = (int)(plthis->pic->size[0] + plthis->offsets[0]);
thismaxs[1] = (int)(plthis->pic->size[1] + plthis->offsets[1]);
for(;;)
{
if (fabs(movex) < 0.01 && fabs(movey) < 0.01)
break;
pHit = 0;
flFract = 1.0;
// step 1 pixel at a time
f = fabs(movex);
if (fabs(movey) > f)
f = fabs(movey);
f = (int)f;
if (f < 1)
f = 1;
dx = movex / f;
dy = movey / f;
dt = 1.0 / f;
lassert(dt > 0);
for(layer = 0; layer < NUM_LAYERS; layer++)
{
for(pOther = ge->units_air[layer]; pOther; pOther = pOther->m_pNext)
{
if (pOther == this ||pOther->m_bRemove)
continue;
if (pOther->IsGoodGuy() == bGoodGuy)
continue;
if (pOther->IsGhost())
continue;
if (!bTangible && !pOther->IsTangible())
continue;
for(i = 0; i < iPrevHit; i++)
if (pOther == pPrevHit[i])
goto skip;
plother = &pOther->m_pSprite->base.frames[0];
omins[0] = (int)plother->offsets[0];
omins[1] = (int)plother->offsets[1];
omaxs[0] = (int)(plother->pic->size[0] + plother->offsets[0]);
omaxs[1] = (int)(plother->pic->size[1] + plother->offsets[1]);
x0 = pOther->m_x + omins[0] - thismaxs[0];
x1 = pOther->m_x + omaxs[0] - thismins[0];
y0 = pOther->m_y + omins[1] - thismaxs[1];
y1 = pOther->m_y + omaxs[1] - thismins[1];
// quick test elimination
if (movex < 0) {
if (m_x < x0 || m_x + movex > x1)
continue;
} else {
if (m_x > x1 || m_x + movex < x0)
continue;
}
if (movey < 0) {
if (m_y < y0 || m_y + movey > y1)
continue;
} else {
if (m_y > y1 || m_y + movey < y0)
continue;
}
for(t = 0, x = m_x, y = m_y; t < flFract; t += dt, x += dx, y += dy)
{
int diffx, diffy;
int pxls;
// bbox intersection?
if (x < x0 || x > x1 || y < y0 || y > y1)
continue;
diffx = (int)((pOther->m_x+plother->offsets[0]) - (x+plthis->offsets[0]));
diffy = (int)((pOther->m_y+plother->offsets[1]) - (y+plthis->offsets[1]));
pxls = BitMaskSect(
plthis->pic->bitmask, plthis->pic->size[0], plthis->pic->size[1],
plother->pic->bitmask, plother->pic->size[0], plother->pic->size[1],
diffx, diffy);
if (pxls) {
if (t < flFract) {
flFract = t;
pHit = pOther;
}
break;
}
}
skip: ;
}
}
// actually move
m_x += flFract * movex;
m_y += flFract * movey;
movex *= 1.0 - flFract;
movey *= 1.0 - flFract;
// if we hit something, take damage
if (!pHit)
return;
Collide(pHit);
pHit->Collide(this);
if (iPrevHit >= MAX_HIT)
return;
pPrevHit[iPrevHit++] = pHit;
}
}
/*
==============
AirBase::Think
Dummy, does nothing
==============
*/
void AirBase::Think()
{
}
/*
==============
AirBase::Draw
Draw the unit
==============
*/
void AirBase::Draw()
{
float x, y;
float time;
int frametime = IsGoodGuy() ? ge->m_iFrameTime : LOGICTIME;
x = LERP(m_lastx, m_x);
y = LERP(m_lasty, m_y);
time = LERP(m_iAnimTime-frametime, m_iAnimTime);
SPR_Draw(m_pSprite, x, y, -1, time);
}
/*
==============================================================================
EEngine Air IMPLEMENTATION
==============================================================================
*/
/*
==============
EEngine::Air_Init
==============
*/
void EEngine::Air_Init()
{
memset(units_air, 0, sizeof(units_air));
}
/*
==============
EEngine::Air_Shutdown
Cleanup any remaining units
==============
*/
void EEngine::Air_Shutdown()
{
int layer;
for(layer = 0; layer < NUM_LAYERS; layer++)
while(units_air[layer])
delete units_air[layer];
while(missile_types) {
if (missile_types->used)
L_Printf("Missile type leak: %s\n", missile_types->name);
Air_DoFreeMissileType(missile_types);
}
}
/*
==============
EEngine::Air_Logic
Run Think functions for all units. Called once every game tick
(i.e. at 33fps)
==============
*/
void EEngine::Air_Logic()
{
AirBase *pNext, *pAir;
int layer;
for(layer = 0; layer < NUM_LAYERS; layer++)
{
pNext = units_air[layer];
while(pNext) {
pAir = pNext;
pNext = pNext->m_pNext;
if (pAir->m_bRemove) {
delete pAir;
continue;
}
pAir->m_lastx = pAir->m_x;
pAir->m_lasty = pAir->m_y;
pAir->Think();
if (pAir->m_bRemove)
delete pAir;
}
}
}
/*
==============
EEngine::Air_Draw
Draws all air units
==============
*/
void EEngine::Air_Draw()
{
AirBase *pAir;
float x, y;
int layer;
// shadow pass
hal->SetAlphaOnly(true);
for(layer = 0; layer <= LYR_MISS; layer++)
{
for(pAir = units_air[layer]; pAir; pAir = pAir->m_pNext) {
spr_frame_t *frame = &pAir->m_pSprite->base.frames[0];
if (pAir->m_bRemove)
continue;
x = LERP(pAir->m_lastx, pAir->m_x);
y = LERP(pAir->m_lasty, pAir->m_y);
x = SHADOWX(x);
y = SHADOWY(y);
x += frame->offsets[0];
y += frame->offsets[1];
frame->pic->Draw(x, y, 255, 255, 255, 64);
}
}
hal->SetAlphaOnly(false);
// normal pass
for(layer = 0; layer < NUM_LAYERS; layer++)
{
for(pAir = units_air[layer]; pAir; pAir = pAir->m_pNext)
if (!pAir->m_bRemove)
pAir->Draw();
}
}
/*
==============
EEngine::Air_Spawn
Create an enemy, big boss, whatever
==============
*/
void EEngine::Air_Spawn(airjob_t *aj)
{
if (!aj->type->bBoss)
new AirEnemy(aj);
else
new AirBoss(aj);
}
/*
==============
EEngine::Air_FindNearest
Find the nearest tangible air unit of the given party
==============
*/
AirBase *EEngine::Air_FindNearest(float x, float y, bool goodguy, float xdir, float ydir)
{
AirBase *pBest, *pAir;
float flBestDist;
float f, dx, dy, dot;
int level;
bool biased;
if (xdir || ydir)
biased = true;
else
biased = false;
pBest = 0;
flBestDist = 999999;
level = LYR_UNIT; // performance hack: only check unit level
{
for(pAir = units_air[level]; pAir; pAir = pAir->m_pNext)
{
if (pAir->m_bRemove)
continue;
if (!pAir->IsTangible())
continue;
if (pAir->IsGoodGuy() != goodguy)
continue;
dx = pAir->m_x - x;
dy = pAir->m_y - y;
f = sqrt(dx*dx + dy*dy);
if (biased) {
dot = (dx*xdir + dy*ydir) / f;
dot = 1.0 - dot;
dot *= dot;
if (dot < 0.1)
dot = 0.1;
f *= dot;
}
if (f < flBestDist) {
pBest = pAir;
flBestDist = f;
}
}
}
return pBest;
}