game: sync g_* rogue logic

This commit is contained in:
Denis Pauk 2023-10-21 18:42:18 +03:00
parent 1ec141c954
commit 30fa1d6b83
27 changed files with 6032 additions and 11681 deletions

View File

@ -1469,11 +1469,11 @@ ROGUE_OBJS_ = \
src/rogue/g_chase.o \
src/game/g_cmds.o \
src/rogue/g_combat.o \
src/rogue/g_func.o \
src/game/g_func.o \
src/rogue/g_items.o \
src/game/g_main.o \
src/rogue/g_misc.o \
src/rogue/g_monster.o \
src/game/g_misc.o \
src/game/g_monster.o \
src/game/g_newai.o \
src/game/g_newdm.o \
src/game/g_newfnc.o \
@ -1481,13 +1481,13 @@ ROGUE_OBJS_ = \
src/game/g_newtrig.o \
src/game/g_newweap.o \
src/game/g_phys.o \
src/rogue/g_spawn.o \
src/game/g_spawn.o \
src/game/g_sphere.o \
src/rogue/g_svcmds.o \
src/rogue/g_target.o \
src/rogue/g_trigger.o \
src/rogue/g_turret.o \
src/rogue/g_utils.o \
src/game/g_svcmds.o \
src/game/g_target.o \
src/game/g_trigger.o \
src/game/g_turret.o \
src/game/g_utils.o \
src/game/g_weapon.o \
src/game/dm/ball.o \
src/game/dm/tag.o \
@ -1496,17 +1496,21 @@ ROGUE_OBJS_ = \
src/game/monster/boss3/boss3.o \
src/game/monster/boss3/boss31.o \
src/game/monster/boss3/boss32.o \
src/game/monster/boss5/boss5.o \
src/rogue/monster/brain/brain.o \
src/game/monster/carrier/carrier.o \
src/rogue/monster/chick/chick.o \
src/game/monster/chick/chick.o \
src/rogue/monster/flipper/flipper.o \
src/rogue/monster/float/float.o \
src/game/monster/flyer/flyer.o \
src/rogue/monster/gladiator/gladiator.o \
src/game/monster/gladiator/gladb.o \
src/game/monster/gekk/gekk.o \
src/rogue/monster/gunner/gunner.o \
src/rogue/monster/hover/hover.o \
src/game/monster/infantry/infantry.o \
src/rogue/monster/insane/insane.o \
src/game/monster/fixbot/fixbot.o \
src/rogue/monster/medic/medic.o \
src/rogue/monster/misc/move.o \
src/rogue/monster/mutant/mutant.o \

File diff suppressed because it is too large Load Diff

View File

@ -46,6 +46,13 @@ void Weapon_Grenade(edict_t *ent);
void Weapon_GrenadeLauncher(edict_t *ent);
void Weapon_Railgun(edict_t *ent);
void Weapon_BFG(edict_t *ent);
void Weapon_ChainFist(edict_t *ent);
void Weapon_Disintegrator(edict_t *ent);
void Weapon_ETF_Rifle(edict_t *ent);
void Weapon_Heatbeam(edict_t *ent);
void Weapon_Prox(edict_t *ent);
void Weapon_Tesla(edict_t *ent);
void Weapon_ProxLauncher(edict_t *ent);
void Weapon_Ionripper(edict_t *ent);
void Weapon_Phalanx(edict_t *ent);
@ -167,6 +174,21 @@ DoRespawn(edict_t *ent)
}
}
if (randomrespawn && randomrespawn->value)
{
edict_t *newEnt;
newEnt = DoRandomRespawn(ent);
/* if we've changed entities, then do some sleight
* of hand. otherwise, the old entity will respawn */
if (newEnt)
{
G_FreeEdict(ent);
ent = newEnt;
}
}
ent->svflags &= ~SVF_NOCLIENT;
ent->solid = SOLID_TRIGGER;
gi.linkentity(ent);
@ -324,6 +346,19 @@ Pickup_Bandolier(edict_t *ent, edict_t *other)
other->client->pers.max_magslug = 75;
}
if (other->client->pers.max_flechettes < 250)
{
other->client->pers.max_flechettes = 250;
}
if (g_disruptor->value)
{
if (other->client->pers.max_rounds < 150)
{
other->client->pers.max_rounds = 150;
}
}
item = FindItem("Bullets");
if (item)
@ -408,6 +443,19 @@ Pickup_Pack(edict_t *ent, edict_t *other)
other->client->pers.max_magslug = 100;
}
if (other->client->pers.max_flechettes < 200)
{
other->client->pers.max_flechettes = 200;
}
if (g_disruptor->value)
{
if (other->client->pers.max_rounds < 200)
{
other->client->pers.max_rounds = 200;
}
}
item = FindItem("Bullets");
if (item)
@ -513,6 +561,36 @@ Pickup_Pack(edict_t *ent, edict_t *other)
}
}
item = FindItem("Flechettes");
if (item)
{
index = ITEM_INDEX(item);
other->client->pers.inventory[index] += item->quantity;
if (other->client->pers.inventory[index] >
other->client->pers.max_flechettes)
{
other->client->pers.inventory[index] =
other->client->pers.max_flechettes;
}
}
item = FindItem("Rounds");
if (item)
{
index = ITEM_INDEX(item);
other->client->pers.inventory[index] += item->quantity;
if (other->client->pers.inventory[index] >
other->client->pers.max_rounds)
{
other->client->pers.inventory[index] =
other->client->pers.max_rounds;
}
}
if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
{
SetRespawn(ent, ent->item->quantity);
@ -523,6 +601,294 @@ Pickup_Pack(edict_t *ent, edict_t *other)
/* ====================================================================== */
qboolean
Pickup_Nuke(edict_t *ent, edict_t *other)
{
int quantity;
if (!ent || !other)
{
return false;
}
quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
if (quantity >= 1)
{
return false;
}
if ((coop->value) && (ent->item->flags & IT_STAY_COOP))
{
return false;
}
other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
if (deathmatch->value)
{
if (!(ent->spawnflags & DROPPED_ITEM))
{
SetRespawn(ent, ent->item->quantity);
}
}
return true;
}
void
Use_IR(edict_t *ent, gitem_t *item)
{
if (!ent || !item)
{
return;
}
ent->client->pers.inventory[ITEM_INDEX(item)]--;
ValidateSelectedItem(ent);
if (ent->client->ir_framenum > level.framenum)
{
ent->client->ir_framenum += 600;
}
else
{
ent->client->ir_framenum = level.framenum + 600;
}
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ir_start.wav"), 1, ATTN_NORM, 0);
}
void
Use_Double(edict_t *ent, gitem_t *item)
{
ent->client->pers.inventory[ITEM_INDEX(item)]--;
ValidateSelectedItem(ent);
if (ent->client->double_framenum > level.framenum)
{
ent->client->double_framenum += 300;
}
else
{
ent->client->double_framenum = level.framenum + 300;
}
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage1.wav"), 1, ATTN_NORM, 0);
}
void
Use_Compass(edict_t *ent, gitem_t *item)
{
int ang;
if (!ent || !item)
{
return;
}
ang = (int)(ent->client->v_angle[1]);
if (ang < 0)
{
ang += 360;
}
gi.cprintf(ent, PRINT_HIGH, "Origin: %0.0f,%0.0f,%0.0f Dir: %d\n",
ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], ang);
}
void
Use_Nuke(edict_t *ent, gitem_t *item)
{
vec3_t forward, right, start;
float speed;
if (!ent || !item)
{
return;
}
ent->client->pers.inventory[ITEM_INDEX(item)]--;
ValidateSelectedItem(ent);
AngleVectors(ent->client->v_angle, forward, right, NULL);
VectorCopy(ent->s.origin, start);
speed = 100;
fire_nuke(ent, start, forward, speed);
}
void
Use_Doppleganger(edict_t *ent, gitem_t *item)
{
vec3_t forward, right;
vec3_t createPt, spawnPt;
vec3_t ang;
if (!ent || !item)
{
return;
}
VectorClear(ang);
ang[YAW] = ent->client->v_angle[YAW];
AngleVectors(ang, forward, right, NULL);
VectorMA(ent->s.origin, 48, forward, createPt);
if (!FindSpawnPoint(createPt, ent->mins, ent->maxs, spawnPt, 32))
{
return;
}
if (!CheckGroundSpawnPoint(spawnPt, ent->mins, ent->maxs, 64, -1))
{
return;
}
ent->client->pers.inventory[ITEM_INDEX(item)]--;
ValidateSelectedItem(ent);
SpawnGrow_Spawn(spawnPt, 0);
fire_doppleganger(ent, spawnPt, forward);
}
qboolean
Pickup_Doppleganger(edict_t *ent, edict_t *other)
{
int quantity;
if (!ent || !other)
{
return false;
}
if (!(deathmatch->value))
{
return false;
}
quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
if (quantity >= 1)
{
return false;
}
other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
if (!(ent->spawnflags & DROPPED_ITEM))
{
SetRespawn(ent, ent->item->quantity);
}
return true;
}
qboolean
Pickup_Sphere(edict_t *ent, edict_t *other)
{
int quantity;
if (!ent || !other)
{
return false;
}
if (other->client && other->client->owned_sphere)
{
return false;
}
quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
if (((skill->value == SKILL_MEDIUM) &&
(quantity >= 2)) || ((skill->value >= SKILL_HARD) && (quantity >= 1)))
{
return false;
}
if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0))
{
return false;
}
other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
if (deathmatch->value)
{
if (!(ent->spawnflags & DROPPED_ITEM))
{
SetRespawn(ent, ent->item->quantity);
}
}
return true;
}
void
Use_Defender(edict_t *ent, gitem_t *item)
{
if (!ent || !item)
{
return;
}
if (ent->client && ent->client->owned_sphere)
{
gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n");
return;
}
ent->client->pers.inventory[ITEM_INDEX(item)]--;
ValidateSelectedItem(ent);
Defender_Launch(ent);
}
void
Use_Hunter(edict_t *ent, gitem_t *item)
{
if (!ent || !item)
{
return;
}
if (ent->client && ent->client->owned_sphere)
{
gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n");
return;
}
ent->client->pers.inventory[ITEM_INDEX(item)]--;
ValidateSelectedItem(ent);
Hunter_Launch(ent);
}
void
Use_Vengeance(edict_t *ent, gitem_t *item)
{
if (!ent || !item)
{
return;
}
if (ent->client && ent->client->owned_sphere)
{
gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n");
return;
}
ent->client->pers.inventory[ITEM_INDEX(item)]--;
ValidateSelectedItem(ent);
Vengeance_Launch(ent);
}
/* ====================================================================== */
void
Use_Quad(edict_t *ent, gitem_t *item)
{
@ -555,7 +921,8 @@ Use_Quad(edict_t *ent, gitem_t *item)
ent->client->quad_framenum = level.framenum + timeout;
}
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM,
0);
}
/* ===================================================================== */
@ -772,8 +1139,25 @@ Add_Ammo(edict_t *ent, gitem_t *item, int count)
{
max = ent->client->pers.max_trap;
}
else if (item->tag == AMMO_FLECHETTES)
{
max = ent->client->pers.max_flechettes;
}
else if (item->tag == AMMO_PROX)
{
max = ent->client->pers.max_prox;
}
else if (item->tag == AMMO_TESLA)
{
max = ent->client->pers.max_tesla;
}
else if (item->tag == AMMO_DISRUPTOR)
{
max = ent->client->pers.max_rounds;
}
else
{
gi.dprintf("undefined ammo type\n");
return false;
}
@ -830,9 +1214,10 @@ Pickup_Ammo(edict_t *ent, edict_t *other)
if (weapon && !oldcount)
{
/* don't switch to tesla */
if ((other->client->pers.weapon != ent->item) &&
(!deathmatch->value ||
(other->client->pers.weapon == FindItem("blaster"))))
(!deathmatch->value || (other->client->pers.weapon == FindItem("blaster"))) &&
(strcmp(ent->classname, "ammo_tesla")))
{
other->client->newweapon = ent->item;
}
@ -1316,6 +1701,10 @@ Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, csurface_
{
ent->item->use(other, ent->item);
}
else
{
gi.dprintf("Powerup has no use function!\n");
}
}
}
}
@ -1499,7 +1888,7 @@ droptofloor(edict_t *ent)
{
gi.setmodel(ent, ent->model);
}
else
else if (ent->item->world_model)
{
gi.setmodel(ent, ent->item->world_model);
}
@ -1668,6 +2057,47 @@ PrecacheItem(gitem_t *it)
}
}
/*
* Create the item marked for spawn creation
*/
void
Item_TriggeredSpawn(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
self->svflags &= ~SVF_NOCLIENT;
self->use = NULL;
if (strcmp(self->classname, "key_power_cube"))
{
self->spawnflags = 0;
}
droptofloor(self);
}
/*
* Set up an item to spawn in later.
*/
void
SetTriggeredSpawn(edict_t *ent)
{
if (!ent)
{
return;
}
/* don't do anything on key_power_cubes. */
if (!strcmp(ent->classname, "key_power_cube"))
{
return;
}
ent->think = NULL;
ent->nextthink = 0;
ent->use = Item_TriggeredSpawn;
ent->svflags |= SVF_NOCLIENT;
ent->solid = SOLID_NOT;
}
/*
* ============
* Sets the clipping size and
@ -1718,6 +2148,18 @@ SpawnItem(edict_t *ent, gitem_t *item)
G_FreeEdict(ent);
return;
}
if (item->pickup == Pickup_Sphere)
{
G_FreeEdict(ent);
return;
}
if (item->pickup == Pickup_Doppleganger)
{
G_FreeEdict(ent);
return;
}
}
if ((int)dmflags->value & DF_NO_HEALTH)
@ -1740,6 +2182,34 @@ SpawnItem(edict_t *ent, gitem_t *item)
return;
}
}
if ((int)dmflags->value & DF_NO_MINES)
{
if (!strcmp(ent->classname, "ammo_prox") ||
!strcmp(ent->classname, "ammo_tesla"))
{
G_FreeEdict(ent);
return;
}
}
if ((int)dmflags->value & DF_NO_NUKES)
{
if (!strcmp(ent->classname, "ammo_nuke"))
{
G_FreeEdict(ent);
return;
}
}
if ((int)dmflags->value & DF_NO_SPHERES)
{
if (item->pickup == Pickup_Sphere)
{
G_FreeEdict(ent);
return;
}
}
}
if (coop->value && !(ent->spawnflags & ITEM_NO_TOUCH) && (strcmp(ent->classname, "key_power_cube") == 0))
@ -1764,6 +2234,11 @@ SpawnItem(edict_t *ent, gitem_t *item)
{
gi.modelindex(ent->model);
}
if (ent->spawnflags & 1)
{
SetTriggeredSpawn(ent);
}
}
/* ====================================================================== */
@ -1775,7 +2250,7 @@ static const gitem_t gameitemlist[] = {
/*
* QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16)
* QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
*/
{
"item_armor_body",
@ -2931,7 +3406,7 @@ static const gitem_t gameitemlist[] = {
gitem_t itemlist[MAX_ITEMS];
/*
* QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16)
* QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
*/
void
SP_item_health(edict_t *self)
@ -3075,3 +3550,73 @@ SetItemNames(void)
power_screen_index = ITEM_INDEX(FindItem("Power Screen"));
power_shield_index = ITEM_INDEX(FindItem("Power Shield"));
}
void
SP_xatrix_item(edict_t *self)
{
gitem_t *item;
int i;
char *spawnClass = NULL;
if (!self)
{
return;
}
if (!self->classname)
{
return;
}
if (!strcmp(self->classname, "ammo_magslug"))
{
spawnClass = "ammo_flechettes";
}
else if (!strcmp(self->classname, "ammo_trap"))
{
spawnClass = "weapon_proxlauncher";
}
else if (!strcmp(self->classname, "item_quadfire"))
{
float chance;
chance = random();
if (chance < 0.2)
{
spawnClass = "item_sphere_hunter";
}
else if (chance < 0.6)
{
spawnClass = "item_sphere_vengeance";
}
else
{
spawnClass = "item_sphere_defender";
}
}
else if (!strcmp(self->classname, "weapon_boomer"))
{
spawnClass = "weapon_etf_rifle";
}
else if (!strcmp(self->classname, "weapon_phalanx"))
{
spawnClass = "weapon_plasmabeam";
}
/* check item spawn functions */
for (i = 0, item = itemlist; i < game.num_items; i++, item++)
{
if (!item->classname)
{
continue;
}
if (!strcmp(item->classname, spawnClass))
{
/* found it */
SpawnItem(self, item);
return;
}
}
}

View File

@ -30,6 +30,8 @@
int debristhisframe;
int gibsthisframe;
extern void M_WorldEffects(edict_t *ent);
/*
* QUAKED func_group (0 0 0) ?
* Used to group brushes together just for editor convenience.
@ -254,6 +256,7 @@ ThrowGib(edict_t *self, char *gibname, int damage, int type)
gib->think = G_FreeEdict;
gib->nextthink = level.time + 10 + random() * 10;
gib->s.renderfx |= RF_IR_VISIBLE;
gi.linkentity(gib);
}
@ -591,6 +594,7 @@ BecomeExplosion2(edict_t *self)
/*
* QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT
*
* Target: next path corner
* Pathtarget: gets used when an entity that has
* this path_corner targeted touches it
@ -802,6 +806,7 @@ SP_point_combat(edict_t *self)
/*
* QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8)
*
* Just for the debugging level. Don't use
*/
void
@ -842,6 +847,7 @@ SP_viewthing(edict_t *ent)
/*
* QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
*
* Used as a positional target for spotlights, etc.
*/
void
@ -857,6 +863,7 @@ SP_info_null(edict_t *self)
/*
* QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
*
* Used as a positional target for lighting.
*/
void
@ -875,6 +882,7 @@ SP_info_notnull(edict_t *self)
/*
* QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF
*
* Non-displayed light.
* Default light value is 300.
* Default style is 0.
@ -947,6 +955,7 @@ SP_light(edict_t *self)
* START_ON only valid for TRIGGER_SPAWN walls
* the wall will initially be present
*/
void
func_wall_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
@ -1039,8 +1048,10 @@ SP_func_wall(edict_t *self)
/*
* QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST
*
* This is solid bmodel that will fall if it's support it removed.
*/
void
func_object_touch(edict_t *self, edict_t *other, cplane_t *plane,
csurface_t *surf /* unused */)
@ -1052,7 +1063,8 @@ func_object_touch(edict_t *self, edict_t *other, cplane_t *plane,
return;
}
if (plane->normal[2] < 1.0)
/* only squash thing we fall on top of */
if (plane && plane->normal[2] < 1.0)
{
return;
}
@ -1148,13 +1160,17 @@ SP_func_object(edict_t *self)
/* ===================================================== */
/*
* QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST
* Any brush that you want to explode or break apart. If you want an
* QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST INACTIVE
*
* Any brush that you want to explode or break apart. If you want an
* explosion, set dmg and it will do a radius explosion of that amount
* at the center of the bursh.
*
* If targeted it will not be shootable.
*
* INACTIVE - specifies that the entity is not explodable until triggered. If you use this you must
* target the entity you want to trigger it. This is the only entity approved to activate it.
*
* health defaults to 100.
*
* mass defaults to 75. This determines how much debris is emitted when
@ -1170,6 +1186,7 @@ func_explosive_explode(edict_t *self, edict_t *inflictor, edict_t *attacker,
vec3_t size;
int count;
int mass;
edict_t *master;
if (!self || !inflictor || !attacker)
{
@ -1238,6 +1255,23 @@ func_explosive_explode(edict_t *self, edict_t *inflictor, edict_t *attacker,
ThrowDebris(self, "models/objects/debris2/tris.md2", 2, chunkorigin);
}
if (self->flags & FL_TEAMSLAVE)
{
master = self->teammaster;
/* because mappers (other than jim (usually)) are stupid.... */
while (master)
{
if (master->teamchain == self)
{
master->teamchain = self->teamchain;
break;
}
master = master->teamchain;
}
}
G_UseTargets(self, attacker);
if (self->dmg)
@ -1251,14 +1285,43 @@ func_explosive_explode(edict_t *self, edict_t *inflictor, edict_t *attacker,
}
void
func_explosive_use(edict_t *self, edict_t *other, edict_t *activator)
func_explosive_use(edict_t *self, edict_t *other, edict_t *activator /* unused */)
{
if (!self || !other)
{
return;
}
func_explosive_explode(self, self, other, self->health, vec3_origin);
}
void
func_explosive_activate(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
if (!self)
{
return;
}
self->use = func_explosive_use;
if (!self->health)
{
self->health = 100;
}
self->die = func_explosive_explode;
self->takedamage = DAMAGE_YES;
}
void
func_explosive_spawn(edict_t *self, edict_t *other, edict_t *activator)
{
if (!self)
{
return;
}
self->solid = SOLID_BSP;
self->svflags &= ~SVF_NOCLIENT;
self->use = NULL;
@ -1294,6 +1357,15 @@ SP_func_explosive(edict_t *self)
self->solid = SOLID_NOT;
self->use = func_explosive_spawn;
}
else if (self->spawnflags & 8)
{
self->solid = SOLID_BSP;
if (self->targetname)
{
self->use = func_explosive_activate;
}
}
else
{
self->solid = SOLID_BSP;
@ -1314,7 +1386,8 @@ SP_func_explosive(edict_t *self)
self->s.effects |= EF_ANIM_ALLFAST;
}
if (self->use != func_explosive_use)
if ((self->use != func_explosive_use) &&
(self->use != func_explosive_activate))
{
if (!self->health)
{
@ -1332,6 +1405,7 @@ SP_func_explosive(edict_t *self)
/*
* QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40)
*
* Large exploding box. You can override its mass (100),
* health (80), and dmg (150).
*/
@ -1401,7 +1475,7 @@ barrel_explode(edict_t *self)
ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
/* a bunch of little chunks */
spd = 2 * self->dmg / 200;
spd = 2.0 * (float)self->dmg / 200.0;
org[0] = self->s.origin[0] + crandom() * self->size[0];
org[1] = self->s.origin[1] + crandom() * self->size[1];
org[2] = self->s.origin[2] + crandom() * self->size[2];
@ -1462,6 +1536,37 @@ barrel_delay(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker,
self->activator = attacker;
}
void
barrel_think(edict_t *self)
{
if (!self)
{
return;
}
/* the think needs to be first since later stuff may override. */
self->think = barrel_think;
self->nextthink = level.time + FRAMETIME;
M_CatagorizePosition(self);
self->flags |= FL_IMMUNE_SLIME;
self->air_finished = level.time + 100;
M_WorldEffects(self);
}
void
barrel_start(edict_t *self)
{
if (!self)
{
return;
}
M_droptofloor(self);
self->think = barrel_think;
self->nextthink = level.time + FRAMETIME;
}
void
SP_misc_explobox(edict_t *self)
{
@ -1509,8 +1614,7 @@ SP_misc_explobox(edict_t *self)
self->monsterinfo.aiflags = AI_NOSTEP;
self->touch = barrel_touch;
self->think = M_droptofloor;
self->think = barrel_start;
self->nextthink = level.time + 2 * FRAMETIME;
gi.linkentity(self);
@ -1575,7 +1679,7 @@ SP_misc_blackhole(edict_t *ent)
ent->s.renderfx = RF_TRANSLUCENT;
ent->use = misc_blackhole_use;
ent->think = misc_blackhole_think;
ent->prethink = misc_blackhole_transparent;
ent->prethink = misc_blackhole_transparent;
ent->nextthink = level.time + 2 * FRAMETIME;
gi.linkentity(ent);
}
@ -1711,6 +1815,7 @@ SP_misc_easterchick2(edict_t *ent)
/*
* QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48)
*
* Not really a monster, this is the Tank Commander's decapitated body.
* There should be a item_commander_head that has this as it's target.
*/
@ -1836,6 +1941,7 @@ SP_monster_commander_body(edict_t *self)
/*
* QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4)
*
* The origin is the bottom of the banner.
* The banner is 128 tall.
*/
@ -1873,6 +1979,7 @@ SP_misc_banner(edict_t *ent)
/*
* QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED
*
* This is the dead player model. Comes in 6 exciting different poses!
*/
void
@ -1891,7 +1998,8 @@ misc_deadsoldier_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *at
return;
}
gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
gi.sound(self, CHAN_BODY, gi.soundindex(
"misc/udeath.wav"), 1, ATTN_NORM, 0);
for (n = 0; n < 4; n++)
{
@ -1964,6 +2072,7 @@ SP_misc_deadsoldier(edict_t *ent)
/*
* QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32)
*
* This is the Viper for the flyby bombing.
* It is trigger_spawned, so you must have something use it for it to show up.
* There must be a path for it to follow once it is activated.
@ -2064,11 +2173,17 @@ SP_misc_crashviper(edict_t *ent)
/*
* QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72)
*
* This is a large stationary viper as seen in Paul's intro
*/
void
SP_misc_bigviper(edict_t *ent)
{
if (!ent)
{
return;
}
ent->movetype = MOVETYPE_NONE;
ent->solid = SOLID_BBOX;
VectorSet(ent->mins, -176, -120, -24);
@ -2081,6 +2196,7 @@ SP_misc_bigviper(edict_t *ent)
/*
* QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8)
*
* "dmg" how much boom should the bomb make?
*/
void
@ -2240,6 +2356,7 @@ SP_misc_viper_missile(edict_t *self)
/*
* QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32)
*
* This is a Storgg ship for the flybys.
* It is trigger_spawned, so you must have something use it for it to show up.
* There must be a path for it to follow once it is activated.
@ -2412,7 +2529,8 @@ SP_light_mine1(edict_t *ent)
ent->movetype = MOVETYPE_NONE;
ent->solid = SOLID_BBOX;
ent->s.modelindex = gi.modelindex("models/objects/minelite/light1/tris.md2");
ent->s.modelindex =
gi.modelindex("models/objects/minelite/light1/tris.md2");
gi.linkentity(ent);
}
@ -2429,7 +2547,8 @@ SP_light_mine2(edict_t *ent)
ent->movetype = MOVETYPE_NONE;
ent->solid = SOLID_BBOX;
ent->s.modelindex = gi.modelindex("models/objects/minelite/light2/tris.md2");
ent->s.modelindex =
gi.modelindex("models/objects/minelite/light2/tris.md2");
gi.linkentity(ent);
}
@ -2437,6 +2556,7 @@ SP_light_mine2(edict_t *ent)
/*
* QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8)
*
* Intended for use with the target_spawner
*/
void
@ -2523,9 +2643,11 @@ SP_misc_gib_head(edict_t *ent)
/*
* QUAKED target_character (0 0 1) ?
*
* used with target_string (must be on same "team")
* "count" is position in the string (starts at 1)
*/
void
SP_target_character(edict_t *self)
{
@ -2600,6 +2722,11 @@ target_string_use(edict_t *self, edict_t *other /* unused */, edict_t *activator
void
SP_target_string(edict_t *self)
{
if (!self)
{
return;
}
if (!self->message)
{
self->message = "";
@ -2612,6 +2739,7 @@ SP_target_string(edict_t *self)
/*
* QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE
*
* target a target_string with this
*
* The default is to be a time of day clock
@ -2936,6 +3064,7 @@ teleporter_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
/*
* QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16)
*
* Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object.
*/
void
@ -2978,6 +3107,7 @@ SP_misc_teleporter(edict_t *ent)
/*
* QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
*
* Point teleporters at these.
*/
void
@ -3072,3 +3202,40 @@ SP_misc_nuke(edict_t *ent)
ent->use = use_nuke;
}
void
misc_nuke_core_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
if (!self)
{
return;
}
if (self->svflags & SVF_NOCLIENT)
{
self->svflags &= ~SVF_NOCLIENT;
}
else
{
self->svflags |= SVF_NOCLIENT;
}
}
/*
* QUAKED misc_nuke_core (1 0 0) (-16 -16 -16) (16 16 16)
*
* toggles visible/not visible. starts visible.
*/
void
SP_misc_nuke_core(edict_t *ent)
{
if (!ent)
{
return;
}
gi.setmodel(ent, "models/objects/core/tris.md2");
gi.linkentity(ent);
ent->use = misc_nuke_core_use;
}

View File

@ -475,7 +475,7 @@ M_CheckGround(edict_t *ent)
return;
}
if (ent->velocity[2] > 100)
if ((ent->velocity[2] * ent->gravityVector[2]) < -100)
{
ent->groundentity = NULL;
return;
@ -485,7 +485,7 @@ M_CheckGround(edict_t *ent)
is solid the entity is on ground */
point[0] = ent->s.origin[0];
point[1] = ent->s.origin[1];
point[2] = ent->s.origin[2] - 0.25;
point[2] = ent->s.origin[2] + (0.25 * ent->gravityVector[2]);
trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, point,
ent, MASK_MONSTERSOLID);
@ -689,9 +689,18 @@ M_droptofloor(edict_t *ent)
return;
}
ent->s.origin[2] += 1;
VectorCopy(ent->s.origin, end);
end[2] -= 256;
if (ent->gravityVector[2] < 0)
{
ent->s.origin[2] += 1;
VectorCopy(ent->s.origin, end);
end[2] -= 256;
}
else
{
ent->s.origin[2] -= 1;
VectorCopy(ent->s.origin, end);
end[2] += 256;
}
trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, end,
ent, MASK_MONSTERSOLID);
@ -1140,6 +1149,11 @@ monster_start(edict_t *self)
self->monsterinfo.currentmove->firstframe + 1));
}
self->monsterinfo.base_height = self->maxs[2];
self->monsterinfo.quad_framenum = 0;
self->monsterinfo.double_framenum = 0;
self->monsterinfo.invincible_framenum = 0;
return true;
}

View File

@ -346,6 +346,43 @@ static spawn_t spawns[] = {
{"turret_base", SP_turret_base},
{"turret_driver", SP_turret_driver},
{"func_plat2", SP_func_plat2},
{"func_door_secret2", SP_func_door_secret2},
{"func_force_wall", SP_func_force_wall},
{"trigger_teleport", SP_trigger_teleport},
{"trigger_disguise", SP_trigger_disguise},
{"info_teleport_destination", SP_info_teleport_destination},
{"info_player_coop_lava", SP_info_player_coop_lava},
{"monster_stalker", SP_monster_stalker},
{"monster_turret", SP_monster_turret},
{"target_steam", SP_target_steam},
{"target_anger", SP_target_anger},
{"target_killplayers", SP_target_killplayers},
{"target_blacklight", SP_target_blacklight},
{"target_orb", SP_target_orb},
{"monster_daedalus", SP_monster_hover},
{"hint_path", SP_hint_path},
{"monster_carrier", SP_monster_carrier},
{"monster_widow", SP_monster_widow},
{"monster_widow2", SP_monster_widow2},
{"monster_medic_commander", SP_monster_medic},
{"dm_tag_token", SP_dm_tag_token},
{"dm_dball_goal", SP_dm_dball_goal},
{"dm_dball_ball", SP_dm_dball_ball},
{"dm_dball_team1_start", SP_dm_dball_team1_start},
{"dm_dball_team2_start", SP_dm_dball_team2_start},
{"dm_dball_ball_start", SP_dm_dball_ball_start},
{"dm_dball_speed_change", SP_dm_dball_speed_change},
{"monster_kamikaze", SP_monster_kamikaze},
{"turret_invisible_brain", SP_turret_invisible_brain},
{"misc_nuke_core", SP_misc_nuke_core},
{"ammo_magslug", SP_xatrix_item},
{"ammo_trap", SP_xatrix_item},
{"item_quadfire", SP_xatrix_item},
{"weapon_boomer", SP_xatrix_item},
{"weapon_phalanx", SP_xatrix_item},
{NULL, NULL}
};
@ -401,6 +438,25 @@ ED_CallSpawn(edict_t *ent)
return;
}
ent->gravityVector[0] = 0.0;
ent->gravityVector[1] = 0.0;
ent->gravityVector[2] = -1.0;
if (!strcmp(ent->classname, "weapon_nailgun"))
{
ent->classname = (FindItem("ETF Rifle"))->classname;
}
if (!strcmp(ent->classname, "ammo_nails"))
{
ent->classname = (FindItem("Flechettes"))->classname;
}
if (!strcmp(ent->classname, "weapon_heatbeam"))
{
ent->classname = (FindItem("Plasma Beam"))->classname;
}
/* check item spawn functions */
for (i = 0, item = itemlist; i < game.num_items; i++, item++)
{
@ -878,6 +934,30 @@ SpawnEntities(const char *mapname, char *entities, const char *spawnpoint)
continue;
}
}
else if (coop->value && !coop_baseq2->value)
{
if (ent->spawnflags & SPAWNFLAG_NOT_COOP)
{
G_FreeEdict(ent);
inhibit++;
continue;
}
/* stuff marked !easy & !med & !hard are coop only, all levels */
if (!((ent->spawnflags & SPAWNFLAG_NOT_EASY) &&
(ent->spawnflags & SPAWNFLAG_NOT_MEDIUM) &&
(ent->spawnflags & SPAWNFLAG_NOT_HARD)))
{
if (((skill->value == SKILL_EASY) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) ||
((skill->value == SKILL_MEDIUM) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) ||
(((skill->value == SKILL_HARD) || (skill->value == SKILL_HARDPLUS)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)))
{
G_FreeEdict(ent);
inhibit++;
continue;
}
}
}
else
{
if (Spawn_CheckCoop_MapHacks(ent) || (
@ -902,7 +982,13 @@ SpawnEntities(const char *mapname, char *entities, const char *spawnpoint)
SPAWNFLAG_NOT_COOP | SPAWNFLAG_NOT_DEATHMATCH);
}
ent->gravityVector[0] = 0.0;
ent->gravityVector[1] = 0.0;
ent->gravityVector[2] = -1.0;
ED_CallSpawn(ent);
ent->s.renderfx |= RF_IR_VISIBLE;
}
gi.dprintf("%i entities inhibited.\n", inhibit);
@ -910,6 +996,26 @@ SpawnEntities(const char *mapname, char *entities, const char *spawnpoint)
G_FindTeams();
PlayerTrail_Init();
if (deathmatch->value)
{
if (randomrespawn && randomrespawn->value)
{
PrecacheForRandomRespawn();
}
}
else
{
InitHintPaths();
}
if (deathmatch->value && gamerules && gamerules->value)
{
if (DMGame.PostInitSetup)
{
DMGame.PostInitSetup();
}
}
}
/* =================================================================== */

View File

@ -18,7 +18,7 @@
#define VENGEANCE_LIFESPAN 30
#define MINIMUM_FLY_TIME 15
extern char *ED_NewString(char *string);
extern char *ED_NewString(const char *string);
void LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker);
void defender_think(edict_t *self);

View File

@ -27,11 +27,22 @@
#include "header/local.h"
#define LASER_ON 0x0001
#define LASER_RED 0x0002
#define LASER_GREEN 0x0004
#define LASER_BLUE 0x0008
#define LASER_YELLOW 0x0010
#define LASER_ORANGE 0x0020
#define LASER_FAT 0x0040
#define LASER_STOPWINDOW 0x0080
void ED_CallSpawn(edict_t *ent);
/*
* QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
* Fire an origin based temp entity event to the clients.
*
* "style" type byte
* Fire an origin based temp entity event to the clients.
* "style" type byte
*/
void
Use_Target_Tent(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
@ -199,6 +210,7 @@ Use_Target_Help(edict_t *ent, edict_t *other /* unused */, edict_t *activator /*
/*
* QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
*
* When fired, the "message" key becomes the current personal computer string,
* and the message light will be set on all clients status bars.
*/
@ -411,6 +423,7 @@ SP_target_explosion(edict_t *ent)
/*
* QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
*
* Changes level to "map" when fired
*/
void
@ -588,6 +601,8 @@ use_target_spawner(edict_t *self, edict_t *other /* unused */, edict_t *activato
{
VectorCopy(self->movedir, ent->velocity);
}
ent->s.renderfx |= RF_IR_VISIBLE; /* PGM */
}
void
@ -672,10 +687,11 @@ SP_target_blaster(edict_t *self)
/*
* QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
* Once this trigger is touched/used, any trigger_crosslevel_target with
* the same trigger number is automatically used when a level is started
* within the same unit. It is OK to check multiple triggers. Message,
* delay, target, and killtarget also work.
*
* Once this trigger is touched/used, any trigger_crosslevel_target
* with the same trigger number is automatically used when a level
* is started within the same unit. It is OK to check multiple triggers.
* Message, delay, target, and killtarget also work.
*/
void
trigger_crosslevel_trigger_use(edict_t *self, edict_t *other /* unused */,
@ -705,6 +721,7 @@ SP_target_crosslevel_trigger(edict_t *self)
/*
* QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
*
* Triggered by a trigger_crosslevel elsewhere within a unit.
* If multiple triggers are checked, all must be true. Delay,
* target and killtarget also work.
@ -750,9 +767,11 @@ SP_target_crosslevel_target(edict_t *self)
/* ========================================================== */
/*
* QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
* QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT WINDOWSTOP
* When triggered, fires a laser. You can either set a target
* or a direction.
*
* WINDOWSTOP - stops at CONTENTS_WINDOW
*/
void
target_laser_think(edict_t *self)
@ -798,8 +817,15 @@ target_laser_think(edict_t *self)
while (1)
{
tr = gi.trace(start, NULL, NULL, end, ignore,
CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER);
if (self->spawnflags & LASER_STOPWINDOW)
{
tr = gi.trace(start, NULL, NULL, end, ignore, MASK_SHOT);
}
else
{
tr = gi.trace(start, NULL, NULL, end, ignore,
CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER);
}
if (!tr.ent)
{
@ -823,7 +849,8 @@ target_laser_think(edict_t *self)
/* if we hit something that's not a monster
or player or is immune to lasers, we're done */
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client) &&
!(tr.ent->svflags & SVF_DAMAGEABLE))
{
if (self->spawnflags & 0x80000000)
{
@ -1156,9 +1183,11 @@ SP_target_mal_laser(edict_t *self)
/*
* QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
* speed How many seconds the ramping will take
* message two letters; starting lightlevel and ending lightlevel
*
* speed How many seconds the ramping will take
* message two letters; starting lightlevel and ending lightlevel
*/
void
target_lightramp_think(edict_t *self)
{
@ -1285,11 +1314,12 @@ SP_target_lightramp(edict_t *self)
/* ========================================================== */
/*
* QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
* QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) SILENT
*
* When triggered, this initiates a level-wide earthquake.
* All players and monsters are affected.
* "speed" severity of the quake (default:200)
* "count" duration of the quake (default:5)
* "speed" severity of the quake (default:200)
* "count" duration of the quake (default:5)
*/
void
target_earthquake_think(edict_t *self)
@ -1302,16 +1332,19 @@ target_earthquake_think(edict_t *self)
return;
}
if (self->last_move_time < level.time)
if (!(self->spawnflags & 1))
{
gi.positioned_sound(self->s.origin,
self,
CHAN_AUTO,
self->noise_index,
1.0,
ATTN_NONE,
0);
self->last_move_time = level.time + 0.5;
if (self->last_move_time < level.time)
{
gi.positioned_sound(self->s.origin,
self,
CHAN_AUTO,
self->noise_index,
1.0,
ATTN_NONE,
0);
self->last_move_time = level.time + 0.5;
}
}
for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++)
@ -1385,5 +1418,8 @@ SP_target_earthquake(edict_t *self)
self->think = target_earthquake_think;
self->use = target_earthquake_use;
self->noise_index = gi.soundindex("world/quake.wav");
if (!(self->spawnflags & 1))
{
self->noise_index = gi.soundindex("world/quake.wav");
}
}

View File

@ -27,12 +27,21 @@
#include "header/local.h"
#define PUSH_ONCE 1
#define TRIGGER_MONSTER 0x01
#define TRIGGER_NOT_PLAYER 0x02
#define TRIGGER_TRIGGERED 0x04
#define TRIGGER_TOGGLE 0x08
void trigger_push_active(edict_t *self);
#define PUSH_ONCE 0x01
#define PUSH_START_OFF 0x02
#define PUSH_SILENT 0x04
static int windsound;
void trigger_push_active(edict_t *self);
void hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
csurface_t *surf /* unused */);
void
InitTrigger(edict_t *self)
{
@ -114,8 +123,24 @@ Use_Multi(edict_t *ent, edict_t *other /* unused */, edict_t *activator)
return;
}
ent->activator = activator;
multi_trigger(ent);
if (ent->spawnflags & TRIGGER_TOGGLE)
{
if (ent->solid == SOLID_TRIGGER)
{
ent->solid = SOLID_NOT;
}
else
{
ent->solid = SOLID_TRIGGER;
}
gi.linkentity(ent);
}
else
{
ent->activator = activator;
multi_trigger(ent);
}
}
void
@ -163,13 +188,15 @@ Touch_Multi(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
}
/*
* QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED
* QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED TOGGLE
* Variable sized repeatable trigger. Must be targeted at one or more
* entities. If "delay" is set, the trigger waits some time after
* activating before firing.
*
* "wait" : Seconds between triggerings. (.2 default)
*
* TOGGLE - using this trigger will activate/deactivate it. trigger will begin inactive.
*
* sounds
* 1) secret
* 2) beep beep
@ -222,7 +249,7 @@ SP_trigger_multiple(edict_t *ent)
ent->movetype = MOVETYPE_NONE;
ent->svflags |= SVF_NOCLIENT;
if (ent->spawnflags & 4)
if (ent->spawnflags & (TRIGGER_TRIGGERED | TRIGGER_TOGGLE))
{
ent->solid = SOLID_NOT;
ent->use = trigger_enable;
@ -244,6 +271,7 @@ SP_trigger_multiple(edict_t *ent)
/*
* QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED
*
* Triggers once, then removes itself.
*
* You must set the key "target" to the name of another
@ -463,6 +491,7 @@ SP_trigger_key(edict_t *self)
/*
* QUAKED trigger_counter (.5 .5 .5) ? nomessage
*
* Acts as an intermediary for an action that takes multiple inputs.
*
* If nomessage is not set, it will print "1 more.. " etc when
@ -471,6 +500,7 @@ SP_trigger_key(edict_t *self)
* After the counter has been triggered "count" times (default 2),
* it will fire all of it's targets and remove itself.
*/
void
trigger_counter_use(edict_t *self, edict_t *other /* unused */,
edict_t *activator)
@ -530,6 +560,7 @@ SP_trigger_counter(edict_t *self)
/*
* QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
*
* This trigger will always fire. It is activated by the world.
*/
void
@ -573,7 +604,8 @@ trigger_push_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
immediately from this */
VectorCopy(other->velocity, other->client->oldvelocity);
if (other->fly_sound_debounce_time < level.time)
if (!(self->spawnflags & PUSH_SILENT) &&
(other->fly_sound_debounce_time < level.time))
{
other->fly_sound_debounce_time = level.time + 1.5;
gi.sound(other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
@ -664,6 +696,36 @@ trigger_push_active(edict_t *self)
}
}
void
trigger_push_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
if (!self)
{
return;
}
if (self->solid == SOLID_NOT)
{
self->solid = SOLID_TRIGGER;
}
else
{
self->solid = SOLID_NOT;
}
gi.linkentity(self);
}
/*
* QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE START_OFF SILENT
* Pushes the player
* "speed" defaults to 1000
*
* If targeted, it will toggle on and off when used.
*
* START_OFF - toggled trigger_push begins in off setting
* SILENT - doesn't make wind noise
*/
void
SP_trigger_push(edict_t *self)
{
@ -676,7 +738,12 @@ SP_trigger_push(edict_t *self)
windsound = gi.soundindex("misc/windfly.wav");
self->touch = trigger_push_touch;
if (self->spawnflags & 2)
if (!self->speed)
{
self->speed = 1000;
}
if (self->spawnflags & PUSH_START_OFF)
{
if (!self->wait)
{
@ -688,9 +755,22 @@ SP_trigger_push(edict_t *self)
self->delay = self->nextthink + self->wait;
}
if (!self->speed)
if (self->targetname) /* toggleable */
{
self->speed = 1000;
self->use = trigger_push_use;
if (self->spawnflags & PUSH_START_OFF)
{
self->solid = SOLID_NOT;
}
}
else if (self->spawnflags & PUSH_START_OFF)
{
gi.dprintf("trigger_push is START_OFF but not targeted.\n");
self->svflags = 0;
self->touch = NULL;
self->solid = SOLID_BSP;
self->movetype = MOVETYPE_PUSH;
}
gi.linkentity(self);
@ -698,16 +778,56 @@ SP_trigger_push(edict_t *self)
/*
* QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW
*
* Any entity that touches this will be hurt.
*
* It does dmg points of damage each server frame
*
* SILENT supresses playing the sound
* SLOW changes the damage rate to once per second
* SLOW changes the damage rate to once per second
* NO_PROTECTION *nothing* stops the damage
*
* "dmg" default 5 (whole numbers only)
*
*/
void
hurt_use(edict_t *self, edict_t *other /* unused */,
edict_t *activator /* unused */)
{
if (!self)
{
return;
}
if (self->solid == SOLID_NOT)
{
int i, num;
edict_t *touch[MAX_EDICTS], *hurtme;
self->solid = SOLID_TRIGGER;
num = gi.BoxEdicts (self->absmin, self->absmax,
touch, MAX_EDICTS, AREA_SOLID);
/* Check for idle monsters in
trigger hurt */
for (i = 0 ; i < num ; i++)
{
hurtme = touch[i];
hurt_touch (self, hurtme, NULL, NULL);
}
}
else
{
self->solid = SOLID_NOT;
}
gi.linkentity(self);
if (!(self->spawnflags & 2))
{
self->use = NULL;
}
}
void
hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
@ -760,45 +880,6 @@ hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT);
}
void
hurt_use(edict_t *self, edict_t *other /* unused */,
edict_t *activator /* unused */)
{
if (!self)
{
return;
}
if (self->solid == SOLID_NOT)
{
int i, num;
edict_t *touch[MAX_EDICTS], *hurtme;
self->solid = SOLID_TRIGGER;
num = gi.BoxEdicts (self->absmin, self->absmax,
touch, MAX_EDICTS, AREA_SOLID);
/* Check for idle monsters in
trigger hurt */
for (i = 0 ; i < num ; i++)
{
hurtme = touch[i];
hurt_touch (self, hurtme, NULL, NULL);
}
}
else
{
self->solid = SOLID_NOT;
}
gi.linkentity(self);
if (!(self->spawnflags & 2))
{
self->use = NULL;
}
}
void
SP_trigger_hurt(edict_t *self)
{
@ -834,6 +915,26 @@ SP_trigger_hurt(edict_t *self)
gi.linkentity(self);
}
void
trigger_gravity_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
if (!self)
{
return;
}
if (self->solid == SOLID_NOT)
{
self->solid = SOLID_TRIGGER;
}
else
{
self->solid = SOLID_NOT;
}
gi.linkentity(self);
}
/*
* QUAKED trigger_gravity (.5 .5 .5) ?
* Changes the touching entites gravity to
@ -852,6 +953,15 @@ trigger_gravity_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused *
other->gravity = self->gravity;
}
/*
* QUAKED trigger_gravity (.5 .5 .5) ? TOGGLE START_OFF
* Changes the touching entites gravity to
* the value of "gravity". 1.0 is standard
* gravity for the level.
*
* TOGGLE - trigger_gravity can be turned on and off
* START_OFF - trigger_gravity starts turned off (implies TOGGLE)
*/
void
SP_trigger_gravity(edict_t *self)
{
@ -870,7 +980,20 @@ SP_trigger_gravity(edict_t *self)
InitTrigger(self);
self->gravity = (int)strtol(st.gravity, (char **)NULL, 10);
if (self->spawnflags & 1) /* TOGGLE */
{
self->use = trigger_gravity_use;
}
if (self->spawnflags & 2) /* START_OFF */
{
self->use = trigger_gravity_use;
self->solid = SOLID_NOT;
}
self->touch = trigger_gravity_touch;
gi.linkentity(self);
}
/*
@ -880,6 +1003,7 @@ SP_trigger_gravity(edict_t *self)
* "speed" default to 200, the speed thrown forward
* "height" default to 200, the speed thrown upwards
*/
void
trigger_monsterjump_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
csurface_t *surf /* unused */)

View File

@ -32,6 +32,7 @@ void infantry_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
void infantry_stand(edict_t *self);
void monster_use(edict_t *self, edict_t *other, edict_t *activator);
qboolean FindTarget(edict_t *self);
void SpawnTargetingSystem(edict_t *turret);
void
AnglesNormalize(vec3_t vec)
@ -110,12 +111,13 @@ turret_blocked(edict_t *self, edict_t *other)
* "speed" default 50
* "dmg" default 10
* "angle" point this forward
* "target" point this at an info_notnull at the muzzle tip
* "target" point this at an info_notnull at the muzzle tip
* "minpitch" min acceptable pitch angle : default -30
* "maxpitch" max acceptable pitch angle : default 30
* "minyaw" min acceptable yaw angle : default 0
* "maxyaw" max acceptable yaw angle : default 360
* "minyaw" min acceptable yaw angle : default 0
* "maxyaw" max acceptable yaw angle : default 360
*/
void
turret_breach_fire(edict_t *self)
{
@ -321,9 +323,17 @@ turret_breach_finish_init(edict_t *self)
else
{
self->target_ent = G_PickTarget(self->target);
VectorSubtract(self->target_ent->s.origin,
self->s.origin, self->move_origin);
G_FreeEdict(self->target_ent);
if (self->target_ent)
{
VectorSubtract(self->target_ent->s.origin,
self->s.origin, self->move_origin);
G_FreeEdict(self->target_ent);
}
else
{
gi.dprintf("could not find target entity for %s at %s\n", self->classname, vtos(self->s.origin));
}
}
self->teammaster->dmg = self->dmg;
@ -388,6 +398,7 @@ SP_turret_breach(edict_t *self)
* This portion of the turret changes yaw only.
* MUST be teamed with a turret_breach.
*/
void
SP_turret_base(edict_t *self)
{
@ -607,3 +618,228 @@ SP_turret_driver(edict_t *self)
gi.linkentity(self);
}
/*
* invisible turret drivers so we can have unmanned turrets.
* originally designed to shoot at func_trains and such, so they
* fire at the center of the bounding box, rather than the entity's
* origin. */
void
turret_brain_think(edict_t *self)
{
vec3_t dir;
vec3_t endpos;
float reaction_time;
trace_t trace;
if (!self)
{
return;
}
self->nextthink = level.time + FRAMETIME;
if (self->enemy)
{
if (!self->enemy->inuse)
{
self->enemy = NULL;
}
else if (self->enemy->takedamage && (self->enemy->health <= 0))
{
self->enemy = NULL;
}
}
if (!self->enemy)
{
if (!FindTarget(self))
{
return;
}
self->monsterinfo.trail_time = level.time;
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
VectorAdd(self->enemy->absmax, self->enemy->absmin, endpos);
VectorScale(endpos, 0.5, endpos);
}
else
{
VectorAdd(self->enemy->absmax, self->enemy->absmin, endpos);
VectorScale(endpos, 0.5, endpos);
trace = gi.trace(self->target_ent->s.origin, vec3_origin, vec3_origin,
endpos, self->target_ent, MASK_SHOT);
if ((trace.fraction == 1) || (trace.ent == self->enemy))
{
if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
{
self->monsterinfo.trail_time = level.time;
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
}
}
else
{
self->monsterinfo.aiflags |= AI_LOST_SIGHT;
return;
}
}
/* let the turret know where we want it to aim */
VectorSubtract(endpos, self->target_ent->s.origin, dir);
vectoangles(dir, self->target_ent->move_angles);
/* decide if we should shoot */
if (level.time < self->monsterinfo.attack_finished)
{
return;
}
if (self->delay)
{
reaction_time = self->delay;
}
else
{
reaction_time = (3 - skill->value) * 1.0;
}
if ((level.time - self->monsterinfo.trail_time) < reaction_time)
{
return;
}
self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
self->target_ent->spawnflags |= 65536;
}
void
turret_brain_link(edict_t *self)
{
vec3_t vec;
edict_t *ent;
if (!self)
{
return;
}
if (self->killtarget)
{
self->enemy = G_PickTarget(self->killtarget);
}
self->think = turret_brain_think;
self->nextthink = level.time + FRAMETIME;
self->target_ent = G_PickTarget(self->target);
self->target_ent->owner = self;
self->target_ent->teammaster->owner = self;
VectorCopy(self->target_ent->s.angles, self->s.angles);
vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
vec[2] = 0;
self->move_origin[0] = VectorLength(vec);
VectorSubtract(self->s.origin, self->target_ent->s.origin, vec);
vectoangles(vec, vec);
AnglesNormalize(vec);
self->move_origin[1] = vec[1];
self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
/* add the driver to the end of them team chain */
for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
{
}
ent->teamchain = self;
self->teammaster = self->target_ent->teammaster;
self->flags |= FL_TEAMSLAVE;
}
void
turret_brain_deactivate(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
if (!self)
{
return;
}
self->think = NULL;
self->nextthink = 0;
}
void
turret_brain_activate(edict_t *self, edict_t *other /* unused */, edict_t *activator)
{
if (!self || !activator)
{
return;
}
if (!self->enemy)
{
self->enemy = activator;
}
/* wait at least 3 seconds to fire. */
self->monsterinfo.attack_finished = level.time + 3;
self->use = turret_brain_deactivate;
self->think = turret_brain_link;
self->nextthink = level.time + FRAMETIME;
}
/*
* QUAKED turret_invisible_brain (1 .5 0) (-16 -16 -16) (16 16 16)
* Invisible brain to drive the turret.
*
* Does not search for targets. If targeted, can only be turned on once
* and then off once. After that they are completely disabled.
*
* "delay" the delay between firing (default ramps for skill level)
* "Target" the turret breach
* "Killtarget" the item you want it to attack.
* Target the brain if you want it activated later, instead of immediately. It will wait 3 seconds
* before firing to acquire the target.
*/
void
SP_turret_invisible_brain(edict_t *self)
{
if (!self)
{
return;
}
if (!self->killtarget)
{
gi.dprintf("turret_invisible_brain with no killtarget!\n");
G_FreeEdict(self);
return;
}
if (!self->target)
{
gi.dprintf("turret_invisible_brain with no target!\n");
G_FreeEdict(self);
return;
}
if (self->targetname)
{
self->use = turret_brain_activate;
}
else
{
self->think = turret_brain_link;
self->nextthink = level.time + FRAMETIME;
}
self->movetype = MOVETYPE_PUSH;
gi.linkentity(self);
}

View File

@ -290,6 +290,7 @@ void
G_UseTargets(edict_t *ent, edict_t *activator)
{
edict_t *t;
edict_t *master;
if (!ent)
{
@ -345,6 +346,7 @@ G_UseTargets(edict_t *ent, edict_t *activator)
{
level.total_secrets--;
}
/* same deal with target_goal, but also turn off CD music if applicable */
else if (!Q_stricmp(t->classname,"target_goal"))
{
@ -356,6 +358,30 @@ G_UseTargets(edict_t *ent, edict_t *activator)
}
}
/* if this entity is part of a train, cleanly remove it */
if (t->flags & FL_TEAMSLAVE)
{
master = t->teammaster;
while (master)
{
if (master->teamchain == t)
{
master->teamchain = t->teamchain;
break;
}
master = master->teamchain;
}
}
/* correct killcounter if a living monster gets killtargeted */
if ((t->monsterinfo.checkattack || strcmp (t->classname, "turret_driver") == 0) &&
!(t->monsterinfo.aiflags & (AI_GOOD_GUY|AI_DO_NOT_COUNT)) && t->deadflag != DEAD_DEAD)
{
level.killed_monsters++;
}
G_FreeEdict(t);
if (!ent->inuse)

View File

@ -690,28 +690,105 @@ ChickRocket(edict_t *self)
vec3_t start;
vec3_t dir;
vec3_t vec;
trace_t trace; /* check target */
int rocketSpeed;
float dist;
vec3_t target;
qboolean blindfire = false;
if (!self)
{
return;
}
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
{
blindfire = true;
}
else
{
blindfire = false;
}
if (!self->enemy || !self->enemy->inuse)
{
return;
}
AngleVectors(self->s.angles, forward, right, NULL);
G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CHICK_ROCKET_1],
forward, right, start);
VectorCopy(self->enemy->s.origin, vec);
vec[2] += self->enemy->viewheight;
VectorSubtract(vec, start, dir);
VectorNormalize(dir);
rocketSpeed = 500 + (100 * skill->value); /* rock & roll.... :) */
if (!strcmp(self->classname, "monster_chick_heat"))
if (blindfire)
{
monster_fire_heat(self, start, dir, 50, 500, MZ2_CHICK_ROCKET_1);
VectorCopy(self->monsterinfo.blind_fire_target, target);
}
else
{
monster_fire_rocket(self, start, dir, 50, 500, MZ2_CHICK_ROCKET_1);
VectorCopy(self->enemy->s.origin, target);
}
/* blindfire shooting */
if (blindfire)
{
VectorCopy(target, vec);
VectorSubtract(vec, start, dir);
}
/* don't shoot at feet if they're above where i'm shooting from. */
else if ((random() < 0.33) || (start[2] < self->enemy->absmin[2]))
{
VectorCopy(target, vec);
vec[2] += self->enemy->viewheight;
VectorSubtract(vec, start, dir);
}
else
{
VectorCopy(target, vec);
vec[2] = self->enemy->absmin[2];
VectorSubtract(vec, start, dir);
}
/* lead target (not when blindfiring) */
if ((!blindfire) && ((random() < (0.2 + ((3 - skill->value) * 0.15)))))
{
float time;
dist = VectorLength(dir);
time = dist / rocketSpeed;
VectorMA(vec, time, self->enemy->velocity, vec);
VectorSubtract(vec, start, dir);
}
VectorNormalize(dir);
if (blindfire)
{
/* blindfire has different fail criteria for the trace */
if (!blind_rocket_ok(self, start, right, target, 10.0f, dir))
{
return;
}
}
else
{
trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
if (((trace.ent != self->enemy) && (trace.ent != world)) ||
((trace.fraction <= 0.5f) && !trace.ent->client))
{
return;
}
}
if (!strcmp(self->classname, "monster_chick_heat"))
{
monster_fire_heat(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1);
}
else
{
monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1);
}
}
@ -1157,6 +1234,7 @@ SP_monster_chick(edict_t *self)
self->monsterinfo.currentmove = &chick_move_stand;
self->monsterinfo.scale = MODEL_SCALE;
self->monsterinfo.blindfire = true;
walkmonster_start(self);
}

View File

@ -22,8 +22,9 @@
*
* Soldier aka "Guard". This is the most complex enemy in Quake 2, since
* it uses all AI features (dodging, sight, crouching, etc) and comes
* in a myriad of variants. In Xatrix it's even more complex due to
* another 4 variants added.
* in a myriad of variants.
* In Rogue it's even more complex due to the blindfire stuff.
* In Xatrix it's even more complex due to another 4 variants added.
*
* =======================================================================
*/
@ -47,8 +48,11 @@ static int sound_step2;
static int sound_step3;
static int sound_step4;
void soldier_duck_up(edict_t *self);
void soldier_stand(edict_t *self);
void soldier_run(edict_t *self);
void soldier_fire(edict_t *self, int);
void soldier_blind(edict_t *self);
void soldierh_stand(edict_t *self);
void soldierh_run(edict_t *self);
extern void brain_dabeam(edict_t *self);
@ -89,6 +93,27 @@ soldier_footstep(edict_t *self)
}
}
void
soldier_start_charge(edict_t *self)
{
if (!self)
{
return;
}
self->monsterinfo.aiflags |= AI_CHARGING;
}
void
soldier_stop_charge(edict_t *self)
{
if (!self)
{
return;
}
self->monsterinfo.aiflags &= ~AI_CHARGING;
}
void
soldier_idle(edict_t *self)
@ -347,6 +372,20 @@ mmove_t soldier_move_start_run =
soldier_run
};
void
soldier_fire_run(edict_t *self)
{
if (!self)
{
return;
}
if ((self->s.skinnum <= 1) && (self->enemy) && visible(self, self->enemy))
{
soldier_fire(self, 0);
}
}
static mframe_t soldier_frames_run[] = {
{ai_run, 10, NULL},
{ai_run, 11, soldier_footstep},
@ -372,6 +411,8 @@ soldier_run(edict_t *self)
return;
}
monster_done_dodge(self);
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
self->monsterinfo.currentmove = &soldier_move_stand1;
@ -498,6 +539,12 @@ soldier_pain(edict_t *self, edict_t *other /* unused */,
self->s.skinnum |= 1;
}
monster_done_dodge(self);
soldier_stop_charge(self);
/* if we're blind firing, this needs to be turned off here */
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
if (level.time < self->pain_debounce_time)
{
if ((self->velocity[2] > 100) &&
@ -505,6 +552,12 @@ soldier_pain(edict_t *self, edict_t *other /* unused */,
(self->monsterinfo.currentmove == &soldier_move_pain2) ||
(self->monsterinfo.currentmove == &soldier_move_pain3)))
{
/* clear duck flag */
if (self->monsterinfo.aiflags & AI_DUCKED)
{
monster_duck_up(self);
}
self->monsterinfo.currentmove = &soldier_move_pain4;
}
@ -530,6 +583,12 @@ soldier_pain(edict_t *self, edict_t *other /* unused */,
if (self->velocity[2] > 100)
{
/* clear duck flag */
if (self->monsterinfo.aiflags & AI_DUCKED)
{
monster_duck_up(self);
}
self->monsterinfo.currentmove = &soldier_move_pain4;
return;
}
@ -553,6 +612,12 @@ soldier_pain(edict_t *self, edict_t *other /* unused */,
{
self->monsterinfo.currentmove = &soldier_move_pain3;
}
/* clear duck flag */
if (self->monsterinfo.aiflags & AI_DUCKED)
{
monster_duck_up(self);
}
}
static int blaster_flash[] =
@ -592,7 +657,7 @@ static int machinegun_flash[] =
};
void
soldier_fire(edict_t *self, int flash_number)
soldier_fire(edict_t *self, int in_flash_number)
{
vec3_t start;
vec3_t forward, right, up;
@ -601,12 +666,33 @@ soldier_fire(edict_t *self, int flash_number)
vec3_t end;
float r, u;
int flash_index;
int flash_number;
if (!self)
{
return;
}
vec3_t aim_norm;
float angle;
trace_t tr;
vec3_t aim_good;
if ((!self->enemy) || (!self->enemy->inuse))
{
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
return;
}
if (in_flash_number < 0)
{
flash_number = -1 * in_flash_number;
}
else
{
flash_number = in_flash_number;
}
if (self->s.skinnum < 2)
{
flash_index = blaster_flash[flash_number];
@ -624,7 +710,7 @@ soldier_fire(edict_t *self, int flash_number)
G_ProjectSource(self->s.origin, monster_flash_offset[flash_index],
forward, right, start);
if ((flash_number == 5) || (flash_number == 6))
if ((flash_number == 5) || (flash_number == 6)) /* he's dead */
{
VectorCopy(forward, aim);
}
@ -633,11 +719,34 @@ soldier_fire(edict_t *self, int flash_number)
VectorCopy(self->enemy->s.origin, end);
end[2] += self->enemy->viewheight;
VectorSubtract(end, start, aim);
VectorCopy(end, aim_good);
if (in_flash_number < 0)
{
VectorCopy(aim, aim_norm);
VectorNormalize(aim_norm);
angle = DotProduct(aim_norm, forward);
if (angle < 0.9) /* ~25 degree angle */
{
return;
}
}
vectoangles(aim, dir);
AngleVectors(dir, forward, right, up);
r = crandom() * 1000;
u = crandom() * 500;
if (skill->value < SKILL_HARD)
{
r = crandom() * 1000;
u = crandom() * 500;
}
else
{
r = crandom() * 500;
u = crandom() * 250;
}
VectorMA(start, 8192, forward, end);
VectorMA(end, r, right, end);
VectorMA(end, u, up, end);
@ -646,6 +755,16 @@ soldier_fire(edict_t *self, int flash_number)
VectorNormalize(aim);
}
if (!((flash_number == 5) || (flash_number == 6))) /* he's dead */
{
tr = gi.trace(start, NULL, NULL, aim_good, self, MASK_SHOT);
if ((tr.ent != self->enemy) && (tr.ent != world))
{
return;
}
}
if (self->s.skinnum <= 1)
{
monster_fire_blaster(self, start, aim, 5, 600, flash_index, EF_BLASTER);
@ -658,6 +777,7 @@ soldier_fire(edict_t *self, int flash_number)
}
else
{
/* changed to wait from pausetime to not interfere with dodge code */
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
{
self->monsterinfo.pausetime = level.time + (3 + randk() % 8) * FRAMETIME;
@ -698,6 +818,17 @@ soldier_attack1_refire1(edict_t *self)
return;
}
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
{
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
return;
}
if (!self->enemy)
{
return;
}
if (self->s.skinnum > 1)
{
return;
@ -727,6 +858,11 @@ soldier_attack1_refire2(edict_t *self)
return;
}
if (!self->enemy)
{
return;
}
if (self->s.skinnum < 2)
{
return;
@ -787,6 +923,11 @@ soldier_attack2_refire1(edict_t *self)
return;
}
if (!self->enemy)
{
return;
}
if (self->s.skinnum > 1)
{
return;
@ -816,6 +957,11 @@ soldier_attack2_refire2(edict_t *self)
return;
}
if (!self->enemy)
{
return;
}
if (self->s.skinnum < 2)
{
return;
@ -992,24 +1138,33 @@ soldier_attack6_refire(edict_t *self)
return;
}
/* make sure dodge & charge bits are cleared */
monster_done_dodge(self);
soldier_stop_charge(self);
if (!self->enemy)
{
return;
}
if (self->enemy->health <= 0)
{
return;
}
if (range(self, self->enemy) < RANGE_MID)
if (range(self, self->enemy) < RANGE_NEAR)
{
return;
}
if (skill->value == SKILL_HARDPLUS)
if ((skill->value == SKILL_HARDPLUS) || ((random() < (0.25 * ((float)skill->value)))))
{
self->monsterinfo.nextframe = FRAME_runs03;
}
}
static mframe_t soldier_frames_attack6[] = {
{ai_charge, 10, NULL},
{ai_charge, 10, soldier_start_charge},
{ai_charge, 4, NULL},
{ai_charge, 12, soldier_footstep},
{ai_charge, 11, soldier_fire8},
@ -1036,25 +1191,82 @@ mmove_t soldier_move_attack6 =
void
soldier_attack(edict_t *self)
{
float r, chance;
if (!self)
{
return;
}
if (self->s.skinnum < 4)
monster_done_dodge(self);
/* blindfire! */
if (self->monsterinfo.attack_state == AS_BLIND)
{
if (random() < 0.5)
/* setup shot probabilities */
if (self->monsterinfo.blind_fire_delay < 1.0)
{
self->monsterinfo.currentmove = &soldier_move_attack1;
chance = 1.0;
}
else if (self->monsterinfo.blind_fire_delay < 7.5)
{
chance = 0.4;
}
else
{
self->monsterinfo.currentmove = &soldier_move_attack2;
chance = 0.1;
}
r = random();
/* minimum of 2 seconds, plus 0-3, after the shots are done */
self->monsterinfo.blind_fire_delay += 2.1 + 2.0 + random() * 3.0;
/* don't shoot at the origin */
if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin))
{
return;
}
/* don't shoot if the dice say not to */
if (r > chance)
{
return;
}
/* turn on manual steering to signal both manual steering and blindfire */
self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
self->monsterinfo.currentmove = &soldier_move_attack1;
self->monsterinfo.attack_finished = level.time + 1.5 + random();
return;
}
r = random();
if ((!(self->monsterinfo.aiflags & (AI_BLOCKED | AI_STAND_GROUND))) &&
(range(self, self->enemy) >= RANGE_NEAR) &&
((r < (skill->value * 0.25)) &&
(self->s.skinnum <= 3)))
{
self->monsterinfo.currentmove = &soldier_move_attack6;
}
else
{
self->monsterinfo.currentmove = &soldier_move_attack4;
if (self->s.skinnum < 4)
{
if (random() < 0.5)
{
self->monsterinfo.currentmove = &soldier_move_attack1;
}
else
{
self->monsterinfo.currentmove = &soldier_move_attack2;
}
}
else
{
self->monsterinfo.currentmove = &soldier_move_attack4;
}
}
}
@ -1075,9 +1287,11 @@ soldier_sight(edict_t *self, edict_t *other /* unused */)
gi.sound(self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0);
}
if ((skill->value > SKILL_EASY) && (range(self, self->enemy) >= RANGE_MID))
if ((skill->value > SKILL_EASY) && (self->enemy) &&
(range(self, self->enemy) >= RANGE_NEAR))
{
if (random() > 0.5)
/* don't let machinegunners run & shoot */
if ((random() > 0.75) && (self->s.skinnum <= 3))
{
self->monsterinfo.currentmove = &soldier_move_attack6;
}
@ -1118,6 +1332,29 @@ mmove_t soldier_move_duck =
soldier_run
};
qboolean
soldier_blocked(edict_t *self, float dist)
{
if (!self)
{
return false;
}
/* don't do anything if you're dodging */
if ((self->monsterinfo.aiflags & AI_DODGING) ||
(self->monsterinfo.aiflags & AI_DUCKED))
{
return false;
}
if (blocked_checkplat(self, dist))
{
return true;
}
return false;
}
void
soldier_dodge(edict_t *self, edict_t *attacker, float eta,
trace_t *tr /* unused */)
@ -1220,6 +1457,46 @@ soldier_dead(edict_t *self)
gi.linkentity(self);
}
void
soldier_dead2(edict_t *self)
{
vec3_t tempmins, tempmaxs, temporg;
trace_t tr;
if (!self)
{
return;
}
VectorCopy(self->s.origin, temporg);
/* this is because location traces done at the
floor are guaranteed to hit the floor (inside
the sv_trace code it grows the bbox by 1 in
all directions) */
temporg[2] += 1;
VectorSet(tempmins, -32, -32, -24);
VectorSet(tempmaxs, 32, 32, -8);
tr = gi.trace(temporg, tempmins, tempmaxs, temporg, self, MASK_SOLID);
if (tr.startsolid || tr.allsolid)
{
VectorSet(self->mins, -16, -16, -24);
VectorSet(self->maxs, 16, 16, -8);
}
else
{
VectorCopy(tempmins, self->mins);
VectorCopy(tempmaxs, self->maxs);
}
self->movetype = MOVETYPE_TOSS;
self->svflags |= SVF_DEADMONSTER;
self->nextthink = 0;
gi.linkentity(self);
}
static mframe_t soldier_frames_death1[] = {
{ai_move, 0, NULL},
{ai_move, -10, NULL},
@ -1445,7 +1722,7 @@ mmove_t soldier_move_death4 =
FRAME_death401,
FRAME_death453,
soldier_frames_death4,
soldier_dead
soldier_dead2
};
static mframe_t soldier_frames_death5[] = {
@ -1591,6 +1868,117 @@ soldier_die(edict_t *self, edict_t *inflictor /* unused */,
}
}
void
soldier_sidestep(edict_t *self)
{
if (!self)
{
return;
}
if (self->s.skinnum <= 3)
{
if (self->monsterinfo.currentmove != &soldier_move_attack6)
{
self->monsterinfo.currentmove = &soldier_move_attack6;
}
}
else
{
if (self->monsterinfo.currentmove != &soldier_move_start_run)
{
self->monsterinfo.currentmove = &soldier_move_start_run;
}
}
}
void
soldier_duck(edict_t *self, float eta)
{
float r;
if (!self)
{
return;
}
/* has to be done immediately otherwise he can get stuck */
monster_duck_down(self);
if (skill->value == SKILL_EASY)
{
self->monsterinfo.currentmove = &soldier_move_duck;
self->monsterinfo.duck_wait_time = level.time + eta + 1;
return;
}
r = random();
if (r > (skill->value * 0.3))
{
self->monsterinfo.currentmove = &soldier_move_duck;
self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
}
else
{
self->monsterinfo.currentmove = &soldier_move_attack3;
self->monsterinfo.duck_wait_time = level.time + eta + 1;
}
}
static mframe_t soldier_frames_blind[] = {
{ai_move, 0, soldier_idle},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL}
};
mmove_t soldier_move_blind = {
FRAME_stand101,
FRAME_stand130,
soldier_frames_blind,
soldier_blind
};
void
soldier_blind(edict_t *self)
{
if (!self)
{
return;
}
self->monsterinfo.currentmove = &soldier_move_blind;
}
void
SP_monster_soldier_x(edict_t *self)
{
@ -1631,6 +2019,16 @@ SP_monster_soldier_x(edict_t *self)
self->monsterinfo.melee = NULL;
self->monsterinfo.sight = soldier_sight;
self->monsterinfo.blocked = soldier_blocked;
self->monsterinfo.duck = soldier_duck;
self->monsterinfo.unduck = monster_duck_up;
self->monsterinfo.sidestep = soldier_sidestep;
if (self->spawnflags & 8) /* blind */
{
self->monsterinfo.stand = soldier_blind;
}
gi.linkentity(self);
self->monsterinfo.stand(self);
@ -1639,7 +2037,9 @@ SP_monster_soldier_x(edict_t *self)
}
/*
* QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
* QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind
*
* Blind - monster will just stand there until triggered
*/
void
SP_monster_soldier_light(edict_t *self)
@ -1671,10 +2071,13 @@ SP_monster_soldier_light(edict_t *self)
gi.soundindex("soldier/solatck2.wav");
self->s.skinnum = 0;
self->monsterinfo.blindfire = true;
}
/*
* QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
* QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind
*
* Blind - monster will just stand there until triggered
*/
void
SP_monster_soldier(edict_t *self)
@ -1707,7 +2110,9 @@ SP_monster_soldier(edict_t *self)
}
/*
* QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
* QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind
*
* Blind - monster will just stand there until triggered
*/
void
SP_monster_soldier_ss(edict_t *self)

View File

@ -1188,7 +1188,7 @@ extern void rotating_use ( edict_t * self , edict_t * other , edict_t * activato
extern void rotating_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
extern void rotating_blocked ( edict_t * self , edict_t * other ) ;
extern void SP_func_plat ( edict_t * ent ) ;
extern void plat_spawn_inside_trigger ( edict_t * ent ) ;
extern edict_t * plat_spawn_inside_trigger ( edict_t * ent ) ;
extern void Touch_Plat_Center ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
extern void Use_Plat ( edict_t * ent , edict_t * other , edict_t * activator ) ;
extern void plat_blocked ( edict_t * self , edict_t * other ) ;

View File

@ -1,9 +1,23 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
* Licensed under the GNU General Public License 2.0.
*/
/*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Level functions. Platforms, buttons, dooors and so on.
@ -138,13 +152,13 @@ Move_Final(edict_t *ent)
void
Move_Begin(edict_t *ent)
{
float frames;
if (!ent)
{
return;
}
float frames;
if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance)
{
Move_Final(ent);
@ -152,9 +166,11 @@ Move_Begin(edict_t *ent)
}
VectorScale(ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity);
frames = floor( (ent->moveinfo.remaining_distance /
frames = floor(
(ent->moveinfo.remaining_distance /
ent->moveinfo.speed) / FRAMETIME);
ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME;
ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed *
FRAMETIME;
ent->nextthink = level.time + (frames * FRAMETIME);
ent->think = Move_Final;
}
@ -195,8 +211,10 @@ Move_Calc(edict_t *ent, vec3_t dest, void (*func)(edict_t *))
}
}
/* Support routines for angular movement
(changes in angle using avelocity) */
/*
* Support routines for angular movement
* (changes in angle using avelocity)
*/
void
AngleMove_Done(edict_t *ent)
{
@ -366,8 +384,12 @@ plat_CalcAcceleratedMove(moveinfo_t *moveinfo)
{
float f;
f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
f =
(moveinfo->accel +
moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
moveinfo->move_speed =
(-2 +
sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
decel_dist = AccelerationDistance(moveinfo->move_speed, moveinfo->decel);
}
@ -458,8 +480,10 @@ plat_Accelerate(moveinfo_t *moveinfo)
p1_speed = (old_speed + moveinfo->move_speed) / 2.0;
p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed));
distance = p1_distance + p2_distance;
moveinfo->current_speed = (p1_speed * (p1_distance /
distance)) + (moveinfo->move_speed * (p2_distance / distance));
moveinfo->current_speed =
(p1_speed *
(p1_distance /
distance)) + (moveinfo->move_speed * (p2_distance / distance));
moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel *
(p2_distance / distance);
return;
@ -469,6 +493,10 @@ plat_Accelerate(moveinfo_t *moveinfo)
return;
}
/*
* The team has completed a frame of movement,
* so change the speed for the next frame
*/
void
Think_AccelMove(edict_t *ent)
{
@ -478,7 +506,12 @@ Think_AccelMove(edict_t *ent)
}
ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
plat_CalcAcceleratedMove(&ent->moveinfo);
if (ent->moveinfo.current_speed == 0) /* starting or blocked */
{
plat_CalcAcceleratedMove(&ent->moveinfo);
}
plat_Accelerate(&ent->moveinfo);
/* will the entire move complete on next frame? */
@ -506,8 +539,8 @@ plat_hit_top(edict_t *ent)
{
if (ent->moveinfo.sound_end)
{
gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_end,
1, ATTN_STATIC, 0);
gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE,
ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
}
ent->s.sound = 0;
@ -698,7 +731,6 @@ wait_and_change(edict_t* ent, void (*afterwaitfunc)(edict_t *))
else
{
afterwaitfunc(ent);
}
}
@ -729,7 +761,8 @@ Touch_Plat_Center(edict_t *ent, edict_t *other, cplane_t *plane /* unsed */,
}
else if (ent->moveinfo.state == STATE_TOP)
{
ent->nextthink = level.time + 1; /* the player is still on the plat, so delay going down */
/* the player is still on the plat, so delay going down */
ent->nextthink = level.time + 1;
}
}
@ -1472,12 +1505,13 @@ SP_func_plat2(edict_t *ent)
/* ==================================================================== */
/* QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST EAST MED HARD DM COOP ACCEL
/*
* QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST EAST MED HARD DM COOP ACCEL
*
* You need to have an origin brush as part of this entity. The center
* of that brush will bethe point around which it is rotated. It will
* rotate around the Z axis by default. You can check either the
* X_AXIS or Y_AXIS box to change that.
* You need to have an origin brush as part of this entity.
* The center of that brush will be the point around which it
* is rotated. It will rotate around the Z axis by default.
* You can check either the X_AXIS or Y_AXIS box to change that.
*
* func_rotating will use it's targets when it stops and starts.
*
@ -1667,7 +1701,6 @@ SP_func_rotating(edict_t *ent)
}
ent->use = rotating_use;
ent->blocked = rotating_blocked;
if (ent->spawnflags & 1)
@ -1949,7 +1982,6 @@ SP_func_button(edict_t *ent)
gi.linkentity(ent);
}
/* ==================================================================== */
/*
@ -2501,9 +2533,9 @@ door_blocked(edict_t *self, edict_t *other)
return;
}
/* if a door has a negative wait, it would never come
back if blocked, so let it just squash the object
to death real fast */
/* if a door has a negative wait, it would never
come back if blocked, so let it just squash the
object to death real fast */
if (self->moveinfo.wait >= 0)
{
if (self->moveinfo.state == STATE_DOWN)
@ -2762,6 +2794,7 @@ Door_Activate(edict_t *self, edict_t *other /* unused */,
* 3) medium
* 4) heavy
*/
void
SP_func_door_rotating(edict_t *ent)
{
@ -3014,7 +3047,8 @@ SP_func_water(edict_t *self)
self->speed = 25;
}
self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed;
self->moveinfo.accel = self->moveinfo.decel =
self->moveinfo.speed = self->speed;
if (self->spawnflags & 2) /* smart water */
{
@ -3045,6 +3079,8 @@ SP_func_water(edict_t *self)
gi.linkentity(self);
}
/* ==================================================================== */
/*
* QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
*
@ -3135,6 +3171,7 @@ train_wait(edict_t *self)
else if (self->spawnflags & TRAIN_TOGGLE)
{
self->target_ent = NULL;
train_next(self);
self->spawnflags &= ~TRAIN_START_ON;
VectorClear(self->velocity);
self->nextthink = 0;
@ -3145,8 +3182,8 @@ train_wait(edict_t *self)
if (self->moveinfo.sound_end)
{
gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
self->moveinfo.sound_end,
1, ATTN_STATIC, 0);
self->moveinfo.sound_end, 1,
ATTN_STATIC, 0);
}
self->s.sound = 0;
@ -3178,6 +3215,7 @@ train_next(edict_t *self)
first = true;
again:
if (!self->target)
{
return;
@ -3430,7 +3468,7 @@ SP_func_train(edict_t *self)
if (self->target)
{
/* start trains on the second frame, to make
sure their targets have had a chance to spawn */
* sure their targets have had a chance to spawn */
self->nextthink = level.time + FRAMETIME;
self->think = func_train_find;
}
@ -3440,6 +3478,8 @@ SP_func_train(edict_t *self)
}
}
/* ==================================================================== */
/*
* QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
*/
@ -3554,7 +3594,7 @@ func_timer_think(edict_t *self)
void
func_timer_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
{
if (!self)
if (!self || !activator)
{
return;
}
@ -3853,16 +3893,16 @@ door_secret_die(edict_t *self, edict_t *inflictor /* unused */,
void
SP_func_door_secret(edict_t *ent)
{
if (!ent)
{
return;
}
vec3_t forward, right, up;
float side;
float width;
float length;
if (!ent)
{
return;
}
ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav");
ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav");
ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav");
@ -3979,3 +4019,231 @@ SP_func_killbox(edict_t *ent)
ent->use = use_killbox;
ent->svflags = SVF_NOCLIENT;
}
/*
* QUAKED rotating_light (0 .5 .8) (-8 -8 -8) (8 8 8) START_OFF ALARM
* "health" if set, the light may be killed.
*/
#define START_OFF 1
void
rotating_light_alarm(edict_t *self)
{
if (!self)
{
return;
}
if (self->spawnflags & START_OFF)
{
self->think = NULL;
self->nextthink = 0;
}
else
{
gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
self->moveinfo.sound_start, 1,
ATTN_STATIC, 0);
self->nextthink = level.time + 1;
}
}
void
rotating_light_killed(edict_t *self, edict_t *inflictor /* unused */,
edict_t *attacker /* unused */, int damage /* unused */,
vec3_t point /* unused */)
{
if (!self)
{
return;
}
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_WELDING_SPARKS);
gi.WriteByte(30);
gi.WritePosition(self->s.origin);
gi.WriteDir(vec3_origin);
gi.WriteByte(0xe0 + (rand() & 7));
gi.multicast(self->s.origin, MULTICAST_PVS);
self->s.effects &= ~EF_SPINNINGLIGHTS;
self->use = NULL;
self->think = G_FreeEdict;
self->nextthink = level.time + 0.1;
}
void
rotating_light_use(edict_t *self, edict_t *other /* unused */,
edict_t *activator /* unused */)
{
if (!self)
{
return;
}
if (self->spawnflags & START_OFF)
{
self->spawnflags &= ~START_OFF;
self->s.effects |= EF_SPINNINGLIGHTS;
if (self->spawnflags & 2)
{
self->think = rotating_light_alarm;
self->nextthink = level.time + 0.1;
}
}
else
{
self->spawnflags |= START_OFF;
self->s.effects &= ~EF_SPINNINGLIGHTS;
}
}
void
SP_rotating_light(edict_t *self)
{
if (!self)
{
return;
}
self->movetype = MOVETYPE_STOP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/objects/light/tris.md2");
self->s.frame = 0;
self->use = rotating_light_use;
if (self->spawnflags & START_OFF)
{
self->s.effects &= ~EF_SPINNINGLIGHTS;
}
else
{
self->s.effects |= EF_SPINNINGLIGHTS;
}
if (!self->speed)
{
self->speed = 32;
}
if (!self->health)
{
self->health = 10;
self->max_health = self->health;
self->die = rotating_light_killed;
self->takedamage = DAMAGE_YES;
}
else
{
self->max_health = self->health;
self->die = rotating_light_killed;
self->takedamage = DAMAGE_YES;
}
if (self->spawnflags & 2)
{
self->moveinfo.sound_start = gi.soundindex("misc/alarm.wav");
}
gi.linkentity(self);
}
/*
* QUAKED func_object_repair (1 .5 0) (-8 -8 -8) (8 8 8)
* object to be repaired.
* The default delay is 1 second
* "delay" the delay in seconds for spark to occur
*/
void
object_repair_fx(edict_t *ent)
{
if (!ent)
{
return;
}
ent->nextthink = level.time + ent->delay;
if (ent->health <= 100)
{
ent->health++;
}
else
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_WELDING_SPARKS);
gi.WriteByte(10);
gi.WritePosition(ent->s.origin);
gi.WriteDir(vec3_origin);
gi.WriteByte(0xe0 + (rand() & 7));
gi.multicast(ent->s.origin, MULTICAST_PVS);
}
}
void
object_repair_dead(edict_t *ent)
{
if (!ent)
{
return;
}
G_UseTargets(ent, ent);
ent->nextthink = level.time + 0.1;
ent->think = object_repair_fx;
}
void
object_repair_sparks(edict_t *ent)
{
if (!ent)
{
return;
}
if (ent->health < 0)
{
ent->nextthink = level.time + 0.1;
ent->think = object_repair_dead;
return;
}
ent->nextthink = level.time + ent->delay;
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_WELDING_SPARKS);
gi.WriteByte(10);
gi.WritePosition(ent->s.origin);
gi.WriteDir(vec3_origin);
gi.WriteByte(0xe0 + (rand() & 7));
gi.multicast(ent->s.origin, MULTICAST_PVS);
}
void
SP_object_repair(edict_t *ent)
{
if (!ent)
{
return;
}
ent->movetype = MOVETYPE_NONE;
ent->solid = SOLID_BBOX;
ent->classname = "object_repair";
ent->think = object_repair_sparks;
ent->nextthink = level.time + 1.0;
ent->health = 100;
if (!ent->delay)
{
ent->delay = 1.0;
}
gi.linkentity(ent);
}

View File

@ -1,9 +1,23 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
* Licensed under the GNU General Public License 2.0.
*/
/*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Item handling and item definitions.
@ -40,18 +54,25 @@ void Weapon_Prox(edict_t *ent);
void Weapon_Tesla(edict_t *ent);
void Weapon_ProxLauncher(edict_t *ent);
gitem_armor_t jacketarmor_info = {25, 50, .30, .00, ARMOR_JACKET};
gitem_armor_t combatarmor_info = {50, 100, .60, .30, ARMOR_COMBAT};
gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY};
void Weapon_Ionripper(edict_t *ent);
void Weapon_Phalanx(edict_t *ent);
void Weapon_Trap(edict_t *ent);
int jacket_armor_index;
int combat_armor_index;
int body_armor_index;
static gitem_armor_t jacketarmor_info = {25, 50, .30, .00, ARMOR_JACKET};
static gitem_armor_t combatarmor_info = {50, 100, .60, .30, ARMOR_COMBAT};
static gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY};
static int jacket_armor_index;
static int combat_armor_index;
static int body_armor_index;
static int power_screen_index;
static int power_shield_index;
void Use_Quad(edict_t *ent, gitem_t *item);
void Use_QuadFire(edict_t *ent, gitem_t *item);
static int quad_drop_timeout_hack;
static int quad_fire_drop_timeout_hack;
/* ====================================================================== */
@ -199,7 +220,7 @@ Pickup_Powerup(edict_t *ent, edict_t *other)
{
int quantity;
if (!ent || !other)
if (!ent || !other)
{
return false;
}
@ -237,6 +258,7 @@ Drop_General(edict_t *ent, gitem_t *item)
{
return;
}
Drop_Item(ent, item);
ent->client->pers.inventory[ITEM_INDEX(item)]--;
ValidateSelectedItem(ent);
@ -273,7 +295,7 @@ Pickup_Adrenaline(edict_t *ent, edict_t *other)
qboolean
Pickup_AncientHead(edict_t *ent, edict_t *other)
{
if (!ent || !other)
if (!ent || !other)
{
return false;
}
@ -291,14 +313,14 @@ Pickup_AncientHead(edict_t *ent, edict_t *other)
qboolean
Pickup_Bandolier(edict_t *ent, edict_t *other)
{
if (!ent || !other)
gitem_t *item;
int index;
if (!ent || !other)
{
return false;
}
gitem_t *item;
int index;
if (other->client->pers.max_bullets < 250)
{
other->client->pers.max_bullets = 250;
@ -319,6 +341,11 @@ Pickup_Bandolier(edict_t *ent, edict_t *other)
other->client->pers.max_slugs = 75;
}
if (other->client->pers.max_magslug < 75)
{
other->client->pers.max_magslug = 75;
}
if (other->client->pers.max_flechettes < 250)
{
other->client->pers.max_flechettes = 250;
@ -376,7 +403,7 @@ Pickup_Pack(edict_t *ent, edict_t *other)
gitem_t *item;
int index;
if (!ent || !other)
if (!ent || !other)
{
return false;
}
@ -411,6 +438,11 @@ Pickup_Pack(edict_t *ent, edict_t *other)
other->client->pers.max_slugs = 100;
}
if (other->client->pers.max_magslug < 100)
{
other->client->pers.max_magslug = 100;
}
if (other->client->pers.max_flechettes < 200)
{
other->client->pers.max_flechettes = 200;
@ -514,6 +546,21 @@ Pickup_Pack(edict_t *ent, edict_t *other)
}
}
item = FindItem("Mag Slug");
if (item)
{
index = ITEM_INDEX(item);
other->client->pers.inventory[index] += item->quantity;
if (other->client->pers.inventory[index] >
other->client->pers.max_magslug)
{
other->client->pers.inventory[index] =
other->client->pers.max_magslug;
}
}
item = FindItem("Flechettes");
if (item)
@ -552,6 +599,8 @@ Pickup_Pack(edict_t *ent, edict_t *other)
return true;
}
/* ====================================================================== */
qboolean
Pickup_Nuke(edict_t *ent, edict_t *other)
{
@ -872,8 +921,44 @@ Use_Quad(edict_t *ent, gitem_t *item)
ent->client->quad_framenum = level.framenum + timeout;
}
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM,
0);
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
}
/* ===================================================================== */
void
Use_QuadFire(edict_t *ent, gitem_t *item)
{
int timeout;
if (!ent || !item)
{
return;
}
ent->client->pers.inventory[ITEM_INDEX(item)]--;
ValidateSelectedItem(ent);
if (quad_fire_drop_timeout_hack)
{
timeout = quad_fire_drop_timeout_hack;
quad_fire_drop_timeout_hack = 0;
}
else
{
timeout = 300;
}
if (ent->client->quadfire_framenum > level.framenum)
{
ent->client->quadfire_framenum += timeout;
}
else
{
ent->client->quadfire_framenum = level.framenum + timeout;
}
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire1.wav"), 1, ATTN_NORM, 0);
}
/* ====================================================================== */
@ -1021,7 +1106,6 @@ Add_Ammo(edict_t *ent, gitem_t *item, int count)
return false;
}
if (item->tag == AMMO_BULLETS)
{
max = ent->client->pers.max_bullets;
@ -1046,6 +1130,14 @@ Add_Ammo(edict_t *ent, gitem_t *item, int count)
{
max = ent->client->pers.max_slugs;
}
else if (item->tag == AMMO_MAGSLUG)
{
max = ent->client->pers.max_magslug;
}
else if (item->tag == AMMO_TRAP)
{
max = ent->client->pers.max_trap;
}
else if (item->tag == AMMO_FLECHETTES)
{
max = ent->client->pers.max_flechettes;
@ -1130,7 +1222,8 @@ Pickup_Ammo(edict_t *ent, edict_t *other)
}
}
if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value))
if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) &&
(deathmatch->value))
{
SetRespawn(ent, 30);
}
@ -1333,7 +1426,7 @@ Pickup_Armor(edict_t *ent, edict_t *other)
{
oldinfo = &combatarmor_info;
}
else
else /* (old_armor_index == body_armor_index) */
{
oldinfo = &bodyarmor_info;
}
@ -1342,7 +1435,8 @@ Pickup_Armor(edict_t *ent, edict_t *other)
{
/* calc new armor values */
salvage = oldinfo->normal_protection / newinfo->normal_protection;
salvagecount = salvage * other->client->pers.inventory[old_armor_index];
salvagecount = salvage *
other->client->pers.inventory[old_armor_index];
newcount = newinfo->base_count + salvagecount;
if (newcount > newinfo->max_count)
@ -1361,7 +1455,8 @@ Pickup_Armor(edict_t *ent, edict_t *other)
/* calc new armor values */
salvage = newinfo->normal_protection / oldinfo->normal_protection;
salvagecount = salvage * newinfo->base_count;
newcount = other->client->pers.inventory[old_armor_index] + salvagecount;
newcount = other->client->pers.inventory[old_armor_index] +
salvagecount;
if (newcount > oldinfo->max_count)
{
@ -1425,10 +1520,16 @@ Use_PowerArmor(edict_t *ent, gitem_t *item)
{
int index;
if (!ent || !item)
{
return;
}
if (ent->flags & FL_POWER_ARMOR)
{
ent->flags &= ~FL_POWER_ARMOR;
gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
gi.sound(ent, CHAN_AUTO, gi.soundindex(
"misc/power2.wav"), 1, ATTN_NORM, 0);
}
else
{
@ -1441,7 +1542,8 @@ Use_PowerArmor(edict_t *ent, gitem_t *item)
}
ent->flags |= FL_POWER_ARMOR;
gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0);
gi.sound(ent, CHAN_AUTO, gi.soundindex(
"misc/power1.wav"), 1, ATTN_NORM, 0);
}
}
@ -1528,39 +1630,47 @@ Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, csurface_
other->client->bonus_alpha = 0.25;
/* show icon and name on status bar */
other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon);
other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS + ITEM_INDEX(ent->item);
other->client->ps.stats[STAT_PICKUP_ICON] =
gi.imageindex(ent->item->icon);
other->client->ps.stats[STAT_PICKUP_STRING] =
CS_ITEMS + ITEM_INDEX(ent->item);
other->client->pickup_msg_time = level.time + 3.0;
/* change selected item */
if (ent->item->use)
{
other->client->pers.selected_item =
other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item);
other->client->ps.stats[STAT_SELECTED_ITEM] =
ITEM_INDEX(ent->item);
}
if (ent->item->pickup == Pickup_Health)
{
if (ent->count == 2)
{
gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0);
gi.sound(other, CHAN_ITEM, gi.soundindex(
"items/s_health.wav"), 1, ATTN_NORM, 0);
}
else if (ent->count == 10)
{
gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0);
gi.sound(other, CHAN_ITEM, gi.soundindex(
"items/n_health.wav"), 1, ATTN_NORM, 0);
}
else if (ent->count == 25)
{
gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0);
gi.sound(other, CHAN_ITEM, gi.soundindex(
"items/l_health.wav"), 1, ATTN_NORM, 0);
}
else /* (ent->count == 100) */
{
gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0);
gi.sound(other, CHAN_ITEM, gi.soundindex(
"items/m_health.wav"), 1, ATTN_NORM, 0);
}
}
else if (ent->item->pickup_sound)
{
gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0);
gi.sound(other, CHAN_ITEM, gi.soundindex(
ent->item->pickup_sound), 1, ATTN_NORM, 0);
}
/* activate item instantly if appropriate */
@ -1602,7 +1712,8 @@ Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, csurface_
return;
}
if (!((coop->value) && (ent->item->flags & IT_STAY_COOP)) ||
if (!((coop->value) &&
(ent->item->flags & IT_STAY_COOP)) ||
(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))
{
if (ent->flags & FL_RESPAWN)
@ -1631,6 +1742,10 @@ drop_temp_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
return;
}
/* plane and surf are unused in Touch_Item
but since the function is part of the
game <-> client interface dropping
them is too much pain. */
Touch_Item(ent, other, plane, surf);
}
@ -1684,7 +1799,8 @@ Drop_Item(edict_t *ent, gitem_t *item)
AngleVectors(ent->client->v_angle, forward, right, NULL);
VectorSet(offset, 24, 0, -16);
G_ProjectSource(ent->s.origin, offset, forward, right, dropped->s.origin);
G_ProjectSource(ent->s.origin, offset, forward, right,
dropped->s.origin);
trace = gi.trace(ent->s.origin, dropped->mins, dropped->maxs,
dropped->s.origin, ent, CONTENTS_SOLID);
VectorCopy(trace.endpos, dropped->s.origin);
@ -1736,15 +1852,15 @@ Use_Item(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused
void
droptofloor(edict_t *ent)
{
trace_t tr;
vec3_t dest;
float *v;
if (!ent)
{
return;
}
trace_t tr;
vec3_t dest;
float *v;
v = tv(-15, -15, -15);
VectorCopy(v, ent->mins);
v = tv(15, 15, 15);
@ -1770,10 +1886,19 @@ droptofloor(edict_t *ent)
if (tr.startsolid)
{
gi.dprintf("droptofloor: %s startsolid at %s\n", ent->classname,
vtos(ent->s.origin));
G_FreeEdict(ent);
return;
if (strcmp(ent->classname, "foodcube") == 0)
{
VectorCopy(ent->s.origin, tr.endpos);
ent->velocity[2] = 0;
}
else
{
gi.dprintf("droptofloor: %s startsolid at %s\n",
ent->classname,
vtos(ent->s.origin));
G_FreeEdict(ent);
return;
}
}
VectorCopy(tr.endpos, ent->s.origin);
@ -2127,12 +2252,15 @@ SpawnItem(edict_t *ent, gitem_t *item)
/* ====================================================================== */
gitem_t itemlist[] = {
static const gitem_t gameitemlist[] = {
{
NULL
}, /* leave index 0 alone */
/* QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
/*
* QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
*/
{
"item_armor_body",
Pickup_Armor,
@ -3497,6 +3625,8 @@ gitem_t itemlist[] = {
{NULL}
};
gitem_t itemlist[MAX_ITEMS];
/*
* QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
*/
@ -3616,7 +3746,9 @@ SP_item_foodcube(edict_t *self)
void
InitItems(void)
{
game.num_items = sizeof(itemlist) / sizeof(itemlist[0]) - 1;
memset(itemlist, 0, sizeof(itemlist));
memcpy(itemlist, gameitemlist, sizeof(gameitemlist));
game.num_items = sizeof(gameitemlist) / sizeof(gameitemlist[0]) - 1;
}
/*

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,354 +0,0 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Game side of server CMDs. At this time only the ipfilter.
*
* =======================================================================
*/
#include "header/local.h"
#define MAX_IPFILTERS 1024
/*
* ==============================================================================
*
* PACKET FILTERING
*
*
* You can add or remove addresses from the filter list with:
*
* addip <ip>
* removeip <ip>
*
* The ip address is specified in dot format, and any unspecified digits will match
* any value, so you can specify an entire class C network with "addip 192.246.40".
*
* Removeip will only remove an address specified exactly the same way. You cannot
* addip a subnet, then removeip a single host.
*
* listip
* Prints the current list of filters.
*
* writeip
* Dumps "addip <ip>" commands to listip.cfg so it can be execed at a later date.
* The filter lists are not saved and restored by default, because I beleive it
* would cause too much confusion.
*
* filterban <0 or 1>
*
* If 1 (the default), then ip addresses matching the current list will be prohibited
* from entering the game. This is the default setting.
*
* If 0, then only addresses matching the list will be allowed. This lets you easily
* set up a private game, or a game that only allows players from your local network.
*
* ==============================================================================
*/
typedef struct
{
unsigned mask;
unsigned compare;
} ipfilter_t;
ipfilter_t ipfilters[MAX_IPFILTERS];
int numipfilters;
void
Svcmd_Test_f(void)
{
gi.cprintf(NULL, PRINT_HIGH, "Svcmd_Test_f()\n");
}
qboolean
StringToFilter(char *s, ipfilter_t *f)
{
char num[128];
int i, j;
byte b[4];
byte m[4];
if (!s || !f)
{
return false;
}
for (i = 0; i < 4; i++)
{
b[i] = 0;
m[i] = 0;
}
for (i = 0; i < 4; i++)
{
if ((*s < '0') || (*s > '9'))
{
gi.cprintf(NULL, PRINT_HIGH, "Bad filter address: %s\n", s);
return false;
}
j = 0;
while (*s >= '0' && *s <= '9')
{
num[j++] = *s++;
}
num[j] = 0;
b[i] = atoi(num);
if (b[i] != 0)
{
m[i] = 255;
}
if (!*s)
{
break;
}
s++;
}
/* PVS NOTE: maybe use memcpy here? */
f->mask = *(unsigned *)m;
f->compare = *(unsigned *)b;
return true;
}
qboolean
SV_FilterPacket(char *from)
{
int i;
unsigned in;
byte m[4];
char *p;
if (!from)
{
return false;
}
i = 0;
p = from;
while (*p && i < 4)
{
m[i] = 0;
while (*p >= '0' && *p <= '9')
{
m[i] = m[i] * 10 + (*p - '0');
p++;
}
if (!*p || (*p == ':'))
{
break;
}
i++, p++;
}
/* PVS NOTE: maybe use memcpy here? */
in = *(unsigned *)m;
for (i = 0; i < numipfilters; i++)
{
if ((in & ipfilters[i].mask) == ipfilters[i].compare)
{
return (int)filterban->value;
}
}
return (int)!filterban->value;
}
void
SVCmd_AddIP_f(void)
{
int i;
if (gi.argc() < 3)
{
gi.cprintf(NULL, PRINT_HIGH, "Usage: addip <ip-mask>\n");
return;
}
for (i = 0; i < numipfilters; i++)
{
if (ipfilters[i].compare == 0xffffffff)
{
break; /* free spot */
}
}
if (i == numipfilters)
{
if (numipfilters == MAX_IPFILTERS)
{
gi.cprintf(NULL, PRINT_HIGH, "IP filter list is full\n");
return;
}
numipfilters++;
}
if (!StringToFilter(gi.argv(2), &ipfilters[i]))
{
ipfilters[i].compare = 0xffffffff;
}
}
void
SVCmd_RemoveIP_f(void)
{
ipfilter_t f;
int i, j;
if (gi.argc() < 3)
{
gi.cprintf(NULL, PRINT_HIGH, "Usage: sv removeip <ip-mask>\n");
return;
}
if (!StringToFilter(gi.argv(2), &f))
{
return;
}
for (i = 0; i < numipfilters; i++)
{
if ((ipfilters[i].mask == f.mask) &&
(ipfilters[i].compare == f.compare))
{
for (j = i + 1; j < numipfilters; j++)
{
ipfilters[j - 1] = ipfilters[j];
}
numipfilters--;
gi.cprintf(NULL, PRINT_HIGH, "Removed.\n");
return;
}
}
gi.cprintf(NULL, PRINT_HIGH, "Didn't find %s.\n", gi.argv(2));
}
void
SVCmd_ListIP_f(void)
{
int i;
byte b[4];
gi.cprintf(NULL, PRINT_HIGH, "Filter list:\n");
for (i = 0; i < numipfilters; i++)
{
/* PVS NOTE: maybe use memcpy here? */
*(unsigned *)b = ipfilters[i].compare;
gi.cprintf(NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n", b[0], b[1], b[2], b[3]);
}
}
void
SVCmd_WriteIP_f(void)
{
FILE *f;
char name[MAX_OSPATH];
byte b[4];
int i;
cvar_t *game;
game = gi.cvar("game", "", 0);
if (!*game->string)
{
sprintf(name, "%s/listip.cfg", GAMEVERSION);
}
else
{
sprintf(name, "%s/listip.cfg", game->string);
}
gi.cprintf(NULL, PRINT_HIGH, "Writing %s.\n", name);
f = fopen(name, "wb");
if (!f)
{
gi.cprintf(NULL, PRINT_HIGH, "Couldn't open %s\n", name);
return;
}
fprintf(f, "set filterban %d\n", (int)filterban->value);
for (i = 0; i < numipfilters; i++)
{
/* PVS NOTE: maybe use memcpy here? */
*(unsigned *)b = ipfilters[i].compare;
fprintf(f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]);
}
fclose(f);
}
/*
* ServerCommand will be called when an "sv" command is issued.
* The game can issue gi.argc() / gi.argv() commands to get the
* rest of the parameters
*/
void
ServerCommand(void)
{
char *cmd;
cmd = gi.argv(1);
if (Q_stricmp(cmd, "test") == 0)
{
Svcmd_Test_f();
}
else if (Q_stricmp(cmd, "addip") == 0)
{
SVCmd_AddIP_f();
}
else if (Q_stricmp(cmd, "removeip") == 0)
{
SVCmd_RemoveIP_f();
}
else if (Q_stricmp(cmd, "listip") == 0)
{
SVCmd_ListIP_f();
}
else if (Q_stricmp(cmd, "writeip") == 0)
{
SVCmd_WriteIP_f();
}
else
{
gi.cprintf(NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,931 +0,0 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Trigger.
*
* =======================================================================
*/
#include "header/local.h"
#define TRIGGER_MONSTER 0x01
#define TRIGGER_NOT_PLAYER 0x02
#define TRIGGER_TRIGGERED 0x04
#define TRIGGER_TOGGLE 0x08
#define PUSH_ONCE 0x01
#define PUSH_START_OFF 0x02
#define PUSH_SILENT 0x04
static int windsound;
void
InitTrigger(edict_t *self)
{
if (!self)
{
return;
}
if (!VectorCompare(self->s.angles, vec3_origin))
{
G_SetMovedir(self->s.angles, self->movedir);
}
self->solid = SOLID_TRIGGER;
self->movetype = MOVETYPE_NONE;
gi.setmodel(self, self->model);
self->svflags = SVF_NOCLIENT;
}
/*
* the wait time has passed, so set
* back up for another activation
*/
void
multi_wait(edict_t *ent)
{
if (!ent)
{
return;
}
ent->nextthink = 0;
}
void
multi_trigger(edict_t *ent)
{
if (!ent)
{
return;
}
if (ent->nextthink)
{
return; /* already been triggered */
}
G_UseTargets(ent, ent->activator);
if (ent->wait > 0)
{
ent->think = multi_wait;
ent->nextthink = level.time + ent->wait;
}
else
{
/* we can't just remove (self) here, because
this is a touch function called while looping
through area links... */
ent->touch = NULL;
ent->nextthink = level.time + FRAMETIME;
ent->think = G_FreeEdict;
}
}
void
Use_Multi(edict_t *ent, edict_t *other /* unused */, edict_t *activator)
{
if (!ent || !activator)
{
return;
}
if (ent->spawnflags & TRIGGER_TOGGLE)
{
if (ent->solid == SOLID_TRIGGER)
{
ent->solid = SOLID_NOT;
}
else
{
ent->solid = SOLID_TRIGGER;
}
gi.linkentity(ent);
}
else
{
ent->activator = activator;
multi_trigger(ent);
}
}
void
Touch_Multi(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
{
if (!self || !other)
{
return;
}
if (other->client)
{
if (self->spawnflags & 2)
{
return;
}
}
else if (other->svflags & SVF_MONSTER)
{
if (!(self->spawnflags & 1))
{
return;
}
}
else
{
return;
}
if (!VectorCompare(self->movedir, vec3_origin))
{
vec3_t forward;
AngleVectors(other->s.angles, forward, NULL, NULL);
if (_DotProduct(forward, self->movedir) < 0)
{
return;
}
}
self->activator = other;
multi_trigger(self);
}
/*
* QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED TOGGLE
* Variable sized repeatable trigger. Must be targeted at one or more entities.
* If "delay" is set, the trigger waits some time after activating before firing.
* "wait" : Seconds between triggerings. (.2 default)
*
* TOGGLE - using this trigger will activate/deactivate it. trigger will begin inactive.
*
* sounds
* 1) secret
* 2) beep beep
* 3) large switch
* 4)
* set "message" to text string
*/
void
trigger_enable(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
if (!self)
{
return;
}
self->solid = SOLID_TRIGGER;
self->use = Use_Multi;
gi.linkentity(self);
}
void
SP_trigger_multiple(edict_t *ent)
{
if (!ent)
{
return;
}
if (ent->sounds == 1)
{
ent->noise_index = gi.soundindex("misc/secret.wav");
}
else if (ent->sounds == 2)
{
ent->noise_index = gi.soundindex("misc/talk.wav");
}
else if (ent->sounds == 3)
{
ent->noise_index = gi.soundindex("misc/trigger1.wav");
}
if (!ent->wait)
{
ent->wait = 0.2;
}
ent->touch = Touch_Multi;
ent->movetype = MOVETYPE_NONE;
ent->svflags |= SVF_NOCLIENT;
if (ent->spawnflags & (TRIGGER_TRIGGERED | TRIGGER_TOGGLE))
{
ent->solid = SOLID_NOT;
ent->use = trigger_enable;
}
else
{
ent->solid = SOLID_TRIGGER;
ent->use = Use_Multi;
}
if (!VectorCompare(ent->s.angles, vec3_origin))
{
G_SetMovedir(ent->s.angles, ent->movedir);
}
gi.setmodel(ent, ent->model);
gi.linkentity(ent);
}
/*
* QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED
*
* Triggers once, then removes itself.
* You must set the key "target" to the name of another object in the level that has a matching "targetname".
*
* If TRIGGERED, this trigger must be triggered before it is live.
*
* sounds
* 1) secret
* 2) beep beep
* 3) large switch
* 4)
*
* "message" string to be displayed when triggered
*/
void
SP_trigger_once(edict_t *ent)
{
if (!ent)
{
return;
}
/* make old maps work because I messed up on flag assignments here */
if (ent->spawnflags & 1)
{
vec3_t v;
VectorMA(ent->mins, 0.5, ent->size, v);
ent->spawnflags &= ~1;
ent->spawnflags |= 4;
gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v));
}
ent->wait = -1;
SP_trigger_multiple(ent);
}
/*
* QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
*
* This fixed size trigger cannot be touched, it can only be fired by other events.
*/
void
trigger_relay_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
{
if (!self || !activator)
{
return;
}
G_UseTargets(self, activator);
}
void
SP_trigger_relay(edict_t *self)
{
if (!self)
{
return;
}
self->use = trigger_relay_use;
}
/*
* QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8)
*
* A relay trigger that only fires it's targets if player has the proper key.
* Use "item" to specify the required key, for example "key_data_cd"
*/
void
trigger_key_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
{
int index;
if (!self|| !activator)
{
return;
}
if (!self->item)
{
return;
}
if (!activator->client)
{
return;
}
index = ITEM_INDEX(self->item);
if (!activator->client->pers.inventory[index])
{
if (level.time < self->touch_debounce_time)
{
return;
}
self->touch_debounce_time = level.time + 5.0;
gi.centerprintf(activator, "You need the %s", self->item->pickup_name);
gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/keytry.wav"), 1, ATTN_NORM, 0);
return;
}
gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/keyuse.wav"), 1, ATTN_NORM, 0);
if (coop->value)
{
int player;
edict_t *ent;
if (strcmp(self->item->classname, "key_power_cube") == 0)
{
int cube;
for (cube = 0; cube < 8; cube++)
{
if (activator->client->pers.power_cubes & (1 << cube))
{
break;
}
}
for (player = 1; player <= game.maxclients; player++)
{
ent = &g_edicts[player];
if (!ent->inuse)
{
continue;
}
if (!ent->client)
{
continue;
}
if (ent->client->pers.power_cubes & (1 << cube))
{
ent->client->pers.inventory[index]--;
ent->client->pers.power_cubes &= ~(1 << cube);
}
}
}
else
{
for (player = 1; player <= game.maxclients; player++)
{
ent = &g_edicts[player];
if (!ent->inuse)
{
continue;
}
if (!ent->client)
{
continue;
}
ent->client->pers.inventory[index] = 0;
}
}
}
else
{
activator->client->pers.inventory[index]--;
}
G_UseTargets(self, activator);
self->use = NULL;
}
void
SP_trigger_key(edict_t *self)
{
if (!self)
{
return;
}
if (!st.item)
{
gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin));
return;
}
self->item = FindItemByClassname(st.item);
if (!self->item)
{
gi.dprintf("item %s not found for trigger_key at %s\n", st.item,
vtos(self->s.origin));
return;
}
if (!self->target)
{
gi.dprintf("%s at %s has no target\n", self->classname,
vtos(self->s.origin));
return;
}
gi.soundindex("misc/keytry.wav");
gi.soundindex("misc/keyuse.wav");
self->use = trigger_key_use;
}
/*
* QUAKED trigger_counter (.5 .5 .5) ? nomessage
*
* Acts as an intermediary for an action that takes multiple inputs.
*
* If nomessage is not set, t will print "1 more.. " etc when triggered
* and "sequence complete" when finished.
*
* After the counter has been triggered "count" times (default 2),
* it will fire all of it's targets and remove itself.
*/
void
trigger_counter_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
{
if (!self || !activator)
{
return;
}
if (self->count == 0)
{
return;
}
self->count--;
if (self->count)
{
if (!(self->spawnflags & 1))
{
gi.centerprintf(activator, "%i more to go...", self->count);
gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
}
return;
}
if (!(self->spawnflags & 1))
{
gi.centerprintf(activator, "Sequence completed!");
gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
}
self->activator = activator;
multi_trigger(self);
}
void
SP_trigger_counter(edict_t *self)
{
if (!self)
{
return;
}
self->wait = -1;
if (!self->count)
{
self->count = 2;
}
self->use = trigger_counter_use;
}
/*
* QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
*
* This trigger will always fire. It is activated by the world.
*/
void
SP_trigger_always(edict_t *ent)
{
if (!ent)
{
return;
}
/* we must have some delay to make sure our use targets are present */
if (ent->delay < 0.2)
{
ent->delay = 0.2;
}
G_UseTargets(ent, ent);
}
void
trigger_push_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
{
if (!self || !other)
{
return;
}
if (strcmp(other->classname, "grenade") == 0)
{
VectorScale(self->movedir, self->speed * 10, other->velocity);
}
else if (other->health > 0)
{
VectorScale(self->movedir, self->speed * 10, other->velocity);
if (other->client)
{
/* don't take falling damage immediately from this */
VectorCopy(other->velocity, other->client->oldvelocity);
if (!(self->spawnflags & PUSH_SILENT) &&
(other->fly_sound_debounce_time < level.time))
{
other->fly_sound_debounce_time = level.time + 1.5;
gi.sound(other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
}
}
}
if (self->spawnflags & PUSH_ONCE)
{
G_FreeEdict(self);
}
}
void
trigger_push_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
if (!self)
{
return;
}
if (self->solid == SOLID_NOT)
{
self->solid = SOLID_TRIGGER;
}
else
{
self->solid = SOLID_NOT;
}
gi.linkentity(self);
}
/*
* QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE START_OFF SILENT
* Pushes the player
* "speed" defaults to 1000
*
* If targeted, it will toggle on and off when used.
*
* START_OFF - toggled trigger_push begins in off setting
* SILENT - doesn't make wind noise
*/
void
SP_trigger_push(edict_t *self)
{
if (!self)
{
return;
}
InitTrigger(self);
windsound = gi.soundindex("misc/windfly.wav");
self->touch = trigger_push_touch;
if (!self->speed)
{
self->speed = 1000;
}
if (self->targetname) /* toggleable */
{
self->use = trigger_push_use;
if (self->spawnflags & PUSH_START_OFF)
{
self->solid = SOLID_NOT;
}
}
else if (self->spawnflags & PUSH_START_OFF)
{
gi.dprintf("trigger_push is START_OFF but not targeted.\n");
self->svflags = 0;
self->touch = NULL;
self->solid = SOLID_BSP;
self->movetype = MOVETYPE_PUSH;
}
gi.linkentity(self);
}
/*
* QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW
*
* Any entity that touches this will be hurt.
*
* It does dmg points of damage each server frame
*
* SILENT supresses playing the sound
* SLOW changes the damage rate to once per second
* NO_PROTECTION *nothing* stops the damage
*
* "dmg" default 5 (whole numbers only)
*
*/
void
hurt_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
if (!self)
{
return;
}
if (self->solid == SOLID_NOT)
{
self->solid = SOLID_TRIGGER;
}
else
{
self->solid = SOLID_NOT;
}
gi.linkentity(self);
if (!(self->spawnflags & 2))
{
self->use = NULL;
}
}
void
hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
{
int dflags;
if (!self || !other)
{
return;
}
if (!other->takedamage)
{
return;
}
if (self->timestamp > level.time)
{
return;
}
if (self->spawnflags & 16)
{
self->timestamp = level.time + 1;
}
else
{
self->timestamp = level.time + FRAMETIME;
}
if (!(self->spawnflags & 4))
{
if ((level.framenum % 10) == 0)
{
gi.sound(other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
}
}
if (self->spawnflags & 8)
{
dflags = DAMAGE_NO_PROTECTION;
}
else
{
dflags = 0;
}
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin,
self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT);
}
void
SP_trigger_hurt(edict_t *self)
{
if (!self)
{
return;
}
InitTrigger(self);
self->noise_index = gi.soundindex("world/electro.wav");
self->touch = hurt_touch;
if (!self->dmg)
{
self->dmg = 5;
}
if (self->spawnflags & 1)
{
self->solid = SOLID_NOT;
}
else
{
self->solid = SOLID_TRIGGER;
}
if (self->spawnflags & 2)
{
self->use = hurt_use;
}
gi.linkentity(self);
}
void
trigger_gravity_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
if (!self)
{
return;
}
if (self->solid == SOLID_NOT)
{
self->solid = SOLID_TRIGGER;
}
else
{
self->solid = SOLID_NOT;
}
gi.linkentity(self);
}
/* PGM */
void
trigger_gravity_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
{
if (!self || !other)
{
return;
}
other->gravity = self->gravity;
}
/*
* QUAKED trigger_gravity (.5 .5 .5) ? TOGGLE START_OFF
* Changes the touching entites gravity to
* the value of "gravity". 1.0 is standard
* gravity for the level.
*
* TOGGLE - trigger_gravity can be turned on and off
* START_OFF - trigger_gravity starts turned off (implies TOGGLE)
*/
void
SP_trigger_gravity(edict_t *self)
{
if (!self)
{
return;
}
if (st.gravity == 0)
{
gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin));
G_FreeEdict(self);
return;
}
InitTrigger(self);
self->gravity = strtof(st.gravity, (char **)NULL);
if (self->spawnflags & 1) /* TOGGLE */
{
self->use = trigger_gravity_use;
}
if (self->spawnflags & 2) /* START_OFF */
{
self->use = trigger_gravity_use;
self->solid = SOLID_NOT;
}
self->touch = trigger_gravity_touch;
gi.linkentity(self);
}
/*
* QUAKED trigger_monsterjump (.5 .5 .5) ?
*
* Walking monsters that touch this will jump in the direction of the trigger's angle
* "speed" default to 200, the speed thrown forward
* "height" default to 200, the speed thrown upwards
*/
void
trigger_monsterjump_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
{
if (!self || !other)
{
return;
}
if (other->flags & (FL_FLY | FL_SWIM))
{
return;
}
if (other->svflags & SVF_DEADMONSTER)
{
return;
}
if (!(other->svflags & SVF_MONSTER))
{
return;
}
/* set XY even if not on ground, so the jump will clear lips */
other->velocity[0] = self->movedir[0] * self->speed;
other->velocity[1] = self->movedir[1] * self->speed;
if (!other->groundentity)
{
return;
}
other->groundentity = NULL;
other->velocity[2] = self->movedir[2];
}
void
SP_trigger_monsterjump(edict_t *self)
{
if (!self)
{
return;
}
if (!self->speed)
{
self->speed = 200;
}
if (!st.height)
{
st.height = 200;
}
if (self->s.angles[YAW] == 0)
{
self->s.angles[YAW] = 360;
}
InitTrigger(self);
self->touch = trigger_monsterjump_touch;
self->movedir[2] = st.height;
}

View File

@ -1,834 +0,0 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Turrets aka big cannons with a driver.
*
* =======================================================================
*/
#include "header/local.h"
qboolean FindTarget(edict_t *self);
void infantry_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
void infantry_stand(edict_t *self);
void monster_use(edict_t *self, edict_t *other, edict_t *activator);
void SpawnTargetingSystem(edict_t *turret);
void
AnglesNormalize(vec3_t vec)
{
while (vec[0] > 360)
{
vec[0] -= 360;
}
while (vec[0] < 0)
{
vec[0] += 360;
}
while (vec[1] > 360)
{
vec[1] -= 360;
}
while (vec[1] < 0)
{
vec[1] += 360;
}
}
float
SnapToEights(float x)
{
x *= 8.0;
if (x > 0.0)
{
x += 0.5;
}
else
{
x -= 0.5;
}
return 0.125 * (int)x;
}
void
turret_blocked(edict_t *self, edict_t *other)
{
if (!self || !other)
{
return;
}
edict_t *attacker;
if (other->takedamage)
{
if (self->teammaster->owner)
{
attacker = self->teammaster->owner;
}
else
{
attacker = self->teammaster;
}
T_Damage(other, self, attacker, vec3_origin, other->s.origin,
vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
}
}
/*
* QUAKED turret_breach (0 0 0) ?
* This portion of the turret can change both pitch and yaw.
* The model should be made with a flat pitch.
* It (and the associated base) need to be oriented towards 0.
* Use "angle" to set the starting angle.
*
* "speed" default 50
* "dmg" default 10
* "angle" point this forward
* "target" point this at an info_notnull at the muzzle tip
* "minpitch" min acceptable pitch angle : default -30
* "maxpitch" max acceptable pitch angle : default 30
* "minyaw" min acceptable yaw angle : default 0
* "maxyaw" max acceptable yaw angle : default 360
*/
void
turret_breach_fire(edict_t *self)
{
vec3_t f, r, u;
vec3_t start;
int damage;
int speed;
if (!self)
{
return;
}
AngleVectors(self->s.angles, f, r, u);
VectorMA(self->s.origin, self->move_origin[0], f, start);
VectorMA(start, self->move_origin[1], r, start);
VectorMA(start, self->move_origin[2], u, start);
damage = 100 + random() * 50;
speed = 550 + 50 * skill->value;
fire_rocket(self->teammaster->owner, start, f, damage, speed, 150, damage);
gi.positioned_sound(start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
}
void
turret_breach_think(edict_t *self)
{
edict_t *ent;
vec3_t current_angles;
vec3_t delta;
if (!self)
{
return;
}
VectorCopy(self->s.angles, current_angles);
AnglesNormalize(current_angles);
AnglesNormalize(self->move_angles);
if (self->move_angles[PITCH] > 180)
{
self->move_angles[PITCH] -= 360;
}
/* clamp angles to mins & maxs */
if (self->move_angles[PITCH] > self->pos1[PITCH])
{
self->move_angles[PITCH] = self->pos1[PITCH];
}
else if (self->move_angles[PITCH] < self->pos2[PITCH])
{
self->move_angles[PITCH] = self->pos2[PITCH];
}
if ((self->move_angles[YAW] < self->pos1[YAW]) ||
(self->move_angles[YAW] > self->pos2[YAW]))
{
float dmin, dmax;
dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
if (dmin < -180)
{
dmin += 360;
}
else if (dmin > 180)
{
dmin -= 360;
}
dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
if (dmax < -180)
{
dmax += 360;
}
else if (dmax > 180)
{
dmax -= 360;
}
if (fabs(dmin) < fabs(dmax))
{
self->move_angles[YAW] = self->pos1[YAW];
}
else
{
self->move_angles[YAW] = self->pos2[YAW];
}
}
VectorSubtract(self->move_angles, current_angles, delta);
if (delta[0] < -180)
{
delta[0] += 360;
}
else if (delta[0] > 180)
{
delta[0] -= 360;
}
if (delta[1] < -180)
{
delta[1] += 360;
}
else if (delta[1] > 180)
{
delta[1] -= 360;
}
delta[2] = 0;
if (delta[0] > self->speed * FRAMETIME)
{
delta[0] = self->speed * FRAMETIME;
}
if (delta[0] < -1 * self->speed * FRAMETIME)
{
delta[0] = -1 * self->speed * FRAMETIME;
}
if (delta[1] > self->speed * FRAMETIME)
{
delta[1] = self->speed * FRAMETIME;
}
if (delta[1] < -1 * self->speed * FRAMETIME)
{
delta[1] = -1 * self->speed * FRAMETIME;
}
VectorScale(delta, 1.0 / FRAMETIME, self->avelocity);
self->nextthink = level.time + FRAMETIME;
for (ent = self->teammaster; ent; ent = ent->teamchain)
{
ent->avelocity[1] = self->avelocity[1];
}
/* if we have adriver, adjust his velocities */
if (self->owner)
{
float angle;
float target_z;
float diff;
vec3_t target;
vec3_t dir;
/* angular is easy, just copy ours */
self->owner->avelocity[0] = self->avelocity[0];
self->owner->avelocity[1] = self->avelocity[1];
/* x & y */
angle = self->s.angles[1] + self->owner->move_origin[1];
angle *= (M_PI * 2 / 360);
target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
target[2] = self->owner->s.origin[2];
VectorSubtract(target, self->owner->s.origin, dir);
self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
/* z */
angle = self->s.angles[PITCH] * (M_PI * 2 / 360);
target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
diff = target_z - self->owner->s.origin[2];
self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
if (self->spawnflags & 65536)
{
turret_breach_fire(self);
self->spawnflags &= ~65536;
}
}
}
void
turret_breach_finish_init(edict_t *self)
{
if (!self)
{
return;
}
/* get and save info for muzzle location */
if (!self->target)
{
gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
}
else
{
self->target_ent = G_PickTarget(self->target);
if (self->target_ent)
{
VectorSubtract(self->target_ent->s.origin, self->s.origin, self->move_origin);
G_FreeEdict(self->target_ent);
}
else
{
gi.dprintf("could not find target entity for %s at %s\n", self->classname, vtos(self->s.origin));
}
}
self->teammaster->dmg = self->dmg;
self->think = turret_breach_think;
self->think(self);
}
void
SP_turret_breach(edict_t *self)
{
if (!self)
{
return;
}
self->solid = SOLID_BSP;
self->movetype = MOVETYPE_PUSH;
gi.setmodel(self, self->model);
if (!self->speed)
{
self->speed = 50;
}
if (!self->dmg)
{
self->dmg = 10;
}
if (!st.minpitch)
{
st.minpitch = -30;
}
if (!st.maxpitch)
{
st.maxpitch = 30;
}
if (!st.maxyaw)
{
st.maxyaw = 360;
}
self->pos1[PITCH] = -1 * st.minpitch;
self->pos1[YAW] = st.minyaw;
self->pos2[PITCH] = -1 * st.maxpitch;
self->pos2[YAW] = st.maxyaw;
self->ideal_yaw = self->s.angles[YAW];
self->move_angles[YAW] = self->ideal_yaw;
self->blocked = turret_blocked;
self->think = turret_breach_finish_init;
self->nextthink = level.time + FRAMETIME;
gi.linkentity(self);
}
/*
* QUAKED turret_base (0 0 0) ?
* This portion of the turret changes yaw only.
* MUST be teamed with a turret_breach.
*/
void
SP_turret_base(edict_t *self)
{
if (!self)
{
return;
}
self->solid = SOLID_BSP;
self->movetype = MOVETYPE_PUSH;
gi.setmodel(self, self->model);
self->blocked = turret_blocked;
gi.linkentity(self);
}
/*
* QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
* Must NOT be on the team with the rest of the turret parts.
* Instead it must target the turret_breach.
*/
void
turret_driver_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
int damage, vec3_t point)
{
edict_t *ent;
if (!self || !inflictor || !attacker)
{
return;
}
/* level the gun */
self->target_ent->move_angles[0] = 0;
/* remove the driver from the end of them team chain */
for (ent = self->target_ent->teammaster;
ent->teamchain != self;
ent = ent->teamchain)
{
}
ent->teamchain = NULL;
self->teammaster = NULL;
self->flags &= ~FL_TEAMSLAVE;
self->target_ent->owner = NULL;
self->target_ent->teammaster->owner = NULL;
infantry_die(self, inflictor, attacker, damage, point);
}
void
turret_driver_think(edict_t *self)
{
vec3_t target;
vec3_t dir;
float reaction_time;
if (!self)
{
return;
}
self->nextthink = level.time + FRAMETIME;
if (self->enemy && (!self->enemy->inuse || (self->enemy->health <= 0)))
{
self->enemy = NULL;
}
if (!self->enemy)
{
if (!FindTarget(self))
{
return;
}
self->monsterinfo.trail_time = level.time;
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
}
else
{
if (visible(self, self->enemy))
{
if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
{
self->monsterinfo.trail_time = level.time;
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
}
}
else
{
self->monsterinfo.aiflags |= AI_LOST_SIGHT;
return;
}
}
/* let the turret know where we want it to aim */
VectorCopy(self->enemy->s.origin, target);
target[2] += self->enemy->viewheight;
VectorSubtract(target, self->target_ent->s.origin, dir);
vectoangles(dir, self->target_ent->move_angles);
/* decide if we should shoot */
if (level.time < self->monsterinfo.attack_finished)
{
return;
}
reaction_time = (3 - skill->value) * 1.0;
if ((level.time - self->monsterinfo.trail_time) < reaction_time)
{
return;
}
self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
self->target_ent->spawnflags |= 65536;
}
void
turret_driver_link(edict_t *self)
{
vec3_t vec;
edict_t *ent;
if (!self)
{
return;
}
self->think = turret_driver_think;
self->nextthink = level.time + FRAMETIME;
self->target_ent = G_PickTarget(self->target);
self->target_ent->owner = self;
self->target_ent->teammaster->owner = self;
VectorCopy(self->target_ent->s.angles, self->s.angles);
vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
vec[2] = 0;
self->move_origin[0] = VectorLength(vec);
VectorSubtract(self->s.origin, self->target_ent->s.origin, vec);
vectoangles(vec, vec);
AnglesNormalize(vec);
self->move_origin[1] = vec[1];
self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
/* add the driver to the end of them team chain */
for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
{
}
ent->teamchain = self;
self->teammaster = self->target_ent->teammaster;
self->flags |= FL_TEAMSLAVE;
}
void
SP_turret_driver(edict_t *self)
{
if (!self)
{
return;
}
if (deathmatch->value)
{
G_FreeEdict(self);
return;
}
self->movetype = MOVETYPE_PUSH;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
VectorSet(self->mins, -16, -16, -24);
VectorSet(self->maxs, 16, 16, 32);
self->health = 100;
self->gib_health = 0;
self->mass = 200;
self->viewheight = 24;
self->die = turret_driver_die;
self->monsterinfo.stand = infantry_stand;
self->flags |= FL_NO_KNOCKBACK;
level.total_monsters++;
self->svflags |= SVF_MONSTER;
self->s.renderfx |= RF_FRAMELERP;
self->takedamage = DAMAGE_AIM;
self->use = monster_use;
self->clipmask = MASK_MONSTERSOLID;
VectorCopy(self->s.origin, self->s.old_origin);
self->monsterinfo.aiflags |= AI_STAND_GROUND | AI_DUCKED;
if (st.item)
{
self->item = FindItemByClassname(st.item);
if (!self->item)
{
gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
}
}
self->think = turret_driver_link;
self->nextthink = level.time + FRAMETIME;
gi.linkentity(self);
}
/*
* invisible turret drivers so we can have unmanned turrets.
* originally designed to shoot at func_trains and such, so they
* fire at the center of the bounding box, rather than the entity's
* origin. */
void
turret_brain_think(edict_t *self)
{
vec3_t dir;
vec3_t endpos;
float reaction_time;
trace_t trace;
if (!self)
{
return;
}
self->nextthink = level.time + FRAMETIME;
if (self->enemy)
{
if (!self->enemy->inuse)
{
self->enemy = NULL;
}
else if (self->enemy->takedamage && (self->enemy->health <= 0))
{
self->enemy = NULL;
}
}
if (!self->enemy)
{
if (!FindTarget(self))
{
return;
}
self->monsterinfo.trail_time = level.time;
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
VectorAdd(self->enemy->absmax, self->enemy->absmin, endpos);
VectorScale(endpos, 0.5, endpos);
}
else
{
VectorAdd(self->enemy->absmax, self->enemy->absmin, endpos);
VectorScale(endpos, 0.5, endpos);
trace = gi.trace(self->target_ent->s.origin, vec3_origin, vec3_origin,
endpos, self->target_ent, MASK_SHOT);
if ((trace.fraction == 1) || (trace.ent == self->enemy))
{
if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
{
self->monsterinfo.trail_time = level.time;
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
}
}
else
{
self->monsterinfo.aiflags |= AI_LOST_SIGHT;
return;
}
}
/* let the turret know where we want it to aim */
VectorSubtract(endpos, self->target_ent->s.origin, dir);
vectoangles(dir, self->target_ent->move_angles);
/* decide if we should shoot */
if (level.time < self->monsterinfo.attack_finished)
{
return;
}
if (self->delay)
{
reaction_time = self->delay;
}
else
{
reaction_time = (3 - skill->value) * 1.0;
}
if ((level.time - self->monsterinfo.trail_time) < reaction_time)
{
return;
}
self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
self->target_ent->spawnflags |= 65536;
}
void
turret_brain_link(edict_t *self)
{
vec3_t vec;
edict_t *ent;
if (!self)
{
return;
}
if (self->killtarget)
{
self->enemy = G_PickTarget(self->killtarget);
}
self->think = turret_brain_think;
self->nextthink = level.time + FRAMETIME;
self->target_ent = G_PickTarget(self->target);
self->target_ent->owner = self;
self->target_ent->teammaster->owner = self;
VectorCopy(self->target_ent->s.angles, self->s.angles);
vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
vec[2] = 0;
self->move_origin[0] = VectorLength(vec);
VectorSubtract(self->s.origin, self->target_ent->s.origin, vec);
vectoangles(vec, vec);
AnglesNormalize(vec);
self->move_origin[1] = vec[1];
self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
/* add the driver to the end of them team chain */
for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
{
}
ent->teamchain = self;
self->teammaster = self->target_ent->teammaster;
self->flags |= FL_TEAMSLAVE;
}
void
turret_brain_deactivate(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
if (!self)
{
return;
}
self->think = NULL;
self->nextthink = 0;
}
void
turret_brain_activate(edict_t *self, edict_t *other /* unused */, edict_t *activator)
{
if (!self || !activator)
{
return;
}
if (!self->enemy)
{
self->enemy = activator;
}
/* wait at least 3 seconds to fire. */
self->monsterinfo.attack_finished = level.time + 3;
self->use = turret_brain_deactivate;
self->think = turret_brain_link;
self->nextthink = level.time + FRAMETIME;
}
/*
* QUAKED turret_invisible_brain (1 .5 0) (-16 -16 -16) (16 16 16)
* Invisible brain to drive the turret.
*
* Does not search for targets. If targeted, can only be turned on once
* and then off once. After that they are completely disabled.
*
* "delay" the delay between firing (default ramps for skill level)
* "Target" the turret breach
* "Killtarget" the item you want it to attack.
* Target the brain if you want it activated later, instead of immediately. It will wait 3 seconds
* before firing to acquire the target.
*/
void
SP_turret_invisible_brain(edict_t *self)
{
if (!self)
{
return;
}
if (!self->killtarget)
{
gi.dprintf("turret_invisible_brain with no killtarget!\n");
G_FreeEdict(self);
return;
}
if (!self->target)
{
gi.dprintf("turret_invisible_brain with no target!\n");
G_FreeEdict(self);
return;
}
if (self->targetname)
{
self->use = turret_brain_activate;
}
else
{
self->think = turret_brain_link;
self->nextthink = level.time + FRAMETIME;
}
self->movetype = MOVETYPE_PUSH;
gi.linkentity(self);
}

View File

@ -1,916 +0,0 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Misc. utility functions for the game logic.
*
* =======================================================================
*/
#include "header/local.h"
#define MAXCHOICES 8
static vec3_t VEC_UP = {0, -1, 0};
static vec3_t MOVEDIR_UP = {0, 0, 1};
static vec3_t VEC_DOWN = {0, -2, 0};
static vec3_t MOVEDIR_DOWN = {0, 0, -1};
void
G_ProjectSource(vec3_t point, vec3_t distance, vec3_t forward,
vec3_t right, vec3_t result)
{
result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] +
distance[2];
}
void
G_ProjectSource2(vec3_t point, vec3_t distance, vec3_t forward,
vec3_t right, vec3_t up, vec3_t result)
{
result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1] +
up[0] * distance[2];
result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1] +
up[1] * distance[2];
result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] +
up[2] * distance[2];
}
/*
* Searches all active entities for the next
* one that holds the matching string at fieldofs
* (use the FOFS() macro) in the structure.
*
* Searches beginning at the edict after from, or
* the beginning. If NULL, NULL will be returned
* if the end of the list is reached.
*/
edict_t *
G_Find(edict_t *from, int fieldofs, char *match)
{
char *s;
if (!match)
{
return NULL;
}
if (!from)
{
from = g_edicts;
}
else
{
from++;
}
for ( ; from < &g_edicts[globals.num_edicts]; from++)
{
if (!from->inuse)
{
continue;
}
s = *(char **)((byte *)from + fieldofs);
if (!s)
{
continue;
}
if (!Q_stricmp(s, match))
{
return from;
}
}
return NULL;
}
/*
* Returns entities that have origins
* within a spherical area
*/
edict_t *
findradius(edict_t *from, vec3_t org, float rad)
{
vec3_t eorg;
int j;
if (!from)
{
from = g_edicts;
}
else
{
from++;
}
for ( ; from < &g_edicts[globals.num_edicts]; from++)
{
if (!from->inuse)
{
continue;
}
if (from->solid == SOLID_NOT)
{
continue;
}
for (j = 0; j < 3; j++)
{
eorg[j] = org[j] - (from->s.origin[j] +
(from->mins[j] + from->maxs[j]) * 0.5);
}
if (VectorLength(eorg) > rad)
{
continue;
}
return from;
}
return NULL;
}
/*
* Returns entities that have origins within a spherical area
*/
edict_t *
findradius2(edict_t *from, vec3_t org, float rad)
{
/* rad must be positive */
vec3_t eorg;
int j;
if (!from)
{
from = g_edicts;
}
else
{
from++;
}
for ( ; from < &g_edicts[globals.num_edicts]; from++)
{
if (!from->inuse)
{
continue;
}
if (from->solid == SOLID_NOT)
{
continue;
}
if (!from->takedamage)
{
continue;
}
if (!(from->svflags & SVF_DAMAGEABLE))
{
continue;
}
for (j = 0; j < 3; j++)
{
eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j]) * 0.5);
}
if (VectorLength(eorg) > rad)
{
continue;
}
return from;
}
return NULL;
}
/*
* Searches all active entities for
* the next one that holds the matching
* string at fieldofs (use the FOFS() macro)
* in the structure.
*
* Searches beginning at the edict after from,
* or the beginning. If NULL, NULL will be
* returned if the end of the list is reached.
*/
edict_t *
G_PickTarget(char *targetname)
{
edict_t *ent = NULL;
int num_choices = 0;
edict_t *choice[MAXCHOICES];
if (!targetname)
{
gi.dprintf("G_PickTarget called with NULL targetname\n");
return NULL;
}
while (1)
{
ent = G_Find(ent, FOFS(targetname), targetname);
if (!ent)
{
break;
}
choice[num_choices++] = ent;
if (num_choices == MAXCHOICES)
{
break;
}
}
if (!num_choices)
{
gi.dprintf("G_PickTarget: target %s not found\n", targetname);
return NULL;
}
return choice[randk() % num_choices];
}
void
Think_Delay(edict_t *ent)
{
if (!ent)
{
return;
}
G_UseTargets(ent, ent->activator);
G_FreeEdict(ent);
}
/*
* The global "activator" should be set to
* the entity that initiated the firing.
*
* If self.delay is set, a DelayedUse entity
* will be created that will actually do the
* SUB_UseTargets after that many seconds have passed.
*
* Centerprints any self.message to the activator.
*
* Search for (string)targetname in all entities that
* match (string)self.target and call their .use function
*/
void
G_UseTargets(edict_t *ent, edict_t *activator)
{
edict_t *t;
edict_t *master;
if (!ent)
{
return;
}
/* check for a delay */
if (ent->delay)
{
/* create a temp object to fire at a later time */
t = G_Spawn();
t->classname = "DelayedUse";
t->nextthink = level.time + ent->delay;
t->think = Think_Delay;
t->activator = activator;
if (!activator)
{
gi.dprintf("Think_Delay with no activator\n");
}
t->message = ent->message;
t->target = ent->target;
t->killtarget = ent->killtarget;
return;
}
/* print the message */
if (activator && (ent->message) && !(activator->svflags & SVF_MONSTER))
{
gi.centerprintf(activator, "%s", ent->message);
if (ent->noise_index)
{
gi.sound(activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
}
else
{
gi.sound(activator, CHAN_AUTO, gi.soundindex(
"misc/talk1.wav"), 1, ATTN_NORM, 0);
}
}
/* kill killtargets */
if (ent->killtarget)
{
t = NULL;
while ((t = G_Find(t, FOFS(targetname), ent->killtarget)))
{
/* if this entity is part of a train, cleanly remove it */
if (t->flags & FL_TEAMSLAVE)
{
master = t->teammaster;
while (master)
{
if (master->teamchain == t)
{
master->teamchain = t->teamchain;
break;
}
master = master->teamchain;
}
}
/* correct killcounter if a living monster gets killtargeted */
if ((t->monsterinfo.checkattack || strcmp (t->classname, "turret_driver") == 0) &&
!(t->monsterinfo.aiflags & (AI_GOOD_GUY|AI_DO_NOT_COUNT)) && t->deadflag != DEAD_DEAD)
{
level.killed_monsters++;
}
G_FreeEdict(t);
if (!ent->inuse)
{
gi.dprintf("entity was removed while using killtargets\n");
return;
}
}
}
/* fire targets */
if (ent->target)
{
t = NULL;
while ((t = G_Find(t, FOFS(targetname), ent->target)))
{
/* doors fire area portals in a specific way */
if (!Q_stricmp(t->classname, "func_areaportal") &&
(!Q_stricmp(ent->classname, "func_door") ||
!Q_stricmp(ent->classname, "func_door_rotating")))
{
continue;
}
if (t == ent)
{
gi.dprintf("WARNING: Entity used itself.\n");
}
else
{
if (t->use)
{
t->use(t, ent, activator);
}
}
if (!ent->inuse)
{
gi.dprintf("entity was removed while using targets\n");
return;
}
}
}
}
/*
* This is just a convenience function
* for making temporary vectors for function calls
*/
float *
tv(float x, float y, float z)
{
static int index;
static vec3_t vecs[8];
float *v;
/* use an array so that multiple
tempvectors won't collide
for a while */
v = vecs[index];
index = (index + 1) & 7;
v[0] = x;
v[1] = y;
v[2] = z;
return v;
}
/*
* This is just a convenience function
* for printing vectors
*/
char *
vtos(vec3_t v)
{
static int index;
static char str[8][32];
char *s;
/* use an array so that multiple vtos won't collide */
s = str[index];
index = (index + 1) & 7;
Com_sprintf(s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
return s;
}
void
get_normal_vector(const cplane_t *p, vec3_t normal)
{
if (p)
{
VectorCopy(p->normal, normal);
}
else
{
VectorCopy(vec3_origin, normal);
}
}
void
G_SetMovedir(vec3_t angles, vec3_t movedir)
{
if (VectorCompare(angles, VEC_UP))
{
VectorCopy(MOVEDIR_UP, movedir);
}
else if (VectorCompare(angles, VEC_DOWN))
{
VectorCopy(MOVEDIR_DOWN, movedir);
}
else
{
AngleVectors(angles, movedir, NULL, NULL);
}
VectorClear(angles);
}
float
vectoyaw(vec3_t vec)
{
float yaw;
if (vec[PITCH] == 0)
{
yaw = 0;
if (vec[YAW] > 0)
{
yaw = 90;
}
else if (vec[YAW] < 0)
{
yaw = -90;
}
}
else
{
yaw = (int)(atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
if (yaw < 0)
{
yaw += 360;
}
}
return yaw;
}
float
vectoyaw2(vec3_t vec)
{
float yaw;
if (vec[PITCH] == 0)
{
if (vec[YAW] == 0)
{
yaw = 0;
}
else if (vec[YAW] > 0)
{
yaw = 90;
}
else
{
yaw = 270;
}
}
else
{
yaw = (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
if (yaw < 0)
{
yaw += 360;
}
}
return yaw;
}
void
vectoangles(vec3_t value1, vec3_t angles)
{
float forward;
float yaw, pitch;
if ((value1[1] == 0) && (value1[0] == 0))
{
yaw = 0;
if (value1[2] > 0)
{
pitch = 90;
}
else
{
pitch = 270;
}
}
else
{
if (value1[0])
{
yaw = (int)(atan2(value1[1], value1[0]) * 180 / M_PI);
}
else if (value1[1] > 0)
{
yaw = 90;
}
else
{
yaw = -90;
}
if (yaw < 0)
{
yaw += 360;
}
forward = sqrt(value1[0] * value1[0] + value1[1] * value1[1]);
pitch = (int)(atan2(value1[2], forward) * 180 / M_PI);
if (pitch < 0)
{
pitch += 360;
}
}
angles[PITCH] = -pitch;
angles[YAW] = yaw;
angles[ROLL] = 0;
}
void
vectoangles2(vec3_t value1, vec3_t angles)
{
float forward;
float yaw, pitch;
if ((value1[1] == 0) && (value1[0] == 0))
{
yaw = 0;
if (value1[2] > 0)
{
pitch = 90;
}
else
{
pitch = 270;
}
}
else
{
if (value1[0])
{
yaw = (atan2(value1[1], value1[0]) * 180 / M_PI);
}
else if (value1[1] > 0)
{
yaw = 90;
}
else
{
yaw = 270;
}
if (yaw < 0)
{
yaw += 360;
}
forward = sqrt(value1[0] * value1[0] + value1[1] * value1[1]);
pitch = (atan2(value1[2], forward) * 180 / M_PI);
if (pitch < 0)
{
pitch += 360;
}
}
angles[PITCH] = -pitch;
angles[YAW] = yaw;
angles[ROLL] = 0;
}
char *
G_CopyString(char *in)
{
char *out;
if (!in)
{
return NULL;
}
out = gi.TagMalloc(strlen(in) + 1, TAG_LEVEL);
strcpy(out, in);
return out;
}
void
G_InitEdict(edict_t *e)
{
if (!e)
{
return;
}
if (e->nextthink)
{
e->nextthink = 0;
}
e->inuse = true;
e->classname = "noclass";
e->gravity = 1.0;
e->s.number = e - g_edicts;
e->gravityVector[0] = 0.0;
e->gravityVector[1] = 0.0;
e->gravityVector[2] = -1.0;
}
/*
* Either finds a free edict, or allocates a
* new one. Try to avoid reusing an entity
* that was recently freed, because it can
* cause the client to think the entity
* morphed into something else instead of
* being removed and recreated, which can
* cause interpolated angles and bad trails.
*/
#define POLICY_DEFAULT 0
#define POLICY_DESPERATE 1
static edict_t *
G_FindFreeEdict(int policy)
{
edict_t *e;
for (e = g_edicts + game.maxclients + 1 ; e < &g_edicts[globals.num_edicts] ; e++)
{
/* the first couple seconds of server time can involve a lot of
freeing and allocating, so relax the replacement policy
*/
if (!e->inuse && (policy == POLICY_DESPERATE || e->freetime < 2.0f || (level.time - e->freetime) > 0.5f))
{
G_InitEdict (e);
return e;
}
}
return NULL;
}
edict_t *
G_SpawnOptional(void)
{
edict_t *e = G_FindFreeEdict (POLICY_DEFAULT);
if (e)
{
return e;
}
if (globals.num_edicts >= game.maxentities)
{
return G_FindFreeEdict (POLICY_DESPERATE);
}
e = &g_edicts[globals.num_edicts++];
G_InitEdict (e);
return e;
}
edict_t *
G_Spawn(void)
{
edict_t *e = G_SpawnOptional();
if (!e)
gi.error ("ED_Alloc: no free edicts");
return e;
}
/*
* Marks the edict as free
*/
void
G_FreeEdict(edict_t *ed)
{
if (!ed)
{
return;
}
gi.unlinkentity(ed); /* unlink from world */
if (deathmatch->value || coop->value)
{
if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
{
return;
}
}
else
{
if ((ed - g_edicts) <= maxclients->value)
{
return;
}
}
memset(ed, 0, sizeof(*ed));
ed->classname = "freed";
ed->freetime = level.time;
ed->inuse = false;
}
void
G_TouchTriggers(edict_t *ent)
{
int i, num;
edict_t *touch[MAX_EDICTS], *hit;
if (!ent)
{
return;
}
/* dead things don't activate triggers! */
if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
{
return;
}
num = gi.BoxEdicts(ent->absmin, ent->absmax, touch,
MAX_EDICTS, AREA_TRIGGERS);
/* be careful, it is possible to have an entity in this
list removed before we get to it (killtriggered) */
for (i = 0; i < num; i++)
{
hit = touch[i];
if (!hit->inuse)
{
continue;
}
if (!hit->touch)
{
continue;
}
hit->touch(hit, ent, NULL, NULL);
}
}
/*
* Call after linking a new trigger
* in during gameplay to force all
* entities it covers to immediately
* touch it
*/
void
G_TouchSolids(edict_t *ent)
{
int i, num;
edict_t *touch[MAX_EDICTS], *hit;
if (!ent)
{
return;
}
num = gi.BoxEdicts(ent->absmin, ent->absmax, touch,
MAX_EDICTS, AREA_SOLID);
/* be careful, it is possible to have an entity in this
list removed before we get to it (killtriggered) */
for (i = 0; i < num; i++)
{
hit = touch[i];
if (!hit->inuse)
{
continue;
}
if (ent->touch)
{
ent->touch(hit, ent, NULL, NULL);
}
if (!ent->inuse)
{
break;
}
}
}
/*
* Kills all entities that would touch the
* proposed new positioning of ent. Ent s
* hould be unlinked before calling this!
*/
qboolean
KillBox(edict_t *ent)
{
trace_t tr;
if (!ent)
{
return false;
}
while (1)
{
tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin,
NULL, MASK_PLAYERSOLID);
if (!tr.ent)
{
break;
}
/* nail it */
T_Damage(tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin,
100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
/* if we didn't kill it, fail */
if (tr.ent->solid)
{
return false;
}
}
return true; /* all clear */
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,505 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Soldier aka "Guard" animations. This is the new model added in
* Xatrix, used for the new variants of the enemy.
*
* =======================================================================
*/
#define FRAME_attak101 0
#define FRAME_attak102 1
#define FRAME_attak103 2
#define FRAME_attak104 3
#define FRAME_attak105 4
#define FRAME_attak106 5
#define FRAME_attak107 6
#define FRAME_attak108 7
#define FRAME_attak109 8
#define FRAME_attak110 9
#define FRAME_attak111 10
#define FRAME_attak112 11
#define FRAME_attak201 12
#define FRAME_attak202 13
#define FRAME_attak203 14
#define FRAME_attak204 15
#define FRAME_attak205 16
#define FRAME_attak206 17
#define FRAME_attak207 18
#define FRAME_attak208 19
#define FRAME_attak209 20
#define FRAME_attak210 21
#define FRAME_attak211 22
#define FRAME_attak212 23
#define FRAME_attak213 24
#define FRAME_attak214 25
#define FRAME_attak215 26
#define FRAME_attak216 27
#define FRAME_attak217 28
#define FRAME_attak218 29
#define FRAME_attak301 30
#define FRAME_attak302 31
#define FRAME_attak303 32
#define FRAME_attak304 33
#define FRAME_attak305 34
#define FRAME_attak306 35
#define FRAME_attak307 36
#define FRAME_attak308 37
#define FRAME_attak309 38
#define FRAME_attak401 39
#define FRAME_attak402 40
#define FRAME_attak403 41
#define FRAME_attak404 42
#define FRAME_attak405 43
#define FRAME_attak406 44
#define FRAME_duck01 45
#define FRAME_duck02 46
#define FRAME_duck03 47
#define FRAME_duck04 48
#define FRAME_duck05 49
#define FRAME_pain101 50
#define FRAME_pain102 51
#define FRAME_pain103 52
#define FRAME_pain104 53
#define FRAME_pain105 54
#define FRAME_pain201 55
#define FRAME_pain202 56
#define FRAME_pain203 57
#define FRAME_pain204 58
#define FRAME_pain205 59
#define FRAME_pain206 60
#define FRAME_pain207 61
#define FRAME_pain301 62
#define FRAME_pain302 63
#define FRAME_pain303 64
#define FRAME_pain304 65
#define FRAME_pain305 66
#define FRAME_pain306 67
#define FRAME_pain307 68
#define FRAME_pain308 69
#define FRAME_pain309 70
#define FRAME_pain310 71
#define FRAME_pain311 72
#define FRAME_pain312 73
#define FRAME_pain313 74
#define FRAME_pain314 75
#define FRAME_pain315 76
#define FRAME_pain316 77
#define FRAME_pain317 78
#define FRAME_pain318 79
#define FRAME_pain401 80
#define FRAME_pain402 81
#define FRAME_pain403 82
#define FRAME_pain404 83
#define FRAME_pain405 84
#define FRAME_pain406 85
#define FRAME_pain407 86
#define FRAME_pain408 87
#define FRAME_pain409 88
#define FRAME_pain410 89
#define FRAME_pain411 90
#define FRAME_pain412 91
#define FRAME_pain413 92
#define FRAME_pain414 93
#define FRAME_pain415 94
#define FRAME_pain416 95
#define FRAME_pain417 96
#define FRAME_run01 97
#define FRAME_run02 98
#define FRAME_run03 99
#define FRAME_run04 100
#define FRAME_run05 101
#define FRAME_run06 102
#define FRAME_run07 103
#define FRAME_run08 104
#define FRAME_run09 105
#define FRAME_run10 106
#define FRAME_run11 107
#define FRAME_run12 108
#define FRAME_runs01 109
#define FRAME_runs02 110
#define FRAME_runs03 111
#define FRAME_runs04 112
#define FRAME_runs05 113
#define FRAME_runs06 114
#define FRAME_runs07 115
#define FRAME_runs08 116
#define FRAME_runs09 117
#define FRAME_runs10 118
#define FRAME_runs11 119
#define FRAME_runs12 120
#define FRAME_runs13 121
#define FRAME_runs14 122
#define FRAME_runs15 123
#define FRAME_runs16 124
#define FRAME_runs17 125
#define FRAME_runs18 126
#define FRAME_runt01 127
#define FRAME_runt02 128
#define FRAME_runt03 129
#define FRAME_runt04 130
#define FRAME_runt05 131
#define FRAME_runt06 132
#define FRAME_runt07 133
#define FRAME_runt08 134
#define FRAME_runt09 135
#define FRAME_runt10 136
#define FRAME_runt11 137
#define FRAME_runt12 138
#define FRAME_runt13 139
#define FRAME_runt14 140
#define FRAME_runt15 141
#define FRAME_runt16 142
#define FRAME_runt17 143
#define FRAME_runt18 144
#define FRAME_runt19 145
#define FRAME_stand101 146
#define FRAME_stand102 147
#define FRAME_stand103 148
#define FRAME_stand104 149
#define FRAME_stand105 150
#define FRAME_stand106 151
#define FRAME_stand107 152
#define FRAME_stand108 153
#define FRAME_stand109 154
#define FRAME_stand110 155
#define FRAME_stand111 156
#define FRAME_stand112 157
#define FRAME_stand113 158
#define FRAME_stand114 159
#define FRAME_stand115 160
#define FRAME_stand116 161
#define FRAME_stand117 162
#define FRAME_stand118 163
#define FRAME_stand119 164
#define FRAME_stand120 165
#define FRAME_stand121 166
#define FRAME_stand122 167
#define FRAME_stand123 168
#define FRAME_stand124 169
#define FRAME_stand125 170
#define FRAME_stand126 171
#define FRAME_stand127 172
#define FRAME_stand128 173
#define FRAME_stand129 174
#define FRAME_stand130 175
#define FRAME_stand301 176
#define FRAME_stand302 177
#define FRAME_stand303 178
#define FRAME_stand304 179
#define FRAME_stand305 180
#define FRAME_stand306 181
#define FRAME_stand307 182
#define FRAME_stand308 183
#define FRAME_stand309 184
#define FRAME_stand310 185
#define FRAME_stand311 186
#define FRAME_stand312 187
#define FRAME_stand313 188
#define FRAME_stand314 189
#define FRAME_stand315 190
#define FRAME_stand316 191
#define FRAME_stand317 192
#define FRAME_stand318 193
#define FRAME_stand319 194
#define FRAME_stand320 195
#define FRAME_stand321 196
#define FRAME_stand322 197
#define FRAME_stand323 198
#define FRAME_stand324 199
#define FRAME_stand325 200
#define FRAME_stand326 201
#define FRAME_stand327 202
#define FRAME_stand328 203
#define FRAME_stand329 204
#define FRAME_stand330 205
#define FRAME_stand331 206
#define FRAME_stand332 207
#define FRAME_stand333 208
#define FRAME_stand334 209
#define FRAME_stand335 210
#define FRAME_stand336 211
#define FRAME_stand337 212
#define FRAME_stand338 213
#define FRAME_stand339 214
#define FRAME_walk101 215
#define FRAME_walk102 216
#define FRAME_walk103 217
#define FRAME_walk104 218
#define FRAME_walk105 219
#define FRAME_walk106 220
#define FRAME_walk107 221
#define FRAME_walk108 222
#define FRAME_walk109 223
#define FRAME_walk110 224
#define FRAME_walk111 225
#define FRAME_walk112 226
#define FRAME_walk113 227
#define FRAME_walk114 228
#define FRAME_walk115 229
#define FRAME_walk116 230
#define FRAME_walk117 231
#define FRAME_walk118 232
#define FRAME_walk119 233
#define FRAME_walk120 234
#define FRAME_walk121 235
#define FRAME_walk122 236
#define FRAME_walk123 237
#define FRAME_walk124 238
#define FRAME_walk125 239
#define FRAME_walk126 240
#define FRAME_walk127 241
#define FRAME_walk128 242
#define FRAME_walk129 243
#define FRAME_walk130 244
#define FRAME_walk131 245
#define FRAME_walk132 246
#define FRAME_walk133 247
#define FRAME_walk201 248
#define FRAME_walk202 249
#define FRAME_walk203 250
#define FRAME_walk204 251
#define FRAME_walk205 252
#define FRAME_walk206 253
#define FRAME_walk207 254
#define FRAME_walk208 255
#define FRAME_walk209 256
#define FRAME_walk210 257
#define FRAME_walk211 258
#define FRAME_walk212 259
#define FRAME_walk213 260
#define FRAME_walk214 261
#define FRAME_walk215 262
#define FRAME_walk216 263
#define FRAME_walk217 264
#define FRAME_walk218 265
#define FRAME_walk219 266
#define FRAME_walk220 267
#define FRAME_walk221 268
#define FRAME_walk222 269
#define FRAME_walk223 270
#define FRAME_walk224 271
#define FRAME_death101 272
#define FRAME_death102 273
#define FRAME_death103 274
#define FRAME_death104 275
#define FRAME_death105 276
#define FRAME_death106 277
#define FRAME_death107 278
#define FRAME_death108 279
#define FRAME_death109 280
#define FRAME_death110 281
#define FRAME_death111 282
#define FRAME_death112 283
#define FRAME_death113 284
#define FRAME_death114 285
#define FRAME_death115 286
#define FRAME_death116 287
#define FRAME_death117 288
#define FRAME_death118 289
#define FRAME_death119 290
#define FRAME_death120 291
#define FRAME_death121 292
#define FRAME_death122 293
#define FRAME_death123 294
#define FRAME_death124 295
#define FRAME_death125 296
#define FRAME_death126 297
#define FRAME_death127 298
#define FRAME_death128 299
#define FRAME_death129 300
#define FRAME_death130 301
#define FRAME_death131 302
#define FRAME_death132 303
#define FRAME_death133 304
#define FRAME_death134 305
#define FRAME_death135 306
#define FRAME_death136 307
#define FRAME_death201 308
#define FRAME_death202 309
#define FRAME_death203 310
#define FRAME_death204 311
#define FRAME_death205 312
#define FRAME_death206 313
#define FRAME_death207 314
#define FRAME_death208 315
#define FRAME_death209 316
#define FRAME_death210 317
#define FRAME_death211 318
#define FRAME_death212 319
#define FRAME_death213 320
#define FRAME_death214 321
#define FRAME_death215 322
#define FRAME_death216 323
#define FRAME_death217 324
#define FRAME_death218 325
#define FRAME_death219 326
#define FRAME_death220 327
#define FRAME_death221 328
#define FRAME_death222 329
#define FRAME_death223 330
#define FRAME_death224 331
#define FRAME_death225 332
#define FRAME_death226 333
#define FRAME_death227 334
#define FRAME_death228 335
#define FRAME_death229 336
#define FRAME_death230 337
#define FRAME_death231 338
#define FRAME_death232 339
#define FRAME_death233 340
#define FRAME_death234 341
#define FRAME_death235 342
#define FRAME_death301 343
#define FRAME_death302 344
#define FRAME_death303 345
#define FRAME_death304 346
#define FRAME_death305 347
#define FRAME_death306 348
#define FRAME_death307 349
#define FRAME_death308 350
#define FRAME_death309 351
#define FRAME_death310 352
#define FRAME_death311 353
#define FRAME_death312 354
#define FRAME_death313 355
#define FRAME_death314 356
#define FRAME_death315 357
#define FRAME_death316 358
#define FRAME_death317 359
#define FRAME_death318 360
#define FRAME_death319 361
#define FRAME_death320 362
#define FRAME_death321 363
#define FRAME_death322 364
#define FRAME_death323 365
#define FRAME_death324 366
#define FRAME_death325 367
#define FRAME_death326 368
#define FRAME_death327 369
#define FRAME_death328 370
#define FRAME_death329 371
#define FRAME_death330 372
#define FRAME_death331 373
#define FRAME_death332 374
#define FRAME_death333 375
#define FRAME_death334 376
#define FRAME_death335 377
#define FRAME_death336 378
#define FRAME_death337 379
#define FRAME_death338 380
#define FRAME_death339 381
#define FRAME_death340 382
#define FRAME_death341 383
#define FRAME_death342 384
#define FRAME_death343 385
#define FRAME_death344 386
#define FRAME_death345 387
#define FRAME_death401 388
#define FRAME_death402 389
#define FRAME_death403 390
#define FRAME_death404 391
#define FRAME_death405 392
#define FRAME_death406 393
#define FRAME_death407 394
#define FRAME_death408 395
#define FRAME_death409 396
#define FRAME_death410 397
#define FRAME_death411 398
#define FRAME_death412 399
#define FRAME_death413 400
#define FRAME_death414 401
#define FRAME_death415 402
#define FRAME_death416 403
#define FRAME_death417 404
#define FRAME_death418 405
#define FRAME_death419 406
#define FRAME_death420 407
#define FRAME_death421 408
#define FRAME_death422 409
#define FRAME_death423 410
#define FRAME_death424 411
#define FRAME_death425 412
#define FRAME_death426 413
#define FRAME_death427 414
#define FRAME_death428 415
#define FRAME_death429 416
#define FRAME_death430 417
#define FRAME_death431 418
#define FRAME_death432 419
#define FRAME_death433 420
#define FRAME_death434 421
#define FRAME_death435 422
#define FRAME_death436 423
#define FRAME_death437 424
#define FRAME_death438 425
#define FRAME_death439 426
#define FRAME_death440 427
#define FRAME_death441 428
#define FRAME_death442 429
#define FRAME_death443 430
#define FRAME_death444 431
#define FRAME_death445 432
#define FRAME_death446 433
#define FRAME_death447 434
#define FRAME_death448 435
#define FRAME_death449 436
#define FRAME_death450 437
#define FRAME_death451 438
#define FRAME_death452 439
#define FRAME_death453 440
#define FRAME_death501 441
#define FRAME_death502 442
#define FRAME_death503 443
#define FRAME_death504 444
#define FRAME_death505 445
#define FRAME_death506 446
#define FRAME_death507 447
#define FRAME_death508 448
#define FRAME_death509 449
#define FRAME_death510 450
#define FRAME_death511 451
#define FRAME_death512 452
#define FRAME_death513 453
#define FRAME_death514 454
#define FRAME_death515 455
#define FRAME_death516 456
#define FRAME_death517 457
#define FRAME_death518 458
#define FRAME_death519 459
#define FRAME_death520 460
#define FRAME_death521 461
#define FRAME_death522 462
#define FRAME_death523 463
#define FRAME_death524 464
#define FRAME_death601 465
#define FRAME_death602 466
#define FRAME_death603 467
#define FRAME_death604 468
#define FRAME_death605 469
#define FRAME_death606 470
#define FRAME_death607 471
#define FRAME_death608 472
#define FRAME_death609 473
#define FRAME_death610 474
#define MODEL_SCALE 1.200000