game: sync game logic from xatrix

This commit is contained in:
Denis Pauk 2023-10-21 02:28:43 +03:00
parent 0956f965f7
commit 5b7696b789
30 changed files with 1846 additions and 24440 deletions

View file

@ -1466,12 +1466,12 @@ XATRIX_OBJS_ = \
src/common/shared/rand.o \ src/common/shared/rand.o \
src/common/shared/shared.o \ src/common/shared/shared.o \
src/game/dm/tag.o \ src/game/dm/tag.o \
src/xatrix/g_ai.o \ src/game/g_ai.o \
src/xatrix/g_chase.o \ src/game/g_chase.o \
src/xatrix/g_cmds.o \ src/game/g_cmds.o \
src/xatrix/g_combat.o \ src/game/g_combat.o \
src/xatrix/g_func.o \ src/game/g_func.o \
src/xatrix/g_items.o \ src/game/g_items.o \
src/game/g_main.o \ src/game/g_main.o \
src/game/g_misc.o \ src/game/g_misc.o \
src/game/g_monster.o \ src/game/g_monster.o \
@ -1481,15 +1481,15 @@ XATRIX_OBJS_ = \
src/game/g_newtarg.o \ src/game/g_newtarg.o \
src/game/g_newtrig.o \ src/game/g_newtrig.o \
src/game/g_newweap.o \ src/game/g_newweap.o \
src/xatrix/g_phys.o \ src/game/g_phys.o \
src/xatrix/g_spawn.o \ src/game/g_spawn.o \
src/game/g_sphere.o \ src/game/g_sphere.o \
src/xatrix/g_svcmds.o \ src/game/g_svcmds.o \
src/xatrix/g_target.o \ src/game/g_target.o \
src/xatrix/g_trigger.o \ src/game/g_trigger.o \
src/xatrix/g_turret.o \ src/game/g_turret.o \
src/game/g_utils.o \ src/game/g_utils.o \
src/xatrix/g_weapon.o \ src/game/g_weapon.o \
src/game/monster/berserker/berserker.o \ src/game/monster/berserker/berserker.o \
src/game/monster/boss2/boss2.o \ src/game/monster/boss2/boss2.o \
src/game/monster/boss3/boss3.o \ src/game/monster/boss3/boss3.o \
@ -1497,6 +1497,7 @@ XATRIX_OBJS_ = \
src/game/monster/boss3/boss32.o \ src/game/monster/boss3/boss32.o \
src/game/monster/boss5/boss5.o \ src/game/monster/boss5/boss5.o \
src/game/monster/brain/brain.o \ src/game/monster/brain/brain.o \
src/game/monster/carrier/carrier.o \
src/game/monster/chick/chick.o \ src/game/monster/chick/chick.o \
src/game/monster/fixbot/fixbot.o \ src/game/monster/fixbot/fixbot.o \
src/game/monster/flipper/flipper.o \ src/game/monster/flipper/flipper.o \
@ -1514,8 +1515,12 @@ XATRIX_OBJS_ = \
src/game/monster/mutant/mutant.o \ src/game/monster/mutant/mutant.o \
src/game/monster/parasite/parasite.o \ src/game/monster/parasite/parasite.o \
src/game/monster/soldier/soldier.o \ src/game/monster/soldier/soldier.o \
src/game/monster/stalker/stalker.o \
src/game/monster/supertank/supertank.o \ src/game/monster/supertank/supertank.o \
src/game/monster/tank/tank.o \ src/game/monster/tank/tank.o \
src/game/monster/turret/turret.o \
src/game/monster/widow/widow2.o \
src/game/monster/widow/widow.o \
src/game/player/client.o \ src/game/player/client.o \
src/game/player/hud.o \ src/game/player/hud.o \
src/game/player/trail.o \ src/game/player/trail.o \
@ -1591,7 +1596,7 @@ ROGUE_OBJS_ = \
src/common/shared/shared.o \ src/common/shared/shared.o \
src/rogue/g_ai.o \ src/rogue/g_ai.o \
src/rogue/g_chase.o \ src/rogue/g_chase.o \
src/rogue/g_cmds.o \ src/game/g_cmds.o \
src/rogue/g_combat.o \ src/rogue/g_combat.o \
src/rogue/g_func.o \ src/rogue/g_func.o \
src/rogue/g_items.o \ src/rogue/g_items.o \
@ -1604,7 +1609,7 @@ ROGUE_OBJS_ = \
src/game/g_newtarg.o \ src/game/g_newtarg.o \
src/game/g_newtrig.o \ src/game/g_newtrig.o \
src/game/g_newweap.o \ src/game/g_newweap.o \
src/rogue/g_phys.o \ src/game/g_phys.o \
src/rogue/g_spawn.o \ src/rogue/g_spawn.o \
src/game/g_sphere.o \ src/game/g_sphere.o \
src/rogue/g_svcmds.o \ src/rogue/g_svcmds.o \
@ -1642,7 +1647,7 @@ ROGUE_OBJS_ = \
src/game/monster/turret/turret.o \ src/game/monster/turret/turret.o \
src/game/monster/widow/widow.o \ src/game/monster/widow/widow.o \
src/game/monster/widow/widow2.o \ src/game/monster/widow/widow2.o \
src/rogue/player/client.o \ src/game/player/client.o \
src/rogue/player/hud.o \ src/rogue/player/hud.o \
src/rogue/player/trail.o \ src/rogue/player/trail.o \
src/rogue/player/view.o \ src/rogue/player/view.o \

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 1997-2001 Id Software, Inc. * Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 1997-2001 Id Software, Inc. * Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -228,7 +229,7 @@ Cmd_Give_f(edict_t *ent)
if ((deathmatch->value || coop->value) && !sv_cheats->value) if ((deathmatch->value || coop->value) && !sv_cheats->value)
{ {
gi.cprintf( ent, PRINT_HIGH, gi.cprintf(ent, PRINT_HIGH,
"You must run the server with '+set cheats 1' to enable this command.\n"); "You must run the server with '+set cheats 1' to enable this command.\n");
return; return;
} }
@ -362,6 +363,11 @@ Cmd_Give_f(edict_t *ent)
continue; continue;
} }
if (it->flags & IT_NOT_GIVEABLE)
{
continue;
}
if (it->flags & (IT_ARMOR | IT_WEAPON | IT_AMMO)) if (it->flags & (IT_ARMOR | IT_WEAPON | IT_AMMO))
{ {
continue; continue;
@ -393,6 +399,12 @@ Cmd_Give_f(edict_t *ent)
return; return;
} }
if (it->flags & IT_NOT_GIVEABLE)
{
gi.dprintf("item cannot be given\n");
return;
}
index = ITEM_INDEX(it); index = ITEM_INDEX(it);
if (it->flags & IT_AMMO) if (it->flags & IT_AMMO)
@ -411,6 +423,13 @@ Cmd_Give_f(edict_t *ent)
it_ent = G_Spawn(); it_ent = G_Spawn();
it_ent->classname = it->classname; it_ent->classname = it->classname;
SpawnItem(it_ent, it); SpawnItem(it_ent, it);
/* since some items don't actually spawn when you say to .. */
if (!it_ent->inuse)
{
return;
}
Touch_Item(it_ent, ent, NULL, NULL); Touch_Item(it_ent, ent, NULL, NULL);
if (it_ent->inuse) if (it_ent->inuse)
@ -435,7 +454,7 @@ Cmd_God_f(edict_t *ent)
if ((deathmatch->value || coop->value) && !sv_cheats->value) if ((deathmatch->value || coop->value) && !sv_cheats->value)
{ {
gi.cprintf( ent, PRINT_HIGH, gi.cprintf(ent, PRINT_HIGH,
"You must run the server with '+set cheats 1' to enable this command.\n"); "You must run the server with '+set cheats 1' to enable this command.\n");
return; return;
} }
@ -469,7 +488,7 @@ Cmd_Notarget_f(edict_t *ent)
if ((deathmatch->value || coop->value) && !sv_cheats->value) if ((deathmatch->value || coop->value) && !sv_cheats->value)
{ {
gi.cprintf( ent, PRINT_HIGH, gi.cprintf(ent, PRINT_HIGH,
"You must run the server with '+set cheats 1' to enable this command.\n"); "You must run the server with '+set cheats 1' to enable this command.\n");
return; return;
} }
@ -503,7 +522,7 @@ Cmd_Noclip_f(edict_t *ent)
if ((deathmatch->value || coop->value) && !sv_cheats->value) if ((deathmatch->value || coop->value) && !sv_cheats->value)
{ {
gi.cprintf( ent, PRINT_HIGH, gi.cprintf(ent, PRINT_HIGH,
"You must run the server with '+set cheats 1' to enable this command.\n"); "You must run the server with '+set cheats 1' to enable this command.\n");
return; return;
} }
@ -556,8 +575,33 @@ Cmd_Use_f(edict_t *ent)
if (!ent->client->pers.inventory[index]) if (!ent->client->pers.inventory[index])
{ {
gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s); if (strcmp(it->pickup_name, "HyperBlaster") == 0)
return; {
it = FindItem("Ionripper");
index = ITEM_INDEX(it);
if (!ent->client->pers.inventory[index])
{
gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
return;
}
}
else if (strcmp(it->pickup_name, "Railgun") == 0)
{
it = FindItem("Phalanx");
index = ITEM_INDEX(it);
if (!ent->client->pers.inventory[index])
{
gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
return;
}
}
else
{
gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
return;
}
} }
it->use(ent, it); it->use(ent, it);
@ -597,8 +641,33 @@ Cmd_Drop_f(edict_t *ent)
if (!ent->client->pers.inventory[index]) if (!ent->client->pers.inventory[index])
{ {
gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s); if (strcmp(it->pickup_name, "HyperBlaster") == 0)
return; {
it = FindItem("Ionripper");
index = ITEM_INDEX(it);
if (!ent->client->pers.inventory[index])
{
gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
return;
}
}
else if (strcmp(it->pickup_name, "Railgun") == 0)
{
it = FindItem("Phalanx");
index = ITEM_INDEX(it);
if (!ent->client->pers.inventory[index])
{
gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
return;
}
}
else
{
gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
return;
}
} }
it->drop(ent, it); it->drop(ent, it);
@ -742,7 +811,8 @@ Cmd_WeapPrev_f(edict_t *ent)
/* scan for the next valid one */ /* scan for the next valid one */
for (i = 1; i <= MAX_ITEMS; i++) for (i = 1; i <= MAX_ITEMS; i++)
{ {
index = (selected_weapon + i) % MAX_ITEMS; /* prevent scrolling through ALL weapons */
index = (selected_weapon + MAX_ITEMS - i) % MAX_ITEMS;
if (!cl->pers.inventory[index]) if (!cl->pers.inventory[index])
{ {
@ -763,9 +833,10 @@ Cmd_WeapPrev_f(edict_t *ent)
it->use(ent, it); it->use(ent, it);
if (cl->pers.weapon == it) /* prevent scrolling through ALL weapons */
if (cl->newweapon == it)
{ {
return; /* successful */ return;
} }
} }
} }
@ -795,7 +866,8 @@ Cmd_WeapNext_f(edict_t *ent)
/* scan for the next valid one */ /* scan for the next valid one */
for (i = 1; i <= MAX_ITEMS; i++) for (i = 1; i <= MAX_ITEMS; i++)
{ {
index = (selected_weapon + MAX_ITEMS - i) % MAX_ITEMS; /* prevent scrolling through ALL weapons */
index = (selected_weapon + i) % MAX_ITEMS;
if (!cl->pers.inventory[index]) if (!cl->pers.inventory[index])
{ {
@ -816,9 +888,10 @@ Cmd_WeapNext_f(edict_t *ent)
it->use(ent, it); it->use(ent, it);
if (cl->pers.weapon == it) /* prevent scrolling through ALL weapons */
if (cl->newweapon == it)
{ {
return; /* successful */ return;
} }
} }
} }
@ -910,6 +983,19 @@ Cmd_Kill_f(edict_t *ent)
ent->flags &= ~FL_GODMODE; ent->flags &= ~FL_GODMODE;
ent->health = 0; ent->health = 0;
meansOfDeath = MOD_SUICIDE; meansOfDeath = MOD_SUICIDE;
/* make sure no trackers are still hurting us. */
if (ent->client->tracker_pain_framenum)
{
RemoveAttackingPainDaemons(ent);
}
if (ent->client->owned_sphere)
{
G_FreeEdict(ent->client->owned_sphere);
ent->client->owned_sphere = NULL;
}
player_die(ent, ent, ent, 100000, vec3_origin); player_die(ent, ent, ent, 100000, vec3_origin);
} }
@ -1224,6 +1310,30 @@ Cmd_Say_f(edict_t *ent, qboolean team, qboolean arg0)
} }
} }
void
Cmd_Ent_Count_f(edict_t *ent)
{
int x;
edict_t *e;
if (!ent)
{
return;
}
x = 0;
for (e = g_edicts; e < &g_edicts[globals.num_edicts]; e++)
{
if (e->inuse)
{
x++;
}
}
gi.dprintf("%d entites active\n", x);
}
void void
Cmd_PlayerList_f(edict_t *ent) Cmd_PlayerList_f(edict_t *ent)
{ {
@ -1242,6 +1352,8 @@ Cmd_PlayerList_f(edict_t *ent)
for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++) for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++)
{ {
int text_len;
if (!e2->inuse) if (!e2->inuse)
{ {
continue; continue;
@ -1255,9 +1367,11 @@ Cmd_PlayerList_f(edict_t *ent)
e2->client->pers.netname, e2->client->pers.netname,
e2->client->resp.spectator ? " (spectator)" : ""); e2->client->resp.spectator ? " (spectator)" : "");
if (strlen(text) + strlen(st) > sizeof(text) - 50) text_len = strlen(text);
if ((text_len + strlen(st)) > (sizeof(text) - 50))
{ {
strcpy(text + strlen(text), "And more...\n"); snprintf(text + text_len, sizeof(text) - text_len, "And more...\n");
gi.cprintf(ent, PRINT_HIGH, "%s", text); gi.cprintf(ent, PRINT_HIGH, "%s", text);
return; return;
} }
@ -1962,6 +2076,14 @@ ClientCommand(edict_t *ent)
{ {
Cmd_PlayerList_f(ent); Cmd_PlayerList_f(ent);
} }
else if (Q_stricmp(cmd, "entcount") == 0)
{
Cmd_Ent_Count_f(ent);
}
else if (Q_stricmp(cmd, "disguise") == 0)
{
ent->flags |= FL_DISGUISED;
}
else if (Q_stricmp(cmd, "teleport") == 0) else if (Q_stricmp(cmd, "teleport") == 0)
{ {
Cmd_Teleport_f(ent); Cmd_Teleport_f(ent);

View file

@ -200,7 +200,7 @@ SpawnDamage(int type, vec3_t origin, vec3_t normal)
* targ entity that is being damaged * targ entity that is being damaged
* inflictor entity that is causing the damage * inflictor entity that is causing the damage
* attacker entity that caused the inflictor to damage targ * attacker entity that caused the inflictor to damage targ
* example: targ=monster, inflictor=rocket, attacker=player * example: targ=monster, inflictor=rocket, attacker=player
* *
* dir direction of the attack * dir direction of the attack
* point point at which the damage is being inflicted * point point at which the damage is being inflicted
@ -217,8 +217,8 @@ SpawnDamage(int type, vec3_t origin, vec3_t normal)
* DAMAGE_NO_PROTECTION kills godmode, armor, everything * DAMAGE_NO_PROTECTION kills godmode, armor, everything
*/ */
int int
CheckPowerArmor(edict_t *ent, vec3_t point, vec3_t normal, int damage, CheckPowerArmor(edict_t *ent, vec3_t point, vec3_t normal,
int dflags) int damage, int dflags)
{ {
gclient_t *client; gclient_t *client;
int save; int save;
@ -441,7 +441,7 @@ M_ReactToDamage(edict_t *targ, edict_t *attacker)
targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET; targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
/* this can only happen in coop (both new and old /* this can only happen in coop (both new and old
enemies are clients) only switch if can't see enemies are clients) only switch if can't see
the current enemy */ the current enemy */
if (targ->enemy && targ->enemy->client) if (targ->enemy && targ->enemy->client)
{ {
@ -533,9 +533,9 @@ apply_knockback(edict_t *targ, vec3_t dir, float knockback, float scale)
mass = (targ->mass < 50) ? 50.0f : (float)targ->mass; mass = (targ->mass < 50) ? 50.0f : (float)targ->mass;
VectorNormalize2 (dir, kvel); VectorNormalize2(dir, kvel);
VectorScale (kvel, scale * (knockback / mass), kvel); VectorScale(kvel, scale * (knockback / mass), kvel);
VectorAdd (targ->velocity, kvel, targ->velocity); VectorAdd(targ->velocity, kvel, targ->velocity);
} }
void void
@ -564,7 +564,7 @@ T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker,
can't hurt teammates (but you can hurt can't hurt teammates (but you can hurt
yourself) knockback still occurs */ yourself) knockback still occurs */
if ((targ != attacker) && ((deathmatch->value && if ((targ != attacker) && ((deathmatch->value &&
((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) ||
coop->value)) coop->value))
{ {
if (OnSameTeam(targ, attacker)) if (OnSameTeam(targ, attacker))
@ -699,7 +699,7 @@ T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker,
{ {
M_ReactToDamage(targ, attacker); M_ReactToDamage(targ, attacker);
if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) if (!(targ->monsterinfo.aiflags & (AI_DUCKED|AI_IGNORE_PAIN)) && (take))
{ {
targ->pain(targ, attacker, knockback, take); targ->pain(targ, attacker, knockback, take);

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 1997-2001 Id Software, Inc. * Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -176,8 +177,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 void
AngleMove_Done(edict_t *ent) AngleMove_Done(edict_t *ent)
{ {
@ -289,6 +292,11 @@ AngleMove_Calc(edict_t *ent, void (*func)(edict_t *))
} }
} }
/*
* The team has completed a frame of movement, so
* change the speed for the next frame
*/
#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) #define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2)
void void
@ -691,7 +699,6 @@ plat_spawn_inside_trigger(edict_t *ent)
tmin[0] = ent->mins[0] + 25; tmin[0] = ent->mins[0] + 25;
tmin[1] = ent->mins[1] + 25; tmin[1] = ent->mins[1] + 25;
// tmin[2] = ent->mins[2];
tmax[0] = ent->maxs[0] - 25; tmax[0] = ent->maxs[0] - 25;
tmax[1] = ent->maxs[1] - 25; tmax[1] = ent->maxs[1] - 25;
@ -1648,8 +1655,9 @@ door_blocked(edict_t *self, edict_t *other)
return; return;
} }
/* if a door has a negative wait, it would never come back if blocked, /* if a door has a negative wait, it would never
so let it just squash the object to death real fast */ come back if blocked, so let it just squash the
object to death real fast */
if (self->moveinfo.wait >= 0) if (self->moveinfo.wait >= 0)
{ {
if (self->moveinfo.state == STATE_DOWN) if (self->moveinfo.state == STATE_DOWN)
@ -2452,8 +2460,8 @@ SP_func_train(edict_t *self)
if (self->target) if (self->target)
{ {
/* start trains on the second frame, to make sure /* start trains on the second frame, to make
their targets have had a chance to spawn */ * sure their targets have had a chance to spawn */
self->nextthink = level.time + FRAMETIME; self->nextthink = level.time + FRAMETIME;
self->think = func_train_find; self->think = func_train_find;
} }
@ -3010,3 +3018,231 @@ SP_func_killbox(edict_t *ent)
ent->use = use_killbox; ent->use = use_killbox;
ent->svflags = SVF_NOCLIENT; 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,5 +1,6 @@
/* /*
* Copyright (C) 1997-2001 Id Software, Inc. * Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -46,6 +47,10 @@ void Weapon_GrenadeLauncher(edict_t *ent);
void Weapon_Railgun(edict_t *ent); void Weapon_Railgun(edict_t *ent);
void Weapon_BFG(edict_t *ent); void Weapon_BFG(edict_t *ent);
void Weapon_Ionripper(edict_t *ent);
void Weapon_Phalanx(edict_t *ent);
void Weapon_Trap(edict_t *ent);
static gitem_armor_t jacketarmor_info = {25, 50, .30, .00, ARMOR_JACKET}; 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 combatarmor_info = {50, 100, .60, .30, ARMOR_COMBAT};
static gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY}; static gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY};
@ -60,6 +65,7 @@ void Use_Quad(edict_t *ent, gitem_t *item);
void Use_QuadFire(edict_t *ent, gitem_t *item); void Use_QuadFire(edict_t *ent, gitem_t *item);
static int quad_drop_timeout_hack; static int quad_drop_timeout_hack;
static int quad_fire_drop_timeout_hack;
/* ====================================================================== */ /* ====================================================================== */
@ -226,6 +232,11 @@ Pickup_Powerup(edict_t *ent, edict_t *other)
void void
Drop_General(edict_t *ent, gitem_t *item) Drop_General(edict_t *ent, gitem_t *item)
{ {
if (!ent || !item)
{
return;
}
Drop_Item(ent, item); Drop_Item(ent, item);
ent->client->pers.inventory[ITEM_INDEX(item)]--; ent->client->pers.inventory[ITEM_INDEX(item)]--;
ValidateSelectedItem(ent); ValidateSelectedItem(ent);
@ -308,6 +319,11 @@ Pickup_Bandolier(edict_t *ent, edict_t *other)
other->client->pers.max_slugs = 75; other->client->pers.max_slugs = 75;
} }
if (other->client->pers.max_magslug < 75)
{
other->client->pers.max_magslug = 75;
}
item = FindItem("Bullets"); item = FindItem("Bullets");
if (item) if (item)
@ -387,6 +403,11 @@ Pickup_Pack(edict_t *ent, edict_t *other)
other->client->pers.max_slugs = 100; other->client->pers.max_slugs = 100;
} }
if (other->client->pers.max_magslug < 100)
{
other->client->pers.max_magslug = 100;
}
item = FindItem("Bullets"); item = FindItem("Bullets");
if (item) if (item)
@ -477,6 +498,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;
}
}
if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
{ {
SetRespawn(ent, ent->item->quantity); SetRespawn(ent, ent->item->quantity);
@ -522,6 +558,43 @@ Use_Quad(edict_t *ent, gitem_t *item)
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);
}
/* ====================================================================== */ /* ====================================================================== */
void void
@ -691,6 +764,14 @@ Add_Ammo(edict_t *ent, gitem_t *item, int count)
{ {
max = ent->client->pers.max_slugs; 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 else
{ {
return false; return false;
@ -941,12 +1022,16 @@ Pickup_Armor(edict_t *ent, edict_t *other)
other->client->pers.inventory[old_armor_index] += 2; other->client->pers.inventory[old_armor_index] += 2;
} }
} }
else if (!old_armor_index) /* if player has no armor, just use it */
/* if player has no armor, just use it */
else if (!old_armor_index)
{ {
other->client->pers.inventory[ITEM_INDEX(ent->item)] = other->client->pers.inventory[ITEM_INDEX(ent->item)] =
newinfo->base_count; newinfo->base_count;
} }
else /* use the better armor */
/* use the better armor */
else
{ {
/* get info on old armor */ /* get info on old armor */
if (old_armor_index == jacket_armor_index) if (old_armor_index == jacket_armor_index)
@ -957,7 +1042,7 @@ Pickup_Armor(edict_t *ent, edict_t *other)
{ {
oldinfo = &combatarmor_info; oldinfo = &combatarmor_info;
} }
else else /* (old_armor_index == body_armor_index) */
{ {
oldinfo = &bodyarmor_info; oldinfo = &bodyarmor_info;
} }
@ -1162,9 +1247,9 @@ Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, csurface_
/* show icon and name on status bar */ /* show icon and name on status bar */
other->client->ps.stats[STAT_PICKUP_ICON] = other->client->ps.stats[STAT_PICKUP_ICON] =
gi.imageindex( ent->item->icon); gi.imageindex(ent->item->icon);
other->client->ps.stats[STAT_PICKUP_STRING] = other->client->ps.stats[STAT_PICKUP_STRING] =
CS_ITEMS + ITEM_INDEX( ent->item); CS_ITEMS + ITEM_INDEX(ent->item);
other->client->pickup_msg_time = level.time + 3.0; other->client->pickup_msg_time = level.time + 3.0;
/* change selected item */ /* change selected item */
@ -1172,7 +1257,7 @@ Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, csurface_
{ {
other->client->pers.selected_item = other->client->pers.selected_item =
other->client->ps.stats[STAT_SELECTED_ITEM] = other->client->ps.stats[STAT_SELECTED_ITEM] =
ITEM_INDEX( ent->item); ITEM_INDEX(ent->item);
} }
if (ent->item->pickup == Pickup_Health) if (ent->item->pickup == Pickup_Health)
@ -1210,14 +1295,21 @@ Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, csurface_
{ {
if ((((int)dmflags->value & DF_INSTANT_ITEMS) && if ((((int)dmflags->value & DF_INSTANT_ITEMS) &&
(ent->item->flags & IT_INSTANT_USE)) || (ent->item->flags & IT_INSTANT_USE)) ||
((ent->item->use == Use_Quad) && (((ent->item->use == Use_Quad) || (ent->item->use == Use_QuadFire)) &&
(ent->spawnflags & DROPPED_PLAYER_ITEM))) (ent->spawnflags & DROPPED_PLAYER_ITEM)))
{ {
if ((ent->item->use == Use_Quad) && if (ent->spawnflags & DROPPED_PLAYER_ITEM)
(ent->spawnflags & DROPPED_PLAYER_ITEM))
{ {
quad_drop_timeout_hack = if (ent->item->use == Use_Quad)
(ent->nextthink - level.time) / FRAMETIME; {
quad_drop_timeout_hack =
(ent->nextthink - level.time) / FRAMETIME;
}
else if (ent->item->use == Use_QuadFire)
{
quad_fire_drop_timeout_hack =
(ent->nextthink - level.time) / FRAMETIME;
}
} }
if (ent->item->use) if (ent->item->use)
@ -1423,10 +1515,19 @@ droptofloor(edict_t *ent)
if (tr.startsolid) if (tr.startsolid)
{ {
gi.dprintf("droptofloor: %s startsolid at %s\n", ent->classname, if (strcmp(ent->classname, "foodcube") == 0)
vtos(ent->s.origin)); {
G_FreeEdict(ent); VectorCopy(ent->s.origin, tr.endpos);
return; 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); VectorCopy(tr.endpos, ent->s.origin);
@ -1672,7 +1773,10 @@ static const gitem_t gameitemlist[] = {
NULL NULL
}, /* leave index 0 alone */ }, /* leave index 0 alone */
/* 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)
*/
{ {
"item_armor_body", "item_armor_body",
Pickup_Armor, Pickup_Armor,
@ -1694,7 +1798,9 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"item_armor_combat", "item_armor_combat",
Pickup_Armor, Pickup_Armor,
@ -1716,7 +1822,9 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"item_armor_jacket", "item_armor_jacket",
Pickup_Armor, Pickup_Armor,
@ -1738,7 +1846,9 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"item_armor_shard", "item_armor_shard",
Pickup_Armor, Pickup_Armor,
@ -1760,7 +1870,9 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"item_power_screen", "item_power_screen",
Pickup_PowerArmor, Pickup_PowerArmor,
@ -1782,7 +1894,9 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"item_power_shield", "item_power_shield",
Pickup_PowerArmor, Pickup_PowerArmor,
@ -1804,8 +1918,10 @@ static const gitem_t gameitemlist[] = {
"misc/power2.wav misc/power1.wav" "misc/power2.wav misc/power1.wav"
}, },
/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) /*
always owned, never in the world */ * weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16)
* always owned, never in the world
*/
{ {
"weapon_blaster", "weapon_blaster",
NULL, NULL,
@ -1827,7 +1943,9 @@ static const gitem_t gameitemlist[] = {
"weapons/blastf1a.wav misc/lasfly.wav" "weapons/blastf1a.wav misc/lasfly.wav"
}, },
/* QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"weapon_shotgun", "weapon_shotgun",
Pickup_Weapon, Pickup_Weapon,
@ -1849,7 +1967,9 @@ static const gitem_t gameitemlist[] = {
"weapons/shotgf1b.wav weapons/shotgr1b.wav" "weapons/shotgf1b.wav weapons/shotgr1b.wav"
}, },
/* QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"weapon_supershotgun", "weapon_supershotgun",
Pickup_Weapon, Pickup_Weapon,
@ -1871,7 +1991,9 @@ static const gitem_t gameitemlist[] = {
"weapons/sshotf1b.wav" "weapons/sshotf1b.wav"
}, },
/* QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"weapon_machinegun", "weapon_machinegun",
Pickup_Weapon, Pickup_Weapon,
@ -1890,10 +2012,13 @@ static const gitem_t gameitemlist[] = {
WEAP_MACHINEGUN, WEAP_MACHINEGUN,
NULL, NULL,
0, 0,
"weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav" "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav"
}, },
/* QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"weapon_chaingun", "weapon_chaingun",
Pickup_Weapon, Pickup_Weapon,
@ -1912,10 +2037,13 @@ static const gitem_t gameitemlist[] = {
WEAP_CHAINGUN, WEAP_CHAINGUN,
NULL, NULL,
0, 0,
"weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav" "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav"
}, },
/* QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"ammo_grenades", "ammo_grenades",
Pickup_Ammo, Pickup_Ammo,
@ -1934,10 +2062,38 @@ static const gitem_t gameitemlist[] = {
WEAP_GRENADES, WEAP_GRENADES,
NULL, NULL,
AMMO_GRENADES, AMMO_GRENADES,
"weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav "
}, },
/* QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED ammo_trap (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{
"ammo_trap",
Pickup_Ammo,
Use_Weapon,
Drop_Ammo,
Weapon_Trap,
"misc/am_pkup.wav",
"models/weapons/g_trap/tris.md2", EF_ROTATE,
"models/weapons/v_trap/tris.md2",
"a_trap",
"Trap",
3,
1,
"trap",
IT_AMMO | IT_WEAPON,
0,
NULL,
AMMO_TRAP,
"weapons/trapcock.wav weapons/traploop.wav weapons/trapsuck.wav weapons/trapdown.wav"
},
/*
* QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"weapon_grenadelauncher", "weapon_grenadelauncher",
Pickup_Weapon, Pickup_Weapon,
@ -1956,10 +2112,13 @@ static const gitem_t gameitemlist[] = {
WEAP_GRENADELAUNCHER, WEAP_GRENADELAUNCHER,
NULL, NULL,
0, 0,
"models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav"
}, },
/* QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"weapon_rocketlauncher", "weapon_rocketlauncher",
Pickup_Weapon, Pickup_Weapon,
@ -1978,14 +2137,17 @@ static const gitem_t gameitemlist[] = {
WEAP_ROCKETLAUNCHER, WEAP_ROCKETLAUNCHER,
NULL, NULL,
0, 0,
"models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2"
}, },
/* QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"weapon_hyperblaster", "weapon_hyperblaster",
Pickup_Weapon, Pickup_Weapon,
Use_Weapon, Use_Weapon2,
Drop_Weapon, Drop_Weapon,
Weapon_HyperBlaster, Weapon_HyperBlaster,
"misc/w_pkup.wav", "misc/w_pkup.wav",
@ -2000,14 +2162,41 @@ static const gitem_t gameitemlist[] = {
WEAP_HYPERBLASTER, WEAP_HYPERBLASTER,
NULL, NULL,
0, 0,
"weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav" "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav"
}, },
/* QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED weapon_boomer (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{
"weapon_boomer",
Pickup_Weapon,
Use_Weapon,
Drop_Weapon,
Weapon_Ionripper,
"misc/w_pkup.wav",
"models/weapons/g_boom/tris.md2", EF_ROTATE,
"models/weapons/v_boomer/tris.md2",
"w_ripper",
"Ionripper",
0,
2,
"Cells",
IT_WEAPON,
WEAP_BOOMER,
NULL,
0,
"weapons/rg_hum.wav weapons/rippfire.wav"
},
/*
* QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"weapon_railgun", "weapon_railgun",
Pickup_Weapon, Pickup_Weapon,
Use_Weapon, Use_Weapon2,
Drop_Weapon, Drop_Weapon,
Weapon_Railgun, Weapon_Railgun,
"misc/w_pkup.wav", "misc/w_pkup.wav",
@ -2025,7 +2214,34 @@ static const gitem_t gameitemlist[] = {
"weapons/rg_hum.wav" "weapons/rg_hum.wav"
}, },
/* QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED weapon_phalanx (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{
"weapon_phalanx",
Pickup_Weapon,
Use_Weapon,
Drop_Weapon,
Weapon_Phalanx,
"misc/w_pkup.wav",
"models/weapons/g_shotx/tris.md2", EF_ROTATE,
"models/weapons/v_shotx/tris.md2",
"w_phallanx",
"Phalanx",
0,
1,
"Mag Slug",
IT_WEAPON,
WEAP_PHALANX,
NULL,
0,
"weapons/plasshot.wav"
},
/*
* QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"weapon_bfg", "weapon_bfg",
Pickup_Weapon, Pickup_Weapon,
@ -2044,10 +2260,13 @@ static const gitem_t gameitemlist[] = {
WEAP_BFG, WEAP_BFG,
NULL, NULL,
0, 0,
"sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav" "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav"
}, },
/* QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"ammo_shells", "ammo_shells",
Pickup_Ammo, Pickup_Ammo,
@ -2069,7 +2288,9 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"ammo_bullets", "ammo_bullets",
Pickup_Ammo, Pickup_Ammo,
@ -2091,7 +2312,9 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"ammo_cells", "ammo_cells",
Pickup_Ammo, Pickup_Ammo,
@ -2113,7 +2336,9 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"ammo_rockets", "ammo_rockets",
Pickup_Ammo, Pickup_Ammo,
@ -2135,7 +2360,9 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"ammo_slugs", "ammo_slugs",
Pickup_Ammo, Pickup_Ammo,
@ -2157,7 +2384,33 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED ammo_magslug (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{
"ammo_magslug",
Pickup_Ammo,
NULL,
Drop_Ammo,
NULL,
"misc/am_pkup.wav",
"models/objects/ammo/tris.md2", 0,
NULL,
"a_mslugs",
"Mag Slug",
3,
10,
NULL,
IT_AMMO,
0,
NULL,
AMMO_MAGSLUG,
""
},
/*
* QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"item_quad", "item_quad",
Pickup_Powerup, Pickup_Powerup,
@ -2179,7 +2432,34 @@ static const gitem_t gameitemlist[] = {
"items/damage.wav items/damage2.wav items/damage3.wav" "items/damage.wav items/damage2.wav items/damage3.wav"
}, },
/* QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED item_quadfire (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{
"item_quadfire",
Pickup_Powerup,
Use_QuadFire,
Drop_General,
NULL,
"items/pkup.wav",
"models/items/quadfire/tris.md2", EF_ROTATE,
NULL,
"p_quadfire",
"DualFire Damage",
2,
60,
NULL,
IT_POWERUP | IT_INSTANT_USE,
0,
NULL,
0,
"items/quadfire1.wav items/quadfire2.wav items/quadfire3.wav"
},
/*
* QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"item_invulnerability", "item_invulnerability",
Pickup_Powerup, Pickup_Powerup,
@ -2201,7 +2481,9 @@ static const gitem_t gameitemlist[] = {
"items/protect.wav items/protect2.wav items/protect4.wav" "items/protect.wav items/protect2.wav items/protect4.wav"
}, },
/* QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"item_silencer", "item_silencer",
Pickup_Powerup, Pickup_Powerup,
@ -2223,7 +2505,9 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"item_breather", "item_breather",
Pickup_Powerup, Pickup_Powerup,
@ -2245,7 +2529,9 @@ static const gitem_t gameitemlist[] = {
"items/airout.wav" "items/airout.wav"
}, },
/* QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"item_enviro", "item_enviro",
Pickup_Powerup, Pickup_Powerup,
@ -2267,8 +2553,10 @@ static const gitem_t gameitemlist[] = {
"items/airout.wav" "items/airout.wav"
}, },
/* QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) /*
Special item that gives +2 to maximum health */ * QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16)
* Special item that gives +2 to maximum health
*/
{ {
"item_ancient_head", "item_ancient_head",
Pickup_AncientHead, Pickup_AncientHead,
@ -2290,8 +2578,10 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) /*
gives +1 to maximum health */ * QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16)
* gives +1 to maximum health
*/
{ {
"item_adrenaline", "item_adrenaline",
Pickup_Adrenaline, Pickup_Adrenaline,
@ -2313,7 +2603,9 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"item_bandolier", "item_bandolier",
Pickup_Bandolier, Pickup_Bandolier,
@ -2335,7 +2627,9 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) */ /*
* QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{ {
"item_pack", "item_pack",
Pickup_Pack, Pickup_Pack,
@ -2357,8 +2651,10 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) /*
key for computer centers */ * QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16)
* key for computer centers
*/
{ {
"key_data_cd", "key_data_cd",
Pickup_Key, Pickup_Key,
@ -2380,8 +2676,10 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH /*
warehouse circuits */ * QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH
* warehouse circuits
*/
{ {
"key_power_cube", "key_power_cube",
Pickup_Key, Pickup_Key,
@ -2403,8 +2701,10 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) /*
key for the entrance of jail3 */ * QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16)
* key for the entrance of jail3
*/
{ {
"key_pyramid", "key_pyramid",
Pickup_Key, Pickup_Key,
@ -2426,8 +2726,10 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) /*
key for the city computer */ * QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16)
* key for the city computer
*/
{ {
"key_data_spinner", "key_data_spinner",
Pickup_Key, Pickup_Key,
@ -2449,8 +2751,10 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) /*
security pass for the security level */ * QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16)
* security pass for the security level
*/
{ {
"key_pass", "key_pass",
Pickup_Key, Pickup_Key,
@ -2472,8 +2776,10 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) /*
normal door key - blue */ * QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16)
* normal door key - blue
*/
{ {
"key_blue_key", "key_blue_key",
Pickup_Key, Pickup_Key,
@ -2495,8 +2801,10 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) /*
normal door key - red */ * QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16)
* normal door key - red
*/
{ {
"key_red_key", "key_red_key",
Pickup_Key, Pickup_Key,
@ -2518,8 +2826,36 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16)
tank commander's head */ /*
* QUAKED key_green_key (0 .5 .8) (-16 -16 -16) (16 16 16)
* normal door key - blue
*/
{
"key_green_key",
Pickup_Key,
NULL,
Drop_General,
NULL,
"items/pkup.wav",
"models/items/keys/green_key/tris.md2", EF_ROTATE,
NULL,
"k_green",
"Green Key",
2,
0,
NULL,
IT_STAY_COOP | IT_KEY,
0,
NULL,
0,
""
},
/*
* QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16)
* tank commander's head
*/
{ {
"key_commander_head", "key_commander_head",
Pickup_Key, Pickup_Key,
@ -2541,7 +2877,10 @@ static const gitem_t gameitemlist[] = {
"" ""
}, },
/* QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) */ /*
* QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16)
* tank commander's head
*/
{ {
"key_airstrike_target", "key_airstrike_target",
Pickup_Key, Pickup_Key,
@ -2581,6 +2920,7 @@ static const gitem_t gameitemlist[] = {
0, 0,
NULL, NULL,
0, 0,
"items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav" "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav"
}, },
@ -2614,7 +2954,7 @@ SP_item_health(edict_t *self)
} }
/* /*
* QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) * QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
*/ */
void void
SP_item_health_small(edict_t *self) SP_item_health_small(edict_t *self)
@ -2638,7 +2978,7 @@ SP_item_health_small(edict_t *self)
} }
/* /*
* QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) * QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
*/ */
void void
SP_item_health_large(edict_t *self) SP_item_health_large(edict_t *self)
@ -2661,7 +3001,7 @@ SP_item_health_large(edict_t *self)
} }
/* /*
* QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) * QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
*/ */
void void
SP_item_health_mega(edict_t *self) SP_item_health_mega(edict_t *self)

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 1997-2001 Id Software, Inc. * Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -24,31 +25,40 @@
* ======================================================================= * =======================================================================
*/ */
#include "header/local.h" #include "header/local.h"
#define STOP_EPSILON 0.1 #define STOP_EPSILON 0.1
#define MAX_CLIP_PLANES 5 #define MAX_CLIP_PLANES 5
#define STOPSPEED 100
#define FRICTION 6 #define FRICTION 6
#define WATERFRICTION 1 #define WATERFRICTION 1
void SV_Physics_NewToss(edict_t *ent);
typedef struct
{
edict_t *ent;
vec3_t origin;
vec3_t angles;
} pushed_t;
static pushed_t pushed[MAX_EDICTS], *pushed_p;
static edict_t *obstacle;
/* /*
* pushmove objects do not obey gravity, and do not interact * pushmove objects do not obey gravity, and do not interact with each other or
* with each other or trigger fields, but block normal movement * trigger fields, but block normal movement and push normal objects when they move.
* and push normal objects when they move.
* *
* onground is set for toss objects when they come to a complete * onground is set for toss objects when they come to a complete rest. it is set for
* rest. It is set for steping or walking objects. * steping or walking objects
* *
* doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH * - doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH
* bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS * - bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS
* corpses are SOLID_NOT and MOVETYPE_TOSS * - corpses are SOLID_NOT and MOVETYPE_TOSS
* crates are SOLID_BBOX and MOVETYPE_TOSS * - crates are SOLID_BBOX and MOVETYPE_TOSS
* walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP * - walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP
* flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY * - flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY
* - solid_edge items only clip against bsp models.
* *
* solid_edge items only clip against bsp models.
*/ */
edict_t * edict_t *
@ -167,8 +177,9 @@ SV_Impact(edict_t *e1, trace_t *trace)
/* /*
* Slide off of the impacting object * Slide off of the impacting object
* returns the blocked flags (1 = floor, * returns the blocked flags:
* 2 = step / wall) * 1 = floor
* 2 = step / wall
*/ */
int int
ClipVelocity(vec3_t in, vec3_t normal, vec3_t out, float overbounce) ClipVelocity(vec3_t in, vec3_t normal, vec3_t out, float overbounce)
@ -378,7 +389,15 @@ SV_AddGravity(edict_t *ent)
return; return;
} }
ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; if (ent->gravityVector[2] > 0)
{
VectorMA(ent->velocity, ent->gravity * sv_gravity->value * FRAMETIME,
ent->gravityVector, ent->velocity);
}
else
{
ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME;
}
} }
/* /*
@ -398,6 +417,11 @@ RealBoundingBox(edict_t *ent, vec3_t mins, vec3_t maxs)
vec3_t p[8]; vec3_t p[8];
int i, j, k, j2, k4; int i, j, k, j2, k4;
if (!ent)
{
return;
}
for (k = 0; k < 2; k++) for (k = 0; k < 2; k++)
{ {
k4 = k * 4; k4 = k * 4;
@ -481,7 +505,7 @@ RealBoundingBox(edict_t *ent, vec3_t mins, vec3_t maxs)
maxs[0] = p[i][0]; maxs[0] = p[i][0];
} }
if (maxs[1] < p[i][1]) if (maxs[1] < p[i][1])
{ {
maxs[1] = p[i][1]; maxs[1] = p[i][1];
} }
@ -569,6 +593,8 @@ retry:
} }
} }
ent->gravity = 1.0;
if (ent->inuse) if (ent->inuse)
{ {
G_TouchTriggers(ent); G_TouchTriggers(ent);
@ -577,16 +603,6 @@ retry:
return trace; return trace;
} }
typedef struct
{
edict_t *ent;
vec3_t origin;
vec3_t angles;
} pushed_t;
static pushed_t pushed[MAX_EDICTS], *pushed_p;
static edict_t *obstacle;
/* /*
* Objects need to be moved back on a failed push, * Objects need to be moved back on a failed push,
* otherwise riders would continue to slide. * otherwise riders would continue to slide.
@ -641,7 +657,7 @@ SV_Push(edict_t *pusher, vec3_t move, vec3_t amove)
/* Create a real bounding box for /* Create a real bounding box for
rotating brush models. */ rotating brush models. */
RealBoundingBox(pusher,realmins,realmaxs); RealBoundingBox(pusher, realmins, realmaxs);
/* see if any solid entities /* see if any solid entities
are inside the final position */ are inside the final position */
@ -706,7 +722,23 @@ SV_Push(edict_t *pusher, vec3_t move, vec3_t amove)
VectorSubtract(check->s.origin, pusher->s.origin, org); VectorSubtract(check->s.origin, pusher->s.origin, org);
org2[0] = DotProduct(org, forward); org2[0] = DotProduct(org, forward);
org2[1] = -DotProduct(org, right); org2[1] = -DotProduct(org, right);
org2[2] = DotProduct(org, up);
/* Quirk for blocking Elevators when
running under amd64. This is most
likey caused by a too high float
precision. -_- */
if (((pusher->s.number == 285) &&
(Q_strcasecmp(level.mapname, "xcompnd2") == 0)) ||
((pusher->s.number == 520) &&
(Q_strcasecmp(level.mapname, "xsewer2") == 0)))
{
org2[2] = DotProduct(org, up) + 2;
}
else
{
org2[2] = DotProduct(org, up);
}
VectorSubtract(org2, org, move2); VectorSubtract(org2, org, move2);
VectorAdd(check->s.origin, move2, check->s.origin); VectorAdd(check->s.origin, move2, check->s.origin);
@ -719,9 +751,11 @@ SV_Push(edict_t *pusher, vec3_t move, vec3_t amove)
block = SV_TestEntityPosition(check); block = SV_TestEntityPosition(check);
if (!block) if (!block)
{
{ /* pushed ok */ /* pushed ok */
gi.linkentity(check); gi.linkentity(check);
/* impact? */
continue; continue;
} }
@ -808,7 +842,7 @@ SV_Physics_Pusher(edict_t *ent)
} }
} }
if (pushed_p > &pushed[MAX_EDICTS -1 ]) if (pushed_p > &pushed[MAX_EDICTS - 1])
{ {
gi.error("pushed_p > &pushed[MAX_EDICTS - 1], memory corrupted"); gi.error("pushed_p > &pushed[MAX_EDICTS - 1], memory corrupted");
} }
@ -838,7 +872,11 @@ SV_Physics_Pusher(edict_t *ent)
/* the move succeeded, so call all think functions */ /* the move succeeded, so call all think functions */
for (part = ent; part; part = part->teamchain) for (part = ent; part; part = part->teamchain)
{ {
SV_RunThink(part); /* prevent entities that are on trains that have gone away from thinking! */
if (part->inuse)
{
SV_RunThink(part);
}
} }
} }
} }
@ -938,7 +976,7 @@ SV_Physics_Toss(edict_t *ent)
} }
/* if onground, return without moving */ /* if onground, return without moving */
if (ent->groundentity) if (ent->groundentity && (ent->gravity > 0.0))
{ {
return; return;
} }
@ -949,7 +987,8 @@ SV_Physics_Toss(edict_t *ent)
/* add gravity */ /* add gravity */
if ((ent->movetype != MOVETYPE_FLY) && if ((ent->movetype != MOVETYPE_FLY) &&
(ent->movetype != MOVETYPE_FLYMISSILE)) (ent->movetype != MOVETYPE_FLYMISSILE)
&& (ent->movetype != MOVETYPE_WALLBOUNCE))
{ {
SV_AddGravity(ent); SV_AddGravity(ent);
} }
@ -968,7 +1007,11 @@ SV_Physics_Toss(edict_t *ent)
if (trace.fraction < 1) if (trace.fraction < 1)
{ {
if (ent->movetype == MOVETYPE_BOUNCE) if (ent->movetype == MOVETYPE_WALLBOUNCE)
{
backoff = 2.0;
}
else if (ent->movetype == MOVETYPE_BOUNCE)
{ {
backoff = 1.5; backoff = 1.5;
} }
@ -979,8 +1022,14 @@ SV_Physics_Toss(edict_t *ent)
ClipVelocity(ent->velocity, trace.plane.normal, ent->velocity, backoff); ClipVelocity(ent->velocity, trace.plane.normal, ent->velocity, backoff);
if (ent->movetype == MOVETYPE_WALLBOUNCE)
{
vectoangles(ent->velocity, ent->s.angles);
}
/* stop if on ground */ /* stop if on ground */
if (trace.plane.normal[2] > 0.7) if ((trace.plane.normal[2] > 0.7) &&
(ent->movetype != MOVETYPE_WALLBOUNCE))
{ {
if ((ent->velocity[2] < 60) || (ent->movetype != MOVETYPE_BOUNCE)) if ((ent->velocity[2] < 60) || (ent->movetype != MOVETYPE_BOUNCE))
{ {
@ -1054,7 +1103,7 @@ SV_AddRotationalFriction(edict_t *ent)
} }
VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
adjustment = FRAMETIME * STOPSPEED * FRICTION; adjustment = FRAMETIME * sv_stopspeed->value * FRICTION;
for (n = 0; n < 3; n++) for (n = 0; n < 3; n++)
{ {
@ -1122,8 +1171,8 @@ SV_Physics_Step(edict_t *ent)
} }
/* add gravity except: /* add gravity except:
flying monsters - flying monsters
swimming monsters who are in the water */ - swimming monsters who are in the water */
if (!wasonground) if (!wasonground)
{ {
if (!(ent->flags & FL_FLY)) if (!(ent->flags & FL_FLY))
@ -1147,7 +1196,7 @@ SV_Physics_Step(edict_t *ent)
if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0))
{ {
speed = fabs(ent->velocity[2]); speed = fabs(ent->velocity[2]);
control = speed < STOPSPEED ? STOPSPEED : speed; control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed;
friction = FRICTION / 3; friction = FRICTION / 3;
newspeed = speed - (FRAMETIME * control * friction); newspeed = speed - (FRAMETIME * control * friction);
@ -1164,7 +1213,7 @@ SV_Physics_Step(edict_t *ent)
if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0))
{ {
speed = fabs(ent->velocity[2]); speed = fabs(ent->velocity[2]);
control = speed < STOPSPEED ? STOPSPEED : speed; control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed;
newspeed = speed - (FRAMETIME * control * WATERFRICTION * ent->waterlevel); newspeed = speed - (FRAMETIME * control * WATERFRICTION * ent->waterlevel);
if (newspeed < 0) if (newspeed < 0)
@ -1191,7 +1240,7 @@ SV_Physics_Step(edict_t *ent)
{ {
friction = FRICTION; friction = FRICTION;
control = speed < STOPSPEED ? STOPSPEED : speed; control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed;
newspeed = speed - FRAMETIME * control * friction; newspeed = speed - FRAMETIME * control * friction;
if (newspeed < 0) if (newspeed < 0)
@ -1235,6 +1284,7 @@ SV_Physics_Step(edict_t *ent)
} }
gi.linkentity(ent); gi.linkentity(ent);
ent->gravity = 1.0;
G_TouchTriggers(ent); G_TouchTriggers(ent);
if (!ent->inuse) if (!ent->inuse)
@ -1254,6 +1304,11 @@ SV_Physics_Step(edict_t *ent)
} }
} }
if (!ent->inuse) /* g_touchtrigger free problem */
{
return;
}
/* regular thinking */ /* regular thinking */
SV_RunThink(ent); SV_RunThink(ent);
} }
@ -1263,11 +1318,25 @@ SV_Physics_Step(edict_t *ent)
void void
G_RunEntity(edict_t *ent) G_RunEntity(edict_t *ent)
{ {
trace_t trace;
vec3_t previous_origin;
qboolean saved_origin;
if (!ent) if (!ent)
{ {
return; return;
} }
if (ent->movetype == MOVETYPE_STEP)
{
VectorCopy(ent->s.origin, previous_origin);
saved_origin = true;
}
else
{
saved_origin = false;
}
if (ent->prethink) if (ent->prethink)
{ {
ent->prethink(ent); ent->prethink(ent);
@ -1292,9 +1361,166 @@ G_RunEntity(edict_t *ent)
case MOVETYPE_BOUNCE: case MOVETYPE_BOUNCE:
case MOVETYPE_FLY: case MOVETYPE_FLY:
case MOVETYPE_FLYMISSILE: case MOVETYPE_FLYMISSILE:
case MOVETYPE_WALLBOUNCE:
SV_Physics_Toss(ent); SV_Physics_Toss(ent);
break; break;
case MOVETYPE_NEWTOSS:
SV_Physics_NewToss(ent);
break;
default: default:
gi.error("SV_Physics: bad movetype %i", (int)ent->movetype); gi.error("SV_Physics: bad movetype %i", (int)ent->movetype);
} }
/* if we moved, check and fix origin if needed */
/* also check inuse since entities are very often freed while thinking */
if (saved_origin && ent->inuse && !VectorCompare(ent->s.origin, previous_origin))
{
trace = gi.trace(ent->s.origin, ent->mins, ent->maxs,
previous_origin, ent, MASK_MONSTERSOLID);
if (trace.allsolid || trace.startsolid)
{
VectorCopy(previous_origin, ent->s.origin);
}
}
}
/*
* Toss, bounce, and fly movement. When on ground and
* no velocity, do nothing. With velocity, slide.
*/
void
SV_Physics_NewToss(edict_t *ent)
{
trace_t trace;
vec3_t move;
edict_t *slave;
qboolean wasinwater;
qboolean isinwater;
float speed, newspeed;
vec3_t old_origin;
if (!ent)
{
return;
}
/* regular thinking */
SV_RunThink(ent);
/* if not a team captain, so movement will be handled elsewhere */
if (ent->flags & FL_TEAMSLAVE)
{
return;
}
/* find out what we're sitting on. */
VectorCopy(ent->s.origin, move);
move[2] -= 0.25;
trace = gi.trace(ent->s.origin, ent->mins, ent->maxs,
move, ent, ent->clipmask);
if (ent->groundentity && ent->groundentity->inuse)
{
ent->groundentity = trace.ent;
}
else
{
ent->groundentity = NULL;
}
/* if we're sitting on something flat and have no velocity of our own, return. */
if (ent->groundentity && (trace.plane.normal[2] == 1.0) &&
!ent->velocity[0] && !ent->velocity[1] && !ent->velocity[2])
{
return;
}
/* store the old origin */
VectorCopy(ent->s.origin, old_origin);
SV_CheckVelocity(ent);
/* add gravity */
SV_AddGravity(ent);
if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
{
SV_AddRotationalFriction(ent);
}
/* add friction */
speed = VectorLength(ent->velocity);
if (ent->waterlevel) /* friction for water movement */
{
newspeed = speed - (WATERFRICTION * 6 * ent->waterlevel);
if (newspeed < 0)
{
newspeed = 0;
}
newspeed /= speed;
VectorScale(ent->velocity, newspeed, ent->velocity);
}
else if (!ent->groundentity) /* friction for air movement */
{
newspeed = speed - ((FRICTION));
if (newspeed < 0)
{
newspeed = 0;
}
newspeed /= speed;
VectorScale(ent->velocity, newspeed, ent->velocity);
}
else /* use ground friction */
{
newspeed = speed - (FRICTION * 6);
if (newspeed < 0)
{
newspeed = 0;
}
newspeed /= speed;
VectorScale(ent->velocity, newspeed, ent->velocity);
}
SV_FlyMove(ent, FRAMETIME, ent->clipmask);
gi.linkentity(ent);
G_TouchTriggers(ent);
/* check for water transition */
wasinwater = (ent->watertype & MASK_WATER);
ent->watertype = gi.pointcontents(ent->s.origin);
isinwater = ent->watertype & MASK_WATER;
if (isinwater)
{
ent->waterlevel = 1;
}
else
{
ent->waterlevel = 0;
}
if (!wasinwater && isinwater)
{
gi.positioned_sound(old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
}
else if (wasinwater && !isinwater)
{
gi.positioned_sound(ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
}
/* move teamslaves */
for (slave = ent->teamchain; slave; slave = slave->teamchain)
{
VectorCopy(ent->s.origin, slave->s.origin);
gi.linkentity(slave);
}
} }

View file

@ -155,6 +155,23 @@ void SP_turret_breach(edict_t *self);
void SP_turret_base(edict_t *self); void SP_turret_base(edict_t *self);
void SP_turret_driver(edict_t *self); void SP_turret_driver(edict_t *self);
void SP_monster_soldier_hypergun(edict_t *self);
void SP_monster_soldier_lasergun(edict_t *self);
void SP_monster_soldier_ripper(edict_t *self);
void SP_monster_fixbot(edict_t *self);
void SP_monster_gekk(edict_t *self);
void SP_monster_chick_heat(edict_t *self);
void SP_monster_gladb(edict_t *self);
void SP_monster_boss5(edict_t *self);
void SP_rotating_light(edict_t *self);
void SP_object_repair(edict_t *self);
void SP_misc_crashviper(edict_t *ent);
void SP_misc_viper_missile(edict_t *self);
void SP_misc_amb4(edict_t *ent);
void SP_target_mal_laser(edict_t *ent);
void SP_misc_transport(edict_t *ent);
void SP_misc_nuke(edict_t *ent);
void SP_func_plat2(edict_t *ent); void SP_func_plat2(edict_t *ent);
void SP_func_door_secret2(edict_t *ent); void SP_func_door_secret2(edict_t *ent);
void SP_func_force_wall(edict_t *ent); void SP_func_force_wall(edict_t *ent);
@ -222,6 +239,9 @@ static spawn_t spawns[] = {
{"func_explosive", SP_func_explosive}, {"func_explosive", SP_func_explosive},
{"func_killbox", SP_func_killbox}, {"func_killbox", SP_func_killbox},
{"func_object_repair", SP_object_repair},
{"rotating_light", SP_rotating_light},
{"trigger_always", SP_trigger_always}, {"trigger_always", SP_trigger_always},
{"trigger_once", SP_trigger_once}, {"trigger_once", SP_trigger_once},
{"trigger_multiple", SP_trigger_multiple}, {"trigger_multiple", SP_trigger_multiple},
@ -251,6 +271,7 @@ static spawn_t spawns[] = {
{"target_earthquake", SP_target_earthquake}, {"target_earthquake", SP_target_earthquake},
{"target_character", SP_target_character}, {"target_character", SP_target_character},
{"target_string", SP_target_string}, {"target_string", SP_target_string},
{"target_mal_laser", SP_target_mal_laser},
{"worldspawn", SP_worldspawn}, {"worldspawn", SP_worldspawn},
{"viewthing", SP_viewthing}, {"viewthing", SP_viewthing},
@ -282,6 +303,11 @@ static spawn_t spawns[] = {
{"misc_eastertank", SP_misc_eastertank}, {"misc_eastertank", SP_misc_eastertank},
{"misc_easterchick", SP_misc_easterchick}, {"misc_easterchick", SP_misc_easterchick},
{"misc_easterchick2", SP_misc_easterchick2}, {"misc_easterchick2", SP_misc_easterchick2},
{"misc_crashviper", SP_misc_crashviper},
{"misc_viper_missile", SP_misc_viper_missile},
{"misc_amb4", SP_misc_amb4},
{"misc_transport", SP_misc_transport},
{"misc_nuke", SP_misc_nuke},
{"monster_berserk", SP_monster_berserk}, {"monster_berserk", SP_monster_berserk},
{"monster_gladiator", SP_monster_gladiator}, {"monster_gladiator", SP_monster_gladiator},
@ -306,8 +332,15 @@ static spawn_t spawns[] = {
{"monster_boss3_stand", SP_monster_boss3_stand}, {"monster_boss3_stand", SP_monster_boss3_stand},
{"monster_makron", SP_monster_makron}, {"monster_makron", SP_monster_makron},
{"monster_jorg", SP_monster_jorg}, {"monster_jorg", SP_monster_jorg},
{"monster_commander_body", SP_monster_commander_body}, {"monster_commander_body", SP_monster_commander_body},
{"monster_soldier_hypergun", SP_monster_soldier_hypergun},
{"monster_soldier_lasergun", SP_monster_soldier_lasergun},
{"monster_soldier_ripper", SP_monster_soldier_ripper},
{"monster_fixbot", SP_monster_fixbot},
{"monster_gekk", SP_monster_gekk},
{"monster_chick_heat", SP_monster_chick_heat},
{"monster_gladb", SP_monster_gladb},
{"monster_boss5", SP_monster_boss5},
{"turret_breach", SP_turret_breach}, {"turret_breach", SP_turret_breach},
{"turret_base", SP_turret_base}, {"turret_base", SP_turret_base},
@ -316,6 +349,35 @@ static spawn_t spawns[] = {
{NULL, NULL} {NULL, NULL}
}; };
qboolean Spawn_CheckCoop_MapHacks (edict_t *ent)
{
if(!coop->value || !ent)
{
return false;
}
if(!Q_stricmp(level.mapname, "xsewer1"))
{
if(ent->classname && !Q_stricmp(ent->classname, "trigger_relay") && ent->target && !Q_stricmp(ent->target, "t3") && ent->targetname && !Q_stricmp(ent->targetname, "t2"))
{
return true;
}
if(ent->classname && !Q_stricmp(ent->classname, "func_button") && ent->target && !Q_stricmp(ent->target, "t16") && ent->model && !Q_stricmp(ent->model, "*71"))
{
ent->message = "Overflow valve maintenance\nhatch A opened.";
return false;
}
if(ent->classname && !Q_stricmp(ent->classname, "trigger_once") && ent->model && !Q_stricmp(ent->model, "*3"))
{
ent->message = "Overflow valve maintenance\nhatch B opened.";
return false;
}
}
return false;
}
/* /*
* Finds the spawn function for * Finds the spawn function for
* the entity and calls it * the entity and calls it
@ -479,7 +541,7 @@ ED_ParseField(const char *key, const char *value, edict_t *ent)
/* /*
* Parses an edict out of the given string, * Parses an edict out of the given string,
* returning the new position ed should be * returning the new position. ed should be
* a properly initialized empty edict. * a properly initialized empty edict.
*/ */
char * char *
@ -818,13 +880,14 @@ SpawnEntities(const char *mapname, char *entities, const char *spawnpoint)
} }
else else
{ {
if (((skill->value == SKILL_EASY) && if (Spawn_CheckCoop_MapHacks(ent) || (
((skill->value == SKILL_EASY) &&
(ent->spawnflags & SPAWNFLAG_NOT_EASY)) || (ent->spawnflags & SPAWNFLAG_NOT_EASY)) ||
((skill->value == SKILL_MEDIUM) && ((skill->value == SKILL_MEDIUM) &&
(ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) ||
(((skill->value == SKILL_HARD) || (((skill->value == SKILL_HARD) ||
(skill->value == SKILL_HARDPLUS)) && (skill->value == SKILL_HARDPLUS)) &&
(ent->spawnflags & SPAWNFLAG_NOT_HARD)) (ent->spawnflags & SPAWNFLAG_NOT_HARD)))
) )
{ {
G_FreeEdict(ent); G_FreeEdict(ent);
@ -1137,6 +1200,9 @@ SP_worldspawn(edict_t *ent)
gi.modelindex("#w_plasma.md2"); gi.modelindex("#w_plasma.md2");
gi.modelindex("#w_plauncher.md2"); gi.modelindex("#w_plauncher.md2");
gi.modelindex("#w_chainfist.md2"); gi.modelindex("#w_chainfist.md2");
gi.modelindex("#w_phalanx.md2");
gi.modelindex("#w_ripper.md2");
} }
/* ------------------- */ /* ------------------- */

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 1997-2001 Id Software, Inc. * Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -130,6 +131,7 @@ StringToFilter(char *s, ipfilter_t *f)
s++; s++;
} }
/* PVS NOTE: maybe use memcpy here instead? */
f->mask = *(unsigned *)m; f->mask = *(unsigned *)m;
f->compare = *(unsigned *)b; f->compare = *(unsigned *)b;
@ -170,6 +172,7 @@ SV_FilterPacket(char *from)
i++, p++; i++, p++;
} }
/* PVS NOTE: maybe use memcpy instead? */
in = *(unsigned *)m; in = *(unsigned *)m;
for (i = 0; i < numipfilters; i++) for (i = 0; i < numipfilters; i++)
@ -265,6 +268,7 @@ SVCmd_ListIP_f(void)
for (i = 0; i < numipfilters; i++) for (i = 0; i < numipfilters; i++)
{ {
/* PVS NOTE: maybe use memcpy instead? */
*(unsigned *)b = ipfilters[i].compare; *(unsigned *)b = ipfilters[i].compare;
gi.cprintf(NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n", b[0], gi.cprintf(NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n", b[0],
b[1], b[2], b[3]); b[1], b[2], b[3]);
@ -305,6 +309,7 @@ SVCmd_WriteIP_f(void)
for (i = 0; i < numipfilters; i++) for (i = 0; i < numipfilters; i++)
{ {
/* PVS NOTE: maybe use memcpy instead? */
*(unsigned *)b = ipfilters[i].compare; *(unsigned *)b = ipfilters[i].compare;
fprintf(f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]); fprintf(f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]);
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 1997-2001 Id Software, Inc. * Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -415,7 +416,7 @@ SP_target_explosion(edict_t *ent)
void void
use_target_changelevel(edict_t *self, edict_t *other, edict_t *activator) use_target_changelevel(edict_t *self, edict_t *other, edict_t *activator)
{ {
if (!self || !other) if (!self || !other || !activator)
{ {
return; return;
} }
@ -479,7 +480,7 @@ SP_target_changelevel(edict_t *ent)
/* Mapquirk for secret exists in fact1 and fact3 */ /* Mapquirk for secret exists in fact1 and fact3 */
if ((Q_stricmp(level.mapname, "fact1") == 0) && if ((Q_stricmp(level.mapname, "fact1") == 0) &&
(Q_stricmp(ent->map, "fact3") == 0)) (Q_stricmp(ent->map, "fact3") == 0))
{ {
ent->map = "fact3$secret1"; ent->map = "fact3$secret1";
} }
@ -1002,6 +1003,155 @@ SP_target_laser(edict_t *self)
self->nextthink = level.time + 1; self->nextthink = level.time + 1;
} }
/* QUAKED target_mal_laser (1 0 0) (-4 -4 -4) (4 4 4) START_ON RED GREEN BLUE YELLOW ORANGE FAT
* Mal's laser
*/
void
target_mal_laser_on(edict_t *self)
{
if (!self)
{
return;
}
if (!self->activator)
{
self->activator = self;
}
self->spawnflags |= 0x80000001;
self->svflags &= ~SVF_NOCLIENT;
self->nextthink = level.time + self->wait + self->delay;
}
void
target_mal_laser_off(edict_t *self)
{
if (!self)
{
return;
}
self->spawnflags &= ~1;
self->svflags |= SVF_NOCLIENT;
self->nextthink = 0;
}
void
target_mal_laser_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
{
if (!self || !activator)
{
return;
}
self->activator = activator;
if (self->spawnflags & 1)
{
target_mal_laser_off(self);
}
else
{
target_mal_laser_on(self);
}
}
void
mal_laser_think(edict_t *self)
{
if (!self)
{
return;
}
target_laser_think(self);
self->nextthink = level.time + self->wait + 0.1;
self->spawnflags |= 0x80000000;
}
void
SP_target_mal_laser(edict_t *self)
{
if (!self)
{
return;
}
self->movetype = MOVETYPE_NONE;
self->solid = SOLID_NOT;
self->s.renderfx |= RF_BEAM | RF_TRANSLUCENT;
self->s.modelindex = 1; /* must be non-zero */
/* set the beam diameter */
if (self->spawnflags & 64)
{
self->s.frame = 16;
}
else
{
self->s.frame = 4;
}
/* set the color */
if (self->spawnflags & 2)
{
self->s.skinnum = 0xf2f2f0f0;
}
else if (self->spawnflags & 4)
{
self->s.skinnum = 0xd0d1d2d3;
}
else if (self->spawnflags & 8)
{
self->s.skinnum = 0xf3f3f1f1;
}
else if (self->spawnflags & 16)
{
self->s.skinnum = 0xdcdddedf;
}
else if (self->spawnflags & 32)
{
self->s.skinnum = 0xe0e1e2e3;
}
G_SetMovedir(self->s.angles, self->movedir);
if (!self->delay)
{
self->delay = 0.1;
}
if (!self->wait)
{
self->wait = 0.1;
}
if (!self->dmg)
{
self->dmg = 5;
}
VectorSet(self->mins, -8, -8, -8);
VectorSet(self->maxs, 8, 8, 8);
self->nextthink = level.time + self->delay;
self->think = mal_laser_think;
self->use = target_mal_laser_use;
gi.linkentity(self);
if (self->spawnflags & 1)
{
target_mal_laser_on(self);
}
else
{
target_mal_laser_off(self);
}
}
/* ========================================================== */ /* ========================================================== */
/* /*
@ -1154,8 +1304,13 @@ target_earthquake_think(edict_t *self)
if (self->last_move_time < level.time) if (self->last_move_time < level.time)
{ {
gi.positioned_sound(self->s.origin, self, CHAN_AUTO, gi.positioned_sound(self->s.origin,
self->noise_index, 1.0, ATTN_NONE, 0); self,
CHAN_AUTO,
self->noise_index,
1.0,
ATTN_NONE,
0);
self->last_move_time = level.time + 0.5; self->last_move_time = level.time + 0.5;
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 1997-2001 Id Software, Inc. * Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -28,6 +29,8 @@
#define PUSH_ONCE 1 #define PUSH_ONCE 1
void trigger_push_active(edict_t *self);
static int windsound; static int windsound;
void void
@ -184,7 +187,6 @@ trigger_enable(edict_t *self, edict_t *other /* unused */,
return; return;
} }
self->solid = SOLID_TRIGGER; self->solid = SOLID_TRIGGER;
self->use = Use_Multi; self->use = Use_Multi;
gi.linkentity(self); gi.linkentity(self);
@ -208,7 +210,7 @@ SP_trigger_multiple(edict_t *ent)
} }
else if (ent->sounds == 3) else if (ent->sounds == 3)
{ {
ent->noise_index = gi.soundindex ("misc/trigger1.wav"); ent->noise_index = gi.soundindex("misc/trigger1.wav");
} }
if (!ent->wait) if (!ent->wait)
@ -591,6 +593,77 @@ trigger_push_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
* *
* "speed" defaults to 1000 * "speed" defaults to 1000
*/ */
void
trigger_effect(edict_t *self)
{
vec3_t origin;
vec3_t size;
int i;
if (!self)
{
return;
}
VectorScale(self->size, 0.5, size);
VectorAdd(self->absmin, size, origin);
for (i = 0; i < 10; i++)
{
origin[2] += (self->speed * 0.01) * (i + random());
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_TUNNEL_SPARKS);
gi.WriteByte(1);
gi.WritePosition(origin);
gi.WriteDir(vec3_origin);
gi.WriteByte(0x74 + (rand() & 7));
gi.multicast(self->s.origin, MULTICAST_PVS);
}
}
void
trigger_push_inactive(edict_t *self)
{
if (!self)
{
return;
}
if (self->delay > level.time)
{
self->nextthink = level.time + 0.1;
}
else
{
self->touch = trigger_push_touch;
self->think = trigger_push_active;
self->nextthink = level.time + 0.1;
self->delay = self->nextthink + self->wait;
}
}
void
trigger_push_active(edict_t *self)
{
if (!self)
{
return;
}
if (self->delay > level.time)
{
self->nextthink = level.time + 0.1;
trigger_effect(self);
}
else
{
self->touch = NULL;
self->think = trigger_push_inactive;
self->nextthink = level.time + 0.1;
self->delay = self->nextthink + self->wait;
}
}
void void
SP_trigger_push(edict_t *self) SP_trigger_push(edict_t *self)
{ {
@ -603,6 +676,18 @@ SP_trigger_push(edict_t *self)
windsound = gi.soundindex("misc/windfly.wav"); windsound = gi.soundindex("misc/windfly.wav");
self->touch = trigger_push_touch; self->touch = trigger_push_touch;
if (self->spawnflags & 2)
{
if (!self->wait)
{
self->wait = 10;
}
self->think = trigger_push_active;
self->nextthink = level.time + 0.1;
self->delay = self->nextthink + self->wait;
}
if (!self->speed) if (!self->speed)
{ {
self->speed = 1000; self->speed = 1000;
@ -623,9 +708,10 @@ SP_trigger_push(edict_t *self)
* *
* "dmg" default 5 (whole numbers only) * "dmg" default 5 (whole numbers only)
*/ */
void void
hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
csurface_t *surf /* unused */) csurface_t *surf /* unused */)
{ {
int dflags; int dflags;
@ -634,7 +720,6 @@ hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
return; return;
} }
if (!other->takedamage) if (!other->takedamage)
{ {
return; return;
@ -755,7 +840,6 @@ SP_trigger_hurt(edict_t *self)
* the value of "gravity". 1.0 is standard * the value of "gravity". 1.0 is standard
* gravity for the level. * gravity for the level.
*/ */
void void
trigger_gravity_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, trigger_gravity_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
csurface_t *surf /* unused */) csurface_t *surf /* unused */)
@ -796,7 +880,6 @@ SP_trigger_gravity(edict_t *self)
* "speed" default to 200, the speed thrown forward * "speed" default to 200, the speed thrown forward
* "height" default to 200, the speed thrown upwards * "height" default to 200, the speed thrown upwards
*/ */
void void
trigger_monsterjump_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, trigger_monsterjump_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
csurface_t *surf /* unused */) csurface_t *surf /* unused */)

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 1997-2001 Id Software, Inc. * Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -260,7 +261,7 @@ turret_breach_think(edict_t *self)
ent->avelocity[1] = self->avelocity[1]; ent->avelocity[1] = self->avelocity[1];
} }
/* if we have adriver, adjust his velocities */ /* if we have a driver, adjust his velocities */
if (self->owner) if (self->owner)
{ {
float angle; float angle;
@ -387,7 +388,6 @@ SP_turret_breach(edict_t *self)
* This portion of the turret changes yaw only. * This portion of the turret changes yaw only.
* MUST be teamed with a turret_breach. * MUST be teamed with a turret_breach.
*/ */
void void
SP_turret_base(edict_t *self) SP_turret_base(edict_t *self)
{ {

View file

@ -1526,7 +1526,16 @@ plasma_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
ent->dmg_radius, MOD_PHALANX); ent->dmg_radius, MOD_PHALANX);
gi.WriteByte(svc_temp_entity); gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_PLASMA_EXPLOSION);
if (ent->waterlevel)
{
gi.WriteByte(TE_ROCKET_EXPLOSION_WATER);
}
else
{
gi.WriteByte(TE_PLASMA_EXPLOSION);
}
gi.WritePosition(origin); gi.WritePosition(origin);
gi.multicast(ent->s.origin, MULTICAST_PVS); gi.multicast(ent->s.origin, MULTICAST_PVS);

View file

@ -28,6 +28,8 @@
#include "../header/local.h" #include "../header/local.h"
#include "../monster/misc/player.h" #include "../monster/misc/player.h"
edict_t *pm_passent;
void ClientUserinfoChanged(edict_t *ent, char *userinfo); void ClientUserinfoChanged(edict_t *ent, char *userinfo);
void SP_misc_teleporter_dest(edict_t *ent); void SP_misc_teleporter_dest(edict_t *ent);
void Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); void Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
@ -295,6 +297,7 @@ SP_CreateUnnamedSpawn(edict_t *self)
/* /*
* QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) * QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
*
* The normal starting point for a level. * The normal starting point for a level.
*/ */
void void
@ -305,7 +308,7 @@ SP_info_player_start(edict_t *self)
return; return;
} }
/* Call function to hack unnamed spawn points */ /* Call function to hack unnamed spawn points */
self->think = SP_CreateUnnamedSpawn; self->think = SP_CreateUnnamedSpawn;
self->nextthink = level.time + FRAMETIME; self->nextthink = level.time + FRAMETIME;
@ -327,6 +330,7 @@ SP_info_player_start(edict_t *self)
/* /*
* QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) * QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32)
*
* potential spawning position for deathmatch games * potential spawning position for deathmatch games
*/ */
void void
@ -389,8 +393,30 @@ SP_info_player_coop(edict_t *self)
} }
} }
/*
* QUAKED info_player_coop_lava (1 0 1) (-16 -16 -24) (16 16 32)
*
* potential spawning position for coop games on rmine2 where lava level
* needs to be checked
*/
void
SP_info_player_coop_lava(edict_t *self)
{
if (!self)
{
return;
}
if (!coop->value)
{
G_FreeEdict(self);
return;
}
}
/* /*
* QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) * QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
*
* The deathmatch intermission point will be at one of these * The deathmatch intermission point will be at one of these
* Use 'angles' instead of 'angle', so you can set pitch or * Use 'angles' instead of 'angle', so you can set pitch or
* roll as well as yaw. 'pitch yaw roll' * roll as well as yaw. 'pitch yaw roll'
@ -487,7 +513,7 @@ ClientObituary(edict_t *self, edict_t *inflictor /* unused */,
char *message2; char *message2;
qboolean ff; qboolean ff;
if (!self || !inflictor) if (!self || !attacker || !inflictor)
{ {
return; return;
} }
@ -546,6 +572,8 @@ ClientObituary(edict_t *self, edict_t *inflictor /* unused */,
case MOD_BRAINTENTACLE: case MOD_BRAINTENTACLE:
message = "that's gotta hurt"; message = "that's gotta hurt";
break; break;
default:
break;
} }
if (attacker == self) if (attacker == self)
@ -591,6 +619,21 @@ ClientObituary(edict_t *self, edict_t *inflictor /* unused */,
case MOD_BFG_BLAST: case MOD_BFG_BLAST:
message = "should have used a smaller gun"; message = "should have used a smaller gun";
break; break;
case MOD_DOPPLE_EXPLODE:
if (IsNeutral(self))
{
message = "got caught in it's own trap";
}
else if (IsFemale(self))
{
message = "got caught in her own trap";
}
else
{
message = "got caught in his own trap";
}
case MOD_TRAP: case MOD_TRAP:
message = "sucked into his own trap"; message = "sucked into his own trap";
break; break;
@ -712,6 +755,63 @@ ClientObituary(edict_t *self, edict_t *inflictor /* unused */,
case MOD_TRAP: case MOD_TRAP:
message = "caught in trap by"; message = "caught in trap by";
break; break;
case MOD_CHAINFIST:
message = "was shredded by";
message2 = "'s ripsaw";
break;
case MOD_DISINTEGRATOR:
message = "lost his grip courtesy of";
message2 = "'s disintegrator";
break;
case MOD_ETF_RIFLE:
message = "was perforated by";
break;
case MOD_HEATBEAM:
message = "was scorched by";
message2 = "'s plasma beam";
break;
case MOD_TESLA:
message = "was enlightened by";
message2 = "'s tesla mine";
break;
case MOD_PROX:
message = "got too close to";
message2 = "'s proximity mine";
break;
case MOD_NUKE:
message = "was nuked by";
message2 = "'s antimatter bomb";
break;
case MOD_VENGEANCE_SPHERE:
message = "was purged by";
message2 = "'s vengeance sphere";
break;
case MOD_DEFENDER_SPHERE:
message = "had a blast with";
message2 = "'s defender sphere";
break;
case MOD_HUNTER_SPHERE:
message = "was killed like a dog by";
message2 = "'s hunter sphere";
break;
case MOD_TRACKER:
message = "was annihilated by";
message2 = "'s disruptor";
break;
case MOD_DOPPLE_EXPLODE:
message = "was blown up by";
message2 = "'s doppleganger";
break;
case MOD_DOPPLE_VENGEANCE:
message = "was purged by";
message2 = "'s doppleganger";
break;
case MOD_DOPPLE_HUNTER:
message = "was hunted down by";
message2 = "'s doppleganger";
break;
default:
break;
} }
if (message) if (message)
@ -721,6 +821,23 @@ ClientObituary(edict_t *self, edict_t *inflictor /* unused */,
message, attacker->client->pers.netname, message, attacker->client->pers.netname,
message2); message2);
if (gamerules && gamerules->value)
{
if (DMGame.Score)
{
if (ff)
{
DMGame.Score(attacker, self, -1);
}
else
{
DMGame.Score(attacker, self, 1);
}
}
return;
}
if (deathmatch->value) if (deathmatch->value)
{ {
if (ff) if (ff)
@ -742,7 +859,19 @@ ClientObituary(edict_t *self, edict_t *inflictor /* unused */,
if (deathmatch->value) if (deathmatch->value)
{ {
self->client->resp.score--; if (gamerules && gamerules->value)
{
if (DMGame.Score)
{
DMGame.Score(self, self, -1);
}
return;
}
else
{
self->client->resp.score--;
}
} }
} }
@ -870,7 +999,15 @@ LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker)
if (dir[0]) if (dir[0])
{ {
self->client->killer_yaw = 180 / M_PI *atan2(dir[1], dir[0]); self->client->killer_yaw = 180 / M_PI * atan2(dir[1], dir[0]);
}
else if (dir[1] > 0)
{
self->client->killer_yaw = 90;
}
else if (dir[1] < 0)
{
self->client->killer_yaw = 270;
} }
else else
{ {
@ -947,6 +1084,14 @@ player_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
} }
} }
if (gamerules && gamerules->value) /* if we're in a dm game, alert the game */
{
if (DMGame.PlayerDeath)
{
DMGame.PlayerDeath(self, inflictor, attacker);
}
}
/* remove powerups */ /* remove powerups */
self->client->quad_framenum = 0; self->client->quad_framenum = 0;
self->client->invincible_framenum = 0; self->client->invincible_framenum = 0;
@ -955,18 +1100,59 @@ player_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
self->flags &= ~FL_POWER_ARMOR; self->flags &= ~FL_POWER_ARMOR;
self->client->quadfire_framenum = 0; self->client->quadfire_framenum = 0;
self->client->double_framenum = 0;
if (self->client->owned_sphere)
{
edict_t *sphere;
sphere = self->client->owned_sphere;
sphere->die(sphere, self, self, 0, vec3_origin);
}
/* if we've been killed by the tracker, GIB! */
if ((meansOfDeath & ~MOD_FRIENDLY_FIRE) == MOD_TRACKER)
{
self->health = -100;
damage = 400;
}
/* make sure no trackers are still hurting us. */
if (self->client->tracker_pain_framenum)
{
RemoveAttackingPainDaemons(self);
}
/* if we got obliterated by the nuke, don't gib */
if ((self->health < -80) && (meansOfDeath == MOD_NUKE))
{
self->flags |= FL_NOGIB;
}
if (self->health < -40) if (self->health < -40)
{ {
/* gib (sound is played at end of server frame) */ /* don't toss gibs if we got vaped by the nuke */
self->sounds = gi.soundindex("misc/udeath.wav"); if (!(self->flags & FL_NOGIB))
for (n = 0; n < 4; n++)
{ {
ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", /* gib (sound is played at end of server frame) */
damage, GIB_ORGANIC); self->sounds = gi.soundindex("misc/udeath.wav");
/* more meaty gibs for your dollar! */
if ((deathmatch->value) && (self->health < -80))
{
for (n = 0; n < 4; n++)
{
ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
}
}
for (n = 0; n < 4; n++)
{
ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
}
} }
self->flags &= ~FL_NOGIB;
ThrowClientHead(self, damage); ThrowClientHead(self, damage);
self->takedamage = DAMAGE_NO; self->takedamage = DAMAGE_NO;
@ -1058,6 +1244,10 @@ InitClientPersistant(gclient_t *client)
client->pers.max_magslug = 50; client->pers.max_magslug = 50;
client->pers.max_trap = 5; client->pers.max_trap = 5;
client->pers.max_prox = 50;
client->pers.max_tesla = 50;
client->pers.max_flechettes = 200;
client->pers.max_rounds = 100;
client->pers.connected = true; client->pers.connected = true;
} }
@ -1143,7 +1333,7 @@ PlayersRangeFromSpot(edict_t *spot)
if (!spot) if (!spot)
{ {
return 0; return 0.0;
} }
bestplayerdistance = 9999999; bestplayerdistance = 9999999;
@ -1296,6 +1486,112 @@ SelectDeathmatchSpawnPoint(void)
} }
} }
edict_t *
SelectLavaCoopSpawnPoint(edict_t *ent)
{
int index;
edict_t *spot = NULL;
float lavatop;
edict_t *lava;
edict_t *pointWithLeastLava;
float lowest;
edict_t *spawnPoints[64];
vec3_t center;
int numPoints;
edict_t *highestlava;
if (!ent)
{
return NULL;
}
lavatop = -99999;
highestlava = NULL;
lava = NULL;
while (1)
{
lava = G_Find(lava, FOFS(classname), "func_door");
if (!lava)
{
break;
}
VectorAdd(lava->absmax, lava->absmin, center);
VectorScale(center, 0.5, center);
if (lava->spawnflags & 2 && (gi.pointcontents(center) & MASK_WATER))
{
if (lava->absmax[2] > lavatop)
{
lavatop = lava->absmax[2];
highestlava = lava;
}
}
}
/* if we didn't find ANY lava, then return NULL */
if (!highestlava)
{
return NULL;
}
/* find the top of the lava and include a small margin of error (plus bbox size) */
lavatop = highestlava->absmax[2] + 64;
/* find all the lava spawn points and store them in spawnPoints[] */
spot = NULL;
numPoints = 0;
while ((spot = (G_Find(spot, FOFS(classname), "info_player_coop_lava"))))
{
if (numPoints == 64)
{
break;
}
spawnPoints[numPoints++] = spot;
}
if (numPoints < 1)
{
return NULL;
}
/* walk up the sorted list and return the lowest, open, non-lava spawn point */
spot = NULL;
lowest = 999999;
pointWithLeastLava = NULL;
for (index = 0; index < numPoints; index++)
{
if (spawnPoints[index]->s.origin[2] < lavatop)
{
continue;
}
if (PlayersRangeFromSpot(spawnPoints[index]) > 32)
{
if (spawnPoints[index]->s.origin[2] < lowest)
{
/* save the last point */
pointWithLeastLava = spawnPoints[index];
lowest = spawnPoints[index]->s.origin[2];
}
}
}
/* well, we may telefrag someone, but oh well... */
if (pointWithLeastLava)
{
return pointWithLeastLava;
}
return NULL;
}
edict_t * edict_t *
SelectCoopSpawnPoint(edict_t *ent) SelectCoopSpawnPoint(edict_t *ent)
{ {
@ -1308,6 +1604,11 @@ SelectCoopSpawnPoint(edict_t *ent)
return NULL; return NULL;
} }
if (!Q_stricmp(level.mapname, "rmine2p") || !Q_stricmp(level.mapname, "rmine2"))
{
return SelectLavaCoopSpawnPoint(ent);
}
index = ent->client - game.clients; index = ent->client - game.clients;
/* player 0 starts in normal player spawn point */ /* player 0 starts in normal player spawn point */
@ -1718,11 +2019,17 @@ PutClientInServer(edict_t *ent)
return; return;
} }
/* find a spawn point do it before setting /* find a spawn point do it before setting
health back up, so farthest ranging health back up, so farthest ranging
doesn't count this client */ doesn't count this client */
SelectSpawnPoint(ent, spawn_origin, spawn_angles); if (gamerules && gamerules->value && DMGame.SelectSpawnPoint)
{
DMGame.SelectSpawnPoint(ent, spawn_origin, spawn_angles);
}
else
{
SelectSpawnPoint(ent, spawn_origin, spawn_angles);
}
index = ent - g_edicts - 1; index = ent - g_edicts - 1;
client = ent->client; client = ent->client;
@ -1823,7 +2130,15 @@ PutClientInServer(edict_t *ent)
} }
} }
client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); if (client->pers.weapon)
{
client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
}
else
{
client->ps.gunindex = 0;
}
/* clear entity state values */ /* clear entity state values */
ent->s.effects = 0; ent->s.effects = 0;
@ -1878,6 +2193,22 @@ PutClientInServer(edict_t *ent)
gi.linkentity(ent); gi.linkentity(ent);
/* my tribute to cash's level-specific hacks. I hope
* live up to his trailblazing cheese. */
if (Q_stricmp(level.mapname, "rboss") == 0)
{
/* if you get on to rboss in single player or coop, ensure
the player has the nuke key. (not in DM) */
if (!(deathmatch->value))
{
gitem_t *item;
item = FindItem("Antimatter Bomb");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] = 1;
}
}
/* force the current weapon up */ /* force the current weapon up */
client->newweapon = client->pers.weapon; client->newweapon = client->pers.weapon;
ChangeWeapon(ent); ChangeWeapon(ent);
@ -1897,9 +2228,13 @@ ClientBeginDeathmatch(edict_t *ent)
} }
G_InitEdict(ent); G_InitEdict(ent);
InitClientResp(ent->client); InitClientResp(ent->client);
if (gamerules && gamerules->value && DMGame.ClientBegin)
{
DMGame.ClientBegin(ent);
}
/* locate ent at a spawn point */ /* locate ent at a spawn point */
PutClientInServer(ent); PutClientInServer(ent);
@ -2192,6 +2527,30 @@ ClientDisconnect(edict_t *ent)
gi.bprintf(PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname); gi.bprintf(PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
/* make sure no trackers are still hurting us. */
if (ent->client->tracker_pain_framenum)
{
RemoveAttackingPainDaemons(ent);
}
if (ent->client->owned_sphere)
{
if (ent->client->owned_sphere->inuse)
{
G_FreeEdict(ent->client->owned_sphere);
}
ent->client->owned_sphere = NULL;
}
if (gamerules && gamerules->value)
{
if (DMGame.PlayerDisconnect)
{
DMGame.PlayerDisconnect(ent);
}
}
/* send effect */ /* send effect */
gi.WriteByte(svc_muzzleflash); gi.WriteByte(svc_muzzleflash);
gi.WriteShort(ent - g_edicts); gi.WriteShort(ent - g_edicts);
@ -2211,8 +2570,6 @@ ClientDisconnect(edict_t *ent)
/* ============================================================== */ /* ============================================================== */
static edict_t *pm_passent;
/* /*
* pmove doesn't need to know * pmove doesn't need to know
* about passent and contentmask * about passent and contentmask
@ -2329,7 +2686,7 @@ ClientThink(edict_t *ent, usercmd_t *ucmd)
client->ps.pmove.pm_type = PM_NORMAL; client->ps.pmove.pm_type = PM_NORMAL;
} }
client->ps.pmove.gravity = sv_gravity->value; client->ps.pmove.gravity = sv_gravity->value * ent->gravity;
pm.s = client->ps.pmove; pm.s = client->ps.pmove;
for (i = 0; i < 3; i++) for (i = 0; i < 3; i++)
@ -2379,7 +2736,15 @@ ClientThink(edict_t *ent, usercmd_t *ucmd)
PlayerNoise(ent, ent->s.origin, PNOISE_SELF); PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
} }
ent->viewheight = pm.viewheight; if (ent->flags & FL_SAM_RAIMI)
{
ent->viewheight = 8;
}
else
{
ent->viewheight = pm.viewheight;
}
ent->waterlevel = pm.waterlevel; ent->waterlevel = pm.waterlevel;
ent->watertype = pm.watertype; ent->watertype = pm.watertype;
ent->groundentity = pm.groundentity; ent->groundentity = pm.groundentity;
@ -2403,6 +2768,8 @@ ClientThink(edict_t *ent, usercmd_t *ucmd)
gi.linkentity(ent); gi.linkentity(ent);
ent->gravity = 1.0;
if (ent->movetype != MOVETYPE_NOCLIP) if (ent->movetype != MOVETYPE_NOCLIP)
{ {
G_TouchTriggers(ent); G_TouchTriggers(ent);
@ -2581,3 +2948,35 @@ ClientBeginServerFrame(edict_t *ent)
client->latched_buttons = 0; client->latched_buttons = 0;
} }
/*
* This is called to clean up the pain daemons that
* the disruptor attaches to clients to damage them.
*/
void
RemoveAttackingPainDaemons(edict_t *self)
{
edict_t *tracker;
if (!self)
{
return;
}
tracker = G_Find(NULL, FOFS(classname), "pain daemon");
while (tracker)
{
if (tracker->enemy == self)
{
G_FreeEdict(tracker);
}
tracker = G_Find(tracker, FOFS(classname), "pain daemon");
}
if (self->client)
{
self->client->tracker_pain_framenum = 0;
}
}

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

File diff suppressed because it is too large Load diff

View file

@ -1,250 +0,0 @@
/*
* Copyright (c) ZeniMax Media Inc.
* Licensed under the GNU General Public License 2.0.
*/
/*
* =======================================================================
*
* Chase cam. Only used in multiplayer mode.
*
* =======================================================================
*/
#include "header/local.h"
void
UpdateChaseCam(edict_t *ent)
{
vec3_t o, ownerv, goal;
edict_t *targ;
vec3_t forward, right;
trace_t trace;
int i;
vec3_t angles;
if (!ent)
{
return;
}
/* is our chase target gone? */
if (!ent->client->chase_target->inuse ||
ent->client->chase_target->client->resp.spectator)
{
edict_t *old = ent->client->chase_target;
ChaseNext(ent);
if (ent->client->chase_target == old)
{
ent->client->chase_target = NULL;
ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
return;
}
}
targ = ent->client->chase_target;
VectorCopy(targ->s.origin, ownerv);
ownerv[2] += targ->viewheight;
VectorCopy(targ->client->v_angle, angles);
if (angles[PITCH] > 56)
{
angles[PITCH] = 56;
}
AngleVectors(angles, forward, right, NULL);
VectorNormalize(forward);
VectorMA(ownerv, -30, forward, o);
if (o[2] < targ->s.origin[2] + 20)
{
o[2] = targ->s.origin[2] + 20;
}
/* jump animation lifts */
if (!targ->groundentity)
{
o[2] += 16;
}
trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
VectorCopy(trace.endpos, goal);
VectorMA(goal, 2, forward, goal);
/* pad for floors and ceilings */
VectorCopy(goal, o);
o[2] += 6;
trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
if (trace.fraction < 1)
{
VectorCopy(trace.endpos, goal);
goal[2] -= 6;
}
VectorCopy(goal, o);
o[2] -= 6;
trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
if (trace.fraction < 1)
{
VectorCopy(trace.endpos, goal);
goal[2] += 6;
}
if (targ->deadflag)
{
ent->client->ps.pmove.pm_type = PM_DEAD;
}
else
{
ent->client->ps.pmove.pm_type = PM_FREEZE;
}
VectorCopy(goal, ent->s.origin);
for (i = 0; i < 3; i++)
{
ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(
targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]);
}
if (targ->deadflag)
{
ent->client->ps.viewangles[ROLL] = 40;
ent->client->ps.viewangles[PITCH] = -15;
ent->client->ps.viewangles[YAW] = targ->client->killer_yaw;
}
else
{
VectorCopy(targ->client->v_angle, ent->client->ps.viewangles);
VectorCopy(targ->client->v_angle, ent->client->v_angle);
}
ent->viewheight = 0;
ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
gi.linkentity(ent);
}
void
ChaseNext(edict_t *ent)
{
int i;
edict_t *e;
if (!ent)
{
return;
}
if (!ent->client->chase_target)
{
return;
}
i = ent->client->chase_target - g_edicts;
do
{
i++;
if (i > maxclients->value)
{
i = 1;
}
e = g_edicts + i;
if (!e->inuse)
{
continue;
}
if (!e->client->resp.spectator)
{
break;
}
}
while (e != ent->client->chase_target);
ent->client->chase_target = e;
ent->client->update_chase = true;
}
void
ChasePrev(edict_t *ent)
{
int i;
edict_t *e;
if (!ent)
{
return;
}
if (!ent->client->chase_target)
{
return;
}
i = ent->client->chase_target - g_edicts;
do
{
i--;
if (i < 1)
{
i = maxclients->value;
}
e = g_edicts + i;
if (!e->inuse)
{
continue;
}
if (!e->client->resp.spectator)
{
break;
}
}
while (e != ent->client->chase_target);
ent->client->chase_target = e;
ent->client->update_chase = true;
}
void
GetChaseTarget(edict_t *ent)
{
int i;
edict_t *other;
if (!ent)
{
return;
}
for (i = 1; i <= maxclients->value; i++)
{
other = g_edicts + i;
if (other->inuse && !other->client->resp.spectator)
{
ent->client->chase_target = other;
ent->client->update_chase = true;
UpdateChaseCam(ent);
return;
}
}
gi.centerprintf(ent, "No other players to chase.");
}

File diff suppressed because it is too large Load diff

View file

@ -1,964 +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.
*
* =======================================================================
*
* Combat code like damage, death and so on.
*
* =======================================================================
*/
#include "header/local.h"
void M_SetEffects(edict_t *self);
/*
* clean up heal targets for medic
*/
void
cleanupHealTarget(edict_t *ent)
{
if (!ent)
{
return;
}
ent->monsterinfo.healer = NULL;
ent->takedamage = DAMAGE_YES;
ent->monsterinfo.aiflags &= ~AI_RESURRECTING;
M_SetEffects(ent);
}
/*
* Returns true if the inflictor can
* directly damage the target. Used for
* explosions and melee attacks.
*/
qboolean
CanDamage(edict_t *targ, edict_t *inflictor)
{
vec3_t dest;
trace_t trace;
if (!targ || !inflictor)
{
return false;
}
/* bmodels need special checking because their origin is 0,0,0 */
if (targ->movetype == MOVETYPE_PUSH)
{
VectorAdd(targ->absmin, targ->absmax, dest);
VectorScale(dest, 0.5, dest);
trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
dest, inflictor, MASK_SOLID);
if (trace.fraction == 1.0)
{
return true;
}
if (trace.ent == targ)
{
return true;
}
return false;
}
trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
targ->s.origin, inflictor, MASK_SOLID);
if (trace.fraction == 1.0)
{
return true;
}
VectorCopy(targ->s.origin, dest);
dest[0] += 15.0;
dest[1] += 15.0;
trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
dest, inflictor, MASK_SOLID);
if (trace.fraction == 1.0)
{
return true;
}
VectorCopy(targ->s.origin, dest);
dest[0] += 15.0;
dest[1] -= 15.0;
trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
dest, inflictor, MASK_SOLID);
if (trace.fraction == 1.0)
{
return true;
}
VectorCopy(targ->s.origin, dest);
dest[0] -= 15.0;
dest[1] += 15.0;
trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
dest, inflictor, MASK_SOLID);
if (trace.fraction == 1.0)
{
return true;
}
VectorCopy(targ->s.origin, dest);
dest[0] -= 15.0;
dest[1] -= 15.0;
trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
dest, inflictor, MASK_SOLID);
if (trace.fraction == 1.0)
{
return true;
}
return false;
}
void
Killed(edict_t *targ, edict_t *inflictor, edict_t *attacker,
int damage, vec3_t point)
{
if (!targ || !inflictor || !attacker)
{
return;
}
targ->enemy = attacker;
if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
{
if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY))
{
level.killed_monsters++;
if (coop->value && attacker->client)
{
attacker->client->resp.score++;
}
/* medics won't heal monsters that they kill themselves */
if (attacker->classname && strcmp(attacker->classname, "monster_medic") == 0)
{
targ->owner = attacker;
}
}
}
if ((targ->movetype == MOVETYPE_PUSH) ||
(targ->movetype == MOVETYPE_STOP) ||
(targ->movetype == MOVETYPE_NONE))
{
/* doors, triggers, etc */
targ->die(targ, inflictor, attacker, damage, point);
return;
}
if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
{
targ->touch = NULL;
monster_death_use(targ);
}
targ->die(targ, inflictor, attacker, damage, point);
}
void
SpawnDamage(int type, vec3_t origin, vec3_t normal)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(type);
gi.WritePosition(origin);
gi.WriteDir(normal);
gi.multicast(origin, MULTICAST_PVS);
}
/*
* targ entity that is being damaged
* inflictor entity that is causing the damage
* attacker entity that caused the inflictor to damage targ
* example: targ=monster, inflictor=rocket, attacker=player
*
* dir direction of the attack
* point point at which the damage is being inflicted
* normal normal vector from that point
* damage amount of damage being inflicted
* knockback force to be applied against targ as a result of the damage
*
* dflags these flags are used to control how T_Damage works
* DAMAGE_RADIUS damage was indirect (from a nearby explosion)
* DAMAGE_NO_ARMOR armor does not protect from this damage
* DAMAGE_ENERGY damage is from an energy based weapon
* DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
* DAMAGE_BULLET damage is from a bullet (used for ricochets)
* DAMAGE_NO_PROTECTION kills godmode, armor, everything
* ============
*/
int
CheckPowerArmor(edict_t *ent, vec3_t point, vec3_t normal,
int damage, int dflags)
{
gclient_t *client;
int save;
int power_armor_type;
int index = 0;
int damagePerCell;
int pa_te_type;
int power = 0;
int power_used;
if (!ent)
{
return 0;
}
if (!damage)
{
return 0;
}
index = 0;
client = ent->client;
if (dflags & DAMAGE_NO_ARMOR)
{
return 0;
}
if (client)
{
power_armor_type = PowerArmorType(ent);
if (power_armor_type != POWER_ARMOR_NONE)
{
index = ITEM_INDEX(FindItem("Cells"));
power = client->pers.inventory[index];
}
}
else if (ent->svflags & SVF_MONSTER)
{
power_armor_type = ent->monsterinfo.power_armor_type;
power = ent->monsterinfo.power_armor_power;
index = 0;
}
else
{
return 0;
}
if (power_armor_type == POWER_ARMOR_NONE)
{
return 0;
}
if (!power)
{
return 0;
}
if (power_armor_type == POWER_ARMOR_SCREEN)
{
vec3_t vec;
float dot;
vec3_t forward;
/* only works if damage point is in front */
AngleVectors(ent->s.angles, forward, NULL, NULL);
VectorSubtract(point, ent->s.origin, vec);
VectorNormalize(vec);
dot = DotProduct(vec, forward);
if (dot <= 0.3)
{
return 0;
}
damagePerCell = 1;
pa_te_type = TE_SCREEN_SPARKS;
damage = damage / 3;
}
else
{
damagePerCell = 2;
pa_te_type = TE_SHIELD_SPARKS;
damage = (2 * damage) / 3;
}
save = power * damagePerCell;
if (!save)
{
return 0;
}
if (save > damage)
{
save = damage;
}
SpawnDamage(pa_te_type, point, normal);
ent->powerarmor_time = level.time + 0.2;
power_used = save / damagePerCell;
if (client)
{
client->pers.inventory[index] -= power_used;
}
else
{
ent->monsterinfo.power_armor_power -= power_used;
}
return save;
}
int
CheckArmor(edict_t *ent, vec3_t point, vec3_t normal,
int damage, int te_sparks, int dflags)
{
gclient_t *client;
int save;
int index;
gitem_t *armor;
if (!ent)
{
return 0;
}
if (!damage)
{
return 0;
}
client = ent->client;
if (!client)
{
return 0;
}
if (dflags & DAMAGE_NO_ARMOR)
{
return 0;
}
index = ArmorIndex(ent);
if (!index)
{
return 0;
}
armor = GetItemByIndex(index);
if (dflags & DAMAGE_ENERGY)
{
save = ceil(((gitem_armor_t *)armor->info)->energy_protection * damage);
}
else
{
save = ceil(((gitem_armor_t *)armor->info)->normal_protection * damage);
}
if (save >= client->pers.inventory[index])
{
save = client->pers.inventory[index];
}
if (!save)
{
return 0;
}
client->pers.inventory[index] -= save;
SpawnDamage(te_sparks, point, normal);
return save;
}
void
M_ReactToDamage(edict_t *targ, edict_t *attacker)
{
if (!targ || !attacker)
{
return;
}
if (targ->health <= 0)
{
return;
}
if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
{
return;
}
if ((attacker == targ) || (attacker == targ->enemy))
{
return;
}
/* if we are a good guy monster and our attacker is a
player or another good guy, do not get mad at them */
if (targ->monsterinfo.aiflags & AI_GOOD_GUY)
{
if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
{
return;
}
}
/* if attacker is a client, get mad at
them because he's good and we're not */
if (attacker->client)
{
targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
/* this can only happen in coop (both new and old
enemies are clients) only switch if can't see the
current enemy */
if (targ->enemy && targ->enemy->client)
{
if (visible(targ, targ->enemy))
{
targ->oldenemy = attacker;
return;
}
targ->oldenemy = targ->enemy;
}
targ->enemy = attacker;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
{
FoundTarget(targ);
}
return;
}
/* it's the same base (walk/swim/fly) type and
a different classname and it's not a tank
(they spray too much), get mad at them */
if (((targ->flags & (FL_FLY | FL_SWIM)) ==
(attacker->flags & (FL_FLY | FL_SWIM))) &&
(strcmp(targ->classname, attacker->classname) != 0) &&
(strcmp(attacker->classname, "monster_tank") != 0) &&
(strcmp(attacker->classname, "monster_supertank") != 0) &&
(strcmp(attacker->classname, "monster_makron") != 0) &&
(strcmp(attacker->classname, "monster_jorg") != 0))
{
if (targ->enemy && targ->enemy->client)
{
targ->oldenemy = targ->enemy;
}
targ->enemy = attacker;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
{
FoundTarget(targ);
}
}
/* if they *meant* to shoot us, then shoot back */
else if (attacker->enemy == targ)
{
if (targ->enemy && targ->enemy->client)
{
targ->oldenemy = targ->enemy;
}
targ->enemy = attacker;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
{
FoundTarget(targ);
}
}
/* otherwise get mad at whoever they are mad at (help our buddy) unless it is us! */
else if (attacker->enemy)
{
if (targ->enemy && targ->enemy->client)
{
targ->oldenemy = targ->enemy;
}
targ->enemy = attacker->enemy;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
{
FoundTarget(targ);
}
}
}
static void
apply_knockback(edict_t *targ, vec3_t dir, float knockback, float scale)
{
vec3_t kvel;
float mass;
if (!knockback)
{
return;
}
mass = (targ->mass < 50) ? 50.0f : (float)targ->mass;
VectorNormalize2(dir, kvel);
VectorScale(kvel, scale * (knockback / mass), kvel);
VectorAdd(targ->velocity, kvel, targ->velocity);
}
void
T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir,
vec3_t point, vec3_t normal, int damage, int knockback, int dflags,
int mod)
{
gclient_t *client;
int take;
int save;
int asave;
int psave;
int te_sparks;
if (!targ || !inflictor || !attacker)
{
return;
}
if (!targ->takedamage)
{
return;
}
/* friendly fire avoidance. If enabled you can't
hurt teammates (but you can hurt yourself)
knockback still occurs */
if ((targ != attacker) && ((deathmatch->value &&
((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) ||
coop->value))
{
if (OnSameTeam(targ, attacker))
{
if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
{
damage = 0;
}
else
{
mod |= MOD_FRIENDLY_FIRE;
}
}
}
meansOfDeath = mod;
/* easy mode takes half damage */
if ((skill->value == SKILL_EASY) && (deathmatch->value == 0) && targ->client)
{
damage *= 0.5;
if (!damage)
{
damage = 1;
}
}
client = targ->client;
if (dflags & DAMAGE_BULLET)
{
te_sparks = TE_BULLET_SPARKS;
}
else
{
te_sparks = TE_SPARKS;
}
/* bonus damage for suprising a monster */
if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) &&
(attacker->client) && (!targ->enemy) && (targ->health > 0))
{
damage *= 2;
}
if (targ->flags & FL_NO_KNOCKBACK)
{
knockback = 0;
}
/* figure momentum add */
if (!(dflags & DAMAGE_NO_KNOCKBACK) &&
(targ->movetype != MOVETYPE_NONE) &&
(targ->movetype != MOVETYPE_BOUNCE) &&
(targ->movetype != MOVETYPE_PUSH) &&
(targ->movetype != MOVETYPE_STOP))
{
apply_knockback (targ, dir, knockback,
((client && attacker == targ) ? 1600.0f : 500.0f));
}
take = damage;
save = 0;
/* check for godmode */
if ((targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION))
{
take = 0;
save = damage;
SpawnDamage(te_sparks, point, normal);
}
/* check for invincibility */
if ((client && (client->invincible_framenum > level.framenum)) &&
!(dflags & DAMAGE_NO_PROTECTION) && (mod != MOD_TRAP))
{
if (targ->pain_debounce_time < level.time)
{
gi.sound(targ, CHAN_ITEM, gi.soundindex(
"items/protect4.wav"), 1, ATTN_NORM, 0);
targ->pain_debounce_time = level.time + 2;
}
take = 0;
save = damage;
}
psave = CheckPowerArmor(targ, point, normal, take, dflags);
take -= psave;
asave = CheckArmor(targ, point, normal, take, te_sparks, dflags);
take -= asave;
/* treat cheat/powerup savings the same as armor */
asave += save;
/* do the damage */
if (take)
{
if ((targ->svflags & SVF_MONSTER) || (client))
{
if (strcmp(targ->classname, "monster_gekk") == 0)
{
SpawnDamage(TE_GREENBLOOD, point, normal);
}
else
{
SpawnDamage(TE_BLOOD, point, normal);
}
}
else
{
SpawnDamage(te_sparks, point, normal);
}
targ->health = targ->health - take;
if (targ->health <= 0)
{
if ((targ->svflags & SVF_MONSTER) || (client))
{
targ->flags |= FL_NO_KNOCKBACK;
}
Killed(targ, inflictor, attacker, take, point);
return;
}
}
if (targ->svflags & SVF_MONSTER)
{
M_ReactToDamage(targ, attacker);
if (!(targ->monsterinfo.aiflags & (AI_DUCKED|AI_IGNORE_PAIN)) && (take))
{
targ->pain(targ, attacker, knockback, take);
/* nightmare mode monsters don't go into pain frames often */
if (skill->value == SKILL_HARDPLUS)
{
targ->pain_debounce_time = level.time + 5;
}
}
}
else if (client)
{
if (!(targ->flags & FL_GODMODE) && (take))
{
targ->pain(targ, attacker, knockback, take);
}
}
else if (take)
{
if (targ->pain)
{
targ->pain(targ, attacker, knockback, take);
}
}
/* add to the damage inflicted on a player this frame
the total will be turned into screen blends and view angle kicks
at the end of the frame */
if (client)
{
client->damage_parmor += psave;
client->damage_armor += asave;
client->damage_blood += take;
client->damage_knockback += knockback;
VectorCopy(point, client->damage_from);
}
}
void
T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage,
edict_t *ignore, float radius, int mod)
{
float points;
edict_t *ent = NULL;
vec3_t v;
vec3_t dir;
if (!inflictor || !attacker)
{
return;
}
while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
{
if (ent == ignore)
{
continue;
}
if (!ent->takedamage)
{
continue;
}
VectorAdd(ent->mins, ent->maxs, v);
VectorMA(ent->s.origin, 0.5, v, v);
VectorSubtract(inflictor->s.origin, v, v);
points = damage - 0.5 * VectorLength(v);
if (ent == attacker)
{
points = points * 0.5;
}
if (points > 0)
{
if (CanDamage(ent, inflictor))
{
VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin,
vec3_origin, (int)points, (int)points, DAMAGE_RADIUS,
mod);
}
}
}
}
void
T_RadiusNukeDamage(edict_t *inflictor, edict_t *attacker, float damage,
edict_t *ignore, float radius, int mod)
{
float points;
edict_t *ent = NULL;
vec3_t v;
vec3_t dir;
float len;
float killzone, killzone2;
trace_t tr;
float dist;
killzone = radius;
killzone2 = radius * 2.0;
if (!inflictor || !attacker || !ignore)
{
return;
}
while ((ent = findradius(ent, inflictor->s.origin, killzone2)) != NULL)
{
/* ignore nobody */
if (ent == ignore)
{
continue;
}
if (!ent->takedamage)
{
continue;
}
if (!ent->inuse)
{
continue;
}
if (!(ent->client || (ent->svflags & SVF_MONSTER) ||
(ent->svflags & SVF_DAMAGEABLE)))
{
continue;
}
VectorAdd(ent->mins, ent->maxs, v);
VectorMA(ent->s.origin, 0.5, v, v);
VectorSubtract(inflictor->s.origin, v, v);
len = VectorLength(v);
if (len <= killzone)
{
if (ent->client)
{
ent->flags |= FL_NOGIB;
}
points = 10000;
}
else if (len <= killzone2)
{
points = (damage / killzone) * (killzone2 - len);
}
else
{
points = 0;
}
if (points > 0)
{
if (ent->client)
{
ent->client->nuke_framenum = level.framenum + 20;
}
VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin,
vec3_origin, (int)points, (int)points, DAMAGE_RADIUS,
mod);
}
}
/* skip the worldspawn */
ent = g_edicts + 1;
/* cycle through players */
while (ent)
{
if ((ent->client) &&
(ent->client->nuke_framenum != level.framenum + 20) && (ent->inuse))
{
tr = gi.trace(inflictor->s.origin, NULL, NULL, ent->s.origin,
inflictor, MASK_SOLID);
if (tr.fraction == 1.0)
{
ent->client->nuke_framenum = level.framenum + 20;
}
else
{
dist = realrange(ent, inflictor);
if (dist < 2048)
{
ent->client->nuke_framenum = max(ent->client->nuke_framenum,
level.framenum + 15);
}
else
{
ent->client->nuke_framenum = max(ent->client->nuke_framenum,
level.framenum + 10);
}
}
ent++;
}
else
{
ent = NULL;
}
}
}
/*
* Like T_RadiusDamage, but ignores
* anything with classname=ignoreClass
*/
void
T_RadiusClassDamage(edict_t *inflictor, edict_t *attacker, float damage,
char *ignoreClass, float radius, int mod)
{
float points;
edict_t *ent = NULL;
vec3_t v;
vec3_t dir;
if (!inflictor || !attacker || !ignoreClass)
{
return;
}
while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
{
if (ent->classname && !strcmp(ent->classname, ignoreClass))
{
continue;
}
if (!ent->takedamage)
{
continue;
}
VectorAdd(ent->mins, ent->maxs, v);
VectorMA(ent->s.origin, 0.5, v, v);
VectorSubtract(inflictor->s.origin, v, v);
points = damage - 0.5 * VectorLength(v);
if (ent == attacker)
{
points = points * 0.5;
}
if (points > 0)
{
if (CanDamage(ent, inflictor))
{
VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin,
vec3_origin, (int)points, (int)points, DAMAGE_RADIUS,
mod);
}
}
}
}

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

File diff suppressed because it is too large Load diff

View file

@ -1,356 +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
void
Svcmd_Test_f(void)
{
gi.cprintf(NULL, PRINT_HIGH, "Svcmd_Test_f()\n");
}
/*
* ==============================================================================
*
* 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 belive 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;
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 instead? */
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 instead? */
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 instead? */
*(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 instead? */
*(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,925 +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 PUSH_ONCE 1
void trigger_push_active(edict_t *self);
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;
}
/*
* The trigger was just activated. ent->activator should
* be set to the activator so it can be held through a delay
* so wait for the delay time before firing
*/
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;
}
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
* 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)
*
* 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 & 4)
{
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
*
* "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. triggered was
on bit 1 when it should have been on bit 4 */
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 /* may be NULL */)
{
if (!self)
{
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, it 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 (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);
}
}
/*
* QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE
* Pushes the player
*
* "speed" defaults to 1000
*/
void
trigger_effect(edict_t *self)
{
vec3_t origin;
vec3_t size;
int i;
if (!self)
{
return;
}
VectorScale(self->size, 0.5, size);
VectorAdd(self->absmin, size, origin);
for (i = 0; i < 10; i++)
{
origin[2] += (self->speed * 0.01) * (i + random());
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_TUNNEL_SPARKS);
gi.WriteByte(1);
gi.WritePosition(origin);
gi.WriteDir(vec3_origin);
gi.WriteByte(0x74 + (rand() & 7));
gi.multicast(self->s.origin, MULTICAST_PVS);
}
}
void
trigger_push_inactive(edict_t *self)
{
if (!self)
{
return;
}
if (self->delay > level.time)
{
self->nextthink = level.time + 0.1;
}
else
{
self->touch = trigger_push_touch;
self->think = trigger_push_active;
self->nextthink = level.time + 0.1;
self->delay = self->nextthink + self->wait;
}
}
void
trigger_push_active(edict_t *self)
{
if (!self)
{
return;
}
if (self->delay > level.time)
{
self->nextthink = level.time + 0.1;
trigger_effect(self);
}
else
{
self->touch = NULL;
self->think = trigger_push_inactive;
self->nextthink = level.time + 0.1;
self->delay = self->nextthink + self->wait;
}
}
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->spawnflags & 2)
{
if (!self->wait)
{
self->wait = 10;
}
self->think = trigger_push_active;
self->nextthink = level.time + 0.1;
self->delay = self->nextthink + self->wait;
}
if (!self->speed)
{
self->speed = 1000;
}
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);
}
/*
* QUAKED trigger_gravity (.5 .5 .5) ?
* Changes the touching entites gravity to
* the value of "gravity". 1.0 is standard
* gravity for the level.
*/
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;
}
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 = (int)strtol(st.gravity, (char **)NULL, 10);
self->touch = trigger_gravity_touch;
}
/*
* 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,609 +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"
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);
qboolean FindTarget(edict_t *self);
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)
{
edict_t *attacker;
if (!self || !other)
{
return;
}
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 a driver, 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);
VectorSubtract(self->target_ent->s.origin, self->s.origin,
self->move_origin);
G_FreeEdict(self->target_ent);
}
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);
}

File diff suppressed because it is too large Load diff