mirror of
https://github.com/yquake2/ctf.git
synced 2024-11-10 06:31:34 +00:00
Move ctf/ into it's own subproject.
This commit is contained in:
commit
87478789d6
30 changed files with 28083 additions and 0 deletions
1094
src/g_ai.c
Normal file
1094
src/g_ai.c
Normal file
File diff suppressed because it is too large
Load diff
158
src/g_chase.c
Normal file
158
src/g_chase.c
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
#include "g_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 oldgoal;
|
||||||
|
vec3_t angles;
|
||||||
|
|
||||||
|
// is our chase target gone?
|
||||||
|
if (!ent->client->chase_target->inuse) {
|
||||||
|
ent->client->chase_target = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
targ = ent->client->chase_target;
|
||||||
|
|
||||||
|
VectorCopy(targ->s.origin, ownerv);
|
||||||
|
VectorCopy(ent->s.origin, oldgoal);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if ((!ent->client->showscores && !ent->client->menu &&
|
||||||
|
!ent->client->showinventory && !ent->client->showhelp &&
|
||||||
|
!(level.framenum & 31)) || ent->client->update_chase) {
|
||||||
|
char s[1024];
|
||||||
|
|
||||||
|
ent->client->update_chase = false;
|
||||||
|
sprintf(s, "xv 0 yb -68 string2 \"Chasing %s\"",
|
||||||
|
targ->client->pers.netname);
|
||||||
|
gi.WriteByte (svc_layout);
|
||||||
|
gi.WriteString (s);
|
||||||
|
gi.unicast(ent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChaseNext(edict_t *ent)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
edict_t *e;
|
||||||
|
|
||||||
|
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->solid != SOLID_NOT)
|
||||||
|
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->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->solid != SOLID_NOT)
|
||||||
|
break;
|
||||||
|
} while (e != ent->client->chase_target);
|
||||||
|
|
||||||
|
ent->client->chase_target = e;
|
||||||
|
ent->client->update_chase = true;
|
||||||
|
}
|
||||||
|
|
1066
src/g_cmds.c
Normal file
1066
src/g_cmds.c
Normal file
File diff suppressed because it is too large
Load diff
595
src/g_combat.c
Normal file
595
src/g_combat.c
Normal file
|
@ -0,0 +1,595 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
// g_combat.c
|
||||||
|
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
CanDamage
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
Killed
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
||||||
|
{
|
||||||
|
if (targ->health < -999)
|
||||||
|
targ->health = -999;
|
||||||
|
|
||||||
|
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 (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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
SpawnDamage
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage)
|
||||||
|
{
|
||||||
|
if (damage > 255)
|
||||||
|
damage = 255;
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (type);
|
||||||
|
gi.WritePosition (origin);
|
||||||
|
gi.WriteDir (normal);
|
||||||
|
gi.multicast (origin, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
T_Damage
|
||||||
|
|
||||||
|
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
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static 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 (!damage)
|
||||||
|
return 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 = 1; // power armor is weaker in CTF
|
||||||
|
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, save);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static 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 (!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, save);
|
||||||
|
|
||||||
|
return save;
|
||||||
|
}
|
||||||
|
|
||||||
|
void M_ReactToDamage (edict_t *targ, edict_t *attacker)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we now know that we are not both good guys
|
||||||
|
|
||||||
|
// if attacker is a client, get mad at them because he's good and we're not
|
||||||
|
if (attacker->client)
|
||||||
|
{
|
||||||
|
// 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)
|
||||||
|
if (targ->enemy->client)
|
||||||
|
targ->oldenemy = targ->enemy;
|
||||||
|
targ->enemy = attacker;
|
||||||
|
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
|
||||||
|
FoundTarget (targ);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// otherwise get mad at whoever they are mad at (help our buddy)
|
||||||
|
{
|
||||||
|
if (targ->enemy)
|
||||||
|
if (targ->enemy->client)
|
||||||
|
targ->oldenemy = targ->enemy;
|
||||||
|
targ->enemy = attacker->enemy;
|
||||||
|
FoundTarget (targ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker)
|
||||||
|
{
|
||||||
|
//ZOID
|
||||||
|
if (ctf->value && targ->client && attacker->client)
|
||||||
|
if (targ->client->resp.ctf_team == attacker->client->resp.ctf_team &&
|
||||||
|
targ != attacker)
|
||||||
|
return true;
|
||||||
|
//ZOID
|
||||||
|
|
||||||
|
//FIXME make the next line real and uncomment this block
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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->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 == 0 && 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;
|
||||||
|
|
||||||
|
VectorNormalize(dir);
|
||||||
|
|
||||||
|
// bonus damage for suprising a monster
|
||||||
|
if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
|
||||||
|
damage *= 2;
|
||||||
|
|
||||||
|
//ZOID
|
||||||
|
//strength tech
|
||||||
|
damage = CTFApplyStrength(attacker, damage);
|
||||||
|
//ZOID
|
||||||
|
|
||||||
|
if (targ->flags & FL_NO_KNOCKBACK)
|
||||||
|
knockback = 0;
|
||||||
|
|
||||||
|
// figure momentum add
|
||||||
|
if (!(dflags & DAMAGE_NO_KNOCKBACK))
|
||||||
|
{
|
||||||
|
if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
|
||||||
|
{
|
||||||
|
vec3_t kvel;
|
||||||
|
float mass;
|
||||||
|
|
||||||
|
if (targ->mass < 50)
|
||||||
|
mass = 50;
|
||||||
|
else
|
||||||
|
mass = targ->mass;
|
||||||
|
|
||||||
|
if (targ->client && attacker == targ)
|
||||||
|
VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack...
|
||||||
|
else
|
||||||
|
VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
|
||||||
|
|
||||||
|
VectorAdd (targ->velocity, kvel, targ->velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, save);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for invincibility
|
||||||
|
if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ZOID
|
||||||
|
//team armor protect
|
||||||
|
if (ctf->value && targ->client && attacker->client &&
|
||||||
|
targ->client->resp.ctf_team == attacker->client->resp.ctf_team &&
|
||||||
|
targ != attacker && ((int)dmflags->value & DF_ARMOR_PROTECT)) {
|
||||||
|
psave = asave = 0;
|
||||||
|
} else {
|
||||||
|
//ZOID
|
||||||
|
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;
|
||||||
|
|
||||||
|
//ZOID
|
||||||
|
//resistance tech
|
||||||
|
take = CTFApplyResistance(targ, take);
|
||||||
|
//ZOID
|
||||||
|
|
||||||
|
// team damage avoidance
|
||||||
|
if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
|
||||||
|
return;
|
||||||
|
|
||||||
|
//ZOID
|
||||||
|
CTFCheckHurtCarrier(targ, attacker);
|
||||||
|
//ZOID
|
||||||
|
|
||||||
|
// do the damage
|
||||||
|
if (take)
|
||||||
|
{
|
||||||
|
if ((targ->svflags & SVF_MONSTER) || (client))
|
||||||
|
SpawnDamage (TE_BLOOD, point, normal, take);
|
||||||
|
else
|
||||||
|
SpawnDamage (te_sparks, point, normal, take);
|
||||||
|
|
||||||
|
if (!CTFMatchSetup())
|
||||||
|
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) && (take))
|
||||||
|
{
|
||||||
|
targ->pain (targ, attacker, knockback, take);
|
||||||
|
// nightmare mode monsters don't go into pain frames often
|
||||||
|
if (skill->value == 3)
|
||||||
|
targ->pain_debounce_time = level.time + 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (client)
|
||||||
|
{
|
||||||
|
if (!(targ->flags & FL_GODMODE) && (take) && !CTFMatchSetup())
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
T_RadiusDamage
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
4098
src/g_ctf.c
Normal file
4098
src/g_ctf.c
Normal file
File diff suppressed because it is too large
Load diff
192
src/g_ctf.h
Normal file
192
src/g_ctf.h
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define CTF_VERSION 1.52
|
||||||
|
#define CTF_VSTRING2(x) #x
|
||||||
|
#define CTF_VSTRING(x) CTF_VSTRING2(x)
|
||||||
|
#define CTF_STRING_VERSION CTF_VSTRING(CTF_VERSION)
|
||||||
|
|
||||||
|
#define STAT_CTF_TEAM1_PIC 17
|
||||||
|
#define STAT_CTF_TEAM1_CAPS 18
|
||||||
|
#define STAT_CTF_TEAM2_PIC 19
|
||||||
|
#define STAT_CTF_TEAM2_CAPS 20
|
||||||
|
#define STAT_CTF_FLAG_PIC 21
|
||||||
|
#define STAT_CTF_JOINED_TEAM1_PIC 22
|
||||||
|
#define STAT_CTF_JOINED_TEAM2_PIC 23
|
||||||
|
#define STAT_CTF_TEAM1_HEADER 24
|
||||||
|
#define STAT_CTF_TEAM2_HEADER 25
|
||||||
|
#define STAT_CTF_TECH 26
|
||||||
|
#define STAT_CTF_ID_VIEW 27
|
||||||
|
#define STAT_CTF_MATCH 28
|
||||||
|
#define STAT_CTF_ID_VIEW_COLOR 29
|
||||||
|
#define STAT_CTF_TEAMINFO 30
|
||||||
|
|
||||||
|
#define CONFIG_CTF_MATCH (CS_AIRACCEL-1)
|
||||||
|
#define CONFIG_CTF_TEAMINFO (CS_AIRACCEL-2)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CTF_NOTEAM,
|
||||||
|
CTF_TEAM1,
|
||||||
|
CTF_TEAM2
|
||||||
|
} ctfteam_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CTF_GRAPPLE_STATE_FLY,
|
||||||
|
CTF_GRAPPLE_STATE_PULL,
|
||||||
|
CTF_GRAPPLE_STATE_HANG
|
||||||
|
} ctfgrapplestate_t;
|
||||||
|
|
||||||
|
typedef struct ghost_s {
|
||||||
|
char netname[16];
|
||||||
|
int number;
|
||||||
|
|
||||||
|
// stats
|
||||||
|
int deaths;
|
||||||
|
int kills;
|
||||||
|
int caps;
|
||||||
|
int basedef;
|
||||||
|
int carrierdef;
|
||||||
|
|
||||||
|
int code; // ghost code
|
||||||
|
int team; // team
|
||||||
|
int score; // frags at time of disconnect
|
||||||
|
edict_t *ent;
|
||||||
|
} ghost_t;
|
||||||
|
|
||||||
|
extern cvar_t *ctf;
|
||||||
|
|
||||||
|
#define CTF_TEAM1_SKIN "ctf_r"
|
||||||
|
#define CTF_TEAM2_SKIN "ctf_b"
|
||||||
|
|
||||||
|
#define DF_CTF_FORCEJOIN 131072
|
||||||
|
#define DF_ARMOR_PROTECT 262144
|
||||||
|
#define DF_CTF_NO_TECH 524288
|
||||||
|
|
||||||
|
#define CTF_CAPTURE_BONUS 15 // what you get for capture
|
||||||
|
#define CTF_TEAM_BONUS 10 // what your team gets for capture
|
||||||
|
#define CTF_RECOVERY_BONUS 1 // what you get for recovery
|
||||||
|
#define CTF_FLAG_BONUS 0 // what you get for picking up enemy flag
|
||||||
|
#define CTF_FRAG_CARRIER_BONUS 2 // what you get for fragging enemy flag carrier
|
||||||
|
#define CTF_FLAG_RETURN_TIME 40 // seconds until auto return
|
||||||
|
|
||||||
|
#define CTF_CARRIER_DANGER_PROTECT_BONUS 2 // bonus for fraggin someone who has recently hurt your flag carrier
|
||||||
|
#define CTF_CARRIER_PROTECT_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag carrier
|
||||||
|
#define CTF_FLAG_DEFENSE_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag
|
||||||
|
#define CTF_RETURN_FLAG_ASSIST_BONUS 1 // awarded for returning a flag that causes a capture to happen almost immediately
|
||||||
|
#define CTF_FRAG_CARRIER_ASSIST_BONUS 2 // award for fragging a flag carrier if a capture happens almost immediately
|
||||||
|
|
||||||
|
#define CTF_TARGET_PROTECT_RADIUS 400 // the radius around an object being defended where a target will be worth extra frags
|
||||||
|
#define CTF_ATTACKER_PROTECT_RADIUS 400 // the radius around an object being defended where an attacker will get extra frags when making kills
|
||||||
|
|
||||||
|
#define CTF_CARRIER_DANGER_PROTECT_TIMEOUT 8
|
||||||
|
#define CTF_FRAG_CARRIER_ASSIST_TIMEOUT 10
|
||||||
|
#define CTF_RETURN_FLAG_ASSIST_TIMEOUT 10
|
||||||
|
|
||||||
|
#define CTF_AUTO_FLAG_RETURN_TIMEOUT 30 // number of seconds before dropped flag auto-returns
|
||||||
|
|
||||||
|
#define CTF_TECH_TIMEOUT 60 // seconds before techs spawn again
|
||||||
|
|
||||||
|
#define CTF_GRAPPLE_SPEED 650 // speed of grapple in flight
|
||||||
|
#define CTF_GRAPPLE_PULL_SPEED 650 // speed player is pulled at
|
||||||
|
|
||||||
|
void CTFInit(void);
|
||||||
|
void CTFSpawn(void);
|
||||||
|
void CTFPrecache(void);
|
||||||
|
|
||||||
|
void SP_info_player_team1(edict_t *self);
|
||||||
|
void SP_info_player_team2(edict_t *self);
|
||||||
|
|
||||||
|
char *CTFTeamName(int team);
|
||||||
|
char *CTFOtherTeamName(int team);
|
||||||
|
void CTFAssignSkin(edict_t *ent, char *s);
|
||||||
|
void CTFAssignTeam(gclient_t *who);
|
||||||
|
edict_t *SelectCTFSpawnPoint (edict_t *ent);
|
||||||
|
qboolean CTFPickup_Flag(edict_t *ent, edict_t *other);
|
||||||
|
void CTFDrop_Flag(edict_t *ent, gitem_t *item);
|
||||||
|
void CTFEffects(edict_t *player);
|
||||||
|
void CTFCalcScores(void);
|
||||||
|
void SetCTFStats(edict_t *ent);
|
||||||
|
void CTFDeadDropFlag(edict_t *self);
|
||||||
|
void CTFScoreboardMessage (edict_t *ent, edict_t *killer);
|
||||||
|
void CTFTeam_f (edict_t *ent);
|
||||||
|
void CTFID_f (edict_t *ent);
|
||||||
|
void CTFSay_Team(edict_t *who, char *msg);
|
||||||
|
void CTFFlagSetup (edict_t *ent);
|
||||||
|
void CTFResetFlag(int ctf_team);
|
||||||
|
void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker);
|
||||||
|
void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker);
|
||||||
|
|
||||||
|
// GRAPPLE
|
||||||
|
void CTFWeapon_Grapple (edict_t *ent);
|
||||||
|
void CTFPlayerResetGrapple(edict_t *ent);
|
||||||
|
void CTFGrapplePull(edict_t *self);
|
||||||
|
void CTFResetGrapple(edict_t *self);
|
||||||
|
|
||||||
|
//TECH
|
||||||
|
gitem_t *CTFWhat_Tech(edict_t *ent);
|
||||||
|
qboolean CTFPickup_Tech (edict_t *ent, edict_t *other);
|
||||||
|
void CTFDrop_Tech(edict_t *ent, gitem_t *item);
|
||||||
|
void CTFDeadDropTech(edict_t *ent);
|
||||||
|
void CTFSetupTechSpawn(void);
|
||||||
|
int CTFApplyResistance(edict_t *ent, int dmg);
|
||||||
|
int CTFApplyStrength(edict_t *ent, int dmg);
|
||||||
|
qboolean CTFApplyStrengthSound(edict_t *ent);
|
||||||
|
qboolean CTFApplyHaste(edict_t *ent);
|
||||||
|
void CTFApplyHasteSound(edict_t *ent);
|
||||||
|
void CTFApplyRegeneration(edict_t *ent);
|
||||||
|
qboolean CTFHasRegeneration(edict_t *ent);
|
||||||
|
void CTFRespawnTech(edict_t *ent);
|
||||||
|
void CTFResetTech(void);
|
||||||
|
|
||||||
|
void CTFOpenJoinMenu(edict_t *ent);
|
||||||
|
qboolean CTFStartClient(edict_t *ent);
|
||||||
|
void CTFVoteYes(edict_t *ent);
|
||||||
|
void CTFVoteNo(edict_t *ent);
|
||||||
|
void CTFReady(edict_t *ent);
|
||||||
|
void CTFNotReady(edict_t *ent);
|
||||||
|
qboolean CTFNextMap(void);
|
||||||
|
qboolean CTFMatchSetup(void);
|
||||||
|
qboolean CTFMatchOn(void);
|
||||||
|
void CTFGhost(edict_t *ent);
|
||||||
|
void CTFAdmin(edict_t *ent);
|
||||||
|
qboolean CTFInMatch(void);
|
||||||
|
void CTFStats(edict_t *ent);
|
||||||
|
void CTFWarp(edict_t *ent);
|
||||||
|
void CTFBoot(edict_t *ent);
|
||||||
|
void CTFPlayerList(edict_t *ent);
|
||||||
|
|
||||||
|
qboolean CTFCheckRules(void);
|
||||||
|
|
||||||
|
void SP_misc_ctf_banner (edict_t *ent);
|
||||||
|
void SP_misc_ctf_small_banner (edict_t *ent);
|
||||||
|
|
||||||
|
extern char *ctf_statusbar;
|
||||||
|
|
||||||
|
void UpdateChaseCam(edict_t *ent);
|
||||||
|
void ChaseNext(edict_t *ent);
|
||||||
|
void ChasePrev(edict_t *ent);
|
||||||
|
|
||||||
|
void CTFObserver(edict_t *ent);
|
||||||
|
|
||||||
|
void SP_trigger_teleport (edict_t *ent);
|
||||||
|
void SP_info_teleport_destination (edict_t *ent);
|
||||||
|
|
||||||
|
void CTFSetPowerUpEffect(edict_t *ent, int def);
|
||||||
|
|
2043
src/g_func.c
Normal file
2043
src/g_func.c
Normal file
File diff suppressed because it is too large
Load diff
2443
src/g_items.c
Normal file
2443
src/g_items.c
Normal file
File diff suppressed because it is too large
Load diff
1146
src/g_local.h
Normal file
1146
src/g_local.h
Normal file
File diff suppressed because it is too large
Load diff
429
src/g_main.c
Normal file
429
src/g_main.c
Normal file
|
@ -0,0 +1,429 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
game_locals_t game;
|
||||||
|
level_locals_t level;
|
||||||
|
game_import_t gi;
|
||||||
|
game_export_t globals;
|
||||||
|
spawn_temp_t st;
|
||||||
|
|
||||||
|
int sm_meat_index;
|
||||||
|
int snd_fry;
|
||||||
|
int meansOfDeath;
|
||||||
|
|
||||||
|
edict_t *g_edicts;
|
||||||
|
|
||||||
|
cvar_t *deathmatch;
|
||||||
|
cvar_t *coop;
|
||||||
|
cvar_t *dmflags;
|
||||||
|
cvar_t *skill;
|
||||||
|
cvar_t *fraglimit;
|
||||||
|
cvar_t *timelimit;
|
||||||
|
//ZOID
|
||||||
|
cvar_t *capturelimit;
|
||||||
|
cvar_t *instantweap;
|
||||||
|
//ZOID
|
||||||
|
cvar_t *password;
|
||||||
|
cvar_t *maxclients;
|
||||||
|
cvar_t *maxentities;
|
||||||
|
cvar_t *g_select_empty;
|
||||||
|
cvar_t *dedicated;
|
||||||
|
|
||||||
|
cvar_t *filterban;
|
||||||
|
|
||||||
|
cvar_t *sv_maxvelocity;
|
||||||
|
cvar_t *sv_gravity;
|
||||||
|
|
||||||
|
cvar_t *sv_rollspeed;
|
||||||
|
cvar_t *sv_rollangle;
|
||||||
|
cvar_t *gun_x;
|
||||||
|
cvar_t *gun_y;
|
||||||
|
cvar_t *gun_z;
|
||||||
|
|
||||||
|
cvar_t *run_pitch;
|
||||||
|
cvar_t *run_roll;
|
||||||
|
cvar_t *bob_up;
|
||||||
|
cvar_t *bob_pitch;
|
||||||
|
cvar_t *bob_roll;
|
||||||
|
|
||||||
|
cvar_t *sv_cheats;
|
||||||
|
|
||||||
|
cvar_t *flood_msgs;
|
||||||
|
cvar_t *flood_persecond;
|
||||||
|
cvar_t *flood_waitdelay;
|
||||||
|
|
||||||
|
cvar_t *sv_maplist;
|
||||||
|
|
||||||
|
void SpawnEntities (char *mapname, char *entities, char *spawnpoint);
|
||||||
|
void ClientThink (edict_t *ent, usercmd_t *cmd);
|
||||||
|
qboolean ClientConnect (edict_t *ent, char *userinfo);
|
||||||
|
void ClientUserinfoChanged (edict_t *ent, char *userinfo);
|
||||||
|
void ClientDisconnect (edict_t *ent);
|
||||||
|
void ClientBegin (edict_t *ent);
|
||||||
|
void ClientCommand (edict_t *ent);
|
||||||
|
void RunEntity (edict_t *ent);
|
||||||
|
void WriteGame (char *filename, qboolean autosave);
|
||||||
|
void ReadGame (char *filename);
|
||||||
|
void WriteLevel (char *filename);
|
||||||
|
void ReadLevel (char *filename);
|
||||||
|
void InitGame (void);
|
||||||
|
void G_RunFrame (void);
|
||||||
|
|
||||||
|
|
||||||
|
//===================================================================
|
||||||
|
|
||||||
|
|
||||||
|
void ShutdownGame (void)
|
||||||
|
{
|
||||||
|
gi.dprintf ("==== ShutdownGame ====\n");
|
||||||
|
|
||||||
|
gi.FreeTags (TAG_LEVEL);
|
||||||
|
gi.FreeTags (TAG_GAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
GetGameAPI
|
||||||
|
|
||||||
|
Returns a pointer to the structure with all entry points
|
||||||
|
and global variables
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
game_export_t *GetGameAPI (game_import_t *import)
|
||||||
|
{
|
||||||
|
gi = *import;
|
||||||
|
|
||||||
|
globals.apiversion = GAME_API_VERSION;
|
||||||
|
globals.Init = InitGame;
|
||||||
|
globals.Shutdown = ShutdownGame;
|
||||||
|
globals.SpawnEntities = SpawnEntities;
|
||||||
|
|
||||||
|
globals.WriteGame = WriteGame;
|
||||||
|
globals.ReadGame = ReadGame;
|
||||||
|
globals.WriteLevel = WriteLevel;
|
||||||
|
globals.ReadLevel = ReadLevel;
|
||||||
|
|
||||||
|
globals.ClientThink = ClientThink;
|
||||||
|
globals.ClientConnect = ClientConnect;
|
||||||
|
globals.ClientUserinfoChanged = ClientUserinfoChanged;
|
||||||
|
globals.ClientDisconnect = ClientDisconnect;
|
||||||
|
globals.ClientBegin = ClientBegin;
|
||||||
|
globals.ClientCommand = ClientCommand;
|
||||||
|
|
||||||
|
globals.RunFrame = G_RunFrame;
|
||||||
|
|
||||||
|
globals.ServerCommand = ServerCommand;
|
||||||
|
|
||||||
|
globals.edict_size = sizeof(edict_t);
|
||||||
|
|
||||||
|
return &globals;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef GAME_HARD_LINKED
|
||||||
|
// this is only here so the functions in q_shared.c and q_shwin.c can link
|
||||||
|
void Sys_Error (char *error, ...)
|
||||||
|
{
|
||||||
|
va_list argptr;
|
||||||
|
char text[1024];
|
||||||
|
|
||||||
|
va_start (argptr, error);
|
||||||
|
vsprintf (text, error, argptr);
|
||||||
|
va_end (argptr);
|
||||||
|
|
||||||
|
gi.error (ERR_FATAL, "%s", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Com_Printf (char *msg, ...)
|
||||||
|
{
|
||||||
|
va_list argptr;
|
||||||
|
char text[1024];
|
||||||
|
|
||||||
|
va_start (argptr, msg);
|
||||||
|
vsprintf (text, msg, argptr);
|
||||||
|
va_end (argptr);
|
||||||
|
|
||||||
|
gi.dprintf ("%s", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//======================================================================
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
ClientEndServerFrames
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void ClientEndServerFrames (void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
edict_t *ent;
|
||||||
|
|
||||||
|
// calc the player views now that all pushing
|
||||||
|
// and damage has been added
|
||||||
|
for (i=0 ; i<maxclients->value ; i++)
|
||||||
|
{
|
||||||
|
ent = g_edicts + 1 + i;
|
||||||
|
if (!ent->inuse || !ent->client)
|
||||||
|
continue;
|
||||||
|
ClientEndServerFrame (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
CreateTargetChangeLevel
|
||||||
|
|
||||||
|
Returns the created target changelevel
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
edict_t *CreateTargetChangeLevel(char *map)
|
||||||
|
{
|
||||||
|
edict_t *ent;
|
||||||
|
|
||||||
|
ent = G_Spawn ();
|
||||||
|
ent->classname = "target_changelevel";
|
||||||
|
Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map);
|
||||||
|
ent->map = level.nextmap;
|
||||||
|
return ent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
EndDMLevel
|
||||||
|
|
||||||
|
The timelimit or fraglimit has been exceeded
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void EndDMLevel (void)
|
||||||
|
{
|
||||||
|
edict_t *ent;
|
||||||
|
char *s, *t, *f;
|
||||||
|
static const char *seps = " ,\n\r";
|
||||||
|
|
||||||
|
// stay on same level flag
|
||||||
|
if ((int)dmflags->value & DF_SAME_LEVEL)
|
||||||
|
{
|
||||||
|
BeginIntermission (CreateTargetChangeLevel (level.mapname) );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*level.forcemap) {
|
||||||
|
BeginIntermission (CreateTargetChangeLevel (level.forcemap) );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// see if it's in the map list
|
||||||
|
if (*sv_maplist->string) {
|
||||||
|
s = strdup(sv_maplist->string);
|
||||||
|
f = NULL;
|
||||||
|
t = strtok(s, seps);
|
||||||
|
while (t != NULL) {
|
||||||
|
if (Q_stricmp(t, level.mapname) == 0) {
|
||||||
|
// it's in the list, go to the next one
|
||||||
|
t = strtok(NULL, seps);
|
||||||
|
if (t == NULL) { // end of list, go to first one
|
||||||
|
if (f == NULL) // there isn't a first one, same level
|
||||||
|
BeginIntermission (CreateTargetChangeLevel (level.mapname) );
|
||||||
|
else
|
||||||
|
BeginIntermission (CreateTargetChangeLevel (f) );
|
||||||
|
} else
|
||||||
|
BeginIntermission (CreateTargetChangeLevel (t) );
|
||||||
|
free(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!f)
|
||||||
|
f = t;
|
||||||
|
t = strtok(NULL, seps);
|
||||||
|
}
|
||||||
|
free(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level.nextmap[0]) // go to a specific map
|
||||||
|
BeginIntermission (CreateTargetChangeLevel (level.nextmap) );
|
||||||
|
else { // search for a changelevel
|
||||||
|
ent = G_Find (NULL, FOFS(classname), "target_changelevel");
|
||||||
|
if (!ent)
|
||||||
|
{ // the map designer didn't include a changelevel,
|
||||||
|
// so create a fake ent that goes back to the same level
|
||||||
|
BeginIntermission (CreateTargetChangeLevel (level.mapname) );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BeginIntermission (ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
CheckDMRules
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void CheckDMRules (void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
gclient_t *cl;
|
||||||
|
|
||||||
|
if (level.intermissiontime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!deathmatch->value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//ZOID
|
||||||
|
if (ctf->value && CTFCheckRules()) {
|
||||||
|
EndDMLevel ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CTFInMatch())
|
||||||
|
return; // no checking in match mode
|
||||||
|
//ZOID
|
||||||
|
|
||||||
|
if (timelimit->value)
|
||||||
|
{
|
||||||
|
if (level.time >= timelimit->value*60)
|
||||||
|
{
|
||||||
|
gi.bprintf (PRINT_HIGH, "Timelimit hit.\n");
|
||||||
|
EndDMLevel ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fraglimit->value)
|
||||||
|
for (i=0 ; i<maxclients->value ; i++)
|
||||||
|
{
|
||||||
|
cl = game.clients + i;
|
||||||
|
if (!g_edicts[i+1].inuse)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (cl->resp.score >= fraglimit->value)
|
||||||
|
{
|
||||||
|
gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n");
|
||||||
|
EndDMLevel ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
ExitLevel
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
void ExitLevel (void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
edict_t *ent;
|
||||||
|
char command [256];
|
||||||
|
|
||||||
|
level.exitintermission = 0;
|
||||||
|
level.intermissiontime = 0;
|
||||||
|
|
||||||
|
if (CTFNextMap())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap);
|
||||||
|
gi.AddCommandString (command);
|
||||||
|
ClientEndServerFrames ();
|
||||||
|
|
||||||
|
level.changemap = NULL;
|
||||||
|
|
||||||
|
// clear some things before going to next level
|
||||||
|
for (i=0 ; i<maxclients->value ; i++)
|
||||||
|
{
|
||||||
|
ent = g_edicts + 1 + i;
|
||||||
|
if (!ent->inuse)
|
||||||
|
continue;
|
||||||
|
if (ent->health > ent->client->pers.max_health)
|
||||||
|
ent->health = ent->client->pers.max_health;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
G_RunFrame
|
||||||
|
|
||||||
|
Advances the world by 0.1 seconds
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
void G_RunFrame (void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
edict_t *ent;
|
||||||
|
|
||||||
|
level.framenum++;
|
||||||
|
level.time = level.framenum*FRAMETIME;
|
||||||
|
|
||||||
|
// choose a client for monsters to target this frame
|
||||||
|
AI_SetSightClient ();
|
||||||
|
|
||||||
|
// exit intermissions
|
||||||
|
|
||||||
|
if (level.exitintermission)
|
||||||
|
{
|
||||||
|
ExitLevel ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// treat each object in turn
|
||||||
|
// even the world gets a chance to think
|
||||||
|
//
|
||||||
|
ent = &g_edicts[0];
|
||||||
|
for (i=0 ; i<globals.num_edicts ; i++, ent++)
|
||||||
|
{
|
||||||
|
if (!ent->inuse)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
level.current_entity = ent;
|
||||||
|
|
||||||
|
VectorCopy (ent->s.origin, ent->s.old_origin);
|
||||||
|
|
||||||
|
// if the ground entity moved, make sure we are still on it
|
||||||
|
if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount))
|
||||||
|
{
|
||||||
|
ent->groundentity = NULL;
|
||||||
|
if ( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->svflags & SVF_MONSTER) )
|
||||||
|
{
|
||||||
|
M_CheckGround (ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > 0 && i <= maxclients->value)
|
||||||
|
{
|
||||||
|
ClientBeginServerFrame (ent);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
G_RunEntity (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// see if it is time to end a deathmatch
|
||||||
|
CheckDMRules ();
|
||||||
|
|
||||||
|
// build the playerstate_t structures for all players
|
||||||
|
ClientEndServerFrames ();
|
||||||
|
}
|
||||||
|
|
1898
src/g_misc.c
Normal file
1898
src/g_misc.c
Normal file
File diff suppressed because it is too large
Load diff
737
src/g_monster.c
Normal file
737
src/g_monster.c
Normal file
|
@ -0,0 +1,737 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// monster weapons
|
||||||
|
//
|
||||||
|
|
||||||
|
//FIXME mosnters should call these with a totally accurate direction
|
||||||
|
// and we can mess it up based on skill. Spread should be for normal
|
||||||
|
// and we can tighten or loosen based on skill. We could muck with
|
||||||
|
// the damages too, but I'm not sure that's such a good idea.
|
||||||
|
void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype)
|
||||||
|
{
|
||||||
|
fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
|
||||||
|
|
||||||
|
gi.WriteByte (svc_muzzleflash2);
|
||||||
|
gi.WriteShort (self - g_edicts);
|
||||||
|
gi.WriteByte (flashtype);
|
||||||
|
gi.multicast (start, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype)
|
||||||
|
{
|
||||||
|
fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN);
|
||||||
|
|
||||||
|
gi.WriteByte (svc_muzzleflash2);
|
||||||
|
gi.WriteShort (self - g_edicts);
|
||||||
|
gi.WriteByte (flashtype);
|
||||||
|
gi.multicast (start, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
|
||||||
|
{
|
||||||
|
fire_blaster (self, start, dir, damage, speed, effect, false);
|
||||||
|
|
||||||
|
gi.WriteByte (svc_muzzleflash2);
|
||||||
|
gi.WriteShort (self - g_edicts);
|
||||||
|
gi.WriteByte (flashtype);
|
||||||
|
gi.multicast (start, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype)
|
||||||
|
{
|
||||||
|
fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40);
|
||||||
|
|
||||||
|
gi.WriteByte (svc_muzzleflash2);
|
||||||
|
gi.WriteShort (self - g_edicts);
|
||||||
|
gi.WriteByte (flashtype);
|
||||||
|
gi.multicast (start, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype)
|
||||||
|
{
|
||||||
|
fire_rocket (self, start, dir, damage, speed, damage+20, damage);
|
||||||
|
|
||||||
|
gi.WriteByte (svc_muzzleflash2);
|
||||||
|
gi.WriteShort (self - g_edicts);
|
||||||
|
gi.WriteByte (flashtype);
|
||||||
|
gi.multicast (start, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype)
|
||||||
|
{
|
||||||
|
fire_rail (self, start, aimdir, damage, kick);
|
||||||
|
|
||||||
|
gi.WriteByte (svc_muzzleflash2);
|
||||||
|
gi.WriteShort (self - g_edicts);
|
||||||
|
gi.WriteByte (flashtype);
|
||||||
|
gi.multicast (start, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype)
|
||||||
|
{
|
||||||
|
fire_bfg (self, start, aimdir, damage, speed, damage_radius);
|
||||||
|
|
||||||
|
gi.WriteByte (svc_muzzleflash2);
|
||||||
|
gi.WriteShort (self - g_edicts);
|
||||||
|
gi.WriteByte (flashtype);
|
||||||
|
gi.multicast (start, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Monster utility functions
|
||||||
|
//
|
||||||
|
|
||||||
|
static void M_FliesOff (edict_t *self)
|
||||||
|
{
|
||||||
|
self->s.effects &= ~EF_FLIES;
|
||||||
|
self->s.sound = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void M_FliesOn (edict_t *self)
|
||||||
|
{
|
||||||
|
if (self->waterlevel)
|
||||||
|
return;
|
||||||
|
self->s.effects |= EF_FLIES;
|
||||||
|
self->s.sound = gi.soundindex ("infantry/inflies1.wav");
|
||||||
|
self->think = M_FliesOff;
|
||||||
|
self->nextthink = level.time + 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
void M_FlyCheck (edict_t *self)
|
||||||
|
{
|
||||||
|
if (self->waterlevel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (random() > 0.5)
|
||||||
|
return;
|
||||||
|
|
||||||
|
self->think = M_FliesOn;
|
||||||
|
self->nextthink = level.time + 5 + 10 * random();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttackFinished (edict_t *self, float time)
|
||||||
|
{
|
||||||
|
self->monsterinfo.attack_finished = level.time + time;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void M_CheckGround (edict_t *ent)
|
||||||
|
{
|
||||||
|
vec3_t point;
|
||||||
|
trace_t trace;
|
||||||
|
|
||||||
|
if (ent->flags & (FL_SWIM|FL_FLY))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ent->velocity[2] > 100)
|
||||||
|
{
|
||||||
|
ent->groundentity = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the hull point one-quarter unit down is solid the entity is on ground
|
||||||
|
point[0] = ent->s.origin[0];
|
||||||
|
point[1] = ent->s.origin[1];
|
||||||
|
point[2] = ent->s.origin[2] - 0.25;
|
||||||
|
|
||||||
|
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID);
|
||||||
|
|
||||||
|
// check steepness
|
||||||
|
if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
|
||||||
|
{
|
||||||
|
ent->groundentity = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trace.startsolid && !trace.allsolid)
|
||||||
|
{
|
||||||
|
VectorCopy (trace.endpos, ent->s.origin);
|
||||||
|
ent->groundentity = trace.ent;
|
||||||
|
ent->groundentity_linkcount = trace.ent->linkcount;
|
||||||
|
ent->velocity[2] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void M_CatagorizePosition (edict_t *ent)
|
||||||
|
{
|
||||||
|
vec3_t point;
|
||||||
|
int cont;
|
||||||
|
|
||||||
|
//
|
||||||
|
// get waterlevel
|
||||||
|
//
|
||||||
|
point[0] = ent->s.origin[0];
|
||||||
|
point[1] = ent->s.origin[1];
|
||||||
|
point[2] = ent->s.origin[2] + ent->mins[2] + 1;
|
||||||
|
cont = gi.pointcontents (point);
|
||||||
|
|
||||||
|
if (!(cont & MASK_WATER))
|
||||||
|
{
|
||||||
|
ent->waterlevel = 0;
|
||||||
|
ent->watertype = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ent->watertype = cont;
|
||||||
|
ent->waterlevel = 1;
|
||||||
|
point[2] += 26;
|
||||||
|
cont = gi.pointcontents (point);
|
||||||
|
if (!(cont & MASK_WATER))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ent->waterlevel = 2;
|
||||||
|
point[2] += 22;
|
||||||
|
cont = gi.pointcontents (point);
|
||||||
|
if (cont & MASK_WATER)
|
||||||
|
ent->waterlevel = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void M_WorldEffects (edict_t *ent)
|
||||||
|
{
|
||||||
|
int dmg;
|
||||||
|
|
||||||
|
if (ent->health > 0)
|
||||||
|
{
|
||||||
|
if (!(ent->flags & FL_SWIM))
|
||||||
|
{
|
||||||
|
if (ent->waterlevel < 3)
|
||||||
|
{
|
||||||
|
ent->air_finished = level.time + 12;
|
||||||
|
}
|
||||||
|
else if (ent->air_finished < level.time)
|
||||||
|
{ // drown!
|
||||||
|
if (ent->pain_debounce_time < level.time)
|
||||||
|
{
|
||||||
|
dmg = 2 + 2 * floor(level.time - ent->air_finished);
|
||||||
|
if (dmg > 15)
|
||||||
|
dmg = 15;
|
||||||
|
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
|
||||||
|
ent->pain_debounce_time = level.time + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ent->waterlevel > 0)
|
||||||
|
{
|
||||||
|
ent->air_finished = level.time + 9;
|
||||||
|
}
|
||||||
|
else if (ent->air_finished < level.time)
|
||||||
|
{ // suffocate!
|
||||||
|
if (ent->pain_debounce_time < level.time)
|
||||||
|
{
|
||||||
|
dmg = 2 + 2 * floor(level.time - ent->air_finished);
|
||||||
|
if (dmg > 15)
|
||||||
|
dmg = 15;
|
||||||
|
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
|
||||||
|
ent->pain_debounce_time = level.time + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ent->waterlevel == 0)
|
||||||
|
{
|
||||||
|
if (ent->flags & FL_INWATER)
|
||||||
|
{
|
||||||
|
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
|
||||||
|
ent->flags &= ~FL_INWATER;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
|
||||||
|
{
|
||||||
|
if (ent->damage_debounce_time < level.time)
|
||||||
|
{
|
||||||
|
ent->damage_debounce_time = level.time + 0.2;
|
||||||
|
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
|
||||||
|
{
|
||||||
|
if (ent->damage_debounce_time < level.time)
|
||||||
|
{
|
||||||
|
ent->damage_debounce_time = level.time + 1;
|
||||||
|
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !(ent->flags & FL_INWATER) )
|
||||||
|
{
|
||||||
|
if (!(ent->svflags & SVF_DEADMONSTER))
|
||||||
|
{
|
||||||
|
if (ent->watertype & CONTENTS_LAVA)
|
||||||
|
if (random() <= 0.5)
|
||||||
|
gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
|
||||||
|
else
|
||||||
|
gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
|
||||||
|
else if (ent->watertype & CONTENTS_SLIME)
|
||||||
|
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
|
||||||
|
else if (ent->watertype & CONTENTS_WATER)
|
||||||
|
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ent->flags |= FL_INWATER;
|
||||||
|
ent->damage_debounce_time = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void M_droptofloor (edict_t *ent)
|
||||||
|
{
|
||||||
|
vec3_t end;
|
||||||
|
trace_t trace;
|
||||||
|
|
||||||
|
ent->s.origin[2] += 1;
|
||||||
|
VectorCopy (ent->s.origin, end);
|
||||||
|
end[2] -= 256;
|
||||||
|
|
||||||
|
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
|
||||||
|
|
||||||
|
if (trace.fraction == 1 || trace.allsolid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
VectorCopy (trace.endpos, ent->s.origin);
|
||||||
|
|
||||||
|
gi.linkentity (ent);
|
||||||
|
M_CheckGround (ent);
|
||||||
|
M_CatagorizePosition (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void M_SetEffects (edict_t *ent)
|
||||||
|
{
|
||||||
|
ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN);
|
||||||
|
ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
|
||||||
|
|
||||||
|
if (ent->monsterinfo.aiflags & AI_RESURRECTING)
|
||||||
|
{
|
||||||
|
ent->s.effects |= EF_COLOR_SHELL;
|
||||||
|
ent->s.renderfx |= RF_SHELL_RED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ent->health <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ent->powerarmor_time > level.time)
|
||||||
|
{
|
||||||
|
if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN)
|
||||||
|
{
|
||||||
|
ent->s.effects |= EF_POWERSCREEN;
|
||||||
|
}
|
||||||
|
else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD)
|
||||||
|
{
|
||||||
|
ent->s.effects |= EF_COLOR_SHELL;
|
||||||
|
ent->s.renderfx |= RF_SHELL_GREEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void M_MoveFrame (edict_t *self)
|
||||||
|
{
|
||||||
|
mmove_t *move;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
move = self->monsterinfo.currentmove;
|
||||||
|
self->nextthink = level.time + FRAMETIME;
|
||||||
|
|
||||||
|
if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe))
|
||||||
|
{
|
||||||
|
self->s.frame = self->monsterinfo.nextframe;
|
||||||
|
self->monsterinfo.nextframe = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (self->s.frame == move->lastframe)
|
||||||
|
{
|
||||||
|
if (move->endfunc)
|
||||||
|
{
|
||||||
|
move->endfunc (self);
|
||||||
|
|
||||||
|
// regrab move, endfunc is very likely to change it
|
||||||
|
move = self->monsterinfo.currentmove;
|
||||||
|
|
||||||
|
// check for death
|
||||||
|
if (self->svflags & SVF_DEADMONSTER)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self->s.frame < move->firstframe || self->s.frame > move->lastframe)
|
||||||
|
{
|
||||||
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
||||||
|
self->s.frame = move->firstframe;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
|
||||||
|
{
|
||||||
|
self->s.frame++;
|
||||||
|
if (self->s.frame > move->lastframe)
|
||||||
|
self->s.frame = move->firstframe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index = self->s.frame - move->firstframe;
|
||||||
|
if (move->frame[index].aifunc)
|
||||||
|
{
|
||||||
|
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
|
||||||
|
move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale);
|
||||||
|
else
|
||||||
|
move->frame[index].aifunc (self, 0);
|
||||||
|
}
|
||||||
|
if (move->frame[index].thinkfunc)
|
||||||
|
move->frame[index].thinkfunc (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void monster_think (edict_t *self)
|
||||||
|
{
|
||||||
|
M_MoveFrame (self);
|
||||||
|
if (self->linkcount != self->monsterinfo.linkcount)
|
||||||
|
{
|
||||||
|
self->monsterinfo.linkcount = self->linkcount;
|
||||||
|
M_CheckGround (self);
|
||||||
|
}
|
||||||
|
M_CatagorizePosition (self);
|
||||||
|
M_WorldEffects (self);
|
||||||
|
M_SetEffects (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
monster_use
|
||||||
|
|
||||||
|
Using a monster makes it angry at the current activator
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
void monster_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
if (self->enemy)
|
||||||
|
return;
|
||||||
|
if (self->health <= 0)
|
||||||
|
return;
|
||||||
|
if (activator->flags & FL_NOTARGET)
|
||||||
|
return;
|
||||||
|
if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// delay reaction so if the monster is teleported, its sound is still heard
|
||||||
|
self->enemy = activator;
|
||||||
|
FoundTarget (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void monster_start_go (edict_t *self);
|
||||||
|
|
||||||
|
|
||||||
|
void monster_triggered_spawn (edict_t *self)
|
||||||
|
{
|
||||||
|
self->s.origin[2] += 1;
|
||||||
|
KillBox (self);
|
||||||
|
|
||||||
|
self->solid = SOLID_BBOX;
|
||||||
|
self->movetype = MOVETYPE_STEP;
|
||||||
|
self->svflags &= ~SVF_NOCLIENT;
|
||||||
|
self->air_finished = level.time + 12;
|
||||||
|
gi.linkentity (self);
|
||||||
|
|
||||||
|
monster_start_go (self);
|
||||||
|
|
||||||
|
if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
|
||||||
|
{
|
||||||
|
FoundTarget (self);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self->enemy = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
// we have a one frame delay here so we don't telefrag the guy who activated us
|
||||||
|
self->think = monster_triggered_spawn;
|
||||||
|
self->nextthink = level.time + FRAMETIME;
|
||||||
|
if (activator->client)
|
||||||
|
self->enemy = activator;
|
||||||
|
self->use = monster_use;
|
||||||
|
}
|
||||||
|
|
||||||
|
void monster_triggered_start (edict_t *self)
|
||||||
|
{
|
||||||
|
self->solid = SOLID_NOT;
|
||||||
|
self->movetype = MOVETYPE_NONE;
|
||||||
|
self->svflags |= SVF_NOCLIENT;
|
||||||
|
self->nextthink = 0;
|
||||||
|
self->use = monster_triggered_spawn_use;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
monster_death_use
|
||||||
|
|
||||||
|
When a monster dies, it fires all of its targets with the current
|
||||||
|
enemy as activator.
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
void monster_death_use (edict_t *self)
|
||||||
|
{
|
||||||
|
self->flags &= ~(FL_FLY|FL_SWIM);
|
||||||
|
self->monsterinfo.aiflags &= AI_GOOD_GUY;
|
||||||
|
|
||||||
|
if (self->item)
|
||||||
|
{
|
||||||
|
Drop_Item (self, self->item);
|
||||||
|
self->item = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self->deathtarget)
|
||||||
|
self->target = self->deathtarget;
|
||||||
|
|
||||||
|
if (!self->target)
|
||||||
|
return;
|
||||||
|
|
||||||
|
G_UseTargets (self, self->enemy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
qboolean monster_start (edict_t *self)
|
||||||
|
{
|
||||||
|
if (deathmatch->value)
|
||||||
|
{
|
||||||
|
G_FreeEdict (self);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
|
||||||
|
{
|
||||||
|
self->spawnflags &= ~4;
|
||||||
|
self->spawnflags |= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
|
||||||
|
level.total_monsters++;
|
||||||
|
|
||||||
|
self->nextthink = level.time + FRAMETIME;
|
||||||
|
self->svflags |= SVF_MONSTER;
|
||||||
|
self->s.renderfx |= RF_FRAMELERP;
|
||||||
|
self->takedamage = DAMAGE_AIM;
|
||||||
|
self->air_finished = level.time + 12;
|
||||||
|
self->use = monster_use;
|
||||||
|
self->max_health = self->health;
|
||||||
|
self->clipmask = MASK_MONSTERSOLID;
|
||||||
|
|
||||||
|
self->s.skinnum = 0;
|
||||||
|
self->deadflag = DEAD_NO;
|
||||||
|
self->svflags &= ~SVF_DEADMONSTER;
|
||||||
|
|
||||||
|
if (!self->monsterinfo.checkattack)
|
||||||
|
self->monsterinfo.checkattack = M_CheckAttack;
|
||||||
|
VectorCopy (self->s.origin, self->s.old_origin);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomize what frame they start on
|
||||||
|
if (self->monsterinfo.currentmove)
|
||||||
|
self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void monster_start_go (edict_t *self)
|
||||||
|
{
|
||||||
|
vec3_t v;
|
||||||
|
|
||||||
|
if (self->health <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// check for target to combat_point and change to combattarget
|
||||||
|
if (self->target)
|
||||||
|
{
|
||||||
|
qboolean notcombat;
|
||||||
|
qboolean fixup;
|
||||||
|
edict_t *target;
|
||||||
|
|
||||||
|
target = NULL;
|
||||||
|
notcombat = false;
|
||||||
|
fixup = false;
|
||||||
|
while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL)
|
||||||
|
{
|
||||||
|
if (strcmp(target->classname, "point_combat") == 0)
|
||||||
|
{
|
||||||
|
self->combattarget = self->target;
|
||||||
|
fixup = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
notcombat = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (notcombat && self->combattarget)
|
||||||
|
gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin));
|
||||||
|
if (fixup)
|
||||||
|
self->target = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate combattarget
|
||||||
|
if (self->combattarget)
|
||||||
|
{
|
||||||
|
edict_t *target;
|
||||||
|
|
||||||
|
target = NULL;
|
||||||
|
while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL)
|
||||||
|
{
|
||||||
|
if (strcmp(target->classname, "point_combat") != 0)
|
||||||
|
{
|
||||||
|
gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
|
||||||
|
self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2],
|
||||||
|
self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1],
|
||||||
|
(int)target->s.origin[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self->target)
|
||||||
|
{
|
||||||
|
self->goalentity = self->movetarget = G_PickTarget(self->target);
|
||||||
|
if (!self->movetarget)
|
||||||
|
{
|
||||||
|
gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
|
||||||
|
self->target = NULL;
|
||||||
|
self->monsterinfo.pausetime = 100000000;
|
||||||
|
self->monsterinfo.stand (self);
|
||||||
|
}
|
||||||
|
else if (strcmp (self->movetarget->classname, "path_corner") == 0)
|
||||||
|
{
|
||||||
|
VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
|
||||||
|
self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
|
||||||
|
self->monsterinfo.walk (self);
|
||||||
|
self->target = NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self->goalentity = self->movetarget = NULL;
|
||||||
|
self->monsterinfo.pausetime = 100000000;
|
||||||
|
self->monsterinfo.stand (self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self->monsterinfo.pausetime = 100000000;
|
||||||
|
self->monsterinfo.stand (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
self->think = monster_think;
|
||||||
|
self->nextthink = level.time + FRAMETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void walkmonster_start_go (edict_t *self)
|
||||||
|
{
|
||||||
|
if (!(self->spawnflags & 2) && level.time < 1)
|
||||||
|
{
|
||||||
|
M_droptofloor (self);
|
||||||
|
|
||||||
|
if (self->groundentity)
|
||||||
|
if (!M_walkmove (self, 0, 0))
|
||||||
|
gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self->yaw_speed)
|
||||||
|
self->yaw_speed = 20;
|
||||||
|
self->viewheight = 25;
|
||||||
|
|
||||||
|
monster_start_go (self);
|
||||||
|
|
||||||
|
if (self->spawnflags & 2)
|
||||||
|
monster_triggered_start (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void walkmonster_start (edict_t *self)
|
||||||
|
{
|
||||||
|
self->think = walkmonster_start_go;
|
||||||
|
monster_start (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void flymonster_start_go (edict_t *self)
|
||||||
|
{
|
||||||
|
if (!M_walkmove (self, 0, 0))
|
||||||
|
gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
|
||||||
|
|
||||||
|
if (!self->yaw_speed)
|
||||||
|
self->yaw_speed = 10;
|
||||||
|
self->viewheight = 25;
|
||||||
|
|
||||||
|
monster_start_go (self);
|
||||||
|
|
||||||
|
if (self->spawnflags & 2)
|
||||||
|
monster_triggered_start (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void flymonster_start (edict_t *self)
|
||||||
|
{
|
||||||
|
self->flags |= FL_FLY;
|
||||||
|
self->think = flymonster_start_go;
|
||||||
|
monster_start (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void swimmonster_start_go (edict_t *self)
|
||||||
|
{
|
||||||
|
if (!self->yaw_speed)
|
||||||
|
self->yaw_speed = 10;
|
||||||
|
self->viewheight = 10;
|
||||||
|
|
||||||
|
monster_start_go (self);
|
||||||
|
|
||||||
|
if (self->spawnflags & 2)
|
||||||
|
monster_triggered_start (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void swimmonster_start (edict_t *self)
|
||||||
|
{
|
||||||
|
self->flags |= FL_SWIM;
|
||||||
|
self->think = swimmonster_start_go;
|
||||||
|
monster_start (self);
|
||||||
|
}
|
||||||
|
|
950
src/g_phys.c
Normal file
950
src/g_phys.c
Normal file
|
@ -0,0 +1,950 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
// g_phys.c
|
||||||
|
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move.
|
||||||
|
|
||||||
|
onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects
|
||||||
|
|
||||||
|
doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH
|
||||||
|
bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS
|
||||||
|
corpses are SOLID_NOT and MOVETYPE_TOSS
|
||||||
|
crates are SOLID_BBOX and MOVETYPE_TOSS
|
||||||
|
walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP
|
||||||
|
flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY
|
||||||
|
|
||||||
|
solid_edge items only clip against bsp models.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
SV_TestEntityPosition
|
||||||
|
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
edict_t *SV_TestEntityPosition (edict_t *ent)
|
||||||
|
{
|
||||||
|
trace_t trace;
|
||||||
|
int mask;
|
||||||
|
|
||||||
|
|
||||||
|
if (ent->clipmask)
|
||||||
|
mask = ent->clipmask;
|
||||||
|
else
|
||||||
|
mask = MASK_SOLID;
|
||||||
|
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask);
|
||||||
|
|
||||||
|
if (trace.startsolid)
|
||||||
|
return g_edicts;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
SV_CheckVelocity
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
void SV_CheckVelocity (edict_t *ent)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
//
|
||||||
|
// bound velocity
|
||||||
|
//
|
||||||
|
for (i=0 ; i<3 ; i++)
|
||||||
|
{
|
||||||
|
if (ent->velocity[i] > sv_maxvelocity->value)
|
||||||
|
ent->velocity[i] = sv_maxvelocity->value;
|
||||||
|
else if (ent->velocity[i] < -sv_maxvelocity->value)
|
||||||
|
ent->velocity[i] = -sv_maxvelocity->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
SV_RunThink
|
||||||
|
|
||||||
|
Runs thinking code for this frame if necessary
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
qboolean SV_RunThink (edict_t *ent)
|
||||||
|
{
|
||||||
|
float thinktime;
|
||||||
|
|
||||||
|
thinktime = ent->nextthink;
|
||||||
|
if (thinktime <= 0)
|
||||||
|
return true;
|
||||||
|
if (thinktime > level.time+0.001)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
ent->nextthink = 0;
|
||||||
|
if (!ent->think)
|
||||||
|
gi.error ("NULL ent->think");
|
||||||
|
ent->think (ent);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
==================
|
||||||
|
SV_Impact
|
||||||
|
|
||||||
|
Two entities have touched, so run their touch functions
|
||||||
|
==================
|
||||||
|
*/
|
||||||
|
void SV_Impact (edict_t *e1, trace_t *trace)
|
||||||
|
{
|
||||||
|
edict_t *e2;
|
||||||
|
// cplane_t backplane;
|
||||||
|
|
||||||
|
e2 = trace->ent;
|
||||||
|
|
||||||
|
if (e1->touch && e1->solid != SOLID_NOT)
|
||||||
|
e1->touch (e1, e2, &trace->plane, trace->surface);
|
||||||
|
|
||||||
|
if (e2->touch && e2->solid != SOLID_NOT)
|
||||||
|
e2->touch (e2, e1, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==================
|
||||||
|
ClipVelocity
|
||||||
|
|
||||||
|
Slide off of the impacting object
|
||||||
|
returns the blocked flags (1 = floor, 2 = step / wall)
|
||||||
|
==================
|
||||||
|
*/
|
||||||
|
#define STOP_EPSILON 0.1
|
||||||
|
|
||||||
|
int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce)
|
||||||
|
{
|
||||||
|
float backoff;
|
||||||
|
float change;
|
||||||
|
int i, blocked;
|
||||||
|
|
||||||
|
blocked = 0;
|
||||||
|
if (normal[2] > 0)
|
||||||
|
blocked |= 1; // floor
|
||||||
|
if (!normal[2])
|
||||||
|
blocked |= 2; // step
|
||||||
|
|
||||||
|
backoff = DotProduct (in, normal) * overbounce;
|
||||||
|
|
||||||
|
for (i=0 ; i<3 ; i++)
|
||||||
|
{
|
||||||
|
change = normal[i]*backoff;
|
||||||
|
out[i] = in[i] - change;
|
||||||
|
if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
|
||||||
|
out[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
SV_FlyMove
|
||||||
|
|
||||||
|
The basic solid body movement clip that slides along multiple planes
|
||||||
|
Returns the clipflags if the velocity was modified (hit something solid)
|
||||||
|
1 = floor
|
||||||
|
2 = wall / step
|
||||||
|
4 = dead stop
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
#define MAX_CLIP_PLANES 5
|
||||||
|
int SV_FlyMove (edict_t *ent, float time, int mask)
|
||||||
|
{
|
||||||
|
edict_t *hit;
|
||||||
|
int bumpcount, numbumps;
|
||||||
|
vec3_t dir;
|
||||||
|
float d;
|
||||||
|
int numplanes;
|
||||||
|
vec3_t planes[MAX_CLIP_PLANES];
|
||||||
|
vec3_t primal_velocity, original_velocity, new_velocity;
|
||||||
|
int i, j;
|
||||||
|
trace_t trace;
|
||||||
|
vec3_t end;
|
||||||
|
float time_left;
|
||||||
|
int blocked;
|
||||||
|
|
||||||
|
numbumps = 4;
|
||||||
|
|
||||||
|
blocked = 0;
|
||||||
|
VectorCopy (ent->velocity, original_velocity);
|
||||||
|
VectorCopy (ent->velocity, primal_velocity);
|
||||||
|
numplanes = 0;
|
||||||
|
|
||||||
|
time_left = time;
|
||||||
|
|
||||||
|
ent->groundentity = NULL;
|
||||||
|
for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
|
||||||
|
{
|
||||||
|
for (i=0 ; i<3 ; i++)
|
||||||
|
end[i] = ent->s.origin[i] + time_left * ent->velocity[i];
|
||||||
|
|
||||||
|
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask);
|
||||||
|
|
||||||
|
if (trace.allsolid)
|
||||||
|
{ // entity is trapped in another solid
|
||||||
|
VectorCopy (vec3_origin, ent->velocity);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trace.fraction > 0)
|
||||||
|
{ // actually covered some distance
|
||||||
|
VectorCopy (trace.endpos, ent->s.origin);
|
||||||
|
VectorCopy (ent->velocity, original_velocity);
|
||||||
|
numplanes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trace.fraction == 1)
|
||||||
|
break; // moved the entire distance
|
||||||
|
|
||||||
|
hit = trace.ent;
|
||||||
|
|
||||||
|
if (trace.plane.normal[2] > 0.7)
|
||||||
|
{
|
||||||
|
blocked |= 1; // floor
|
||||||
|
if ( hit->solid == SOLID_BSP)
|
||||||
|
{
|
||||||
|
ent->groundentity = hit;
|
||||||
|
ent->groundentity_linkcount = hit->linkcount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!trace.plane.normal[2])
|
||||||
|
{
|
||||||
|
blocked |= 2; // step
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// run the impact function
|
||||||
|
//
|
||||||
|
SV_Impact (ent, &trace);
|
||||||
|
if (!ent->inuse)
|
||||||
|
break; // removed by the impact function
|
||||||
|
|
||||||
|
|
||||||
|
time_left -= time_left * trace.fraction;
|
||||||
|
|
||||||
|
// cliped to another plane
|
||||||
|
if (numplanes >= MAX_CLIP_PLANES)
|
||||||
|
{ // this shouldn't really happen
|
||||||
|
VectorCopy (vec3_origin, ent->velocity);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
VectorCopy (trace.plane.normal, planes[numplanes]);
|
||||||
|
numplanes++;
|
||||||
|
|
||||||
|
//
|
||||||
|
// modify original_velocity so it parallels all of the clip planes
|
||||||
|
//
|
||||||
|
for (i=0 ; i<numplanes ; i++)
|
||||||
|
{
|
||||||
|
ClipVelocity (original_velocity, planes[i], new_velocity, 1);
|
||||||
|
for (j=0 ; j<numplanes ; j++)
|
||||||
|
if (j != i)
|
||||||
|
{
|
||||||
|
if (DotProduct (new_velocity, planes[j]) < 0)
|
||||||
|
break; // not ok
|
||||||
|
}
|
||||||
|
if (j == numplanes)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i != numplanes)
|
||||||
|
{ // go along this plane
|
||||||
|
VectorCopy (new_velocity, ent->velocity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // go along the crease
|
||||||
|
if (numplanes != 2)
|
||||||
|
{
|
||||||
|
VectorCopy (vec3_origin, ent->velocity);
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
CrossProduct (planes[0], planes[1], dir);
|
||||||
|
d = DotProduct (dir, ent->velocity);
|
||||||
|
VectorScale (dir, d, ent->velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// if original velocity is against the original velocity, stop dead
|
||||||
|
// to avoid tiny occilations in sloping corners
|
||||||
|
//
|
||||||
|
if (DotProduct (ent->velocity, primal_velocity) <= 0)
|
||||||
|
{
|
||||||
|
VectorCopy (vec3_origin, ent->velocity);
|
||||||
|
return blocked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
SV_AddGravity
|
||||||
|
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
void SV_AddGravity (edict_t *ent)
|
||||||
|
{
|
||||||
|
ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
===============================================================================
|
||||||
|
|
||||||
|
PUSHMOVE
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
SV_PushEntity
|
||||||
|
|
||||||
|
Does not change the entities velocity at all
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
trace_t SV_PushEntity (edict_t *ent, vec3_t push)
|
||||||
|
{
|
||||||
|
trace_t trace;
|
||||||
|
vec3_t start;
|
||||||
|
vec3_t end;
|
||||||
|
int mask;
|
||||||
|
|
||||||
|
VectorCopy (ent->s.origin, start);
|
||||||
|
VectorAdd (start, push, end);
|
||||||
|
|
||||||
|
retry:
|
||||||
|
if (ent->clipmask)
|
||||||
|
mask = ent->clipmask;
|
||||||
|
else
|
||||||
|
mask = MASK_SOLID;
|
||||||
|
|
||||||
|
trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask);
|
||||||
|
|
||||||
|
VectorCopy (trace.endpos, ent->s.origin);
|
||||||
|
gi.linkentity (ent);
|
||||||
|
|
||||||
|
if (trace.fraction != 1.0)
|
||||||
|
{
|
||||||
|
SV_Impact (ent, &trace);
|
||||||
|
|
||||||
|
// if the pushed entity went away and the pusher is still there
|
||||||
|
if (!trace.ent->inuse && ent->inuse)
|
||||||
|
{
|
||||||
|
// move the pusher back and try again
|
||||||
|
VectorCopy (start, ent->s.origin);
|
||||||
|
gi.linkentity (ent);
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ent->inuse)
|
||||||
|
G_TouchTriggers (ent);
|
||||||
|
|
||||||
|
return trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
edict_t *ent;
|
||||||
|
vec3_t origin;
|
||||||
|
vec3_t angles;
|
||||||
|
float deltayaw;
|
||||||
|
} pushed_t;
|
||||||
|
pushed_t pushed[MAX_EDICTS], *pushed_p;
|
||||||
|
|
||||||
|
edict_t *obstacle;
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
SV_Push
|
||||||
|
|
||||||
|
Objects need to be moved back on a failed push,
|
||||||
|
otherwise riders would continue to slide.
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove)
|
||||||
|
{
|
||||||
|
int i, e;
|
||||||
|
edict_t *check, *block;
|
||||||
|
vec3_t mins, maxs;
|
||||||
|
pushed_t *p;
|
||||||
|
vec3_t org, org2, move2, forward, right, up;
|
||||||
|
|
||||||
|
// clamp the move to 1/8 units, so the position will
|
||||||
|
// be accurate for client side prediction
|
||||||
|
for (i=0 ; i<3 ; i++)
|
||||||
|
{
|
||||||
|
float temp;
|
||||||
|
temp = move[i]*8.0;
|
||||||
|
if (temp > 0.0)
|
||||||
|
temp += 0.5;
|
||||||
|
else
|
||||||
|
temp -= 0.5;
|
||||||
|
move[i] = 0.125 * (int)temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the bounding box
|
||||||
|
for (i=0 ; i<3 ; i++)
|
||||||
|
{
|
||||||
|
mins[i] = pusher->absmin[i] + move[i];
|
||||||
|
maxs[i] = pusher->absmax[i] + move[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need this for pushing things later
|
||||||
|
VectorSubtract (vec3_origin, amove, org);
|
||||||
|
AngleVectors (org, forward, right, up);
|
||||||
|
|
||||||
|
// save the pusher's original position
|
||||||
|
pushed_p->ent = pusher;
|
||||||
|
VectorCopy (pusher->s.origin, pushed_p->origin);
|
||||||
|
VectorCopy (pusher->s.angles, pushed_p->angles);
|
||||||
|
if (pusher->client)
|
||||||
|
pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW];
|
||||||
|
pushed_p++;
|
||||||
|
|
||||||
|
// move the pusher to it's final position
|
||||||
|
VectorAdd (pusher->s.origin, move, pusher->s.origin);
|
||||||
|
VectorAdd (pusher->s.angles, amove, pusher->s.angles);
|
||||||
|
gi.linkentity (pusher);
|
||||||
|
|
||||||
|
// see if any solid entities are inside the final position
|
||||||
|
check = g_edicts+1;
|
||||||
|
for (e = 1; e < globals.num_edicts; e++, check++)
|
||||||
|
{
|
||||||
|
if (!check->inuse)
|
||||||
|
continue;
|
||||||
|
if (check->movetype == MOVETYPE_PUSH
|
||||||
|
|| check->movetype == MOVETYPE_STOP
|
||||||
|
|| check->movetype == MOVETYPE_NONE
|
||||||
|
|| check->movetype == MOVETYPE_NOCLIP)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!check->area.prev)
|
||||||
|
continue; // not linked in anywhere
|
||||||
|
|
||||||
|
// if the entity is standing on the pusher, it will definitely be moved
|
||||||
|
if (check->groundentity != pusher)
|
||||||
|
{
|
||||||
|
// see if the ent needs to be tested
|
||||||
|
if ( check->absmin[0] >= maxs[0]
|
||||||
|
|| check->absmin[1] >= maxs[1]
|
||||||
|
|| check->absmin[2] >= maxs[2]
|
||||||
|
|| check->absmax[0] <= mins[0]
|
||||||
|
|| check->absmax[1] <= mins[1]
|
||||||
|
|| check->absmax[2] <= mins[2] )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// see if the ent's bbox is inside the pusher's final position
|
||||||
|
if (!SV_TestEntityPosition (check))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher))
|
||||||
|
{
|
||||||
|
// move this entity
|
||||||
|
pushed_p->ent = check;
|
||||||
|
VectorCopy (check->s.origin, pushed_p->origin);
|
||||||
|
VectorCopy (check->s.angles, pushed_p->angles);
|
||||||
|
pushed_p++;
|
||||||
|
|
||||||
|
// try moving the contacted entity
|
||||||
|
VectorAdd (check->s.origin, move, check->s.origin);
|
||||||
|
if (check->client)
|
||||||
|
{ // FIXME: doesn't rotate monsters?
|
||||||
|
check->client->ps.pmove.delta_angles[YAW] += amove[YAW];
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure movement due to the pusher's amove
|
||||||
|
VectorSubtract (check->s.origin, pusher->s.origin, org);
|
||||||
|
org2[0] = DotProduct (org, forward);
|
||||||
|
org2[1] = -DotProduct (org, right);
|
||||||
|
org2[2] = DotProduct (org, up);
|
||||||
|
VectorSubtract (org2, org, move2);
|
||||||
|
VectorAdd (check->s.origin, move2, check->s.origin);
|
||||||
|
|
||||||
|
// may have pushed them off an edge
|
||||||
|
if (check->groundentity != pusher)
|
||||||
|
check->groundentity = NULL;
|
||||||
|
|
||||||
|
block = SV_TestEntityPosition (check);
|
||||||
|
if (!block)
|
||||||
|
{ // pushed ok
|
||||||
|
gi.linkentity (check);
|
||||||
|
// impact?
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it is ok to leave in the old position, do it
|
||||||
|
// this is only relevent for riding entities, not pushed
|
||||||
|
// FIXME: this doesn't acount for rotation
|
||||||
|
VectorSubtract (check->s.origin, move, check->s.origin);
|
||||||
|
block = SV_TestEntityPosition (check);
|
||||||
|
if (!block)
|
||||||
|
{
|
||||||
|
pushed_p--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save off the obstacle so we can call the block function
|
||||||
|
obstacle = check;
|
||||||
|
|
||||||
|
// move back any entities we already moved
|
||||||
|
// go backwards, so if the same entity was pushed
|
||||||
|
// twice, it goes back to the original position
|
||||||
|
for (p=pushed_p-1 ; p>=pushed ; p--)
|
||||||
|
{
|
||||||
|
VectorCopy (p->origin, p->ent->s.origin);
|
||||||
|
VectorCopy (p->angles, p->ent->s.angles);
|
||||||
|
if (p->ent->client)
|
||||||
|
{
|
||||||
|
p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw;
|
||||||
|
}
|
||||||
|
gi.linkentity (p->ent);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//FIXME: is there a better way to handle this?
|
||||||
|
// see if anything we moved has touched a trigger
|
||||||
|
for (p=pushed_p-1 ; p>=pushed ; p--)
|
||||||
|
G_TouchTriggers (p->ent);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
SV_Physics_Pusher
|
||||||
|
|
||||||
|
Bmodel objects don't interact with each other, but
|
||||||
|
push all box objects
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
void SV_Physics_Pusher (edict_t *ent)
|
||||||
|
{
|
||||||
|
vec3_t move, amove;
|
||||||
|
edict_t *part, *mv;
|
||||||
|
|
||||||
|
// if not a team captain, so movement will be handled elsewhere
|
||||||
|
if ( ent->flags & FL_TEAMSLAVE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// make sure all team slaves can move before commiting
|
||||||
|
// any moves or calling any think functions
|
||||||
|
// if the move is blocked, all moved objects will be backed out
|
||||||
|
pushed_p = pushed;
|
||||||
|
for (part = ent ; part ; part=part->teamchain)
|
||||||
|
{
|
||||||
|
if (part->velocity[0] || part->velocity[1] || part->velocity[2] ||
|
||||||
|
part->avelocity[0] || part->avelocity[1] || part->avelocity[2]
|
||||||
|
)
|
||||||
|
{ // object is moving
|
||||||
|
VectorScale (part->velocity, FRAMETIME, move);
|
||||||
|
VectorScale (part->avelocity, FRAMETIME, amove);
|
||||||
|
|
||||||
|
if (!SV_Push (part, move, amove))
|
||||||
|
break; // move was blocked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pushed_p > &pushed[MAX_EDICTS])
|
||||||
|
gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted");
|
||||||
|
|
||||||
|
if (part)
|
||||||
|
{
|
||||||
|
// the move failed, bump all nextthink times and back out moves
|
||||||
|
for (mv = ent ; mv ; mv=mv->teamchain)
|
||||||
|
{
|
||||||
|
if (mv->nextthink > 0)
|
||||||
|
mv->nextthink += FRAMETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the pusher has a "blocked" function, call it
|
||||||
|
// otherwise, just stay in place until the obstacle is gone
|
||||||
|
if (part->blocked)
|
||||||
|
part->blocked (part, obstacle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// the move succeeded, so call all think functions
|
||||||
|
for (part = ent ; part ; part=part->teamchain)
|
||||||
|
{
|
||||||
|
SV_RunThink (part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
SV_Physics_None
|
||||||
|
|
||||||
|
Non moving objects can only think
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
void SV_Physics_None (edict_t *ent)
|
||||||
|
{
|
||||||
|
// regular thinking
|
||||||
|
SV_RunThink (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
SV_Physics_Noclip
|
||||||
|
|
||||||
|
A moving object that doesn't obey physics
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
void SV_Physics_Noclip (edict_t *ent)
|
||||||
|
{
|
||||||
|
// regular thinking
|
||||||
|
if (!SV_RunThink (ent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
|
||||||
|
VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin);
|
||||||
|
|
||||||
|
gi.linkentity (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
TOSS / BOUNCE
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
SV_Physics_Toss
|
||||||
|
|
||||||
|
Toss, bounce, and fly movement. When onground, do nothing.
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
void SV_Physics_Toss (edict_t *ent)
|
||||||
|
{
|
||||||
|
trace_t trace;
|
||||||
|
vec3_t move;
|
||||||
|
float backoff;
|
||||||
|
edict_t *slave;
|
||||||
|
qboolean wasinwater;
|
||||||
|
qboolean isinwater;
|
||||||
|
vec3_t old_origin;
|
||||||
|
|
||||||
|
// regular thinking
|
||||||
|
SV_RunThink (ent);
|
||||||
|
|
||||||
|
// if not a team captain, so movement will be handled elsewhere
|
||||||
|
if ( ent->flags & FL_TEAMSLAVE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ent->velocity[2] > 0)
|
||||||
|
ent->groundentity = NULL;
|
||||||
|
|
||||||
|
// check for the groundentity going away
|
||||||
|
if (ent->groundentity)
|
||||||
|
if (!ent->groundentity->inuse)
|
||||||
|
ent->groundentity = NULL;
|
||||||
|
|
||||||
|
// if onground, return without moving
|
||||||
|
if ( ent->groundentity )
|
||||||
|
return;
|
||||||
|
|
||||||
|
VectorCopy (ent->s.origin, old_origin);
|
||||||
|
|
||||||
|
SV_CheckVelocity (ent);
|
||||||
|
|
||||||
|
// add gravity
|
||||||
|
if (ent->movetype != MOVETYPE_FLY
|
||||||
|
&& ent->movetype != MOVETYPE_FLYMISSILE)
|
||||||
|
SV_AddGravity (ent);
|
||||||
|
|
||||||
|
// move angles
|
||||||
|
VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
|
||||||
|
|
||||||
|
// move origin
|
||||||
|
VectorScale (ent->velocity, FRAMETIME, move);
|
||||||
|
trace = SV_PushEntity (ent, move);
|
||||||
|
if (!ent->inuse)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (trace.fraction < 1)
|
||||||
|
{
|
||||||
|
if (ent->movetype == MOVETYPE_BOUNCE)
|
||||||
|
backoff = 1.5;
|
||||||
|
else
|
||||||
|
backoff = 1;
|
||||||
|
|
||||||
|
ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff);
|
||||||
|
|
||||||
|
// stop if on ground
|
||||||
|
if (trace.plane.normal[2] > 0.7)
|
||||||
|
{
|
||||||
|
if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE )
|
||||||
|
{
|
||||||
|
ent->groundentity = trace.ent;
|
||||||
|
ent->groundentity_linkcount = trace.ent->linkcount;
|
||||||
|
VectorCopy (vec3_origin, ent->velocity);
|
||||||
|
VectorCopy (vec3_origin, ent->avelocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
===============================================================================
|
||||||
|
|
||||||
|
STEPPING MOVEMENT
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
SV_Physics_Step
|
||||||
|
|
||||||
|
Monsters freefall when they don't have a ground entity, otherwise
|
||||||
|
all movement is done with discrete steps.
|
||||||
|
|
||||||
|
This is also used for objects that have become still on the ground, but
|
||||||
|
will fall if the floor is pulled out from under them.
|
||||||
|
FIXME: is this true?
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
|
||||||
|
//FIXME: hacked in for E3 demo
|
||||||
|
#define sv_stopspeed 100
|
||||||
|
#define sv_friction 6
|
||||||
|
#define sv_waterfriction 1
|
||||||
|
|
||||||
|
void SV_AddRotationalFriction (edict_t *ent)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
float adjustment;
|
||||||
|
|
||||||
|
VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
|
||||||
|
adjustment = FRAMETIME * sv_stopspeed * sv_friction;
|
||||||
|
for (n = 0; n < 3; n++)
|
||||||
|
{
|
||||||
|
if (ent->avelocity[n] > 0)
|
||||||
|
{
|
||||||
|
ent->avelocity[n] -= adjustment;
|
||||||
|
if (ent->avelocity[n] < 0)
|
||||||
|
ent->avelocity[n] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ent->avelocity[n] += adjustment;
|
||||||
|
if (ent->avelocity[n] > 0)
|
||||||
|
ent->avelocity[n] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SV_Physics_Step (edict_t *ent)
|
||||||
|
{
|
||||||
|
qboolean wasonground;
|
||||||
|
qboolean hitsound = false;
|
||||||
|
float *vel;
|
||||||
|
float speed, newspeed, control;
|
||||||
|
float friction;
|
||||||
|
edict_t *groundentity;
|
||||||
|
int mask;
|
||||||
|
|
||||||
|
// airborn monsters should always check for ground
|
||||||
|
if (!ent->groundentity)
|
||||||
|
M_CheckGround (ent);
|
||||||
|
|
||||||
|
groundentity = ent->groundentity;
|
||||||
|
|
||||||
|
SV_CheckVelocity (ent);
|
||||||
|
|
||||||
|
if (groundentity)
|
||||||
|
wasonground = true;
|
||||||
|
else
|
||||||
|
wasonground = false;
|
||||||
|
|
||||||
|
if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
|
||||||
|
SV_AddRotationalFriction (ent);
|
||||||
|
|
||||||
|
// add gravity except:
|
||||||
|
// flying monsters
|
||||||
|
// swimming monsters who are in the water
|
||||||
|
if (! wasonground)
|
||||||
|
if (!(ent->flags & FL_FLY))
|
||||||
|
if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2)))
|
||||||
|
{
|
||||||
|
if (ent->velocity[2] < sv_gravity->value*-0.1)
|
||||||
|
hitsound = true;
|
||||||
|
if (ent->waterlevel == 0)
|
||||||
|
SV_AddGravity (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// friction for flying monsters that have been given vertical velocity
|
||||||
|
if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0))
|
||||||
|
{
|
||||||
|
speed = fabs(ent->velocity[2]);
|
||||||
|
control = speed < sv_stopspeed ? sv_stopspeed : speed;
|
||||||
|
friction = sv_friction/3;
|
||||||
|
newspeed = speed - (FRAMETIME * control * friction);
|
||||||
|
if (newspeed < 0)
|
||||||
|
newspeed = 0;
|
||||||
|
newspeed /= speed;
|
||||||
|
ent->velocity[2] *= newspeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// friction for flying monsters that have been given vertical velocity
|
||||||
|
if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0))
|
||||||
|
{
|
||||||
|
speed = fabs(ent->velocity[2]);
|
||||||
|
control = speed < sv_stopspeed ? sv_stopspeed : speed;
|
||||||
|
newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel);
|
||||||
|
if (newspeed < 0)
|
||||||
|
newspeed = 0;
|
||||||
|
newspeed /= speed;
|
||||||
|
ent->velocity[2] *= newspeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0])
|
||||||
|
{
|
||||||
|
// apply friction
|
||||||
|
// let dead monsters who aren't completely onground slide
|
||||||
|
if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY)))
|
||||||
|
if (!(ent->health <= 0.0 && !M_CheckBottom(ent)))
|
||||||
|
{
|
||||||
|
vel = ent->velocity;
|
||||||
|
speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]);
|
||||||
|
if (speed)
|
||||||
|
{
|
||||||
|
friction = sv_friction;
|
||||||
|
|
||||||
|
control = speed < sv_stopspeed ? sv_stopspeed : speed;
|
||||||
|
newspeed = speed - FRAMETIME*control*friction;
|
||||||
|
|
||||||
|
if (newspeed < 0)
|
||||||
|
newspeed = 0;
|
||||||
|
newspeed /= speed;
|
||||||
|
|
||||||
|
vel[0] *= newspeed;
|
||||||
|
vel[1] *= newspeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ent->svflags & SVF_MONSTER)
|
||||||
|
mask = MASK_MONSTERSOLID;
|
||||||
|
else
|
||||||
|
mask = MASK_SOLID;
|
||||||
|
SV_FlyMove (ent, FRAMETIME, mask);
|
||||||
|
|
||||||
|
gi.linkentity (ent);
|
||||||
|
G_TouchTriggers (ent);
|
||||||
|
|
||||||
|
if (ent->groundentity)
|
||||||
|
if (!wasonground)
|
||||||
|
if (hitsound)
|
||||||
|
gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// regular thinking
|
||||||
|
SV_RunThink (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
G_RunEntity
|
||||||
|
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
void G_RunEntity (edict_t *ent)
|
||||||
|
{
|
||||||
|
if (ent->prethink)
|
||||||
|
ent->prethink (ent);
|
||||||
|
|
||||||
|
switch ( (int)ent->movetype)
|
||||||
|
{
|
||||||
|
case MOVETYPE_PUSH:
|
||||||
|
case MOVETYPE_STOP:
|
||||||
|
SV_Physics_Pusher (ent);
|
||||||
|
break;
|
||||||
|
case MOVETYPE_NONE:
|
||||||
|
SV_Physics_None (ent);
|
||||||
|
break;
|
||||||
|
case MOVETYPE_NOCLIP:
|
||||||
|
SV_Physics_Noclip (ent);
|
||||||
|
break;
|
||||||
|
case MOVETYPE_STEP:
|
||||||
|
SV_Physics_Step (ent);
|
||||||
|
break;
|
||||||
|
case MOVETYPE_TOSS:
|
||||||
|
case MOVETYPE_BOUNCE:
|
||||||
|
case MOVETYPE_FLY:
|
||||||
|
case MOVETYPE_FLYMISSILE:
|
||||||
|
SV_Physics_Toss (ent);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
747
src/g_save.c
Normal file
747
src/g_save.c
Normal file
|
@ -0,0 +1,747 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
field_t fields[] = {
|
||||||
|
{"classname", FOFS(classname), F_LSTRING},
|
||||||
|
{"origin", FOFS(s.origin), F_VECTOR},
|
||||||
|
{"model", FOFS(model), F_LSTRING},
|
||||||
|
{"spawnflags", FOFS(spawnflags), F_INT},
|
||||||
|
{"speed", FOFS(speed), F_FLOAT},
|
||||||
|
{"accel", FOFS(accel), F_FLOAT},
|
||||||
|
{"decel", FOFS(decel), F_FLOAT},
|
||||||
|
{"target", FOFS(target), F_LSTRING},
|
||||||
|
{"targetname", FOFS(targetname), F_LSTRING},
|
||||||
|
{"pathtarget", FOFS(pathtarget), F_LSTRING},
|
||||||
|
{"deathtarget", FOFS(deathtarget), F_LSTRING},
|
||||||
|
{"killtarget", FOFS(killtarget), F_LSTRING},
|
||||||
|
{"combattarget", FOFS(combattarget), F_LSTRING},
|
||||||
|
{"message", FOFS(message), F_LSTRING},
|
||||||
|
{"team", FOFS(team), F_LSTRING},
|
||||||
|
{"wait", FOFS(wait), F_FLOAT},
|
||||||
|
{"delay", FOFS(delay), F_FLOAT},
|
||||||
|
{"random", FOFS(random), F_FLOAT},
|
||||||
|
{"move_origin", FOFS(move_origin), F_VECTOR},
|
||||||
|
{"move_angles", FOFS(move_angles), F_VECTOR},
|
||||||
|
{"style", FOFS(style), F_INT},
|
||||||
|
{"count", FOFS(count), F_INT},
|
||||||
|
{"health", FOFS(health), F_INT},
|
||||||
|
{"sounds", FOFS(sounds), F_INT},
|
||||||
|
{"light", 0, F_IGNORE},
|
||||||
|
{"dmg", FOFS(dmg), F_INT},
|
||||||
|
{"angles", FOFS(s.angles), F_VECTOR},
|
||||||
|
{"angle", FOFS(s.angles), F_ANGLEHACK},
|
||||||
|
{"mass", FOFS(mass), F_INT},
|
||||||
|
{"volume", FOFS(volume), F_FLOAT},
|
||||||
|
{"attenuation", FOFS(attenuation), F_FLOAT},
|
||||||
|
{"map", FOFS(map), F_LSTRING},
|
||||||
|
|
||||||
|
// temp spawn vars -- only valid when the spawn function is called
|
||||||
|
{"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP},
|
||||||
|
{"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP},
|
||||||
|
{"height", STOFS(height), F_INT, FFL_SPAWNTEMP},
|
||||||
|
{"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP},
|
||||||
|
{"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP},
|
||||||
|
{"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP},
|
||||||
|
{"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP},
|
||||||
|
{"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP},
|
||||||
|
{"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP},
|
||||||
|
{"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP},
|
||||||
|
{"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP},
|
||||||
|
{"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP},
|
||||||
|
{"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP},
|
||||||
|
{"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP},
|
||||||
|
{"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP}
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------- just for savegames ----------
|
||||||
|
// all pointer fields should be listed here, or savegames
|
||||||
|
// won't work properly (they will crash and burn).
|
||||||
|
// this wasn't just tacked on to the fields array, because
|
||||||
|
// these don't need names, we wouldn't want map fields using
|
||||||
|
// some of these, and if one were accidentally present twice
|
||||||
|
// it would double swizzle (fuck) the pointer.
|
||||||
|
|
||||||
|
field_t savefields[] =
|
||||||
|
{
|
||||||
|
{"", FOFS(classname), F_LSTRING},
|
||||||
|
{"", FOFS(target), F_LSTRING},
|
||||||
|
{"", FOFS(targetname), F_LSTRING},
|
||||||
|
{"", FOFS(killtarget), F_LSTRING},
|
||||||
|
{"", FOFS(team), F_LSTRING},
|
||||||
|
{"", FOFS(pathtarget), F_LSTRING},
|
||||||
|
{"", FOFS(deathtarget), F_LSTRING},
|
||||||
|
{"", FOFS(combattarget), F_LSTRING},
|
||||||
|
{"", FOFS(model), F_LSTRING},
|
||||||
|
{"", FOFS(map), F_LSTRING},
|
||||||
|
{"", FOFS(message), F_LSTRING},
|
||||||
|
|
||||||
|
{"", FOFS(client), F_CLIENT},
|
||||||
|
{"", FOFS(item), F_ITEM},
|
||||||
|
|
||||||
|
{"", FOFS(goalentity), F_EDICT},
|
||||||
|
{"", FOFS(movetarget), F_EDICT},
|
||||||
|
{"", FOFS(enemy), F_EDICT},
|
||||||
|
{"", FOFS(oldenemy), F_EDICT},
|
||||||
|
{"", FOFS(activator), F_EDICT},
|
||||||
|
{"", FOFS(groundentity), F_EDICT},
|
||||||
|
{"", FOFS(teamchain), F_EDICT},
|
||||||
|
{"", FOFS(teammaster), F_EDICT},
|
||||||
|
{"", FOFS(owner), F_EDICT},
|
||||||
|
{"", FOFS(mynoise), F_EDICT},
|
||||||
|
{"", FOFS(mynoise2), F_EDICT},
|
||||||
|
{"", FOFS(target_ent), F_EDICT},
|
||||||
|
{"", FOFS(chain), F_EDICT},
|
||||||
|
|
||||||
|
{NULL, 0, F_INT}
|
||||||
|
};
|
||||||
|
|
||||||
|
field_t levelfields[] =
|
||||||
|
{
|
||||||
|
{"", LLOFS(changemap), F_LSTRING},
|
||||||
|
|
||||||
|
{"", LLOFS(sight_client), F_EDICT},
|
||||||
|
{"", LLOFS(sight_entity), F_EDICT},
|
||||||
|
{"", LLOFS(sound_entity), F_EDICT},
|
||||||
|
{"", LLOFS(sound2_entity), F_EDICT},
|
||||||
|
|
||||||
|
{NULL, 0, F_INT}
|
||||||
|
};
|
||||||
|
|
||||||
|
field_t clientfields[] =
|
||||||
|
{
|
||||||
|
{"", CLOFS(pers.weapon), F_ITEM},
|
||||||
|
{"", CLOFS(pers.lastweapon), F_ITEM},
|
||||||
|
{"", CLOFS(newweapon), F_ITEM},
|
||||||
|
|
||||||
|
{NULL, 0, F_INT}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
InitGame
|
||||||
|
|
||||||
|
This will be called when the dll is first loaded, which
|
||||||
|
only happens when a new game is started or a save game
|
||||||
|
is loaded.
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
void InitGame (void)
|
||||||
|
{
|
||||||
|
gi.dprintf ("Game is starting up.\n");
|
||||||
|
gi.dprintf ("Game is ctf.\n");
|
||||||
|
|
||||||
|
gun_x = gi.cvar ("gun_x", "0", 0);
|
||||||
|
gun_y = gi.cvar ("gun_y", "0", 0);
|
||||||
|
gun_z = gi.cvar ("gun_z", "0", 0);
|
||||||
|
|
||||||
|
//FIXME: sv_ prefix is wrong for these
|
||||||
|
sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0);
|
||||||
|
sv_rollangle = gi.cvar ("sv_rollangle", "2", 0);
|
||||||
|
sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0);
|
||||||
|
sv_gravity = gi.cvar ("sv_gravity", "800", 0);
|
||||||
|
|
||||||
|
// noset vars
|
||||||
|
dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET);
|
||||||
|
|
||||||
|
// latched vars
|
||||||
|
sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
|
||||||
|
gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH);
|
||||||
|
gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH);
|
||||||
|
|
||||||
|
maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
|
||||||
|
deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH);
|
||||||
|
coop = gi.cvar ("coop", "0", CVAR_LATCH);
|
||||||
|
skill = gi.cvar ("skill", "1", CVAR_LATCH);
|
||||||
|
maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH);
|
||||||
|
|
||||||
|
//ZOID
|
||||||
|
//This game.dll only supports deathmatch
|
||||||
|
if (!deathmatch->value) {
|
||||||
|
gi.dprintf("Forcing deathmatch.\n");
|
||||||
|
gi.cvar_set("deathmatch", "1");
|
||||||
|
}
|
||||||
|
//force coop off
|
||||||
|
if (coop->value)
|
||||||
|
gi.cvar_set("coop", "0");
|
||||||
|
//ZOID
|
||||||
|
|
||||||
|
|
||||||
|
// change anytime vars
|
||||||
|
dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO);
|
||||||
|
fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO);
|
||||||
|
timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO);
|
||||||
|
//ZOID
|
||||||
|
capturelimit = gi.cvar ("capturelimit", "0", CVAR_SERVERINFO);
|
||||||
|
instantweap = gi.cvar ("instantweap", "0", CVAR_SERVERINFO);
|
||||||
|
//ZOID
|
||||||
|
password = gi.cvar ("password", "", CVAR_USERINFO);
|
||||||
|
filterban = gi.cvar ("filterban", "1", 0);
|
||||||
|
|
||||||
|
g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE);
|
||||||
|
|
||||||
|
run_pitch = gi.cvar ("run_pitch", "0.002", 0);
|
||||||
|
run_roll = gi.cvar ("run_roll", "0.005", 0);
|
||||||
|
bob_up = gi.cvar ("bob_up", "0.005", 0);
|
||||||
|
bob_pitch = gi.cvar ("bob_pitch", "0.002", 0);
|
||||||
|
bob_roll = gi.cvar ("bob_roll", "0.002", 0);
|
||||||
|
|
||||||
|
// flood control
|
||||||
|
flood_msgs = gi.cvar ("flood_msgs", "4", 0);
|
||||||
|
flood_persecond = gi.cvar ("flood_persecond", "4", 0);
|
||||||
|
flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0);
|
||||||
|
|
||||||
|
// dm map list
|
||||||
|
sv_maplist = gi.cvar ("sv_maplist", "", 0);
|
||||||
|
|
||||||
|
// items
|
||||||
|
InitItems ();
|
||||||
|
|
||||||
|
Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), "");
|
||||||
|
|
||||||
|
Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), "");
|
||||||
|
|
||||||
|
// initialize all entities for this game
|
||||||
|
game.maxentities = maxentities->value;
|
||||||
|
g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
|
||||||
|
globals.edicts = g_edicts;
|
||||||
|
globals.max_edicts = game.maxentities;
|
||||||
|
|
||||||
|
// initialize all clients for this game
|
||||||
|
game.maxclients = maxclients->value;
|
||||||
|
game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
|
||||||
|
globals.num_edicts = game.maxclients+1;
|
||||||
|
|
||||||
|
//ZOID
|
||||||
|
CTFInit();
|
||||||
|
//ZOID
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================
|
||||||
|
|
||||||
|
void WriteField1 (FILE *f, field_t *field, byte *base)
|
||||||
|
{
|
||||||
|
void *p;
|
||||||
|
int len;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
p = (void *)(base + field->ofs);
|
||||||
|
switch (field->type)
|
||||||
|
{
|
||||||
|
case F_INT:
|
||||||
|
case F_FLOAT:
|
||||||
|
case F_ANGLEHACK:
|
||||||
|
case F_VECTOR:
|
||||||
|
case F_IGNORE:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case F_LSTRING:
|
||||||
|
case F_GSTRING:
|
||||||
|
if ( *(char **)p )
|
||||||
|
len = strlen(*(char **)p) + 1;
|
||||||
|
else
|
||||||
|
len = 0;
|
||||||
|
*(int *)p = len;
|
||||||
|
break;
|
||||||
|
case F_EDICT:
|
||||||
|
if ( *(edict_t **)p == NULL)
|
||||||
|
index = -1;
|
||||||
|
else
|
||||||
|
index = *(edict_t **)p - g_edicts;
|
||||||
|
*(int *)p = index;
|
||||||
|
break;
|
||||||
|
case F_CLIENT:
|
||||||
|
if ( *(gclient_t **)p == NULL)
|
||||||
|
index = -1;
|
||||||
|
else
|
||||||
|
index = *(gclient_t **)p - game.clients;
|
||||||
|
*(int *)p = index;
|
||||||
|
break;
|
||||||
|
case F_ITEM:
|
||||||
|
if ( *(edict_t **)p == NULL)
|
||||||
|
index = -1;
|
||||||
|
else
|
||||||
|
index = *(gitem_t **)p - itemlist;
|
||||||
|
*(int *)p = index;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
gi.error ("WriteEdict: unknown field type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteField2 (FILE *f, field_t *field, byte *base)
|
||||||
|
{
|
||||||
|
int len;
|
||||||
|
void *p;
|
||||||
|
|
||||||
|
p = (void *)(base + field->ofs);
|
||||||
|
switch (field->type)
|
||||||
|
{
|
||||||
|
case F_LSTRING:
|
||||||
|
case F_GSTRING:
|
||||||
|
if ( *(char **)p )
|
||||||
|
{
|
||||||
|
len = strlen(*(char **)p) + 1;
|
||||||
|
fwrite (*(char **)p, len, 1, f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReadField (FILE *f, field_t *field, byte *base)
|
||||||
|
{
|
||||||
|
void *p;
|
||||||
|
int len;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
p = (void *)(base + field->ofs);
|
||||||
|
switch (field->type)
|
||||||
|
{
|
||||||
|
case F_INT:
|
||||||
|
case F_FLOAT:
|
||||||
|
case F_ANGLEHACK:
|
||||||
|
case F_VECTOR:
|
||||||
|
case F_IGNORE:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case F_LSTRING:
|
||||||
|
len = *(int *)p;
|
||||||
|
if (!len)
|
||||||
|
*(char **)p = NULL;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*(char **)p = gi.TagMalloc (len, TAG_LEVEL);
|
||||||
|
fread (*(char **)p, len, 1, f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case F_GSTRING:
|
||||||
|
len = *(int *)p;
|
||||||
|
if (!len)
|
||||||
|
*(char **)p = NULL;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*(char **)p = gi.TagMalloc (len, TAG_GAME);
|
||||||
|
fread (*(char **)p, len, 1, f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case F_EDICT:
|
||||||
|
index = *(int *)p;
|
||||||
|
if ( index == -1 )
|
||||||
|
*(edict_t **)p = NULL;
|
||||||
|
else
|
||||||
|
*(edict_t **)p = &g_edicts[index];
|
||||||
|
break;
|
||||||
|
case F_CLIENT:
|
||||||
|
index = *(int *)p;
|
||||||
|
if ( index == -1 )
|
||||||
|
*(gclient_t **)p = NULL;
|
||||||
|
else
|
||||||
|
*(gclient_t **)p = &game.clients[index];
|
||||||
|
break;
|
||||||
|
case F_ITEM:
|
||||||
|
index = *(int *)p;
|
||||||
|
if ( index == -1 )
|
||||||
|
*(gitem_t **)p = NULL;
|
||||||
|
else
|
||||||
|
*(gitem_t **)p = &itemlist[index];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
gi.error ("ReadEdict: unknown field type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============
|
||||||
|
WriteClient
|
||||||
|
|
||||||
|
All pointer variables (except function pointers) must be handled specially.
|
||||||
|
==============
|
||||||
|
*/
|
||||||
|
void WriteClient (FILE *f, gclient_t *client)
|
||||||
|
{
|
||||||
|
field_t *field;
|
||||||
|
gclient_t temp;
|
||||||
|
|
||||||
|
// all of the ints, floats, and vectors stay as they are
|
||||||
|
temp = *client;
|
||||||
|
|
||||||
|
// change the pointers to lengths or indexes
|
||||||
|
for (field=clientfields ; field->name ; field++)
|
||||||
|
{
|
||||||
|
WriteField1 (f, field, (byte *)&temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the block
|
||||||
|
fwrite (&temp, sizeof(temp), 1, f);
|
||||||
|
|
||||||
|
// now write any allocated data following the edict
|
||||||
|
for (field=clientfields ; field->name ; field++)
|
||||||
|
{
|
||||||
|
WriteField2 (f, field, (byte *)client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============
|
||||||
|
ReadClient
|
||||||
|
|
||||||
|
All pointer variables (except function pointers) must be handled specially.
|
||||||
|
==============
|
||||||
|
*/
|
||||||
|
void ReadClient (FILE *f, gclient_t *client)
|
||||||
|
{
|
||||||
|
field_t *field;
|
||||||
|
|
||||||
|
fread (client, sizeof(*client), 1, f);
|
||||||
|
|
||||||
|
for (field=clientfields ; field->name ; field++)
|
||||||
|
{
|
||||||
|
ReadField (f, field, (byte *)client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteGame
|
||||||
|
|
||||||
|
This will be called whenever the game goes to a new level,
|
||||||
|
and when the user explicitly saves the game.
|
||||||
|
|
||||||
|
Game information include cross level data, like multi level
|
||||||
|
triggers, help computer info, and all client states.
|
||||||
|
|
||||||
|
A single player death will automatically restore from the
|
||||||
|
last save position.
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
void WriteGame (char *filename, qboolean autosave)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
int i;
|
||||||
|
char str[16];
|
||||||
|
|
||||||
|
if (!autosave)
|
||||||
|
SaveClientData ();
|
||||||
|
|
||||||
|
f = fopen (filename, "wb");
|
||||||
|
if (!f)
|
||||||
|
gi.error ("Couldn't open %s", filename);
|
||||||
|
|
||||||
|
memset (str, 0, sizeof(str));
|
||||||
|
strcpy (str, __DATE__);
|
||||||
|
fwrite (str, sizeof(str), 1, f);
|
||||||
|
|
||||||
|
game.autosaved = autosave;
|
||||||
|
fwrite (&game, sizeof(game), 1, f);
|
||||||
|
game.autosaved = false;
|
||||||
|
|
||||||
|
for (i=0 ; i<game.maxclients ; i++)
|
||||||
|
WriteClient (f, &game.clients[i]);
|
||||||
|
|
||||||
|
fclose (f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReadGame (char *filename)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
int i;
|
||||||
|
char str[16];
|
||||||
|
|
||||||
|
gi.FreeTags (TAG_GAME);
|
||||||
|
|
||||||
|
f = fopen (filename, "rb");
|
||||||
|
if (!f)
|
||||||
|
gi.error ("Couldn't open %s", filename);
|
||||||
|
|
||||||
|
fread (str, sizeof(str), 1, f);
|
||||||
|
if (strcmp (str, __DATE__))
|
||||||
|
{
|
||||||
|
fclose (f);
|
||||||
|
gi.error ("Savegame from an older version.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
|
||||||
|
globals.edicts = g_edicts;
|
||||||
|
|
||||||
|
fread (&game, sizeof(game), 1, f);
|
||||||
|
game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
|
||||||
|
for (i=0 ; i<game.maxclients ; i++)
|
||||||
|
ReadClient (f, &game.clients[i]);
|
||||||
|
|
||||||
|
fclose (f);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============
|
||||||
|
WriteEdict
|
||||||
|
|
||||||
|
All pointer variables (except function pointers) must be handled specially.
|
||||||
|
==============
|
||||||
|
*/
|
||||||
|
void WriteEdict (FILE *f, edict_t *ent)
|
||||||
|
{
|
||||||
|
field_t *field;
|
||||||
|
edict_t temp;
|
||||||
|
|
||||||
|
// all of the ints, floats, and vectors stay as they are
|
||||||
|
temp = *ent;
|
||||||
|
|
||||||
|
// change the pointers to lengths or indexes
|
||||||
|
for (field=savefields ; field->name ; field++)
|
||||||
|
{
|
||||||
|
WriteField1 (f, field, (byte *)&temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the block
|
||||||
|
fwrite (&temp, sizeof(temp), 1, f);
|
||||||
|
|
||||||
|
// now write any allocated data following the edict
|
||||||
|
for (field=savefields ; field->name ; field++)
|
||||||
|
{
|
||||||
|
WriteField2 (f, field, (byte *)ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============
|
||||||
|
WriteLevelLocals
|
||||||
|
|
||||||
|
All pointer variables (except function pointers) must be handled specially.
|
||||||
|
==============
|
||||||
|
*/
|
||||||
|
void WriteLevelLocals (FILE *f)
|
||||||
|
{
|
||||||
|
field_t *field;
|
||||||
|
level_locals_t temp;
|
||||||
|
|
||||||
|
// all of the ints, floats, and vectors stay as they are
|
||||||
|
temp = level;
|
||||||
|
|
||||||
|
// change the pointers to lengths or indexes
|
||||||
|
for (field=levelfields ; field->name ; field++)
|
||||||
|
{
|
||||||
|
WriteField1 (f, field, (byte *)&temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the block
|
||||||
|
fwrite (&temp, sizeof(temp), 1, f);
|
||||||
|
|
||||||
|
// now write any allocated data following the edict
|
||||||
|
for (field=levelfields ; field->name ; field++)
|
||||||
|
{
|
||||||
|
WriteField2 (f, field, (byte *)&level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============
|
||||||
|
ReadEdict
|
||||||
|
|
||||||
|
All pointer variables (except function pointers) must be handled specially.
|
||||||
|
==============
|
||||||
|
*/
|
||||||
|
void ReadEdict (FILE *f, edict_t *ent)
|
||||||
|
{
|
||||||
|
field_t *field;
|
||||||
|
|
||||||
|
fread (ent, sizeof(*ent), 1, f);
|
||||||
|
|
||||||
|
for (field=savefields ; field->name ; field++)
|
||||||
|
{
|
||||||
|
ReadField (f, field, (byte *)ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============
|
||||||
|
ReadLevelLocals
|
||||||
|
|
||||||
|
All pointer variables (except function pointers) must be handled specially.
|
||||||
|
==============
|
||||||
|
*/
|
||||||
|
void ReadLevelLocals (FILE *f)
|
||||||
|
{
|
||||||
|
field_t *field;
|
||||||
|
|
||||||
|
fread (&level, sizeof(level), 1, f);
|
||||||
|
|
||||||
|
for (field=levelfields ; field->name ; field++)
|
||||||
|
{
|
||||||
|
ReadField (f, field, (byte *)&level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
WriteLevel
|
||||||
|
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void WriteLevel (char *filename)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
edict_t *ent;
|
||||||
|
FILE *f;
|
||||||
|
void *base;
|
||||||
|
|
||||||
|
f = fopen (filename, "wb");
|
||||||
|
if (!f)
|
||||||
|
gi.error ("Couldn't open %s", filename);
|
||||||
|
|
||||||
|
// write out edict size for checking
|
||||||
|
i = sizeof(edict_t);
|
||||||
|
fwrite (&i, sizeof(i), 1, f);
|
||||||
|
|
||||||
|
// write out a function pointer for checking
|
||||||
|
base = (void *)InitGame;
|
||||||
|
fwrite (&base, sizeof(base), 1, f);
|
||||||
|
|
||||||
|
// write out level_locals_t
|
||||||
|
WriteLevelLocals (f);
|
||||||
|
|
||||||
|
// write out all the entities
|
||||||
|
for (i=0 ; i<globals.num_edicts ; i++)
|
||||||
|
{
|
||||||
|
ent = &g_edicts[i];
|
||||||
|
if (!ent->inuse)
|
||||||
|
continue;
|
||||||
|
fwrite (&i, sizeof(i), 1, f);
|
||||||
|
WriteEdict (f, ent);
|
||||||
|
}
|
||||||
|
i = -1;
|
||||||
|
fwrite (&i, sizeof(i), 1, f);
|
||||||
|
|
||||||
|
fclose (f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
ReadLevel
|
||||||
|
|
||||||
|
SpawnEntities will allready have been called on the
|
||||||
|
level the same way it was when the level was saved.
|
||||||
|
|
||||||
|
That is necessary to get the baselines
|
||||||
|
set up identically.
|
||||||
|
|
||||||
|
The server will have cleared all of the world links before
|
||||||
|
calling ReadLevel.
|
||||||
|
|
||||||
|
No clients are connected yet.
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void ReadLevel (char *filename)
|
||||||
|
{
|
||||||
|
int entnum;
|
||||||
|
FILE *f;
|
||||||
|
int i;
|
||||||
|
void *base;
|
||||||
|
edict_t *ent;
|
||||||
|
|
||||||
|
f = fopen (filename, "rb");
|
||||||
|
if (!f)
|
||||||
|
gi.error ("Couldn't open %s", filename);
|
||||||
|
|
||||||
|
// free any dynamic memory allocated by loading the level
|
||||||
|
// base state
|
||||||
|
gi.FreeTags (TAG_LEVEL);
|
||||||
|
|
||||||
|
// wipe all the entities
|
||||||
|
memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0]));
|
||||||
|
globals.num_edicts = maxclients->value+1;
|
||||||
|
|
||||||
|
// check edict size
|
||||||
|
fread (&i, sizeof(i), 1, f);
|
||||||
|
if (i != sizeof(edict_t))
|
||||||
|
{
|
||||||
|
fclose (f);
|
||||||
|
gi.error ("ReadLevel: mismatched edict size");
|
||||||
|
}
|
||||||
|
|
||||||
|
// check function pointer base address
|
||||||
|
fread (&base, sizeof(base), 1, f);
|
||||||
|
if (base != (void *)InitGame)
|
||||||
|
{
|
||||||
|
fclose (f);
|
||||||
|
gi.error ("ReadLevel: function pointers have moved");
|
||||||
|
}
|
||||||
|
|
||||||
|
// load the level locals
|
||||||
|
ReadLevelLocals (f);
|
||||||
|
|
||||||
|
// load all the entities
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
if (fread (&entnum, sizeof(entnum), 1, f) != 1)
|
||||||
|
{
|
||||||
|
fclose (f);
|
||||||
|
gi.error ("ReadLevel: failed to read entnum");
|
||||||
|
}
|
||||||
|
if (entnum == -1)
|
||||||
|
break;
|
||||||
|
if (entnum >= globals.num_edicts)
|
||||||
|
globals.num_edicts = entnum+1;
|
||||||
|
|
||||||
|
ent = &g_edicts[entnum];
|
||||||
|
ReadEdict (f, ent);
|
||||||
|
|
||||||
|
// let the server rebuild world links for this ent
|
||||||
|
memset (&ent->area, 0, sizeof(ent->area));
|
||||||
|
gi.linkentity (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose (f);
|
||||||
|
|
||||||
|
// mark all clients as unconnected
|
||||||
|
for (i=0 ; i<maxclients->value ; i++)
|
||||||
|
{
|
||||||
|
ent = &g_edicts[i+1];
|
||||||
|
ent->client = game.clients + i;
|
||||||
|
ent->client->pers.connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do any load time things at this point
|
||||||
|
for (i=0 ; i<globals.num_edicts ; i++)
|
||||||
|
{
|
||||||
|
ent = &g_edicts[i];
|
||||||
|
|
||||||
|
if (!ent->inuse)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// fire any cross-level triggers
|
||||||
|
if (ent->classname)
|
||||||
|
if (strcmp(ent->classname, "target_crosslevel_target") == 0)
|
||||||
|
ent->nextthink = level.time + ent->delay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
927
src/g_spawn.c
Normal file
927
src/g_spawn.c
Normal file
|
@ -0,0 +1,927 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
void (*spawn)(edict_t *ent);
|
||||||
|
} spawn_t;
|
||||||
|
|
||||||
|
|
||||||
|
void SP_item_health (edict_t *self);
|
||||||
|
void SP_item_health_small (edict_t *self);
|
||||||
|
void SP_item_health_large (edict_t *self);
|
||||||
|
void SP_item_health_mega (edict_t *self);
|
||||||
|
|
||||||
|
void SP_info_player_start (edict_t *ent);
|
||||||
|
void SP_info_player_deathmatch (edict_t *ent);
|
||||||
|
void SP_info_player_coop (edict_t *ent);
|
||||||
|
void SP_info_player_intermission (edict_t *ent);
|
||||||
|
|
||||||
|
void SP_func_plat (edict_t *ent);
|
||||||
|
void SP_func_rotating (edict_t *ent);
|
||||||
|
void SP_func_button (edict_t *ent);
|
||||||
|
void SP_func_door (edict_t *ent);
|
||||||
|
void SP_func_door_secret (edict_t *ent);
|
||||||
|
void SP_func_door_rotating (edict_t *ent);
|
||||||
|
void SP_func_water (edict_t *ent);
|
||||||
|
void SP_func_train (edict_t *ent);
|
||||||
|
void SP_func_conveyor (edict_t *self);
|
||||||
|
void SP_func_wall (edict_t *self);
|
||||||
|
void SP_func_object (edict_t *self);
|
||||||
|
void SP_func_explosive (edict_t *self);
|
||||||
|
void SP_func_timer (edict_t *self);
|
||||||
|
void SP_func_areaportal (edict_t *ent);
|
||||||
|
void SP_func_clock (edict_t *ent);
|
||||||
|
void SP_func_killbox (edict_t *ent);
|
||||||
|
|
||||||
|
void SP_trigger_always (edict_t *ent);
|
||||||
|
void SP_trigger_once (edict_t *ent);
|
||||||
|
void SP_trigger_multiple (edict_t *ent);
|
||||||
|
void SP_trigger_relay (edict_t *ent);
|
||||||
|
void SP_trigger_push (edict_t *ent);
|
||||||
|
void SP_trigger_hurt (edict_t *ent);
|
||||||
|
void SP_trigger_key (edict_t *ent);
|
||||||
|
void SP_trigger_counter (edict_t *ent);
|
||||||
|
void SP_trigger_elevator (edict_t *ent);
|
||||||
|
void SP_trigger_gravity (edict_t *ent);
|
||||||
|
void SP_trigger_monsterjump (edict_t *ent);
|
||||||
|
|
||||||
|
void SP_target_temp_entity (edict_t *ent);
|
||||||
|
void SP_target_speaker (edict_t *ent);
|
||||||
|
void SP_target_explosion (edict_t *ent);
|
||||||
|
void SP_target_changelevel (edict_t *ent);
|
||||||
|
void SP_target_secret (edict_t *ent);
|
||||||
|
void SP_target_goal (edict_t *ent);
|
||||||
|
void SP_target_splash (edict_t *ent);
|
||||||
|
void SP_target_spawner (edict_t *ent);
|
||||||
|
void SP_target_blaster (edict_t *ent);
|
||||||
|
void SP_target_crosslevel_trigger (edict_t *ent);
|
||||||
|
void SP_target_crosslevel_target (edict_t *ent);
|
||||||
|
void SP_target_laser (edict_t *self);
|
||||||
|
void SP_target_help (edict_t *ent);
|
||||||
|
void SP_target_actor (edict_t *ent);
|
||||||
|
void SP_target_lightramp (edict_t *self);
|
||||||
|
void SP_target_earthquake (edict_t *ent);
|
||||||
|
void SP_target_character (edict_t *ent);
|
||||||
|
void SP_target_string (edict_t *ent);
|
||||||
|
|
||||||
|
void SP_worldspawn (edict_t *ent);
|
||||||
|
void SP_viewthing (edict_t *ent);
|
||||||
|
|
||||||
|
void SP_light (edict_t *self);
|
||||||
|
void SP_light_mine1 (edict_t *ent);
|
||||||
|
void SP_light_mine2 (edict_t *ent);
|
||||||
|
void SP_info_null (edict_t *self);
|
||||||
|
void SP_info_notnull (edict_t *self);
|
||||||
|
void SP_path_corner (edict_t *self);
|
||||||
|
void SP_point_combat (edict_t *self);
|
||||||
|
|
||||||
|
void SP_misc_explobox (edict_t *self);
|
||||||
|
void SP_misc_banner (edict_t *self);
|
||||||
|
void SP_misc_satellite_dish (edict_t *self);
|
||||||
|
void SP_misc_actor (edict_t *self);
|
||||||
|
void SP_misc_gib_arm (edict_t *self);
|
||||||
|
void SP_misc_gib_leg (edict_t *self);
|
||||||
|
void SP_misc_gib_head (edict_t *self);
|
||||||
|
void SP_misc_insane (edict_t *self);
|
||||||
|
void SP_misc_deadsoldier (edict_t *self);
|
||||||
|
void SP_misc_viper (edict_t *self);
|
||||||
|
void SP_misc_viper_bomb (edict_t *self);
|
||||||
|
void SP_misc_bigviper (edict_t *self);
|
||||||
|
void SP_misc_strogg_ship (edict_t *self);
|
||||||
|
void SP_misc_teleporter (edict_t *self);
|
||||||
|
void SP_misc_teleporter_dest (edict_t *self);
|
||||||
|
void SP_misc_blackhole (edict_t *self);
|
||||||
|
void SP_misc_eastertank (edict_t *self);
|
||||||
|
void SP_misc_easterchick (edict_t *self);
|
||||||
|
void SP_misc_easterchick2 (edict_t *self);
|
||||||
|
|
||||||
|
void SP_monster_berserk (edict_t *self);
|
||||||
|
void SP_monster_gladiator (edict_t *self);
|
||||||
|
void SP_monster_gunner (edict_t *self);
|
||||||
|
void SP_monster_infantry (edict_t *self);
|
||||||
|
void SP_monster_soldier_light (edict_t *self);
|
||||||
|
void SP_monster_soldier (edict_t *self);
|
||||||
|
void SP_monster_soldier_ss (edict_t *self);
|
||||||
|
void SP_monster_tank (edict_t *self);
|
||||||
|
void SP_monster_medic (edict_t *self);
|
||||||
|
void SP_monster_flipper (edict_t *self);
|
||||||
|
void SP_monster_chick (edict_t *self);
|
||||||
|
void SP_monster_parasite (edict_t *self);
|
||||||
|
void SP_monster_flyer (edict_t *self);
|
||||||
|
void SP_monster_brain (edict_t *self);
|
||||||
|
void SP_monster_floater (edict_t *self);
|
||||||
|
void SP_monster_hover (edict_t *self);
|
||||||
|
void SP_monster_mutant (edict_t *self);
|
||||||
|
void SP_monster_supertank (edict_t *self);
|
||||||
|
void SP_monster_boss2 (edict_t *self);
|
||||||
|
void SP_monster_jorg (edict_t *self);
|
||||||
|
void SP_monster_boss3_stand (edict_t *self);
|
||||||
|
|
||||||
|
void SP_monster_commander_body (edict_t *self);
|
||||||
|
|
||||||
|
void SP_turret_breach (edict_t *self);
|
||||||
|
void SP_turret_base (edict_t *self);
|
||||||
|
void SP_turret_driver (edict_t *self);
|
||||||
|
|
||||||
|
|
||||||
|
spawn_t spawns[] = {
|
||||||
|
{"item_health", SP_item_health},
|
||||||
|
{"item_health_small", SP_item_health_small},
|
||||||
|
{"item_health_large", SP_item_health_large},
|
||||||
|
{"item_health_mega", SP_item_health_mega},
|
||||||
|
|
||||||
|
{"info_player_start", SP_info_player_start},
|
||||||
|
{"info_player_deathmatch", SP_info_player_deathmatch},
|
||||||
|
{"info_player_coop", SP_info_player_coop},
|
||||||
|
{"info_player_intermission", SP_info_player_intermission},
|
||||||
|
//ZOID
|
||||||
|
{"info_player_team1", SP_info_player_team1},
|
||||||
|
{"info_player_team2", SP_info_player_team2},
|
||||||
|
//ZOID
|
||||||
|
|
||||||
|
{"func_plat", SP_func_plat},
|
||||||
|
{"func_button", SP_func_button},
|
||||||
|
{"func_door", SP_func_door},
|
||||||
|
{"func_door_secret", SP_func_door_secret},
|
||||||
|
{"func_door_rotating", SP_func_door_rotating},
|
||||||
|
{"func_rotating", SP_func_rotating},
|
||||||
|
{"func_train", SP_func_train},
|
||||||
|
{"func_water", SP_func_water},
|
||||||
|
{"func_conveyor", SP_func_conveyor},
|
||||||
|
{"func_areaportal", SP_func_areaportal},
|
||||||
|
{"func_clock", SP_func_clock},
|
||||||
|
{"func_wall", SP_func_wall},
|
||||||
|
{"func_object", SP_func_object},
|
||||||
|
{"func_timer", SP_func_timer},
|
||||||
|
{"func_explosive", SP_func_explosive},
|
||||||
|
{"func_killbox", SP_func_killbox},
|
||||||
|
|
||||||
|
{"trigger_always", SP_trigger_always},
|
||||||
|
{"trigger_once", SP_trigger_once},
|
||||||
|
{"trigger_multiple", SP_trigger_multiple},
|
||||||
|
{"trigger_relay", SP_trigger_relay},
|
||||||
|
{"trigger_push", SP_trigger_push},
|
||||||
|
{"trigger_hurt", SP_trigger_hurt},
|
||||||
|
{"trigger_key", SP_trigger_key},
|
||||||
|
{"trigger_counter", SP_trigger_counter},
|
||||||
|
{"trigger_elevator", SP_trigger_elevator},
|
||||||
|
{"trigger_gravity", SP_trigger_gravity},
|
||||||
|
{"trigger_monsterjump", SP_trigger_monsterjump},
|
||||||
|
|
||||||
|
{"target_temp_entity", SP_target_temp_entity},
|
||||||
|
{"target_speaker", SP_target_speaker},
|
||||||
|
{"target_explosion", SP_target_explosion},
|
||||||
|
{"target_changelevel", SP_target_changelevel},
|
||||||
|
{"target_secret", SP_target_secret},
|
||||||
|
{"target_goal", SP_target_goal},
|
||||||
|
{"target_splash", SP_target_splash},
|
||||||
|
{"target_spawner", SP_target_spawner},
|
||||||
|
{"target_blaster", SP_target_blaster},
|
||||||
|
{"target_crosslevel_trigger", SP_target_crosslevel_trigger},
|
||||||
|
{"target_crosslevel_target", SP_target_crosslevel_target},
|
||||||
|
{"target_laser", SP_target_laser},
|
||||||
|
{"target_help", SP_target_help},
|
||||||
|
{"target_lightramp", SP_target_lightramp},
|
||||||
|
{"target_earthquake", SP_target_earthquake},
|
||||||
|
{"target_character", SP_target_character},
|
||||||
|
{"target_string", SP_target_string},
|
||||||
|
|
||||||
|
{"worldspawn", SP_worldspawn},
|
||||||
|
{"viewthing", SP_viewthing},
|
||||||
|
|
||||||
|
{"light", SP_light},
|
||||||
|
{"light_mine1", SP_light_mine1},
|
||||||
|
{"light_mine2", SP_light_mine2},
|
||||||
|
{"info_null", SP_info_null},
|
||||||
|
{"func_group", SP_info_null},
|
||||||
|
{"info_notnull", SP_info_notnull},
|
||||||
|
{"path_corner", SP_path_corner},
|
||||||
|
{"point_combat", SP_point_combat},
|
||||||
|
|
||||||
|
{"misc_explobox", SP_misc_explobox},
|
||||||
|
{"misc_banner", SP_misc_banner},
|
||||||
|
//ZOID
|
||||||
|
{"misc_ctf_banner", SP_misc_ctf_banner},
|
||||||
|
{"misc_ctf_small_banner", SP_misc_ctf_small_banner},
|
||||||
|
//ZOID
|
||||||
|
{"misc_satellite_dish", SP_misc_satellite_dish},
|
||||||
|
{"misc_gib_arm", SP_misc_gib_arm},
|
||||||
|
{"misc_gib_leg", SP_misc_gib_leg},
|
||||||
|
{"misc_gib_head", SP_misc_gib_head},
|
||||||
|
{"misc_viper", SP_misc_viper},
|
||||||
|
{"misc_viper_bomb", SP_misc_viper_bomb},
|
||||||
|
{"misc_bigviper", SP_misc_bigviper},
|
||||||
|
{"misc_strogg_ship", SP_misc_strogg_ship},
|
||||||
|
{"misc_teleporter", SP_misc_teleporter},
|
||||||
|
{"misc_teleporter_dest", SP_misc_teleporter_dest},
|
||||||
|
//ZOID
|
||||||
|
{"trigger_teleport", SP_trigger_teleport},
|
||||||
|
{"info_teleport_destination", SP_info_teleport_destination},
|
||||||
|
//ZOID
|
||||||
|
{"misc_blackhole", SP_misc_blackhole},
|
||||||
|
{"misc_eastertank", SP_misc_eastertank},
|
||||||
|
{"misc_easterchick", SP_misc_easterchick},
|
||||||
|
{"misc_easterchick2", SP_misc_easterchick2},
|
||||||
|
|
||||||
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
===============
|
||||||
|
ED_CallSpawn
|
||||||
|
|
||||||
|
Finds the spawn function for the entity and calls it
|
||||||
|
===============
|
||||||
|
*/
|
||||||
|
void ED_CallSpawn (edict_t *ent)
|
||||||
|
{
|
||||||
|
spawn_t *s;
|
||||||
|
gitem_t *item;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!ent->classname)
|
||||||
|
{
|
||||||
|
gi.dprintf ("ED_CallSpawn: NULL classname\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check item spawn functions
|
||||||
|
for (i=0,item=itemlist ; i<game.num_items ; i++,item++)
|
||||||
|
{
|
||||||
|
if (!item->classname)
|
||||||
|
continue;
|
||||||
|
if (!strcmp(item->classname, ent->classname))
|
||||||
|
{ // found it
|
||||||
|
SpawnItem (ent, item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check normal spawn functions
|
||||||
|
for (s=spawns ; s->name ; s++)
|
||||||
|
{
|
||||||
|
if (!strcmp(s->name, ent->classname))
|
||||||
|
{ // found it
|
||||||
|
s->spawn (ent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gi.dprintf ("%s doesn't have a spawn function\n", ent->classname);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
ED_NewString
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
char *ED_NewString (char *string)
|
||||||
|
{
|
||||||
|
char *newb, *new_p;
|
||||||
|
int i,l;
|
||||||
|
|
||||||
|
l = strlen(string) + 1;
|
||||||
|
|
||||||
|
newb = gi.TagMalloc (l, TAG_LEVEL);
|
||||||
|
|
||||||
|
new_p = newb;
|
||||||
|
|
||||||
|
for (i=0 ; i< l ; i++)
|
||||||
|
{
|
||||||
|
if (string[i] == '\\' && i < l-1)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
if (string[i] == 'n')
|
||||||
|
*new_p++ = '\n';
|
||||||
|
else
|
||||||
|
*new_p++ = '\\';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
*new_p++ = string[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return newb;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
===============
|
||||||
|
ED_ParseField
|
||||||
|
|
||||||
|
Takes a key/value pair and sets the binary values
|
||||||
|
in an edict
|
||||||
|
===============
|
||||||
|
*/
|
||||||
|
void ED_ParseField (char *key, char *value, edict_t *ent)
|
||||||
|
{
|
||||||
|
field_t *f;
|
||||||
|
byte *b;
|
||||||
|
float v;
|
||||||
|
vec3_t vec;
|
||||||
|
|
||||||
|
for (f=fields ; f->name ; f++)
|
||||||
|
{
|
||||||
|
if (!Q_stricmp(f->name, key))
|
||||||
|
{ // found it
|
||||||
|
if (f->flags & FFL_SPAWNTEMP)
|
||||||
|
b = (byte *)&st;
|
||||||
|
else
|
||||||
|
b = (byte *)ent;
|
||||||
|
|
||||||
|
switch (f->type)
|
||||||
|
{
|
||||||
|
case F_LSTRING:
|
||||||
|
*(char **)(b+f->ofs) = ED_NewString (value);
|
||||||
|
break;
|
||||||
|
case F_VECTOR:
|
||||||
|
sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]);
|
||||||
|
((float *)(b+f->ofs))[0] = vec[0];
|
||||||
|
((float *)(b+f->ofs))[1] = vec[1];
|
||||||
|
((float *)(b+f->ofs))[2] = vec[2];
|
||||||
|
break;
|
||||||
|
case F_INT:
|
||||||
|
*(int *)(b+f->ofs) = atoi(value);
|
||||||
|
break;
|
||||||
|
case F_FLOAT:
|
||||||
|
*(float *)(b+f->ofs) = atof(value);
|
||||||
|
break;
|
||||||
|
case F_ANGLEHACK:
|
||||||
|
v = atof(value);
|
||||||
|
((float *)(b+f->ofs))[0] = 0;
|
||||||
|
((float *)(b+f->ofs))[1] = v;
|
||||||
|
((float *)(b+f->ofs))[2] = 0;
|
||||||
|
break;
|
||||||
|
case F_IGNORE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gi.dprintf ("%s is not a field\n", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
====================
|
||||||
|
ED_ParseEdict
|
||||||
|
|
||||||
|
Parses an edict out of the given string, returning the new position
|
||||||
|
ed should be a properly initialized empty edict.
|
||||||
|
====================
|
||||||
|
*/
|
||||||
|
char *ED_ParseEdict (char *data, edict_t *ent)
|
||||||
|
{
|
||||||
|
qboolean init;
|
||||||
|
char keyname[256];
|
||||||
|
char *com_token;
|
||||||
|
|
||||||
|
init = false;
|
||||||
|
memset (&st, 0, sizeof(st));
|
||||||
|
|
||||||
|
// go through all the dictionary pairs
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// parse key
|
||||||
|
com_token = COM_Parse (&data);
|
||||||
|
if (com_token[0] == '}')
|
||||||
|
break;
|
||||||
|
if (!data)
|
||||||
|
gi.error ("ED_ParseEntity: EOF without closing brace");
|
||||||
|
|
||||||
|
strncpy (keyname, com_token, sizeof(keyname)-1);
|
||||||
|
|
||||||
|
// parse value
|
||||||
|
com_token = COM_Parse (&data);
|
||||||
|
if (!data)
|
||||||
|
gi.error ("ED_ParseEntity: EOF without closing brace");
|
||||||
|
|
||||||
|
if (com_token[0] == '}')
|
||||||
|
gi.error ("ED_ParseEntity: closing brace without data");
|
||||||
|
|
||||||
|
init = true;
|
||||||
|
|
||||||
|
// keynames with a leading underscore are used for utility comments,
|
||||||
|
// and are immediately discarded by quake
|
||||||
|
if (keyname[0] == '_')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ED_ParseField (keyname, com_token, ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!init)
|
||||||
|
memset (ent, 0, sizeof(*ent));
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
G_FindTeams
|
||||||
|
|
||||||
|
Chain together all entities with a matching team field.
|
||||||
|
|
||||||
|
All but the first will have the FL_TEAMSLAVE flag set.
|
||||||
|
All but the last will have the teamchain field set to the next one
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
void G_FindTeams (void)
|
||||||
|
{
|
||||||
|
edict_t *e, *e2, *chain;
|
||||||
|
int i, j;
|
||||||
|
int c, c2;
|
||||||
|
|
||||||
|
c = 0;
|
||||||
|
c2 = 0;
|
||||||
|
for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++)
|
||||||
|
{
|
||||||
|
if (!e->inuse)
|
||||||
|
continue;
|
||||||
|
if (!e->team)
|
||||||
|
continue;
|
||||||
|
if (e->flags & FL_TEAMSLAVE)
|
||||||
|
continue;
|
||||||
|
chain = e;
|
||||||
|
e->teammaster = e;
|
||||||
|
c++;
|
||||||
|
c2++;
|
||||||
|
for (j=i+1, e2=e+1 ; j < globals.num_edicts ; j++,e2++)
|
||||||
|
{
|
||||||
|
if (!e2->inuse)
|
||||||
|
continue;
|
||||||
|
if (!e2->team)
|
||||||
|
continue;
|
||||||
|
if (e2->flags & FL_TEAMSLAVE)
|
||||||
|
continue;
|
||||||
|
if (!strcmp(e->team, e2->team))
|
||||||
|
{
|
||||||
|
c2++;
|
||||||
|
chain->teamchain = e2;
|
||||||
|
e2->teammaster = e;
|
||||||
|
chain = e2;
|
||||||
|
e2->flags |= FL_TEAMSLAVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gi.dprintf ("%i teams with %i entities.\n", c, c2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============
|
||||||
|
SpawnEntities
|
||||||
|
|
||||||
|
Creates a server's entity / program execution context by
|
||||||
|
parsing textual entity definitions out of an ent file.
|
||||||
|
==============
|
||||||
|
*/
|
||||||
|
void SpawnEntities (char *mapname, char *entities, char *spawnpoint)
|
||||||
|
{
|
||||||
|
edict_t *ent;
|
||||||
|
int inhibit;
|
||||||
|
char *com_token;
|
||||||
|
int i;
|
||||||
|
float skill_level;
|
||||||
|
|
||||||
|
skill_level = floor (skill->value);
|
||||||
|
if (skill_level < 0)
|
||||||
|
skill_level = 0;
|
||||||
|
if (skill_level > 3)
|
||||||
|
skill_level = 3;
|
||||||
|
if (skill->value != skill_level)
|
||||||
|
gi.cvar_forceset("skill", va("%f", skill_level));
|
||||||
|
|
||||||
|
SaveClientData ();
|
||||||
|
|
||||||
|
gi.FreeTags (TAG_LEVEL);
|
||||||
|
|
||||||
|
memset (&level, 0, sizeof(level));
|
||||||
|
memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0]));
|
||||||
|
|
||||||
|
strncpy (level.mapname, mapname, sizeof(level.mapname)-1);
|
||||||
|
strncpy (game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)-1);
|
||||||
|
|
||||||
|
// set client fields on player ents
|
||||||
|
for (i=0 ; i<game.maxclients ; i++)
|
||||||
|
g_edicts[i+1].client = game.clients + i;
|
||||||
|
|
||||||
|
ent = NULL;
|
||||||
|
inhibit = 0;
|
||||||
|
|
||||||
|
// parse ents
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// parse the opening brace
|
||||||
|
com_token = COM_Parse (&entities);
|
||||||
|
if (!entities)
|
||||||
|
break;
|
||||||
|
if (com_token[0] != '{')
|
||||||
|
gi.error ("ED_LoadFromFile: found %s when expecting {",com_token);
|
||||||
|
|
||||||
|
if (!ent)
|
||||||
|
ent = g_edicts;
|
||||||
|
else
|
||||||
|
ent = G_Spawn ();
|
||||||
|
entities = ED_ParseEdict (entities, ent);
|
||||||
|
|
||||||
|
// yet another map hack
|
||||||
|
if (!Q_stricmp(level.mapname, "command") && !Q_stricmp(ent->classname, "trigger_once") && !Q_stricmp(ent->model, "*27"))
|
||||||
|
ent->spawnflags &= ~SPAWNFLAG_NOT_HARD;
|
||||||
|
|
||||||
|
// remove things (except the world) from different skill levels or deathmatch
|
||||||
|
if (ent != g_edicts)
|
||||||
|
{
|
||||||
|
if (deathmatch->value)
|
||||||
|
{
|
||||||
|
if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH )
|
||||||
|
{
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
inhibit++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */
|
||||||
|
((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) ||
|
||||||
|
((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) ||
|
||||||
|
(((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
inhibit++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
ED_CallSpawn (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
gi.dprintf ("%i entities inhibited.\n", inhibit);
|
||||||
|
|
||||||
|
G_FindTeams ();
|
||||||
|
|
||||||
|
PlayerTrail_Init ();
|
||||||
|
|
||||||
|
//ZOID
|
||||||
|
CTFSpawn();
|
||||||
|
//ZOID
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//===================================================================
|
||||||
|
|
||||||
|
char *single_statusbar =
|
||||||
|
"yb -24 "
|
||||||
|
|
||||||
|
// health
|
||||||
|
"xv 0 "
|
||||||
|
"hnum "
|
||||||
|
"xv 50 "
|
||||||
|
"pic 0 "
|
||||||
|
|
||||||
|
// ammo
|
||||||
|
"if 2 "
|
||||||
|
" xv 100 "
|
||||||
|
" anum "
|
||||||
|
" xv 150 "
|
||||||
|
" pic 2 "
|
||||||
|
"endif "
|
||||||
|
|
||||||
|
// armor
|
||||||
|
"if 4 "
|
||||||
|
" xv 200 "
|
||||||
|
" rnum "
|
||||||
|
" xv 250 "
|
||||||
|
" pic 4 "
|
||||||
|
"endif "
|
||||||
|
|
||||||
|
// selected item
|
||||||
|
"if 6 "
|
||||||
|
" xv 296 "
|
||||||
|
" pic 6 "
|
||||||
|
"endif "
|
||||||
|
|
||||||
|
"yb -50 "
|
||||||
|
|
||||||
|
// picked up item
|
||||||
|
"if 7 "
|
||||||
|
" xv 0 "
|
||||||
|
" pic 7 "
|
||||||
|
" xv 26 "
|
||||||
|
" yb -42 "
|
||||||
|
" stat_string 8 "
|
||||||
|
" yb -50 "
|
||||||
|
"endif "
|
||||||
|
|
||||||
|
// timer
|
||||||
|
"if 9 "
|
||||||
|
" xv 262 "
|
||||||
|
" num 2 10 "
|
||||||
|
" xv 296 "
|
||||||
|
" pic 9 "
|
||||||
|
"endif "
|
||||||
|
|
||||||
|
// help / weapon icon
|
||||||
|
"if 11 "
|
||||||
|
" xv 148 "
|
||||||
|
" pic 11 "
|
||||||
|
"endif "
|
||||||
|
;
|
||||||
|
|
||||||
|
char *dm_statusbar =
|
||||||
|
"yb -24 "
|
||||||
|
|
||||||
|
// health
|
||||||
|
"xv 0 "
|
||||||
|
"hnum "
|
||||||
|
"xv 50 "
|
||||||
|
"pic 0 "
|
||||||
|
|
||||||
|
// ammo
|
||||||
|
"if 2 "
|
||||||
|
" xv 100 "
|
||||||
|
" anum "
|
||||||
|
" xv 150 "
|
||||||
|
" pic 2 "
|
||||||
|
"endif "
|
||||||
|
|
||||||
|
// armor
|
||||||
|
"if 4 "
|
||||||
|
" xv 200 "
|
||||||
|
" rnum "
|
||||||
|
" xv 250 "
|
||||||
|
" pic 4 "
|
||||||
|
"endif "
|
||||||
|
|
||||||
|
// selected item
|
||||||
|
"if 6 "
|
||||||
|
" xv 296 "
|
||||||
|
" pic 6 "
|
||||||
|
"endif "
|
||||||
|
|
||||||
|
"yb -50 "
|
||||||
|
|
||||||
|
// picked up item
|
||||||
|
"if 7 "
|
||||||
|
" xv 0 "
|
||||||
|
" pic 7 "
|
||||||
|
" xv 26 "
|
||||||
|
" yb -42 "
|
||||||
|
" stat_string 8 "
|
||||||
|
" yb -50 "
|
||||||
|
"endif "
|
||||||
|
|
||||||
|
// timer
|
||||||
|
"if 9 "
|
||||||
|
" xv 246 "
|
||||||
|
" num 2 10 "
|
||||||
|
" xv 296 "
|
||||||
|
" pic 9 "
|
||||||
|
"endif "
|
||||||
|
|
||||||
|
// help / weapon icon
|
||||||
|
"if 11 "
|
||||||
|
" xv 148 "
|
||||||
|
" pic 11 "
|
||||||
|
"endif "
|
||||||
|
|
||||||
|
// frags
|
||||||
|
"xr -50 "
|
||||||
|
"yt 2 "
|
||||||
|
"num 3 14"
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
/*QUAKED worldspawn (0 0 0) ?
|
||||||
|
|
||||||
|
Only used for the world.
|
||||||
|
"sky" environment map name
|
||||||
|
"skyaxis" vector axis for rotating sky
|
||||||
|
"skyrotate" speed of rotation in degrees/second
|
||||||
|
"sounds" music cd track number
|
||||||
|
"gravity" 800 is default gravity
|
||||||
|
"message" text to print at user logon
|
||||||
|
*/
|
||||||
|
void SP_worldspawn (edict_t *ent)
|
||||||
|
{
|
||||||
|
ent->movetype = MOVETYPE_PUSH;
|
||||||
|
ent->solid = SOLID_BSP;
|
||||||
|
ent->inuse = true; // since the world doesn't use G_Spawn()
|
||||||
|
ent->s.modelindex = 1; // world model is always index 1
|
||||||
|
|
||||||
|
//---------------
|
||||||
|
|
||||||
|
// reserve some spots for dead player bodies for coop / deathmatch
|
||||||
|
InitBodyQue ();
|
||||||
|
|
||||||
|
// set configstrings for items
|
||||||
|
SetItemNames ();
|
||||||
|
|
||||||
|
if (st.nextmap)
|
||||||
|
strcpy (level.nextmap, st.nextmap);
|
||||||
|
|
||||||
|
// make some data visible to the server
|
||||||
|
|
||||||
|
if (ent->message && ent->message[0])
|
||||||
|
{
|
||||||
|
gi.configstring (CS_NAME, ent->message);
|
||||||
|
strncpy (level.level_name, ent->message, sizeof(level.level_name));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
strncpy (level.level_name, level.mapname, sizeof(level.level_name));
|
||||||
|
|
||||||
|
if (st.sky && st.sky[0])
|
||||||
|
gi.configstring (CS_SKY, st.sky);
|
||||||
|
else
|
||||||
|
gi.configstring (CS_SKY, "unit1_");
|
||||||
|
|
||||||
|
gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) );
|
||||||
|
|
||||||
|
gi.configstring (CS_SKYAXIS, va("%f %f %f",
|
||||||
|
st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) );
|
||||||
|
|
||||||
|
gi.configstring (CS_CDTRACK, va("%i", ent->sounds) );
|
||||||
|
|
||||||
|
gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) );
|
||||||
|
|
||||||
|
// status bar program
|
||||||
|
if (deathmatch->value)
|
||||||
|
//ZOID
|
||||||
|
if (ctf->value) {
|
||||||
|
gi.configstring (CS_STATUSBAR, ctf_statusbar);
|
||||||
|
CTFPrecache();
|
||||||
|
} else
|
||||||
|
//ZOID
|
||||||
|
gi.configstring (CS_STATUSBAR, dm_statusbar);
|
||||||
|
else
|
||||||
|
gi.configstring (CS_STATUSBAR, single_statusbar);
|
||||||
|
|
||||||
|
//---------------
|
||||||
|
|
||||||
|
|
||||||
|
// help icon for statusbar
|
||||||
|
gi.imageindex ("i_help");
|
||||||
|
level.pic_health = gi.imageindex ("i_health");
|
||||||
|
gi.imageindex ("help");
|
||||||
|
gi.imageindex ("field_3");
|
||||||
|
|
||||||
|
if (!st.gravity)
|
||||||
|
gi.cvar_set("sv_gravity", "800");
|
||||||
|
else
|
||||||
|
gi.cvar_set("sv_gravity", st.gravity);
|
||||||
|
|
||||||
|
snd_fry = gi.soundindex ("player/fry.wav"); // standing in lava / slime
|
||||||
|
|
||||||
|
PrecacheItem (FindItem ("Blaster"));
|
||||||
|
|
||||||
|
gi.soundindex ("player/lava1.wav");
|
||||||
|
gi.soundindex ("player/lava2.wav");
|
||||||
|
|
||||||
|
gi.soundindex ("misc/pc_up.wav");
|
||||||
|
gi.soundindex ("misc/talk1.wav");
|
||||||
|
|
||||||
|
gi.soundindex ("misc/udeath.wav");
|
||||||
|
|
||||||
|
// gibs
|
||||||
|
gi.soundindex ("items/respawn1.wav");
|
||||||
|
|
||||||
|
// sexed sounds
|
||||||
|
gi.soundindex ("*death1.wav");
|
||||||
|
gi.soundindex ("*death2.wav");
|
||||||
|
gi.soundindex ("*death3.wav");
|
||||||
|
gi.soundindex ("*death4.wav");
|
||||||
|
gi.soundindex ("*fall1.wav");
|
||||||
|
gi.soundindex ("*fall2.wav");
|
||||||
|
gi.soundindex ("*gurp1.wav"); // drowning damage
|
||||||
|
gi.soundindex ("*gurp2.wav");
|
||||||
|
gi.soundindex ("*jump1.wav"); // player jump
|
||||||
|
gi.soundindex ("*pain25_1.wav");
|
||||||
|
gi.soundindex ("*pain25_2.wav");
|
||||||
|
gi.soundindex ("*pain50_1.wav");
|
||||||
|
gi.soundindex ("*pain50_2.wav");
|
||||||
|
gi.soundindex ("*pain75_1.wav");
|
||||||
|
gi.soundindex ("*pain75_2.wav");
|
||||||
|
gi.soundindex ("*pain100_1.wav");
|
||||||
|
gi.soundindex ("*pain100_2.wav");
|
||||||
|
|
||||||
|
// sexed models
|
||||||
|
// THIS ORDER MUST MATCH THE DEFINES IN g_local.h
|
||||||
|
// you can add more, max 15
|
||||||
|
gi.modelindex ("#w_blaster.md2");
|
||||||
|
gi.modelindex ("#w_shotgun.md2");
|
||||||
|
gi.modelindex ("#w_sshotgun.md2");
|
||||||
|
gi.modelindex ("#w_machinegun.md2");
|
||||||
|
gi.modelindex ("#w_chaingun.md2");
|
||||||
|
gi.modelindex ("#a_grenades.md2");
|
||||||
|
gi.modelindex ("#w_glauncher.md2");
|
||||||
|
gi.modelindex ("#w_rlauncher.md2");
|
||||||
|
gi.modelindex ("#w_hyperblaster.md2");
|
||||||
|
gi.modelindex ("#w_railgun.md2");
|
||||||
|
gi.modelindex ("#w_bfg.md2");
|
||||||
|
gi.modelindex ("#w_grapple.md2");
|
||||||
|
|
||||||
|
//-------------------
|
||||||
|
|
||||||
|
gi.soundindex ("player/gasp1.wav"); // gasping for air
|
||||||
|
gi.soundindex ("player/gasp2.wav"); // head breaking surface, not gasping
|
||||||
|
|
||||||
|
gi.soundindex ("player/watr_in.wav"); // feet hitting water
|
||||||
|
gi.soundindex ("player/watr_out.wav"); // feet leaving water
|
||||||
|
|
||||||
|
gi.soundindex ("player/watr_un.wav"); // head going underwater
|
||||||
|
|
||||||
|
gi.soundindex ("player/u_breath1.wav");
|
||||||
|
gi.soundindex ("player/u_breath2.wav");
|
||||||
|
|
||||||
|
gi.soundindex ("items/pkup.wav"); // bonus item pickup
|
||||||
|
gi.soundindex ("world/land.wav"); // landing thud
|
||||||
|
gi.soundindex ("misc/h2ohit1.wav"); // landing splash
|
||||||
|
|
||||||
|
gi.soundindex ("items/damage.wav");
|
||||||
|
gi.soundindex ("items/protect.wav");
|
||||||
|
gi.soundindex ("items/protect4.wav");
|
||||||
|
gi.soundindex ("weapons/noammo.wav");
|
||||||
|
|
||||||
|
gi.soundindex ("infantry/inflies1.wav");
|
||||||
|
|
||||||
|
sm_meat_index = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2");
|
||||||
|
gi.modelindex ("models/objects/gibs/arm/tris.md2");
|
||||||
|
gi.modelindex ("models/objects/gibs/bone/tris.md2");
|
||||||
|
gi.modelindex ("models/objects/gibs/bone2/tris.md2");
|
||||||
|
gi.modelindex ("models/objects/gibs/chest/tris.md2");
|
||||||
|
gi.modelindex ("models/objects/gibs/skull/tris.md2");
|
||||||
|
gi.modelindex ("models/objects/gibs/head2/tris.md2");
|
||||||
|
|
||||||
|
//
|
||||||
|
// Setup light animation tables. 'a' is total darkness, 'z' is doublebright.
|
||||||
|
//
|
||||||
|
|
||||||
|
// 0 normal
|
||||||
|
gi.configstring(CS_LIGHTS+0, "m");
|
||||||
|
|
||||||
|
// 1 FLICKER (first variety)
|
||||||
|
gi.configstring(CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo");
|
||||||
|
|
||||||
|
// 2 SLOW STRONG PULSE
|
||||||
|
gi.configstring(CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
|
||||||
|
|
||||||
|
// 3 CANDLE (first variety)
|
||||||
|
gi.configstring(CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
|
||||||
|
|
||||||
|
// 4 FAST STROBE
|
||||||
|
gi.configstring(CS_LIGHTS+4, "mamamamamama");
|
||||||
|
|
||||||
|
// 5 GENTLE PULSE 1
|
||||||
|
gi.configstring(CS_LIGHTS+5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj");
|
||||||
|
|
||||||
|
// 6 FLICKER (second variety)
|
||||||
|
gi.configstring(CS_LIGHTS+6, "nmonqnmomnmomomno");
|
||||||
|
|
||||||
|
// 7 CANDLE (second variety)
|
||||||
|
gi.configstring(CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm");
|
||||||
|
|
||||||
|
// 8 CANDLE (third variety)
|
||||||
|
gi.configstring(CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
|
||||||
|
|
||||||
|
// 9 SLOW STROBE (fourth variety)
|
||||||
|
gi.configstring(CS_LIGHTS+9, "aaaaaaaazzzzzzzz");
|
||||||
|
|
||||||
|
// 10 FLUORESCENT FLICKER
|
||||||
|
gi.configstring(CS_LIGHTS+10, "mmamammmmammamamaaamammma");
|
||||||
|
|
||||||
|
// 11 SLOW PULSE NOT FADE TO BLACK
|
||||||
|
gi.configstring(CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba");
|
||||||
|
|
||||||
|
// styles 32-62 are assigned by the light program for switchable lights
|
||||||
|
|
||||||
|
// 63 testing
|
||||||
|
gi.configstring(CS_LIGHTS+63, "a");
|
||||||
|
}
|
||||||
|
|
300
src/g_svcmds.c
Normal file
300
src/g_svcmds.c
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
|
||||||
|
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 beleive it would cause too much confusion.
|
||||||
|
|
||||||
|
filterban <0 or 1>
|
||||||
|
|
||||||
|
If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting.
|
||||||
|
|
||||||
|
If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network.
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned mask;
|
||||||
|
unsigned compare;
|
||||||
|
} ipfilter_t;
|
||||||
|
|
||||||
|
#define MAX_IPFILTERS 1024
|
||||||
|
|
||||||
|
ipfilter_t ipfilters[MAX_IPFILTERS];
|
||||||
|
int numipfilters;
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
StringToFilter
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
static qboolean StringToFilter (char *s, ipfilter_t *f)
|
||||||
|
{
|
||||||
|
char num[128];
|
||||||
|
int i, j;
|
||||||
|
byte b[4];
|
||||||
|
byte m[4];
|
||||||
|
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
|
||||||
|
f->mask = *(unsigned *)m;
|
||||||
|
f->compare = *(unsigned *)b;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
SV_FilterPacket
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
qboolean SV_FilterPacket (char *from)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
unsigned in;
|
||||||
|
byte m[4];
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
SV_AddIP_f
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
SV_RemoveIP_f
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
SV_ListIP_f
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void SVCmd_ListIP_f (void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
byte b[4];
|
||||||
|
|
||||||
|
gi.cprintf (NULL, PRINT_HIGH, "Filter list:\n");
|
||||||
|
for (i=0 ; i<numipfilters ; i++)
|
||||||
|
{
|
||||||
|
*(unsigned *)b = ipfilters[i].compare;
|
||||||
|
gi.cprintf (NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n", b[0], b[1], b[2], b[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
SV_WriteIP_f
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
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++)
|
||||||
|
{
|
||||||
|
*(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
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
810
src/g_target.c
Normal file
810
src/g_target.c
Normal file
|
@ -0,0 +1,810 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
|
||||||
|
Fire an origin based temp entity event to the clients.
|
||||||
|
"style" type byte
|
||||||
|
*/
|
||||||
|
void Use_Target_Tent (edict_t *ent, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (ent->style);
|
||||||
|
gi.WritePosition (ent->s.origin);
|
||||||
|
gi.multicast (ent->s.origin, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_temp_entity (edict_t *ent)
|
||||||
|
{
|
||||||
|
ent->use = Use_Target_Tent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
|
||||||
|
"noise" wav file to play
|
||||||
|
"attenuation"
|
||||||
|
-1 = none, send to whole level
|
||||||
|
1 = normal fighting sounds
|
||||||
|
2 = idle sound level
|
||||||
|
3 = ambient sound level
|
||||||
|
"volume" 0.0 to 1.0
|
||||||
|
|
||||||
|
Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers.
|
||||||
|
|
||||||
|
Looped sounds are allways atten 3 / vol 1, and the use function toggles it on/off.
|
||||||
|
Multiple identical looping sounds will just increase volume without any speed cost.
|
||||||
|
*/
|
||||||
|
void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
int chan;
|
||||||
|
|
||||||
|
if (ent->spawnflags & 3)
|
||||||
|
{ // looping sound toggles
|
||||||
|
if (ent->s.sound)
|
||||||
|
ent->s.sound = 0; // turn it off
|
||||||
|
else
|
||||||
|
ent->s.sound = ent->noise_index; // start it
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // normal sound
|
||||||
|
if (ent->spawnflags & 4)
|
||||||
|
chan = CHAN_VOICE|CHAN_RELIABLE;
|
||||||
|
else
|
||||||
|
chan = CHAN_VOICE;
|
||||||
|
// use a positioned_sound, because this entity won't normally be
|
||||||
|
// sent to any clients because it is invisible
|
||||||
|
gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_speaker (edict_t *ent)
|
||||||
|
{
|
||||||
|
char buffer[MAX_QPATH];
|
||||||
|
|
||||||
|
if(!st.noise)
|
||||||
|
{
|
||||||
|
gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!strstr (st.noise, ".wav"))
|
||||||
|
Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
|
||||||
|
else
|
||||||
|
strncpy (buffer, st.noise, sizeof(buffer));
|
||||||
|
ent->noise_index = gi.soundindex (buffer);
|
||||||
|
|
||||||
|
if (!ent->volume)
|
||||||
|
ent->volume = 1.0;
|
||||||
|
|
||||||
|
if (!ent->attenuation)
|
||||||
|
ent->attenuation = 1.0;
|
||||||
|
else if (ent->attenuation == -1) // use -1 so 0 defaults to 1
|
||||||
|
ent->attenuation = 0;
|
||||||
|
|
||||||
|
// check for prestarted looping sound
|
||||||
|
if (ent->spawnflags & 1)
|
||||||
|
ent->s.sound = ent->noise_index;
|
||||||
|
|
||||||
|
ent->use = Use_Target_Speaker;
|
||||||
|
|
||||||
|
// must link the entity so we get areas and clusters so
|
||||||
|
// the server can determine who to send updates to
|
||||||
|
gi.linkentity (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
if (ent->spawnflags & 1)
|
||||||
|
strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1);
|
||||||
|
else
|
||||||
|
strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1);
|
||||||
|
|
||||||
|
game.helpchanged++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
|
||||||
|
When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars.
|
||||||
|
*/
|
||||||
|
void SP_target_help(edict_t *ent)
|
||||||
|
{
|
||||||
|
if (deathmatch->value)
|
||||||
|
{ // auto-remove for deathmatch
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ent->message)
|
||||||
|
{
|
||||||
|
gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin));
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ent->use = Use_Target_Help;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
|
||||||
|
Counts a secret found.
|
||||||
|
These are single use targets.
|
||||||
|
*/
|
||||||
|
void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
|
||||||
|
|
||||||
|
level.found_secrets++;
|
||||||
|
|
||||||
|
G_UseTargets (ent, activator);
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_secret (edict_t *ent)
|
||||||
|
{
|
||||||
|
if (deathmatch->value)
|
||||||
|
{ // auto-remove for deathmatch
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ent->use = use_target_secret;
|
||||||
|
if (!st.noise)
|
||||||
|
st.noise = "misc/secret.wav";
|
||||||
|
ent->noise_index = gi.soundindex (st.noise);
|
||||||
|
ent->svflags = SVF_NOCLIENT;
|
||||||
|
level.total_secrets++;
|
||||||
|
// map bug hack
|
||||||
|
if (!Q_stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624)
|
||||||
|
ent->message = "You have found a secret area.";
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
|
||||||
|
Counts a goal completed.
|
||||||
|
These are single use targets.
|
||||||
|
*/
|
||||||
|
void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
|
||||||
|
|
||||||
|
level.found_goals++;
|
||||||
|
|
||||||
|
if (level.found_goals == level.total_goals)
|
||||||
|
gi.configstring (CS_CDTRACK, "0");
|
||||||
|
|
||||||
|
G_UseTargets (ent, activator);
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_goal (edict_t *ent)
|
||||||
|
{
|
||||||
|
if (deathmatch->value)
|
||||||
|
{ // auto-remove for deathmatch
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ent->use = use_target_goal;
|
||||||
|
if (!st.noise)
|
||||||
|
st.noise = "misc/secret.wav";
|
||||||
|
ent->noise_index = gi.soundindex (st.noise);
|
||||||
|
ent->svflags = SVF_NOCLIENT;
|
||||||
|
level.total_goals++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
|
||||||
|
/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
|
||||||
|
Spawns an explosion temporary entity when used.
|
||||||
|
|
||||||
|
"delay" wait this long before going off
|
||||||
|
"dmg" how much radius damage should be done, defaults to 0
|
||||||
|
*/
|
||||||
|
void target_explosion_explode (edict_t *self)
|
||||||
|
{
|
||||||
|
float save;
|
||||||
|
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (TE_EXPLOSION1);
|
||||||
|
gi.WritePosition (self->s.origin);
|
||||||
|
gi.multicast (self->s.origin, MULTICAST_PHS);
|
||||||
|
|
||||||
|
T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);
|
||||||
|
|
||||||
|
save = self->delay;
|
||||||
|
self->delay = 0;
|
||||||
|
G_UseTargets (self, self->activator);
|
||||||
|
self->delay = save;
|
||||||
|
}
|
||||||
|
|
||||||
|
void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
self->activator = activator;
|
||||||
|
|
||||||
|
if (!self->delay)
|
||||||
|
{
|
||||||
|
target_explosion_explode (self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->think = target_explosion_explode;
|
||||||
|
self->nextthink = level.time + self->delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_explosion (edict_t *ent)
|
||||||
|
{
|
||||||
|
ent->use = use_target_explosion;
|
||||||
|
ent->svflags = SVF_NOCLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
|
||||||
|
Changes level to "map" when fired
|
||||||
|
*/
|
||||||
|
void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
if (level.intermissiontime)
|
||||||
|
return; // allready activated
|
||||||
|
|
||||||
|
if (!deathmatch->value && !coop->value)
|
||||||
|
{
|
||||||
|
if (g_edicts[1].health <= 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if noexit, do a ton of damage to other
|
||||||
|
if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world)
|
||||||
|
{
|
||||||
|
T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if multiplayer, let everyone know who hit the exit
|
||||||
|
if (deathmatch->value)
|
||||||
|
{
|
||||||
|
if (activator && activator->client)
|
||||||
|
gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if going to a new unit, clear cross triggers
|
||||||
|
if (strstr(self->map, "*"))
|
||||||
|
game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);
|
||||||
|
|
||||||
|
BeginIntermission (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_changelevel (edict_t *ent)
|
||||||
|
{
|
||||||
|
if (!ent->map)
|
||||||
|
{
|
||||||
|
gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ugly hack because *SOMEBODY* screwed up their map
|
||||||
|
if((Q_stricmp(level.mapname, "fact1") == 0) && (Q_stricmp(ent->map, "fact3") == 0))
|
||||||
|
ent->map = "fact3$secret1";
|
||||||
|
|
||||||
|
ent->use = use_target_changelevel;
|
||||||
|
ent->svflags = SVF_NOCLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
|
||||||
|
Creates a particle splash effect when used.
|
||||||
|
|
||||||
|
Set "sounds" to one of the following:
|
||||||
|
1) sparks
|
||||||
|
2) blue water
|
||||||
|
3) brown water
|
||||||
|
4) slime
|
||||||
|
5) lava
|
||||||
|
6) blood
|
||||||
|
|
||||||
|
"count" how many pixels in the splash
|
||||||
|
"dmg" if set, does a radius damage at this location when it splashes
|
||||||
|
useful for lava/sparks
|
||||||
|
*/
|
||||||
|
|
||||||
|
void use_target_splash (edict_t *self, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (TE_SPLASH);
|
||||||
|
gi.WriteByte (self->count);
|
||||||
|
gi.WritePosition (self->s.origin);
|
||||||
|
gi.WriteDir (self->movedir);
|
||||||
|
gi.WriteByte (self->sounds);
|
||||||
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
||||||
|
|
||||||
|
if (self->dmg)
|
||||||
|
T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_splash (edict_t *self)
|
||||||
|
{
|
||||||
|
self->use = use_target_splash;
|
||||||
|
G_SetMovedir (self->s.angles, self->movedir);
|
||||||
|
|
||||||
|
if (!self->count)
|
||||||
|
self->count = 32;
|
||||||
|
|
||||||
|
self->svflags = SVF_NOCLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
|
||||||
|
Set target to the type of entity you want spawned.
|
||||||
|
Useful for spawning monsters and gibs in the factory levels.
|
||||||
|
|
||||||
|
For monsters:
|
||||||
|
Set direction to the facing you want it to have.
|
||||||
|
|
||||||
|
For gibs:
|
||||||
|
Set direction if you want it moving and
|
||||||
|
speed how fast it should be moving otherwise it
|
||||||
|
will just be dropped
|
||||||
|
*/
|
||||||
|
void ED_CallSpawn (edict_t *ent);
|
||||||
|
|
||||||
|
void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
edict_t *ent;
|
||||||
|
|
||||||
|
ent = G_Spawn();
|
||||||
|
ent->classname = self->target;
|
||||||
|
VectorCopy (self->s.origin, ent->s.origin);
|
||||||
|
VectorCopy (self->s.angles, ent->s.angles);
|
||||||
|
ED_CallSpawn (ent);
|
||||||
|
gi.unlinkentity (ent);
|
||||||
|
KillBox (ent);
|
||||||
|
gi.linkentity (ent);
|
||||||
|
if (self->speed)
|
||||||
|
VectorCopy (self->movedir, ent->velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_spawner (edict_t *self)
|
||||||
|
{
|
||||||
|
self->use = use_target_spawner;
|
||||||
|
self->svflags = SVF_NOCLIENT;
|
||||||
|
if (self->speed)
|
||||||
|
{
|
||||||
|
G_SetMovedir (self->s.angles, self->movedir);
|
||||||
|
VectorScale (self->movedir, self->speed, self->movedir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
|
||||||
|
Fires a blaster bolt in the set direction when triggered.
|
||||||
|
|
||||||
|
dmg default is 15
|
||||||
|
speed default is 1000
|
||||||
|
*/
|
||||||
|
|
||||||
|
void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
int effect;
|
||||||
|
|
||||||
|
if (self->spawnflags & 2)
|
||||||
|
effect = 0;
|
||||||
|
else if (self->spawnflags & 1)
|
||||||
|
effect = EF_HYPERBLASTER;
|
||||||
|
else
|
||||||
|
effect = EF_BLASTER;
|
||||||
|
|
||||||
|
fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER);
|
||||||
|
gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_blaster (edict_t *self)
|
||||||
|
{
|
||||||
|
self->use = use_target_blaster;
|
||||||
|
G_SetMovedir (self->s.angles, self->movedir);
|
||||||
|
self->noise_index = gi.soundindex ("weapons/laser2.wav");
|
||||||
|
|
||||||
|
if (!self->dmg)
|
||||||
|
self->dmg = 15;
|
||||||
|
if (!self->speed)
|
||||||
|
self->speed = 1000;
|
||||||
|
|
||||||
|
self->svflags = SVF_NOCLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
|
||||||
|
Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work.
|
||||||
|
*/
|
||||||
|
void trigger_crosslevel_trigger_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
game.serverflags |= self->spawnflags;
|
||||||
|
G_FreeEdict (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_crosslevel_trigger (edict_t *self)
|
||||||
|
{
|
||||||
|
self->svflags = SVF_NOCLIENT;
|
||||||
|
self->use = trigger_crosslevel_trigger_use;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
|
||||||
|
Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and
|
||||||
|
killtarget also work.
|
||||||
|
|
||||||
|
"delay" delay before using targets if the trigger has been activated (default 1)
|
||||||
|
*/
|
||||||
|
void target_crosslevel_target_think (edict_t *self)
|
||||||
|
{
|
||||||
|
if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
|
||||||
|
{
|
||||||
|
G_UseTargets (self, self);
|
||||||
|
G_FreeEdict (self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_crosslevel_target (edict_t *self)
|
||||||
|
{
|
||||||
|
if (! self->delay)
|
||||||
|
self->delay = 1;
|
||||||
|
self->svflags = SVF_NOCLIENT;
|
||||||
|
|
||||||
|
self->think = target_crosslevel_target_think;
|
||||||
|
self->nextthink = level.time + self->delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
|
||||||
|
When triggered, fires a laser. You can either set a target
|
||||||
|
or a direction.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void target_laser_think (edict_t *self)
|
||||||
|
{
|
||||||
|
edict_t *ignore;
|
||||||
|
vec3_t start;
|
||||||
|
vec3_t end;
|
||||||
|
trace_t tr;
|
||||||
|
vec3_t point;
|
||||||
|
vec3_t last_movedir;
|
||||||
|
int count;
|
||||||
|
|
||||||
|
if (self->spawnflags & 0x80000000)
|
||||||
|
count = 8;
|
||||||
|
else
|
||||||
|
count = 4;
|
||||||
|
|
||||||
|
if (self->enemy)
|
||||||
|
{
|
||||||
|
VectorCopy (self->movedir, last_movedir);
|
||||||
|
VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
|
||||||
|
VectorSubtract (point, self->s.origin, self->movedir);
|
||||||
|
VectorNormalize (self->movedir);
|
||||||
|
if (!VectorCompare(self->movedir, last_movedir))
|
||||||
|
self->spawnflags |= 0x80000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore = self;
|
||||||
|
VectorCopy (self->s.origin, start);
|
||||||
|
VectorMA (start, 2048, self->movedir, end);
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
|
||||||
|
|
||||||
|
if (!tr.ent)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// hurt it if we can
|
||||||
|
if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
|
||||||
|
T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
|
||||||
|
|
||||||
|
// if we hit something that's not a monster or player or is immune to lasers, we're done
|
||||||
|
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
|
||||||
|
{
|
||||||
|
if (self->spawnflags & 0x80000000)
|
||||||
|
{
|
||||||
|
self->spawnflags &= ~0x80000000;
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (TE_LASER_SPARKS);
|
||||||
|
gi.WriteByte (count);
|
||||||
|
gi.WritePosition (tr.endpos);
|
||||||
|
gi.WriteDir (tr.plane.normal);
|
||||||
|
gi.WriteByte (self->s.skinnum);
|
||||||
|
gi.multicast (tr.endpos, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore = tr.ent;
|
||||||
|
VectorCopy (tr.endpos, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
VectorCopy (tr.endpos, self->s.old_origin);
|
||||||
|
|
||||||
|
self->nextthink = level.time + FRAMETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
void target_laser_on (edict_t *self)
|
||||||
|
{
|
||||||
|
if (!self->activator)
|
||||||
|
self->activator = self;
|
||||||
|
self->spawnflags |= 0x80000001;
|
||||||
|
self->svflags &= ~SVF_NOCLIENT;
|
||||||
|
target_laser_think (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void target_laser_off (edict_t *self)
|
||||||
|
{
|
||||||
|
self->spawnflags &= ~1;
|
||||||
|
self->svflags |= SVF_NOCLIENT;
|
||||||
|
self->nextthink = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void target_laser_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
self->activator = activator;
|
||||||
|
if (self->spawnflags & 1)
|
||||||
|
target_laser_off (self);
|
||||||
|
else
|
||||||
|
target_laser_on (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void target_laser_start (edict_t *self)
|
||||||
|
{
|
||||||
|
edict_t *ent;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (!self->enemy)
|
||||||
|
{
|
||||||
|
if (self->target)
|
||||||
|
{
|
||||||
|
ent = G_Find (NULL, FOFS(targetname), self->target);
|
||||||
|
if (!ent)
|
||||||
|
gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
|
||||||
|
self->enemy = ent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
G_SetMovedir (self->s.angles, self->movedir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self->use = target_laser_use;
|
||||||
|
self->think = target_laser_think;
|
||||||
|
|
||||||
|
if (!self->dmg)
|
||||||
|
self->dmg = 1;
|
||||||
|
|
||||||
|
VectorSet (self->mins, -8, -8, -8);
|
||||||
|
VectorSet (self->maxs, 8, 8, 8);
|
||||||
|
gi.linkentity (self);
|
||||||
|
|
||||||
|
if (self->spawnflags & 1)
|
||||||
|
target_laser_on (self);
|
||||||
|
else
|
||||||
|
target_laser_off (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_laser (edict_t *self)
|
||||||
|
{
|
||||||
|
// let everything else get spawned before we start firing
|
||||||
|
self->think = target_laser_start;
|
||||||
|
self->nextthink = level.time + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
|
||||||
|
speed How many seconds the ramping will take
|
||||||
|
message two letters; starting lightlevel and ending lightlevel
|
||||||
|
*/
|
||||||
|
|
||||||
|
void target_lightramp_think (edict_t *self)
|
||||||
|
{
|
||||||
|
char style[2];
|
||||||
|
|
||||||
|
style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
|
||||||
|
style[1] = 0;
|
||||||
|
gi.configstring (CS_LIGHTS+self->enemy->style, style);
|
||||||
|
|
||||||
|
if ((level.time - self->timestamp) < self->speed)
|
||||||
|
{
|
||||||
|
self->nextthink = level.time + FRAMETIME;
|
||||||
|
}
|
||||||
|
else if (self->spawnflags & 1)
|
||||||
|
{
|
||||||
|
char temp;
|
||||||
|
|
||||||
|
temp = self->movedir[0];
|
||||||
|
self->movedir[0] = self->movedir[1];
|
||||||
|
self->movedir[1] = temp;
|
||||||
|
self->movedir[2] *= -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
if (!self->enemy)
|
||||||
|
{
|
||||||
|
edict_t *e;
|
||||||
|
|
||||||
|
// check all the targets
|
||||||
|
e = NULL;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
e = G_Find (e, FOFS(targetname), self->target);
|
||||||
|
if (!e)
|
||||||
|
break;
|
||||||
|
if (strcmp(e->classname, "light") != 0)
|
||||||
|
{
|
||||||
|
gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
|
||||||
|
gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self->enemy = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self->enemy)
|
||||||
|
{
|
||||||
|
gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin));
|
||||||
|
G_FreeEdict (self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self->timestamp = level.time;
|
||||||
|
target_lightramp_think (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_lightramp (edict_t *self)
|
||||||
|
{
|
||||||
|
if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1])
|
||||||
|
{
|
||||||
|
gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin));
|
||||||
|
G_FreeEdict (self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deathmatch->value)
|
||||||
|
{
|
||||||
|
G_FreeEdict (self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self->target)
|
||||||
|
{
|
||||||
|
gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
|
||||||
|
G_FreeEdict (self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->svflags |= SVF_NOCLIENT;
|
||||||
|
self->use = target_lightramp_use;
|
||||||
|
self->think = target_lightramp_think;
|
||||||
|
|
||||||
|
self->movedir[0] = self->message[0] - 'a';
|
||||||
|
self->movedir[1] = self->message[1] - 'a';
|
||||||
|
self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
|
||||||
|
When triggered, this initiates a level-wide earthquake.
|
||||||
|
All players and monsters are affected.
|
||||||
|
"speed" severity of the quake (default:200)
|
||||||
|
"count" duration of the quake (default:5)
|
||||||
|
*/
|
||||||
|
|
||||||
|
void target_earthquake_think (edict_t *self)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
edict_t *e;
|
||||||
|
|
||||||
|
if (self->last_move_time < level.time)
|
||||||
|
{
|
||||||
|
gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0);
|
||||||
|
self->last_move_time = level.time + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
|
||||||
|
{
|
||||||
|
if (!e->inuse)
|
||||||
|
continue;
|
||||||
|
if (!e->client)
|
||||||
|
continue;
|
||||||
|
if (!e->groundentity)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
e->groundentity = NULL;
|
||||||
|
e->velocity[0] += crandom()* 150;
|
||||||
|
e->velocity[1] += crandom()* 150;
|
||||||
|
e->velocity[2] = self->speed * (100.0 / e->mass);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level.time < self->timestamp)
|
||||||
|
self->nextthink = level.time + FRAMETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
self->timestamp = level.time + self->count;
|
||||||
|
self->nextthink = level.time + FRAMETIME;
|
||||||
|
self->activator = activator;
|
||||||
|
self->last_move_time = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_target_earthquake (edict_t *self)
|
||||||
|
{
|
||||||
|
if (!self->targetname)
|
||||||
|
gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
|
||||||
|
|
||||||
|
if (!self->count)
|
||||||
|
self->count = 5;
|
||||||
|
|
||||||
|
if (!self->speed)
|
||||||
|
self->speed = 200;
|
||||||
|
|
||||||
|
self->svflags |= SVF_NOCLIENT;
|
||||||
|
self->think = target_earthquake_think;
|
||||||
|
self->use = target_earthquake_use;
|
||||||
|
|
||||||
|
self->noise_index = gi.soundindex ("world/quake.wav");
|
||||||
|
}
|
||||||
|
|
598
src/g_trigger.c
Normal file
598
src/g_trigger.c
Normal file
|
@ -0,0 +1,598 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
|
||||||
|
void InitTrigger (edict_t *self)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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->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, edict_t *activator)
|
||||||
|
{
|
||||||
|
ent->activator = activator;
|
||||||
|
multi_trigger (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Touch_Multi (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||||
|
{
|
||||||
|
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, edict_t *activator)
|
||||||
|
{
|
||||||
|
self->solid = SOLID_TRIGGER;
|
||||||
|
self->use = Use_Multi;
|
||||||
|
gi.linkentity (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_trigger_multiple (edict_t *ent)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
4)
|
||||||
|
|
||||||
|
"message" string to be displayed when triggered
|
||||||
|
*/
|
||||||
|
|
||||||
|
void SP_trigger_once(edict_t *ent)
|
||||||
|
{
|
||||||
|
// 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, edict_t *activator)
|
||||||
|
{
|
||||||
|
G_UseTargets (self, activator);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_trigger_relay (edict_t *self)
|
||||||
|
{
|
||||||
|
self->use = trigger_relay_use;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
trigger_key
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*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, edict_t *activator)
|
||||||
|
{
|
||||||
|
int index;
|
||||||
|
|
||||||
|
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 (!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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
trigger_counter
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*QUAKED trigger_counter (.5 .5 .5) ? nomessage
|
||||||
|
Acts as an intermediary for an action that takes multiple inputs.
|
||||||
|
|
||||||
|
If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
|
||||||
|
|
||||||
|
After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void trigger_counter_use(edict_t *self, edict_t *other, edict_t *activator)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
self->wait = -1;
|
||||||
|
if (!self->count)
|
||||||
|
self->count = 2;
|
||||||
|
|
||||||
|
self->use = trigger_counter_use;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
trigger_always
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*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)
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
trigger_push
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define PUSH_ONCE 1
|
||||||
|
|
||||||
|
static int windsound;
|
||||||
|
|
||||||
|
void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||||
|
{
|
||||||
|
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 SP_trigger_push (edict_t *self)
|
||||||
|
{
|
||||||
|
InitTrigger (self);
|
||||||
|
windsound = gi.soundindex ("misc/windfly.wav");
|
||||||
|
self->touch = trigger_push_touch;
|
||||||
|
if (!self->speed)
|
||||||
|
self->speed = 1000;
|
||||||
|
gi.linkentity (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
trigger_hurt
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*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, edict_t *activator)
|
||||||
|
{
|
||||||
|
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, csurface_t *surf)
|
||||||
|
{
|
||||||
|
int dflags;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
trigger_gravity
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*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, csurface_t *surf)
|
||||||
|
{
|
||||||
|
other->gravity = self->gravity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SP_trigger_gravity (edict_t *self)
|
||||||
|
{
|
||||||
|
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 = atoi(st.gravity);
|
||||||
|
self->touch = trigger_gravity_touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
trigger_monsterjump
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*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, csurface_t *surf)
|
||||||
|
{
|
||||||
|
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->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;
|
||||||
|
}
|
||||||
|
|
568
src/g_utils.c
Normal file
568
src/g_utils.c
Normal file
|
@ -0,0 +1,568 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
// g_utils.c -- misc utility functions for game module
|
||||||
|
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
|
||||||
|
void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
|
||||||
|
{
|
||||||
|
result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
|
||||||
|
result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
|
||||||
|
result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
G_Find
|
||||||
|
|
||||||
|
Searches all active entities for the next one that holds
|
||||||
|
the matching string at fieldofs (use the FOFS() macro) in the structure.
|
||||||
|
|
||||||
|
Searches beginning at the edict after from, or the beginning if NULL
|
||||||
|
NULL will be returned if the end of the list is reached.
|
||||||
|
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
edict_t *G_Find (edict_t *from, int fieldofs, char *match)
|
||||||
|
{
|
||||||
|
char *s;
|
||||||
|
|
||||||
|
if (!from)
|
||||||
|
from = g_edicts;
|
||||||
|
else
|
||||||
|
from++;
|
||||||
|
|
||||||
|
for ( ; from < &g_edicts[globals.num_edicts] ; from++)
|
||||||
|
{
|
||||||
|
if (!from->inuse)
|
||||||
|
continue;
|
||||||
|
s = *(char **) ((byte *)from + fieldofs);
|
||||||
|
if (!s)
|
||||||
|
continue;
|
||||||
|
if (!Q_stricmp (s, match))
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
findradius
|
||||||
|
|
||||||
|
Returns entities that have origins within a spherical area
|
||||||
|
|
||||||
|
findradius (origin, radius)
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
edict_t *findradius (edict_t *from, vec3_t org, float rad)
|
||||||
|
{
|
||||||
|
vec3_t eorg;
|
||||||
|
int j;
|
||||||
|
|
||||||
|
if (!from)
|
||||||
|
from = g_edicts;
|
||||||
|
else
|
||||||
|
from++;
|
||||||
|
for ( ; from < &g_edicts[globals.num_edicts]; from++)
|
||||||
|
{
|
||||||
|
if (!from->inuse)
|
||||||
|
continue;
|
||||||
|
if (from->solid == SOLID_NOT)
|
||||||
|
continue;
|
||||||
|
for (j=0 ; j<3 ; j++)
|
||||||
|
eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
|
||||||
|
if (VectorLength(eorg) > rad)
|
||||||
|
continue;
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
G_PickTarget
|
||||||
|
|
||||||
|
Searches all active entities for the next one that holds
|
||||||
|
the matching string at fieldofs (use the FOFS() macro) in the structure.
|
||||||
|
|
||||||
|
Searches beginning at the edict after from, or the beginning if NULL
|
||||||
|
NULL will be returned if the end of the list is reached.
|
||||||
|
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
#define MAXCHOICES 8
|
||||||
|
|
||||||
|
edict_t *G_PickTarget (char *targetname)
|
||||||
|
{
|
||||||
|
edict_t *ent = NULL;
|
||||||
|
int num_choices = 0;
|
||||||
|
edict_t *choice[MAXCHOICES];
|
||||||
|
|
||||||
|
if (!targetname)
|
||||||
|
{
|
||||||
|
gi.dprintf("G_PickTarget called with NULL targetname\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
ent = G_Find (ent, FOFS(targetname), targetname);
|
||||||
|
if (!ent)
|
||||||
|
break;
|
||||||
|
choice[num_choices++] = ent;
|
||||||
|
if (num_choices == MAXCHOICES)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!num_choices)
|
||||||
|
{
|
||||||
|
gi.dprintf("G_PickTarget: target %s not found\n", targetname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return choice[rand() % num_choices];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Think_Delay (edict_t *ent)
|
||||||
|
{
|
||||||
|
G_UseTargets (ent, ent->activator);
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============================
|
||||||
|
G_UseTargets
|
||||||
|
|
||||||
|
the global "activator" should be set to the entity that initiated the firing.
|
||||||
|
|
||||||
|
If self.delay is set, a DelayedUse entity will be created that will actually
|
||||||
|
do the SUB_UseTargets after that many seconds have passed.
|
||||||
|
|
||||||
|
Centerprints any self.message to the activator.
|
||||||
|
|
||||||
|
Search for (string)targetname in all entities that
|
||||||
|
match (string)self.target and call their .use function
|
||||||
|
|
||||||
|
==============================
|
||||||
|
*/
|
||||||
|
void G_UseTargets (edict_t *ent, edict_t *activator)
|
||||||
|
{
|
||||||
|
edict_t *t;
|
||||||
|
|
||||||
|
//
|
||||||
|
// check for a delay
|
||||||
|
//
|
||||||
|
if (ent->delay)
|
||||||
|
{
|
||||||
|
// create a temp object to fire at a later time
|
||||||
|
t = G_Spawn();
|
||||||
|
t->classname = "DelayedUse";
|
||||||
|
t->nextthink = level.time + ent->delay;
|
||||||
|
t->think = Think_Delay;
|
||||||
|
t->activator = activator;
|
||||||
|
if (!activator)
|
||||||
|
gi.dprintf ("Think_Delay with no activator\n");
|
||||||
|
t->message = ent->message;
|
||||||
|
t->target = ent->target;
|
||||||
|
t->killtarget = ent->killtarget;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// print the message
|
||||||
|
//
|
||||||
|
if ((ent->message) && !(activator->svflags & SVF_MONSTER))
|
||||||
|
{
|
||||||
|
gi.centerprintf (activator, "%s", ent->message);
|
||||||
|
if (ent->noise_index)
|
||||||
|
gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
|
||||||
|
else
|
||||||
|
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// kill killtargets
|
||||||
|
//
|
||||||
|
if (ent->killtarget)
|
||||||
|
{
|
||||||
|
t = NULL;
|
||||||
|
while ((t = G_Find (t, FOFS(targetname), ent->killtarget)))
|
||||||
|
{
|
||||||
|
G_FreeEdict (t);
|
||||||
|
if (!ent->inuse)
|
||||||
|
{
|
||||||
|
gi.dprintf("entity was removed while using killtargets\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// fire targets
|
||||||
|
//
|
||||||
|
if (ent->target)
|
||||||
|
{
|
||||||
|
t = NULL;
|
||||||
|
while ((t = G_Find (t, FOFS(targetname), ent->target)))
|
||||||
|
{
|
||||||
|
// doors fire area portals in a specific way
|
||||||
|
if (!Q_stricmp(t->classname, "func_areaportal") &&
|
||||||
|
(!Q_stricmp(ent->classname, "func_door") || !Q_stricmp(ent->classname, "func_door_rotating")))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (t == ent)
|
||||||
|
{
|
||||||
|
gi.dprintf ("WARNING: Entity used itself.\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (t->use)
|
||||||
|
t->use (t, ent, activator);
|
||||||
|
}
|
||||||
|
if (!ent->inuse)
|
||||||
|
{
|
||||||
|
gi.dprintf("entity was removed while using targets\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
TempVector
|
||||||
|
|
||||||
|
This is just a convenience function
|
||||||
|
for making temporary vectors for function calls
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
float *tv (float x, float y, float z)
|
||||||
|
{
|
||||||
|
static int index;
|
||||||
|
static vec3_t vecs[8];
|
||||||
|
float *v;
|
||||||
|
|
||||||
|
// use an array so that multiple tempvectors won't collide
|
||||||
|
// for a while
|
||||||
|
v = vecs[index];
|
||||||
|
index = (index + 1)&7;
|
||||||
|
|
||||||
|
v[0] = x;
|
||||||
|
v[1] = y;
|
||||||
|
v[2] = z;
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
VectorToString
|
||||||
|
|
||||||
|
This is just a convenience function
|
||||||
|
for printing vectors
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
char *vtos (vec3_t v)
|
||||||
|
{
|
||||||
|
static int index;
|
||||||
|
static char str[8][32];
|
||||||
|
char *s;
|
||||||
|
|
||||||
|
// use an array so that multiple vtos won't collide
|
||||||
|
s = str[index];
|
||||||
|
index = (index + 1)&7;
|
||||||
|
|
||||||
|
Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
vec3_t VEC_UP = {0, -1, 0};
|
||||||
|
vec3_t MOVEDIR_UP = {0, 0, 1};
|
||||||
|
vec3_t VEC_DOWN = {0, -2, 0};
|
||||||
|
vec3_t MOVEDIR_DOWN = {0, 0, -1};
|
||||||
|
|
||||||
|
void G_SetMovedir (vec3_t angles, vec3_t movedir)
|
||||||
|
{
|
||||||
|
if (VectorCompare (angles, VEC_UP))
|
||||||
|
{
|
||||||
|
VectorCopy (MOVEDIR_UP, movedir);
|
||||||
|
}
|
||||||
|
else if (VectorCompare (angles, VEC_DOWN))
|
||||||
|
{
|
||||||
|
VectorCopy (MOVEDIR_DOWN, movedir);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AngleVectors (angles, movedir, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
VectorClear (angles);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float vectoyaw (vec3_t vec)
|
||||||
|
{
|
||||||
|
float yaw;
|
||||||
|
|
||||||
|
if (/* vec[YAW] == 0 && */ vec[PITCH] == 0)
|
||||||
|
{
|
||||||
|
yaw = 0;
|
||||||
|
if (vec[YAW] > 0)
|
||||||
|
yaw = 90;
|
||||||
|
else if (vec[YAW] < 0)
|
||||||
|
yaw = -90;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
|
||||||
|
if (yaw < 0)
|
||||||
|
yaw += 360;
|
||||||
|
}
|
||||||
|
|
||||||
|
return yaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void vectoangles (vec3_t value1, vec3_t angles)
|
||||||
|
{
|
||||||
|
float forward;
|
||||||
|
float yaw, pitch;
|
||||||
|
|
||||||
|
if (value1[1] == 0 && value1[0] == 0)
|
||||||
|
{
|
||||||
|
yaw = 0;
|
||||||
|
if (value1[2] > 0)
|
||||||
|
pitch = 90;
|
||||||
|
else
|
||||||
|
pitch = 270;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (value1[0])
|
||||||
|
yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI);
|
||||||
|
else if (value1[1] > 0)
|
||||||
|
yaw = 90;
|
||||||
|
else
|
||||||
|
yaw = -90;
|
||||||
|
if (yaw < 0)
|
||||||
|
yaw += 360;
|
||||||
|
|
||||||
|
forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]);
|
||||||
|
pitch = (int) (atan2(value1[2], forward) * 180 / M_PI);
|
||||||
|
if (pitch < 0)
|
||||||
|
pitch += 360;
|
||||||
|
}
|
||||||
|
|
||||||
|
angles[PITCH] = -pitch;
|
||||||
|
angles[YAW] = yaw;
|
||||||
|
angles[ROLL] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *G_CopyString (char *in)
|
||||||
|
{
|
||||||
|
char *out;
|
||||||
|
|
||||||
|
out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL);
|
||||||
|
strcpy (out, in);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void G_InitEdict (edict_t *e)
|
||||||
|
{
|
||||||
|
e->inuse = true;
|
||||||
|
e->classname = "noclass";
|
||||||
|
e->gravity = 1.0;
|
||||||
|
e->s.number = e - g_edicts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
G_Spawn
|
||||||
|
|
||||||
|
Either finds a free edict, or allocates a new one.
|
||||||
|
Try to avoid reusing an entity that was recently freed, because it
|
||||||
|
can cause the client to think the entity morphed into something else
|
||||||
|
instead of being removed and recreated, which can cause interpolated
|
||||||
|
angles and bad trails.
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
edict_t *G_Spawn (void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
edict_t *e;
|
||||||
|
|
||||||
|
e = &g_edicts[(int)maxclients->value+1];
|
||||||
|
for ( i=maxclients->value+1 ; i<globals.num_edicts ; i++, e++)
|
||||||
|
{
|
||||||
|
// the first couple seconds of server time can involve a lot of
|
||||||
|
// freeing and allocating, so relax the replacement policy
|
||||||
|
if (!e->inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
|
||||||
|
{
|
||||||
|
G_InitEdict (e);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == game.maxentities)
|
||||||
|
gi.error ("ED_Alloc: no free edicts");
|
||||||
|
|
||||||
|
globals.num_edicts++;
|
||||||
|
G_InitEdict (e);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
G_FreeEdict
|
||||||
|
|
||||||
|
Marks the edict as free
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void G_FreeEdict (edict_t *ed)
|
||||||
|
{
|
||||||
|
gi.unlinkentity (ed); // unlink from world
|
||||||
|
|
||||||
|
if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset (ed, 0, sizeof(*ed));
|
||||||
|
ed->classname = "freed";
|
||||||
|
ed->freetime = level.time;
|
||||||
|
ed->inuse = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
G_TouchTriggers
|
||||||
|
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
void G_TouchTriggers (edict_t *ent)
|
||||||
|
{
|
||||||
|
int i, num;
|
||||||
|
edict_t *touch[MAX_EDICTS], *hit;
|
||||||
|
|
||||||
|
// dead things don't activate triggers!
|
||||||
|
if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
|
||||||
|
, MAX_EDICTS, AREA_TRIGGERS);
|
||||||
|
|
||||||
|
// be careful, it is possible to have an entity in this
|
||||||
|
// list removed before we get to it (killtriggered)
|
||||||
|
for (i=0 ; i<num ; i++)
|
||||||
|
{
|
||||||
|
hit = touch[i];
|
||||||
|
if (!hit->inuse)
|
||||||
|
continue;
|
||||||
|
if (!hit->touch)
|
||||||
|
continue;
|
||||||
|
hit->touch (hit, ent, NULL, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
G_TouchSolids
|
||||||
|
|
||||||
|
Call after linking a new trigger in during gameplay
|
||||||
|
to force all entities it covers to immediately touch it
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
void G_TouchSolids (edict_t *ent)
|
||||||
|
{
|
||||||
|
int i, num;
|
||||||
|
edict_t *touch[MAX_EDICTS], *hit;
|
||||||
|
|
||||||
|
num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
|
||||||
|
, MAX_EDICTS, AREA_SOLID);
|
||||||
|
|
||||||
|
// be careful, it is possible to have an entity in this
|
||||||
|
// list removed before we get to it (killtriggered)
|
||||||
|
for (i=0 ; i<num ; i++)
|
||||||
|
{
|
||||||
|
hit = touch[i];
|
||||||
|
if (!hit->inuse)
|
||||||
|
continue;
|
||||||
|
if (ent->touch)
|
||||||
|
ent->touch (hit, ent, NULL, NULL);
|
||||||
|
if (!ent->inuse)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
Kill box
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
KillBox
|
||||||
|
|
||||||
|
Kills all entities that would touch the proposed new positioning
|
||||||
|
of ent. Ent should be unlinked before calling this!
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
qboolean KillBox (edict_t *ent)
|
||||||
|
{
|
||||||
|
trace_t tr;
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID);
|
||||||
|
if (!tr.ent)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// nail it
|
||||||
|
T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
|
||||||
|
|
||||||
|
// if we didn't kill it, fail
|
||||||
|
if (tr.ent->solid)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // all clear
|
||||||
|
}
|
||||||
|
|
921
src/g_weapon.c
Normal file
921
src/g_weapon.c
Normal file
|
@ -0,0 +1,921 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
check_dodge
|
||||||
|
|
||||||
|
This is a support routine used when a client is firing
|
||||||
|
a non-instant attack weapon. It checks to see if a
|
||||||
|
monster's dodge function should be called.
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed)
|
||||||
|
{
|
||||||
|
vec3_t end;
|
||||||
|
vec3_t v;
|
||||||
|
trace_t tr;
|
||||||
|
float eta;
|
||||||
|
|
||||||
|
// easy mode only ducks one quarter the time
|
||||||
|
if (skill->value == 0)
|
||||||
|
{
|
||||||
|
if (random() > 0.25)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VectorMA (start, 8192, dir, end);
|
||||||
|
tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
|
||||||
|
if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self))
|
||||||
|
{
|
||||||
|
VectorSubtract (tr.endpos, start, v);
|
||||||
|
eta = (VectorLength(v) - tr.ent->maxs[0]) / speed;
|
||||||
|
tr.ent->monsterinfo.dodge (tr.ent, self, eta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
fire_hit
|
||||||
|
|
||||||
|
Used for all impact (hit/punch/slash) attacks
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick)
|
||||||
|
{
|
||||||
|
trace_t tr;
|
||||||
|
vec3_t forward, right, up;
|
||||||
|
vec3_t v;
|
||||||
|
vec3_t point;
|
||||||
|
float range;
|
||||||
|
vec3_t dir;
|
||||||
|
|
||||||
|
//see if enemy is in range
|
||||||
|
VectorSubtract (self->enemy->s.origin, self->s.origin, dir);
|
||||||
|
range = VectorLength(dir);
|
||||||
|
if (range > aim[0])
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (aim[1] > self->mins[0] && aim[1] < self->maxs[0])
|
||||||
|
{
|
||||||
|
// the hit is straight on so back the range up to the edge of their bbox
|
||||||
|
range -= self->enemy->maxs[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// this is a side hit so adjust the "right" value out to the edge of their bbox
|
||||||
|
if (aim[1] < 0)
|
||||||
|
aim[1] = self->enemy->mins[0];
|
||||||
|
else
|
||||||
|
aim[1] = self->enemy->maxs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
VectorMA (self->s.origin, range, dir, point);
|
||||||
|
|
||||||
|
tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT);
|
||||||
|
if (tr.fraction < 1)
|
||||||
|
{
|
||||||
|
if (!tr.ent->takedamage)
|
||||||
|
return false;
|
||||||
|
// if it will hit any client/monster then hit the one we wanted to hit
|
||||||
|
if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
|
||||||
|
tr.ent = self->enemy;
|
||||||
|
}
|
||||||
|
|
||||||
|
AngleVectors(self->s.angles, forward, right, up);
|
||||||
|
VectorMA (self->s.origin, range, forward, point);
|
||||||
|
VectorMA (point, aim[1], right, point);
|
||||||
|
VectorMA (point, aim[2], up, point);
|
||||||
|
VectorSubtract (point, self->enemy->s.origin, dir);
|
||||||
|
|
||||||
|
// do the damage
|
||||||
|
T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT);
|
||||||
|
|
||||||
|
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// do our special form of knockback here
|
||||||
|
VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v);
|
||||||
|
VectorSubtract (v, point, v);
|
||||||
|
VectorNormalize (v);
|
||||||
|
VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity);
|
||||||
|
if (self->enemy->velocity[2] > 0)
|
||||||
|
self->enemy->groundentity = NULL;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
fire_lead
|
||||||
|
|
||||||
|
This is an internal support routine used for bullet/pellet based weapons.
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod)
|
||||||
|
{
|
||||||
|
trace_t tr;
|
||||||
|
vec3_t dir;
|
||||||
|
vec3_t forward, right, up;
|
||||||
|
vec3_t end;
|
||||||
|
float r;
|
||||||
|
float u;
|
||||||
|
vec3_t water_start;
|
||||||
|
qboolean water = false;
|
||||||
|
int content_mask = MASK_SHOT | MASK_WATER;
|
||||||
|
|
||||||
|
tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);
|
||||||
|
if (!(tr.fraction < 1.0))
|
||||||
|
{
|
||||||
|
vectoangles (aimdir, dir);
|
||||||
|
AngleVectors (dir, forward, right, up);
|
||||||
|
|
||||||
|
r = crandom()*hspread;
|
||||||
|
u = crandom()*vspread;
|
||||||
|
VectorMA (start, 8192, forward, end);
|
||||||
|
VectorMA (end, r, right, end);
|
||||||
|
VectorMA (end, u, up, end);
|
||||||
|
|
||||||
|
if (gi.pointcontents (start) & MASK_WATER)
|
||||||
|
{
|
||||||
|
water = true;
|
||||||
|
VectorCopy (start, water_start);
|
||||||
|
content_mask &= ~MASK_WATER;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = gi.trace (start, NULL, NULL, end, self, content_mask);
|
||||||
|
|
||||||
|
// see if we hit water
|
||||||
|
if (tr.contents & MASK_WATER)
|
||||||
|
{
|
||||||
|
int color;
|
||||||
|
|
||||||
|
water = true;
|
||||||
|
VectorCopy (tr.endpos, water_start);
|
||||||
|
|
||||||
|
if (!VectorCompare (start, tr.endpos))
|
||||||
|
{
|
||||||
|
if (tr.contents & CONTENTS_WATER)
|
||||||
|
{
|
||||||
|
if (strcmp(tr.surface->name, "*brwater") == 0)
|
||||||
|
color = SPLASH_BROWN_WATER;
|
||||||
|
else
|
||||||
|
color = SPLASH_BLUE_WATER;
|
||||||
|
}
|
||||||
|
else if (tr.contents & CONTENTS_SLIME)
|
||||||
|
color = SPLASH_SLIME;
|
||||||
|
else if (tr.contents & CONTENTS_LAVA)
|
||||||
|
color = SPLASH_LAVA;
|
||||||
|
else
|
||||||
|
color = SPLASH_UNKNOWN;
|
||||||
|
|
||||||
|
if (color != SPLASH_UNKNOWN)
|
||||||
|
{
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (TE_SPLASH);
|
||||||
|
gi.WriteByte (8);
|
||||||
|
gi.WritePosition (tr.endpos);
|
||||||
|
gi.WriteDir (tr.plane.normal);
|
||||||
|
gi.WriteByte (color);
|
||||||
|
gi.multicast (tr.endpos, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// change bullet's course when it enters water
|
||||||
|
VectorSubtract (end, start, dir);
|
||||||
|
vectoangles (dir, dir);
|
||||||
|
AngleVectors (dir, forward, right, up);
|
||||||
|
r = crandom()*hspread*2;
|
||||||
|
u = crandom()*vspread*2;
|
||||||
|
VectorMA (water_start, 8192, forward, end);
|
||||||
|
VectorMA (end, r, right, end);
|
||||||
|
VectorMA (end, u, up, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-trace ignoring water this time
|
||||||
|
tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send gun puff / flash
|
||||||
|
if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
|
||||||
|
{
|
||||||
|
if (tr.fraction < 1.0)
|
||||||
|
{
|
||||||
|
if (tr.ent->takedamage)
|
||||||
|
{
|
||||||
|
T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (strncmp (tr.surface->name, "sky", 3) != 0)
|
||||||
|
{
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (te_impact);
|
||||||
|
gi.WritePosition (tr.endpos);
|
||||||
|
gi.WriteDir (tr.plane.normal);
|
||||||
|
gi.multicast (tr.endpos, MULTICAST_PVS);
|
||||||
|
|
||||||
|
if (self->client)
|
||||||
|
PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if went through water, determine where the end and make a bubble trail
|
||||||
|
if (water)
|
||||||
|
{
|
||||||
|
vec3_t pos;
|
||||||
|
|
||||||
|
VectorSubtract (tr.endpos, water_start, dir);
|
||||||
|
VectorNormalize (dir);
|
||||||
|
VectorMA (tr.endpos, -2, dir, pos);
|
||||||
|
if (gi.pointcontents (pos) & MASK_WATER)
|
||||||
|
VectorCopy (pos, tr.endpos);
|
||||||
|
else
|
||||||
|
tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
|
||||||
|
|
||||||
|
VectorAdd (water_start, tr.endpos, pos);
|
||||||
|
VectorScale (pos, 0.5, pos);
|
||||||
|
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (TE_BUBBLETRAIL);
|
||||||
|
gi.WritePosition (water_start);
|
||||||
|
gi.WritePosition (tr.endpos);
|
||||||
|
gi.multicast (pos, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
fire_bullet
|
||||||
|
|
||||||
|
Fires a single round. Used for machinegun and chaingun. Would be fine for
|
||||||
|
pistols, rifles, etc....
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod)
|
||||||
|
{
|
||||||
|
fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
fire_shotgun
|
||||||
|
|
||||||
|
Shoots shotgun pellets. Used by shotgun and super shotgun.
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
fire_blaster
|
||||||
|
|
||||||
|
Fires a single blaster bolt. Used by the blaster and hyper blaster.
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||||
|
{
|
||||||
|
int mod;
|
||||||
|
|
||||||
|
if (other == self->owner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (surf && (surf->flags & SURF_SKY))
|
||||||
|
{
|
||||||
|
G_FreeEdict (self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self->owner->client)
|
||||||
|
PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
|
||||||
|
|
||||||
|
if (other->takedamage)
|
||||||
|
{
|
||||||
|
if (self->spawnflags & 1)
|
||||||
|
mod = MOD_HYPERBLASTER;
|
||||||
|
else
|
||||||
|
mod = MOD_BLASTER;
|
||||||
|
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (TE_BLASTER);
|
||||||
|
gi.WritePosition (self->s.origin);
|
||||||
|
if (!plane)
|
||||||
|
gi.WriteDir (vec3_origin);
|
||||||
|
else
|
||||||
|
gi.WriteDir (plane->normal);
|
||||||
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
|
||||||
|
G_FreeEdict (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper)
|
||||||
|
{
|
||||||
|
edict_t *bolt;
|
||||||
|
trace_t tr;
|
||||||
|
|
||||||
|
VectorNormalize (dir);
|
||||||
|
|
||||||
|
bolt = G_Spawn();
|
||||||
|
bolt->svflags = SVF_PROJECTILE; // special net code is used for projectiles
|
||||||
|
VectorCopy (start, bolt->s.origin);
|
||||||
|
VectorCopy (start, bolt->s.old_origin);
|
||||||
|
vectoangles (dir, bolt->s.angles);
|
||||||
|
VectorScale (dir, speed, bolt->velocity);
|
||||||
|
bolt->movetype = MOVETYPE_FLYMISSILE;
|
||||||
|
bolt->clipmask = MASK_SHOT;
|
||||||
|
bolt->solid = SOLID_BBOX;
|
||||||
|
bolt->s.effects |= effect;
|
||||||
|
VectorClear (bolt->mins);
|
||||||
|
VectorClear (bolt->maxs);
|
||||||
|
bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2");
|
||||||
|
bolt->s.sound = gi.soundindex ("misc/lasfly.wav");
|
||||||
|
bolt->owner = self;
|
||||||
|
bolt->touch = blaster_touch;
|
||||||
|
bolt->nextthink = level.time + 2;
|
||||||
|
bolt->think = G_FreeEdict;
|
||||||
|
bolt->dmg = damage;
|
||||||
|
bolt->classname = "bolt";
|
||||||
|
if (hyper)
|
||||||
|
bolt->spawnflags = 1;
|
||||||
|
gi.linkentity (bolt);
|
||||||
|
|
||||||
|
if (self->client)
|
||||||
|
check_dodge (self, bolt->s.origin, dir, speed);
|
||||||
|
|
||||||
|
tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
|
||||||
|
if (tr.fraction < 1.0)
|
||||||
|
{
|
||||||
|
VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
|
||||||
|
bolt->touch (bolt, tr.ent, NULL, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
fire_grenade
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
static void Grenade_Explode (edict_t *ent)
|
||||||
|
{
|
||||||
|
vec3_t origin;
|
||||||
|
int mod;
|
||||||
|
|
||||||
|
if (ent->owner->client)
|
||||||
|
PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
|
||||||
|
|
||||||
|
//FIXME: if we are onground then raise our Z just a bit since we are a point?
|
||||||
|
if (ent->enemy)
|
||||||
|
{
|
||||||
|
float points;
|
||||||
|
vec3_t v;
|
||||||
|
vec3_t dir;
|
||||||
|
|
||||||
|
VectorAdd (ent->enemy->mins, ent->enemy->maxs, v);
|
||||||
|
VectorMA (ent->enemy->s.origin, 0.5, v, v);
|
||||||
|
VectorSubtract (ent->s.origin, v, v);
|
||||||
|
points = ent->dmg - 0.5 * VectorLength (v);
|
||||||
|
VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir);
|
||||||
|
if (ent->spawnflags & 1)
|
||||||
|
mod = MOD_HANDGRENADE;
|
||||||
|
else
|
||||||
|
mod = MOD_GRENADE;
|
||||||
|
T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ent->spawnflags & 2)
|
||||||
|
mod = MOD_HELD_GRENADE;
|
||||||
|
else if (ent->spawnflags & 1)
|
||||||
|
mod = MOD_HG_SPLASH;
|
||||||
|
else
|
||||||
|
mod = MOD_G_SPLASH;
|
||||||
|
T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod);
|
||||||
|
|
||||||
|
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
if (ent->waterlevel)
|
||||||
|
{
|
||||||
|
if (ent->groundentity)
|
||||||
|
gi.WriteByte (TE_GRENADE_EXPLOSION_WATER);
|
||||||
|
else
|
||||||
|
gi.WriteByte (TE_ROCKET_EXPLOSION_WATER);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ent->groundentity)
|
||||||
|
gi.WriteByte (TE_GRENADE_EXPLOSION);
|
||||||
|
else
|
||||||
|
gi.WriteByte (TE_ROCKET_EXPLOSION);
|
||||||
|
}
|
||||||
|
gi.WritePosition (origin);
|
||||||
|
gi.multicast (ent->s.origin, MULTICAST_PHS);
|
||||||
|
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||||
|
{
|
||||||
|
if (other == ent->owner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (surf && (surf->flags & SURF_SKY))
|
||||||
|
{
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!other->takedamage)
|
||||||
|
{
|
||||||
|
if (ent->spawnflags & 1)
|
||||||
|
{
|
||||||
|
if (random() > 0.5)
|
||||||
|
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
|
||||||
|
else
|
||||||
|
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ent->enemy = other;
|
||||||
|
Grenade_Explode (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius)
|
||||||
|
{
|
||||||
|
edict_t *grenade;
|
||||||
|
vec3_t dir;
|
||||||
|
vec3_t forward, right, up;
|
||||||
|
|
||||||
|
vectoangles (aimdir, dir);
|
||||||
|
AngleVectors (dir, forward, right, up);
|
||||||
|
|
||||||
|
grenade = G_Spawn();
|
||||||
|
VectorCopy (start, grenade->s.origin);
|
||||||
|
VectorScale (aimdir, speed, grenade->velocity);
|
||||||
|
VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
|
||||||
|
VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
|
||||||
|
VectorSet (grenade->avelocity, 300, 300, 300);
|
||||||
|
grenade->movetype = MOVETYPE_BOUNCE;
|
||||||
|
grenade->clipmask = MASK_SHOT;
|
||||||
|
grenade->solid = SOLID_BBOX;
|
||||||
|
grenade->s.effects |= EF_GRENADE;
|
||||||
|
VectorClear (grenade->mins);
|
||||||
|
VectorClear (grenade->maxs);
|
||||||
|
grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2");
|
||||||
|
grenade->owner = self;
|
||||||
|
grenade->touch = Grenade_Touch;
|
||||||
|
grenade->nextthink = level.time + timer;
|
||||||
|
grenade->think = Grenade_Explode;
|
||||||
|
grenade->dmg = damage;
|
||||||
|
grenade->dmg_radius = damage_radius;
|
||||||
|
grenade->classname = "grenade";
|
||||||
|
|
||||||
|
gi.linkentity (grenade);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held)
|
||||||
|
{
|
||||||
|
edict_t *grenade;
|
||||||
|
vec3_t dir;
|
||||||
|
vec3_t forward, right, up;
|
||||||
|
|
||||||
|
vectoangles (aimdir, dir);
|
||||||
|
AngleVectors (dir, forward, right, up);
|
||||||
|
|
||||||
|
grenade = G_Spawn();
|
||||||
|
VectorCopy (start, grenade->s.origin);
|
||||||
|
VectorScale (aimdir, speed, grenade->velocity);
|
||||||
|
VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
|
||||||
|
VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
|
||||||
|
VectorSet (grenade->avelocity, 300, 300, 300);
|
||||||
|
grenade->movetype = MOVETYPE_BOUNCE;
|
||||||
|
grenade->clipmask = MASK_SHOT;
|
||||||
|
grenade->solid = SOLID_BBOX;
|
||||||
|
grenade->s.effects |= EF_GRENADE;
|
||||||
|
VectorClear (grenade->mins);
|
||||||
|
VectorClear (grenade->maxs);
|
||||||
|
grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2");
|
||||||
|
grenade->owner = self;
|
||||||
|
grenade->touch = Grenade_Touch;
|
||||||
|
grenade->nextthink = level.time + timer;
|
||||||
|
grenade->think = Grenade_Explode;
|
||||||
|
grenade->dmg = damage;
|
||||||
|
grenade->dmg_radius = damage_radius;
|
||||||
|
grenade->classname = "hgrenade";
|
||||||
|
if (held)
|
||||||
|
grenade->spawnflags = 3;
|
||||||
|
else
|
||||||
|
grenade->spawnflags = 1;
|
||||||
|
grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav");
|
||||||
|
|
||||||
|
if (timer <= 0.0)
|
||||||
|
Grenade_Explode (grenade);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0);
|
||||||
|
gi.linkentity (grenade);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
fire_rocket
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||||
|
{
|
||||||
|
vec3_t origin;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if (other == ent->owner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (surf && (surf->flags & SURF_SKY))
|
||||||
|
{
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ent->owner->client)
|
||||||
|
PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
|
||||||
|
|
||||||
|
// calculate position for the explosion entity
|
||||||
|
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
||||||
|
|
||||||
|
if (other->takedamage)
|
||||||
|
{
|
||||||
|
T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// don't throw any debris in net games
|
||||||
|
if (!deathmatch->value && !coop->value)
|
||||||
|
{
|
||||||
|
if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING)))
|
||||||
|
{
|
||||||
|
n = rand() % 5;
|
||||||
|
while(n--)
|
||||||
|
ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH);
|
||||||
|
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
if (ent->waterlevel)
|
||||||
|
gi.WriteByte (TE_ROCKET_EXPLOSION_WATER);
|
||||||
|
else
|
||||||
|
gi.WriteByte (TE_ROCKET_EXPLOSION);
|
||||||
|
gi.WritePosition (origin);
|
||||||
|
gi.multicast (ent->s.origin, MULTICAST_PHS);
|
||||||
|
|
||||||
|
G_FreeEdict (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
|
||||||
|
{
|
||||||
|
edict_t *rocket;
|
||||||
|
|
||||||
|
rocket = G_Spawn();
|
||||||
|
VectorCopy (start, rocket->s.origin);
|
||||||
|
VectorCopy (dir, rocket->movedir);
|
||||||
|
vectoangles (dir, rocket->s.angles);
|
||||||
|
VectorScale (dir, speed, rocket->velocity);
|
||||||
|
rocket->movetype = MOVETYPE_FLYMISSILE;
|
||||||
|
rocket->clipmask = MASK_SHOT;
|
||||||
|
rocket->solid = SOLID_BBOX;
|
||||||
|
rocket->s.effects |= EF_ROCKET;
|
||||||
|
VectorClear (rocket->mins);
|
||||||
|
VectorClear (rocket->maxs);
|
||||||
|
rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
|
||||||
|
rocket->owner = self;
|
||||||
|
rocket->touch = rocket_touch;
|
||||||
|
rocket->nextthink = level.time + 8000/speed;
|
||||||
|
rocket->think = G_FreeEdict;
|
||||||
|
rocket->dmg = damage;
|
||||||
|
rocket->radius_dmg = radius_damage;
|
||||||
|
rocket->dmg_radius = damage_radius;
|
||||||
|
rocket->s.sound = gi.soundindex ("weapons/rockfly.wav");
|
||||||
|
rocket->classname = "rocket";
|
||||||
|
|
||||||
|
if (self->client)
|
||||||
|
check_dodge (self, rocket->s.origin, dir, speed);
|
||||||
|
|
||||||
|
gi.linkentity (rocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
fire_rail
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick)
|
||||||
|
{
|
||||||
|
vec3_t from;
|
||||||
|
vec3_t end;
|
||||||
|
trace_t tr;
|
||||||
|
edict_t *ignore;
|
||||||
|
int mask;
|
||||||
|
qboolean water;
|
||||||
|
|
||||||
|
VectorMA (start, 8192, aimdir, end);
|
||||||
|
VectorCopy (start, from);
|
||||||
|
ignore = self;
|
||||||
|
water = false;
|
||||||
|
mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA;
|
||||||
|
while (ignore)
|
||||||
|
{
|
||||||
|
tr = gi.trace (from, NULL, NULL, end, ignore, mask);
|
||||||
|
|
||||||
|
if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA))
|
||||||
|
{
|
||||||
|
mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA);
|
||||||
|
water = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//ZOID--added so rail goes through SOLID_BBOX entities (gibs, etc)
|
||||||
|
if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) ||
|
||||||
|
(tr.ent->solid == SOLID_BBOX))
|
||||||
|
ignore = tr.ent;
|
||||||
|
else
|
||||||
|
ignore = NULL;
|
||||||
|
|
||||||
|
if ((tr.ent != self) && (tr.ent->takedamage))
|
||||||
|
T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN);
|
||||||
|
}
|
||||||
|
|
||||||
|
VectorCopy (tr.endpos, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send gun puff / flash
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (TE_RAILTRAIL);
|
||||||
|
gi.WritePosition (start);
|
||||||
|
gi.WritePosition (tr.endpos);
|
||||||
|
gi.multicast (self->s.origin, MULTICAST_PHS);
|
||||||
|
if (water)
|
||||||
|
{
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (TE_RAILTRAIL);
|
||||||
|
gi.WritePosition (start);
|
||||||
|
gi.WritePosition (tr.endpos);
|
||||||
|
gi.multicast (tr.endpos, MULTICAST_PHS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self->client)
|
||||||
|
PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
fire_bfg
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
void bfg_explode (edict_t *self)
|
||||||
|
{
|
||||||
|
edict_t *ent;
|
||||||
|
float points;
|
||||||
|
vec3_t v;
|
||||||
|
float dist;
|
||||||
|
|
||||||
|
if (self->s.frame == 0)
|
||||||
|
{
|
||||||
|
// the BFG effect
|
||||||
|
ent = NULL;
|
||||||
|
while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL)
|
||||||
|
{
|
||||||
|
if (!ent->takedamage)
|
||||||
|
continue;
|
||||||
|
if (ent == self->owner)
|
||||||
|
continue;
|
||||||
|
if (!CanDamage (ent, self))
|
||||||
|
continue;
|
||||||
|
if (!CanDamage (ent, self->owner))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
VectorAdd (ent->mins, ent->maxs, v);
|
||||||
|
VectorMA (ent->s.origin, 0.5, v, v);
|
||||||
|
VectorSubtract (self->s.origin, v, v);
|
||||||
|
dist = VectorLength(v);
|
||||||
|
points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius));
|
||||||
|
if (ent == self->owner)
|
||||||
|
points = points * 0.5;
|
||||||
|
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (TE_BFG_EXPLOSION);
|
||||||
|
gi.WritePosition (ent->s.origin);
|
||||||
|
gi.multicast (ent->s.origin, MULTICAST_PHS);
|
||||||
|
T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self->nextthink = level.time + FRAMETIME;
|
||||||
|
self->s.frame++;
|
||||||
|
if (self->s.frame == 5)
|
||||||
|
self->think = G_FreeEdict;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||||
|
{
|
||||||
|
if (other == self->owner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (surf && (surf->flags & SURF_SKY))
|
||||||
|
{
|
||||||
|
G_FreeEdict (self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self->owner->client)
|
||||||
|
PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
|
||||||
|
|
||||||
|
// core explosion - prevents firing it into the wall/floor
|
||||||
|
if (other->takedamage)
|
||||||
|
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST);
|
||||||
|
T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST);
|
||||||
|
|
||||||
|
gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0);
|
||||||
|
self->solid = SOLID_NOT;
|
||||||
|
self->touch = NULL;
|
||||||
|
VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin);
|
||||||
|
VectorClear (self->velocity);
|
||||||
|
self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2");
|
||||||
|
self->s.frame = 0;
|
||||||
|
self->s.sound = 0;
|
||||||
|
self->s.effects &= ~EF_ANIM_ALLFAST;
|
||||||
|
self->think = bfg_explode;
|
||||||
|
self->nextthink = level.time + FRAMETIME;
|
||||||
|
self->enemy = other;
|
||||||
|
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (TE_BFG_BIGEXPLOSION);
|
||||||
|
gi.WritePosition (self->s.origin);
|
||||||
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void bfg_think (edict_t *self)
|
||||||
|
{
|
||||||
|
edict_t *ent;
|
||||||
|
edict_t *ignore;
|
||||||
|
vec3_t point;
|
||||||
|
vec3_t dir;
|
||||||
|
vec3_t start;
|
||||||
|
vec3_t end;
|
||||||
|
int dmg;
|
||||||
|
trace_t tr;
|
||||||
|
|
||||||
|
if (deathmatch->value)
|
||||||
|
dmg = 5;
|
||||||
|
else
|
||||||
|
dmg = 10;
|
||||||
|
|
||||||
|
ent = NULL;
|
||||||
|
while ((ent = findradius(ent, self->s.origin, 256)) != NULL)
|
||||||
|
{
|
||||||
|
if (ent == self)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ent == self->owner)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!ent->takedamage)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
//ZOID
|
||||||
|
//don't target players in CTF
|
||||||
|
if (ctf->value && ent->client &&
|
||||||
|
self->owner->client &&
|
||||||
|
ent->client->resp.ctf_team == self->owner->client->resp.ctf_team)
|
||||||
|
continue;
|
||||||
|
//ZOID
|
||||||
|
|
||||||
|
VectorMA (ent->absmin, 0.5, ent->size, point);
|
||||||
|
|
||||||
|
VectorSubtract (point, self->s.origin, dir);
|
||||||
|
VectorNormalize (dir);
|
||||||
|
|
||||||
|
ignore = self;
|
||||||
|
VectorCopy (self->s.origin, start);
|
||||||
|
VectorMA (start, 2048, dir, end);
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
|
||||||
|
|
||||||
|
if (!tr.ent)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// hurt it if we can
|
||||||
|
if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner))
|
||||||
|
T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER);
|
||||||
|
|
||||||
|
// if we hit something that's not a monster or player we're done
|
||||||
|
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
|
||||||
|
{
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (TE_LASER_SPARKS);
|
||||||
|
gi.WriteByte (4);
|
||||||
|
gi.WritePosition (tr.endpos);
|
||||||
|
gi.WriteDir (tr.plane.normal);
|
||||||
|
gi.WriteByte (self->s.skinnum);
|
||||||
|
gi.multicast (tr.endpos, MULTICAST_PVS);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore = tr.ent;
|
||||||
|
VectorCopy (tr.endpos, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
gi.WriteByte (svc_temp_entity);
|
||||||
|
gi.WriteByte (TE_BFG_LASER);
|
||||||
|
gi.WritePosition (self->s.origin);
|
||||||
|
gi.WritePosition (tr.endpos);
|
||||||
|
gi.multicast (self->s.origin, MULTICAST_PHS);
|
||||||
|
}
|
||||||
|
|
||||||
|
self->nextthink = level.time + FRAMETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius)
|
||||||
|
{
|
||||||
|
edict_t *bfg;
|
||||||
|
|
||||||
|
bfg = G_Spawn();
|
||||||
|
VectorCopy (start, bfg->s.origin);
|
||||||
|
VectorCopy (dir, bfg->movedir);
|
||||||
|
vectoangles (dir, bfg->s.angles);
|
||||||
|
VectorScale (dir, speed, bfg->velocity);
|
||||||
|
bfg->movetype = MOVETYPE_FLYMISSILE;
|
||||||
|
bfg->clipmask = MASK_SHOT;
|
||||||
|
bfg->solid = SOLID_BBOX;
|
||||||
|
bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST;
|
||||||
|
VectorClear (bfg->mins);
|
||||||
|
VectorClear (bfg->maxs);
|
||||||
|
bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2");
|
||||||
|
bfg->owner = self;
|
||||||
|
bfg->touch = bfg_touch;
|
||||||
|
bfg->nextthink = level.time + 8000/speed;
|
||||||
|
bfg->think = G_FreeEdict;
|
||||||
|
bfg->radius_dmg = damage;
|
||||||
|
bfg->dmg_radius = damage_radius;
|
||||||
|
bfg->classname = "bfg blast";
|
||||||
|
bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav");
|
||||||
|
|
||||||
|
bfg->think = bfg_think;
|
||||||
|
bfg->nextthink = level.time + FRAMETIME;
|
||||||
|
bfg->teammaster = bfg;
|
||||||
|
bfg->teamchain = NULL;
|
||||||
|
|
||||||
|
if (self->client)
|
||||||
|
check_dodge (self, bfg->s.origin, dir, speed);
|
||||||
|
|
||||||
|
gi.linkentity (bfg);
|
||||||
|
}
|
||||||
|
|
243
src/game.h
Normal file
243
src/game.h
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.h -- game dll information visible to server
|
||||||
|
|
||||||
|
#define GAME_API_VERSION 3
|
||||||
|
|
||||||
|
// edict->svflags
|
||||||
|
|
||||||
|
#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects
|
||||||
|
#define SVF_DEADMONSTER 0x00000002 // treat as CONTENTS_DEADMONSTER for collision
|
||||||
|
#define SVF_MONSTER 0x00000004 // treat as CONTENTS_MONSTER for collision
|
||||||
|
//ZOID
|
||||||
|
#define SVF_PROJECTILE 0x00000008 // entity is simple projectile, used for network optimization
|
||||||
|
// if an entity is projectile, the model index/x/y/z/pitch/yaw are sent, encoded into
|
||||||
|
// seven (or eight) bytes. This is to speed up projectiles. Currently, only the
|
||||||
|
// hyperblaster makes use of this. use for items that are moving with a constant
|
||||||
|
// velocity that don't change direction or model
|
||||||
|
//ZOID
|
||||||
|
|
||||||
|
// edict->solid values
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SOLID_NOT, // no interaction with other objects
|
||||||
|
SOLID_TRIGGER, // only touch when inside, after moving
|
||||||
|
SOLID_BBOX, // touch on edge
|
||||||
|
SOLID_BSP // bsp clip, touch on edge
|
||||||
|
} solid_t;
|
||||||
|
|
||||||
|
//===============================================================
|
||||||
|
|
||||||
|
// link_t is only used for entity area links now
|
||||||
|
typedef struct link_s
|
||||||
|
{
|
||||||
|
struct link_s *prev, *next;
|
||||||
|
} link_t;
|
||||||
|
|
||||||
|
#define MAX_ENT_CLUSTERS 16
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct edict_s edict_t;
|
||||||
|
typedef struct gclient_s gclient_t;
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef GAME_INCLUDE
|
||||||
|
|
||||||
|
struct gclient_s
|
||||||
|
{
|
||||||
|
player_state_t ps; // communicated by server to clients
|
||||||
|
int ping;
|
||||||
|
// the game dll can add anything it wants after
|
||||||
|
// this point in the structure
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct edict_s
|
||||||
|
{
|
||||||
|
entity_state_t s;
|
||||||
|
struct gclient_s *client;
|
||||||
|
qboolean inuse;
|
||||||
|
int linkcount;
|
||||||
|
|
||||||
|
// FIXME: move these fields to a server private sv_entity_t
|
||||||
|
link_t area; // linked to a division node or leaf
|
||||||
|
|
||||||
|
int num_clusters; // if -1, use headnode instead
|
||||||
|
int clusternums[MAX_ENT_CLUSTERS];
|
||||||
|
int headnode; // unused if num_clusters != -1
|
||||||
|
int areanum, areanum2;
|
||||||
|
|
||||||
|
//================================
|
||||||
|
|
||||||
|
int svflags; // SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc
|
||||||
|
vec3_t mins, maxs;
|
||||||
|
vec3_t absmin, absmax, size;
|
||||||
|
solid_t solid;
|
||||||
|
int clipmask;
|
||||||
|
edict_t *owner;
|
||||||
|
|
||||||
|
// the game dll can add anything it wants after
|
||||||
|
// this point in the structure
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // GAME_INCLUDE
|
||||||
|
|
||||||
|
//===============================================================
|
||||||
|
|
||||||
|
//
|
||||||
|
// functions provided by the main engine
|
||||||
|
//
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
// special messages
|
||||||
|
void (*bprintf) (int printlevel, char *fmt, ...);
|
||||||
|
void (*dprintf) (char *fmt, ...);
|
||||||
|
void (*cprintf) (edict_t *ent, int printlevel, char *fmt, ...);
|
||||||
|
void (*centerprintf) (edict_t *ent, char *fmt, ...);
|
||||||
|
void (*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs);
|
||||||
|
void (*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs);
|
||||||
|
|
||||||
|
// config strings hold all the index strings, the lightstyles,
|
||||||
|
// and misc data like the sky definition and cdtrack.
|
||||||
|
// All of the current configstrings are sent to clients when
|
||||||
|
// they connect, and changes are sent to all connected clients.
|
||||||
|
void (*configstring) (int num, char *string);
|
||||||
|
|
||||||
|
void (*error) (char *fmt, ...);
|
||||||
|
|
||||||
|
// the *index functions create configstrings and some internal server state
|
||||||
|
int (*modelindex) (char *name);
|
||||||
|
int (*soundindex) (char *name);
|
||||||
|
int (*imageindex) (char *name);
|
||||||
|
|
||||||
|
void (*setmodel) (edict_t *ent, char *name);
|
||||||
|
|
||||||
|
// collision detection
|
||||||
|
trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask);
|
||||||
|
int (*pointcontents) (vec3_t point);
|
||||||
|
qboolean (*inPVS) (vec3_t p1, vec3_t p2);
|
||||||
|
qboolean (*inPHS) (vec3_t p1, vec3_t p2);
|
||||||
|
void (*SetAreaPortalState) (int portalnum, qboolean open);
|
||||||
|
qboolean (*AreasConnected) (int area1, int area2);
|
||||||
|
|
||||||
|
// an entity will never be sent to a client or used for collision
|
||||||
|
// if it is not passed to linkentity. If the size, position, or
|
||||||
|
// solidity changes, it must be relinked.
|
||||||
|
void (*linkentity) (edict_t *ent);
|
||||||
|
void (*unlinkentity) (edict_t *ent); // call before removing an interactive edict
|
||||||
|
int (*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype);
|
||||||
|
void (*Pmove) (pmove_t *pmove); // player movement code common with client prediction
|
||||||
|
|
||||||
|
// network messaging
|
||||||
|
void (*multicast) (vec3_t origin, multicast_t to);
|
||||||
|
void (*unicast) (edict_t *ent, qboolean reliable);
|
||||||
|
void (*WriteChar) (int c);
|
||||||
|
void (*WriteByte) (int c);
|
||||||
|
void (*WriteShort) (int c);
|
||||||
|
void (*WriteLong) (int c);
|
||||||
|
void (*WriteFloat) (float f);
|
||||||
|
void (*WriteString) (char *s);
|
||||||
|
void (*WritePosition) (vec3_t pos); // some fractional bits
|
||||||
|
void (*WriteDir) (vec3_t pos); // single byte encoded, very coarse
|
||||||
|
void (*WriteAngle) (float f);
|
||||||
|
|
||||||
|
// managed memory allocation
|
||||||
|
void *(*TagMalloc) (int size, int tag);
|
||||||
|
void (*TagFree) (void *block);
|
||||||
|
void (*FreeTags) (int tag);
|
||||||
|
|
||||||
|
// console variable interaction
|
||||||
|
cvar_t *(*cvar) (char *var_name, char *value, int flags);
|
||||||
|
cvar_t *(*cvar_set) (char *var_name, char *value);
|
||||||
|
cvar_t *(*cvar_forceset) (char *var_name, char *value);
|
||||||
|
|
||||||
|
// ClientCommand and ServerCommand parameter access
|
||||||
|
int (*argc) (void);
|
||||||
|
char *(*argv) (int n);
|
||||||
|
char *(*args) (void); // concatenation of all argv >= 1
|
||||||
|
|
||||||
|
// add commands to the server console as if they were typed in
|
||||||
|
// for map changing, etc
|
||||||
|
void (*AddCommandString) (char *text);
|
||||||
|
|
||||||
|
void (*DebugGraph) (float value, int color);
|
||||||
|
} game_import_t;
|
||||||
|
|
||||||
|
//
|
||||||
|
// functions exported by the game subsystem
|
||||||
|
//
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int apiversion;
|
||||||
|
|
||||||
|
// the init function will only be called when a game starts,
|
||||||
|
// not each time a level is loaded. Persistant data for clients
|
||||||
|
// and the server can be allocated in init
|
||||||
|
void (*Init) (void);
|
||||||
|
void (*Shutdown) (void);
|
||||||
|
|
||||||
|
// each new level entered will cause a call to SpawnEntities
|
||||||
|
void (*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint);
|
||||||
|
|
||||||
|
// Read/Write Game is for storing persistant cross level information
|
||||||
|
// about the world state and the clients.
|
||||||
|
// WriteGame is called every time a level is exited.
|
||||||
|
// ReadGame is called on a loadgame.
|
||||||
|
void (*WriteGame) (char *filename, qboolean autosave);
|
||||||
|
void (*ReadGame) (char *filename);
|
||||||
|
|
||||||
|
// ReadLevel is called after the default map information has been
|
||||||
|
// loaded with SpawnEntities
|
||||||
|
void (*WriteLevel) (char *filename);
|
||||||
|
void (*ReadLevel) (char *filename);
|
||||||
|
|
||||||
|
qboolean (*ClientConnect) (edict_t *ent, char *userinfo);
|
||||||
|
void (*ClientBegin) (edict_t *ent);
|
||||||
|
void (*ClientUserinfoChanged) (edict_t *ent, char *userinfo);
|
||||||
|
void (*ClientDisconnect) (edict_t *ent);
|
||||||
|
void (*ClientCommand) (edict_t *ent);
|
||||||
|
void (*ClientThink) (edict_t *ent, usercmd_t *cmd);
|
||||||
|
|
||||||
|
void (*RunFrame) (void);
|
||||||
|
|
||||||
|
// ServerCommand will be called when an "sv <command>" command is issued on the
|
||||||
|
// server console.
|
||||||
|
// The game can issue gi.argc() / gi.argv() commands to get the rest
|
||||||
|
// of the parameters
|
||||||
|
void (*ServerCommand) (void);
|
||||||
|
|
||||||
|
//
|
||||||
|
// global variables shared between game and server
|
||||||
|
//
|
||||||
|
|
||||||
|
// The edict array is allocated in the game dll so it
|
||||||
|
// can vary in size from one game to another.
|
||||||
|
//
|
||||||
|
// The size will be fixed when ge->Init() is called
|
||||||
|
struct edict_s *edicts;
|
||||||
|
int edict_size;
|
||||||
|
int num_edicts; // current number, <= max_edicts
|
||||||
|
int max_edicts;
|
||||||
|
} game_export_t;
|
||||||
|
|
||||||
|
game_export_t *GetGameApi (game_import_t *import);
|
||||||
|
|
557
src/m_move.c
Normal file
557
src/m_move.c
Normal file
|
@ -0,0 +1,557 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
// m_move.c -- monster movement
|
||||||
|
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
#define STEPSIZE 18
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
M_CheckBottom
|
||||||
|
|
||||||
|
Returns false if any part of the bottom of the entity is off an edge that
|
||||||
|
is not a staircase.
|
||||||
|
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
int c_yes, c_no;
|
||||||
|
|
||||||
|
qboolean M_CheckBottom (edict_t *ent)
|
||||||
|
{
|
||||||
|
vec3_t mins, maxs, start, stop;
|
||||||
|
trace_t trace;
|
||||||
|
int x, y;
|
||||||
|
float mid, bottom;
|
||||||
|
|
||||||
|
VectorAdd (ent->s.origin, ent->mins, mins);
|
||||||
|
VectorAdd (ent->s.origin, ent->maxs, maxs);
|
||||||
|
|
||||||
|
// if all of the points under the corners are solid world, don't bother
|
||||||
|
// with the tougher checks
|
||||||
|
// the corners must be within 16 of the midpoint
|
||||||
|
start[2] = mins[2] - 1;
|
||||||
|
for (x=0 ; x<=1 ; x++)
|
||||||
|
for (y=0 ; y<=1 ; y++)
|
||||||
|
{
|
||||||
|
start[0] = x ? maxs[0] : mins[0];
|
||||||
|
start[1] = y ? maxs[1] : mins[1];
|
||||||
|
if (gi.pointcontents (start) != CONTENTS_SOLID)
|
||||||
|
goto realcheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
c_yes++;
|
||||||
|
return true; // we got out easy
|
||||||
|
|
||||||
|
realcheck:
|
||||||
|
c_no++;
|
||||||
|
//
|
||||||
|
// check it for real...
|
||||||
|
//
|
||||||
|
start[2] = mins[2];
|
||||||
|
|
||||||
|
// the midpoint must be within 16 of the bottom
|
||||||
|
start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
|
||||||
|
start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
|
||||||
|
stop[2] = start[2] - 2*STEPSIZE;
|
||||||
|
trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
|
||||||
|
|
||||||
|
if (trace.fraction == 1.0)
|
||||||
|
return false;
|
||||||
|
mid = bottom = trace.endpos[2];
|
||||||
|
|
||||||
|
// the corners must be within 16 of the midpoint
|
||||||
|
for (x=0 ; x<=1 ; x++)
|
||||||
|
for (y=0 ; y<=1 ; y++)
|
||||||
|
{
|
||||||
|
start[0] = stop[0] = x ? maxs[0] : mins[0];
|
||||||
|
start[1] = stop[1] = y ? maxs[1] : mins[1];
|
||||||
|
|
||||||
|
trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
|
||||||
|
|
||||||
|
if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
|
||||||
|
bottom = trace.endpos[2];
|
||||||
|
if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
c_yes++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
=============
|
||||||
|
SV_movestep
|
||||||
|
|
||||||
|
Called by monster program code.
|
||||||
|
The move will be adjusted for slopes and stairs, but if the move isn't
|
||||||
|
possible, no move is done, false is returned, and
|
||||||
|
pr_global_struct->trace_normal is set to the normal of the blocking wall
|
||||||
|
=============
|
||||||
|
*/
|
||||||
|
//FIXME since we need to test end position contents here, can we avoid doing
|
||||||
|
//it again later in catagorize position?
|
||||||
|
qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink)
|
||||||
|
{
|
||||||
|
float dz;
|
||||||
|
vec3_t oldorg, neworg, end;
|
||||||
|
trace_t trace;
|
||||||
|
int i;
|
||||||
|
float stepsize;
|
||||||
|
vec3_t test;
|
||||||
|
int contents;
|
||||||
|
|
||||||
|
// try the move
|
||||||
|
VectorCopy (ent->s.origin, oldorg);
|
||||||
|
VectorAdd (ent->s.origin, move, neworg);
|
||||||
|
|
||||||
|
// flying monsters don't step up
|
||||||
|
if ( ent->flags & (FL_SWIM | FL_FLY) )
|
||||||
|
{
|
||||||
|
// try one move with vertical motion, then one without
|
||||||
|
for (i=0 ; i<2 ; i++)
|
||||||
|
{
|
||||||
|
VectorAdd (ent->s.origin, move, neworg);
|
||||||
|
if (i == 0 && ent->enemy)
|
||||||
|
{
|
||||||
|
if (!ent->goalentity)
|
||||||
|
ent->goalentity = ent->enemy;
|
||||||
|
dz = ent->s.origin[2] - ent->goalentity->s.origin[2];
|
||||||
|
if (ent->goalentity->client)
|
||||||
|
{
|
||||||
|
if (dz > 40)
|
||||||
|
neworg[2] -= 8;
|
||||||
|
if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2)))
|
||||||
|
if (dz < 30)
|
||||||
|
neworg[2] += 8;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (dz > 8)
|
||||||
|
neworg[2] -= 8;
|
||||||
|
else if (dz > 0)
|
||||||
|
neworg[2] -= dz;
|
||||||
|
else if (dz < -8)
|
||||||
|
neworg[2] += 8;
|
||||||
|
else
|
||||||
|
neworg[2] += dz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID);
|
||||||
|
|
||||||
|
// fly monsters don't enter water voluntarily
|
||||||
|
if (ent->flags & FL_FLY)
|
||||||
|
{
|
||||||
|
if (!ent->waterlevel)
|
||||||
|
{
|
||||||
|
test[0] = trace.endpos[0];
|
||||||
|
test[1] = trace.endpos[1];
|
||||||
|
test[2] = trace.endpos[2] + ent->mins[2] + 1;
|
||||||
|
contents = gi.pointcontents(test);
|
||||||
|
if (contents & MASK_WATER)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// swim monsters don't exit water voluntarily
|
||||||
|
if (ent->flags & FL_SWIM)
|
||||||
|
{
|
||||||
|
if (ent->waterlevel < 2)
|
||||||
|
{
|
||||||
|
test[0] = trace.endpos[0];
|
||||||
|
test[1] = trace.endpos[1];
|
||||||
|
test[2] = trace.endpos[2] + ent->mins[2] + 1;
|
||||||
|
contents = gi.pointcontents(test);
|
||||||
|
if (!(contents & MASK_WATER))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trace.fraction == 1)
|
||||||
|
{
|
||||||
|
VectorCopy (trace.endpos, ent->s.origin);
|
||||||
|
if (relink)
|
||||||
|
{
|
||||||
|
gi.linkentity (ent);
|
||||||
|
G_TouchTriggers (ent);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ent->enemy)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// push down from a step height above the wished position
|
||||||
|
if (!(ent->monsterinfo.aiflags & AI_NOSTEP))
|
||||||
|
stepsize = STEPSIZE;
|
||||||
|
else
|
||||||
|
stepsize = 1;
|
||||||
|
|
||||||
|
neworg[2] += stepsize;
|
||||||
|
VectorCopy (neworg, end);
|
||||||
|
end[2] -= stepsize*2;
|
||||||
|
|
||||||
|
trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
|
||||||
|
|
||||||
|
if (trace.allsolid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (trace.startsolid)
|
||||||
|
{
|
||||||
|
neworg[2] -= stepsize;
|
||||||
|
trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
|
||||||
|
if (trace.allsolid || trace.startsolid)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// don't go in to water
|
||||||
|
if (ent->waterlevel == 0)
|
||||||
|
{
|
||||||
|
test[0] = trace.endpos[0];
|
||||||
|
test[1] = trace.endpos[1];
|
||||||
|
test[2] = trace.endpos[2] + ent->mins[2] + 1;
|
||||||
|
contents = gi.pointcontents(test);
|
||||||
|
|
||||||
|
if (contents & MASK_WATER)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trace.fraction == 1)
|
||||||
|
{
|
||||||
|
// if monster had the ground pulled out, go ahead and fall
|
||||||
|
if ( ent->flags & FL_PARTIALGROUND )
|
||||||
|
{
|
||||||
|
VectorAdd (ent->s.origin, move, ent->s.origin);
|
||||||
|
if (relink)
|
||||||
|
{
|
||||||
|
gi.linkentity (ent);
|
||||||
|
G_TouchTriggers (ent);
|
||||||
|
}
|
||||||
|
ent->groundentity = NULL;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // walked off an edge
|
||||||
|
}
|
||||||
|
|
||||||
|
// check point traces down for dangling corners
|
||||||
|
VectorCopy (trace.endpos, ent->s.origin);
|
||||||
|
|
||||||
|
if (!M_CheckBottom (ent))
|
||||||
|
{
|
||||||
|
if ( ent->flags & FL_PARTIALGROUND )
|
||||||
|
{ // entity had floor mostly pulled out from underneath it
|
||||||
|
// and is trying to correct
|
||||||
|
if (relink)
|
||||||
|
{
|
||||||
|
gi.linkentity (ent);
|
||||||
|
G_TouchTriggers (ent);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
VectorCopy (oldorg, ent->s.origin);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ent->flags & FL_PARTIALGROUND )
|
||||||
|
{
|
||||||
|
ent->flags &= ~FL_PARTIALGROUND;
|
||||||
|
}
|
||||||
|
ent->groundentity = trace.ent;
|
||||||
|
ent->groundentity_linkcount = trace.ent->linkcount;
|
||||||
|
|
||||||
|
// the move is ok
|
||||||
|
if (relink)
|
||||||
|
{
|
||||||
|
gi.linkentity (ent);
|
||||||
|
G_TouchTriggers (ent);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
/*
|
||||||
|
===============
|
||||||
|
M_ChangeYaw
|
||||||
|
|
||||||
|
===============
|
||||||
|
*/
|
||||||
|
void M_ChangeYaw (edict_t *ent)
|
||||||
|
{
|
||||||
|
float ideal;
|
||||||
|
float current;
|
||||||
|
float move;
|
||||||
|
float speed;
|
||||||
|
|
||||||
|
current = anglemod(ent->s.angles[YAW]);
|
||||||
|
ideal = ent->ideal_yaw;
|
||||||
|
|
||||||
|
if (current == ideal)
|
||||||
|
return;
|
||||||
|
|
||||||
|
move = ideal - current;
|
||||||
|
speed = ent->yaw_speed;
|
||||||
|
if (ideal > current)
|
||||||
|
{
|
||||||
|
if (move >= 180)
|
||||||
|
move = move - 360;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (move <= -180)
|
||||||
|
move = move + 360;
|
||||||
|
}
|
||||||
|
if (move > 0)
|
||||||
|
{
|
||||||
|
if (move > speed)
|
||||||
|
move = speed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (move < -speed)
|
||||||
|
move = -speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
ent->s.angles[YAW] = anglemod (current + move);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
======================
|
||||||
|
SV_StepDirection
|
||||||
|
|
||||||
|
Turns to the movement direction, and walks the current distance if
|
||||||
|
facing it.
|
||||||
|
|
||||||
|
======================
|
||||||
|
*/
|
||||||
|
qboolean SV_StepDirection (edict_t *ent, float yaw, float dist)
|
||||||
|
{
|
||||||
|
vec3_t move, oldorigin;
|
||||||
|
float delta;
|
||||||
|
|
||||||
|
ent->ideal_yaw = yaw;
|
||||||
|
M_ChangeYaw (ent);
|
||||||
|
|
||||||
|
yaw = yaw*M_PI*2 / 360;
|
||||||
|
move[0] = cos(yaw)*dist;
|
||||||
|
move[1] = sin(yaw)*dist;
|
||||||
|
move[2] = 0;
|
||||||
|
|
||||||
|
VectorCopy (ent->s.origin, oldorigin);
|
||||||
|
if (SV_movestep (ent, move, false))
|
||||||
|
{
|
||||||
|
delta = ent->s.angles[YAW] - ent->ideal_yaw;
|
||||||
|
if (delta > 45 && delta < 315)
|
||||||
|
{ // not turned far enough, so don't take the step
|
||||||
|
VectorCopy (oldorigin, ent->s.origin);
|
||||||
|
}
|
||||||
|
gi.linkentity (ent);
|
||||||
|
G_TouchTriggers (ent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
gi.linkentity (ent);
|
||||||
|
G_TouchTriggers (ent);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
======================
|
||||||
|
SV_FixCheckBottom
|
||||||
|
|
||||||
|
======================
|
||||||
|
*/
|
||||||
|
void SV_FixCheckBottom (edict_t *ent)
|
||||||
|
{
|
||||||
|
ent->flags |= FL_PARTIALGROUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
SV_NewChaseDir
|
||||||
|
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
#define DI_NODIR -1
|
||||||
|
void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist)
|
||||||
|
{
|
||||||
|
float deltax,deltay;
|
||||||
|
float d[3];
|
||||||
|
float tdir, olddir, turnaround;
|
||||||
|
|
||||||
|
//FIXME: how did we get here with no enemy
|
||||||
|
if (!enemy)
|
||||||
|
return;
|
||||||
|
|
||||||
|
olddir = anglemod( (int)(actor->ideal_yaw/45)*45 );
|
||||||
|
turnaround = anglemod(olddir - 180);
|
||||||
|
|
||||||
|
deltax = enemy->s.origin[0] - actor->s.origin[0];
|
||||||
|
deltay = enemy->s.origin[1] - actor->s.origin[1];
|
||||||
|
if (deltax>10)
|
||||||
|
d[1]= 0;
|
||||||
|
else if (deltax<-10)
|
||||||
|
d[1]= 180;
|
||||||
|
else
|
||||||
|
d[1]= DI_NODIR;
|
||||||
|
if (deltay<-10)
|
||||||
|
d[2]= 270;
|
||||||
|
else if (deltay>10)
|
||||||
|
d[2]= 90;
|
||||||
|
else
|
||||||
|
d[2]= DI_NODIR;
|
||||||
|
|
||||||
|
// try direct route
|
||||||
|
if (d[1] != DI_NODIR && d[2] != DI_NODIR)
|
||||||
|
{
|
||||||
|
if (d[1] == 0)
|
||||||
|
tdir = d[2] == 90 ? 45 : 315;
|
||||||
|
else
|
||||||
|
tdir = d[2] == 90 ? 135 : 215;
|
||||||
|
|
||||||
|
if (tdir != turnaround && SV_StepDirection(actor, tdir, dist))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try other directions
|
||||||
|
if ( ((rand()&3) & 1) || abs(deltay)>abs(deltax))
|
||||||
|
{
|
||||||
|
tdir=d[1];
|
||||||
|
d[1]=d[2];
|
||||||
|
d[2]=tdir;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d[1]!=DI_NODIR && d[1]!=turnaround
|
||||||
|
&& SV_StepDirection(actor, d[1], dist))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (d[2]!=DI_NODIR && d[2]!=turnaround
|
||||||
|
&& SV_StepDirection(actor, d[2], dist))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* there is no direct path to the player, so pick another direction */
|
||||||
|
|
||||||
|
if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (rand()&1) /*randomly determine direction of search*/
|
||||||
|
{
|
||||||
|
for (tdir=0 ; tdir<=315 ; tdir += 45)
|
||||||
|
if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (tdir=315 ; tdir >=0 ; tdir -= 45)
|
||||||
|
if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
actor->ideal_yaw = olddir; // can't move
|
||||||
|
|
||||||
|
// if a bridge was pulled out from underneath a monster, it may not have
|
||||||
|
// a valid standing position at all
|
||||||
|
|
||||||
|
if (!M_CheckBottom (actor))
|
||||||
|
SV_FixCheckBottom (actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
======================
|
||||||
|
SV_CloseEnough
|
||||||
|
|
||||||
|
======================
|
||||||
|
*/
|
||||||
|
qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i=0 ; i<3 ; i++)
|
||||||
|
{
|
||||||
|
if (goal->absmin[i] > ent->absmax[i] + dist)
|
||||||
|
return false;
|
||||||
|
if (goal->absmax[i] < ent->absmin[i] - dist)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
======================
|
||||||
|
M_MoveToGoal
|
||||||
|
======================
|
||||||
|
*/
|
||||||
|
void M_MoveToGoal (edict_t *ent, float dist)
|
||||||
|
{
|
||||||
|
edict_t *goal;
|
||||||
|
|
||||||
|
goal = ent->goalentity;
|
||||||
|
|
||||||
|
if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// if the next step hits the enemy, return immediately
|
||||||
|
if (ent->enemy && SV_CloseEnough (ent, ent->enemy, dist) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// bump around...
|
||||||
|
if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist))
|
||||||
|
{
|
||||||
|
if (ent->inuse)
|
||||||
|
SV_NewChaseDir (ent, goal, dist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
===============
|
||||||
|
M_walkmove
|
||||||
|
===============
|
||||||
|
*/
|
||||||
|
qboolean M_walkmove (edict_t *ent, float yaw, float dist)
|
||||||
|
{
|
||||||
|
vec3_t move;
|
||||||
|
|
||||||
|
if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
yaw = yaw*M_PI*2 / 360;
|
||||||
|
|
||||||
|
move[0] = cos(yaw)*dist;
|
||||||
|
move[1] = sin(yaw)*dist;
|
||||||
|
move[2] = 0;
|
||||||
|
|
||||||
|
return SV_movestep(ent, move, true);
|
||||||
|
}
|
||||||
|
|
225
src/m_player.h
Normal file
225
src/m_player.h
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
// G:\quake2\baseq2\models/player_x/frames
|
||||||
|
|
||||||
|
// This file generated by qdata - Do NOT Modify
|
||||||
|
|
||||||
|
#define FRAME_stand01 0
|
||||||
|
#define FRAME_stand02 1
|
||||||
|
#define FRAME_stand03 2
|
||||||
|
#define FRAME_stand04 3
|
||||||
|
#define FRAME_stand05 4
|
||||||
|
#define FRAME_stand06 5
|
||||||
|
#define FRAME_stand07 6
|
||||||
|
#define FRAME_stand08 7
|
||||||
|
#define FRAME_stand09 8
|
||||||
|
#define FRAME_stand10 9
|
||||||
|
#define FRAME_stand11 10
|
||||||
|
#define FRAME_stand12 11
|
||||||
|
#define FRAME_stand13 12
|
||||||
|
#define FRAME_stand14 13
|
||||||
|
#define FRAME_stand15 14
|
||||||
|
#define FRAME_stand16 15
|
||||||
|
#define FRAME_stand17 16
|
||||||
|
#define FRAME_stand18 17
|
||||||
|
#define FRAME_stand19 18
|
||||||
|
#define FRAME_stand20 19
|
||||||
|
#define FRAME_stand21 20
|
||||||
|
#define FRAME_stand22 21
|
||||||
|
#define FRAME_stand23 22
|
||||||
|
#define FRAME_stand24 23
|
||||||
|
#define FRAME_stand25 24
|
||||||
|
#define FRAME_stand26 25
|
||||||
|
#define FRAME_stand27 26
|
||||||
|
#define FRAME_stand28 27
|
||||||
|
#define FRAME_stand29 28
|
||||||
|
#define FRAME_stand30 29
|
||||||
|
#define FRAME_stand31 30
|
||||||
|
#define FRAME_stand32 31
|
||||||
|
#define FRAME_stand33 32
|
||||||
|
#define FRAME_stand34 33
|
||||||
|
#define FRAME_stand35 34
|
||||||
|
#define FRAME_stand36 35
|
||||||
|
#define FRAME_stand37 36
|
||||||
|
#define FRAME_stand38 37
|
||||||
|
#define FRAME_stand39 38
|
||||||
|
#define FRAME_stand40 39
|
||||||
|
#define FRAME_run1 40
|
||||||
|
#define FRAME_run2 41
|
||||||
|
#define FRAME_run3 42
|
||||||
|
#define FRAME_run4 43
|
||||||
|
#define FRAME_run5 44
|
||||||
|
#define FRAME_run6 45
|
||||||
|
#define FRAME_attack1 46
|
||||||
|
#define FRAME_attack2 47
|
||||||
|
#define FRAME_attack3 48
|
||||||
|
#define FRAME_attack4 49
|
||||||
|
#define FRAME_attack5 50
|
||||||
|
#define FRAME_attack6 51
|
||||||
|
#define FRAME_attack7 52
|
||||||
|
#define FRAME_attack8 53
|
||||||
|
#define FRAME_pain101 54
|
||||||
|
#define FRAME_pain102 55
|
||||||
|
#define FRAME_pain103 56
|
||||||
|
#define FRAME_pain104 57
|
||||||
|
#define FRAME_pain201 58
|
||||||
|
#define FRAME_pain202 59
|
||||||
|
#define FRAME_pain203 60
|
||||||
|
#define FRAME_pain204 61
|
||||||
|
#define FRAME_pain301 62
|
||||||
|
#define FRAME_pain302 63
|
||||||
|
#define FRAME_pain303 64
|
||||||
|
#define FRAME_pain304 65
|
||||||
|
#define FRAME_jump1 66
|
||||||
|
#define FRAME_jump2 67
|
||||||
|
#define FRAME_jump3 68
|
||||||
|
#define FRAME_jump4 69
|
||||||
|
#define FRAME_jump5 70
|
||||||
|
#define FRAME_jump6 71
|
||||||
|
#define FRAME_flip01 72
|
||||||
|
#define FRAME_flip02 73
|
||||||
|
#define FRAME_flip03 74
|
||||||
|
#define FRAME_flip04 75
|
||||||
|
#define FRAME_flip05 76
|
||||||
|
#define FRAME_flip06 77
|
||||||
|
#define FRAME_flip07 78
|
||||||
|
#define FRAME_flip08 79
|
||||||
|
#define FRAME_flip09 80
|
||||||
|
#define FRAME_flip10 81
|
||||||
|
#define FRAME_flip11 82
|
||||||
|
#define FRAME_flip12 83
|
||||||
|
#define FRAME_salute01 84
|
||||||
|
#define FRAME_salute02 85
|
||||||
|
#define FRAME_salute03 86
|
||||||
|
#define FRAME_salute04 87
|
||||||
|
#define FRAME_salute05 88
|
||||||
|
#define FRAME_salute06 89
|
||||||
|
#define FRAME_salute07 90
|
||||||
|
#define FRAME_salute08 91
|
||||||
|
#define FRAME_salute09 92
|
||||||
|
#define FRAME_salute10 93
|
||||||
|
#define FRAME_salute11 94
|
||||||
|
#define FRAME_taunt01 95
|
||||||
|
#define FRAME_taunt02 96
|
||||||
|
#define FRAME_taunt03 97
|
||||||
|
#define FRAME_taunt04 98
|
||||||
|
#define FRAME_taunt05 99
|
||||||
|
#define FRAME_taunt06 100
|
||||||
|
#define FRAME_taunt07 101
|
||||||
|
#define FRAME_taunt08 102
|
||||||
|
#define FRAME_taunt09 103
|
||||||
|
#define FRAME_taunt10 104
|
||||||
|
#define FRAME_taunt11 105
|
||||||
|
#define FRAME_taunt12 106
|
||||||
|
#define FRAME_taunt13 107
|
||||||
|
#define FRAME_taunt14 108
|
||||||
|
#define FRAME_taunt15 109
|
||||||
|
#define FRAME_taunt16 110
|
||||||
|
#define FRAME_taunt17 111
|
||||||
|
#define FRAME_wave01 112
|
||||||
|
#define FRAME_wave02 113
|
||||||
|
#define FRAME_wave03 114
|
||||||
|
#define FRAME_wave04 115
|
||||||
|
#define FRAME_wave05 116
|
||||||
|
#define FRAME_wave06 117
|
||||||
|
#define FRAME_wave07 118
|
||||||
|
#define FRAME_wave08 119
|
||||||
|
#define FRAME_wave09 120
|
||||||
|
#define FRAME_wave10 121
|
||||||
|
#define FRAME_wave11 122
|
||||||
|
#define FRAME_point01 123
|
||||||
|
#define FRAME_point02 124
|
||||||
|
#define FRAME_point03 125
|
||||||
|
#define FRAME_point04 126
|
||||||
|
#define FRAME_point05 127
|
||||||
|
#define FRAME_point06 128
|
||||||
|
#define FRAME_point07 129
|
||||||
|
#define FRAME_point08 130
|
||||||
|
#define FRAME_point09 131
|
||||||
|
#define FRAME_point10 132
|
||||||
|
#define FRAME_point11 133
|
||||||
|
#define FRAME_point12 134
|
||||||
|
#define FRAME_crstnd01 135
|
||||||
|
#define FRAME_crstnd02 136
|
||||||
|
#define FRAME_crstnd03 137
|
||||||
|
#define FRAME_crstnd04 138
|
||||||
|
#define FRAME_crstnd05 139
|
||||||
|
#define FRAME_crstnd06 140
|
||||||
|
#define FRAME_crstnd07 141
|
||||||
|
#define FRAME_crstnd08 142
|
||||||
|
#define FRAME_crstnd09 143
|
||||||
|
#define FRAME_crstnd10 144
|
||||||
|
#define FRAME_crstnd11 145
|
||||||
|
#define FRAME_crstnd12 146
|
||||||
|
#define FRAME_crstnd13 147
|
||||||
|
#define FRAME_crstnd14 148
|
||||||
|
#define FRAME_crstnd15 149
|
||||||
|
#define FRAME_crstnd16 150
|
||||||
|
#define FRAME_crstnd17 151
|
||||||
|
#define FRAME_crstnd18 152
|
||||||
|
#define FRAME_crstnd19 153
|
||||||
|
#define FRAME_crwalk1 154
|
||||||
|
#define FRAME_crwalk2 155
|
||||||
|
#define FRAME_crwalk3 156
|
||||||
|
#define FRAME_crwalk4 157
|
||||||
|
#define FRAME_crwalk5 158
|
||||||
|
#define FRAME_crwalk6 159
|
||||||
|
#define FRAME_crattak1 160
|
||||||
|
#define FRAME_crattak2 161
|
||||||
|
#define FRAME_crattak3 162
|
||||||
|
#define FRAME_crattak4 163
|
||||||
|
#define FRAME_crattak5 164
|
||||||
|
#define FRAME_crattak6 165
|
||||||
|
#define FRAME_crattak7 166
|
||||||
|
#define FRAME_crattak8 167
|
||||||
|
#define FRAME_crattak9 168
|
||||||
|
#define FRAME_crpain1 169
|
||||||
|
#define FRAME_crpain2 170
|
||||||
|
#define FRAME_crpain3 171
|
||||||
|
#define FRAME_crpain4 172
|
||||||
|
#define FRAME_crdeath1 173
|
||||||
|
#define FRAME_crdeath2 174
|
||||||
|
#define FRAME_crdeath3 175
|
||||||
|
#define FRAME_crdeath4 176
|
||||||
|
#define FRAME_crdeath5 177
|
||||||
|
#define FRAME_death101 178
|
||||||
|
#define FRAME_death102 179
|
||||||
|
#define FRAME_death103 180
|
||||||
|
#define FRAME_death104 181
|
||||||
|
#define FRAME_death105 182
|
||||||
|
#define FRAME_death106 183
|
||||||
|
#define FRAME_death201 184
|
||||||
|
#define FRAME_death202 185
|
||||||
|
#define FRAME_death203 186
|
||||||
|
#define FRAME_death204 187
|
||||||
|
#define FRAME_death205 188
|
||||||
|
#define FRAME_death206 189
|
||||||
|
#define FRAME_death301 190
|
||||||
|
#define FRAME_death302 191
|
||||||
|
#define FRAME_death303 192
|
||||||
|
#define FRAME_death304 193
|
||||||
|
#define FRAME_death305 194
|
||||||
|
#define FRAME_death306 195
|
||||||
|
#define FRAME_death307 196
|
||||||
|
#define FRAME_death308 197
|
||||||
|
|
||||||
|
#define MODEL_SCALE 1.000000
|
||||||
|
|
||||||
|
|
1739
src/p_client.c
Normal file
1739
src/p_client.c
Normal file
File diff suppressed because it is too large
Load diff
554
src/p_hud.c
Normal file
554
src/p_hud.c
Normal file
|
@ -0,0 +1,554 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
INTERMISSION
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
void MoveClientToIntermission (edict_t *ent)
|
||||||
|
{
|
||||||
|
if (deathmatch->value || coop->value)
|
||||||
|
ent->client->showscores = true;
|
||||||
|
VectorCopy (level.intermission_origin, ent->s.origin);
|
||||||
|
ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8;
|
||||||
|
ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8;
|
||||||
|
ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8;
|
||||||
|
VectorCopy (level.intermission_angle, ent->client->ps.viewangles);
|
||||||
|
ent->client->ps.pmove.pm_type = PM_FREEZE;
|
||||||
|
ent->client->ps.gunindex = 0;
|
||||||
|
ent->client->ps.blend[3] = 0;
|
||||||
|
ent->client->ps.rdflags &= ~RDF_UNDERWATER;
|
||||||
|
|
||||||
|
// clean up powerup info
|
||||||
|
ent->client->quad_framenum = 0;
|
||||||
|
ent->client->invincible_framenum = 0;
|
||||||
|
ent->client->breather_framenum = 0;
|
||||||
|
ent->client->enviro_framenum = 0;
|
||||||
|
ent->client->grenade_blew_up = false;
|
||||||
|
ent->client->grenade_time = 0;
|
||||||
|
|
||||||
|
ent->viewheight = 0;
|
||||||
|
ent->s.modelindex = 0;
|
||||||
|
ent->s.modelindex2 = 0;
|
||||||
|
ent->s.modelindex3 = 0;
|
||||||
|
ent->s.modelindex = 0;
|
||||||
|
ent->s.effects = 0;
|
||||||
|
ent->s.sound = 0;
|
||||||
|
ent->solid = SOLID_NOT;
|
||||||
|
|
||||||
|
// add the layout
|
||||||
|
|
||||||
|
if (deathmatch->value || coop->value)
|
||||||
|
{
|
||||||
|
DeathmatchScoreboardMessage (ent, NULL);
|
||||||
|
gi.unicast (ent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeginIntermission (edict_t *targ)
|
||||||
|
{
|
||||||
|
int i, n;
|
||||||
|
edict_t *ent, *client;
|
||||||
|
|
||||||
|
if (level.intermissiontime)
|
||||||
|
return; // allready activated
|
||||||
|
|
||||||
|
//ZOID
|
||||||
|
if (deathmatch->value && ctf->value)
|
||||||
|
CTFCalcScores();
|
||||||
|
//ZOID
|
||||||
|
|
||||||
|
game.autosaved = false;
|
||||||
|
|
||||||
|
// respawn any dead clients
|
||||||
|
for (i=0 ; i<maxclients->value ; i++)
|
||||||
|
{
|
||||||
|
client = g_edicts + 1 + i;
|
||||||
|
if (!client->inuse)
|
||||||
|
continue;
|
||||||
|
if (client->health <= 0)
|
||||||
|
respawn(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
level.intermissiontime = level.time;
|
||||||
|
level.changemap = targ->map;
|
||||||
|
|
||||||
|
if (strstr(level.changemap, "*"))
|
||||||
|
{
|
||||||
|
if (coop->value)
|
||||||
|
{
|
||||||
|
for (i=0 ; i<maxclients->value ; i++)
|
||||||
|
{
|
||||||
|
client = g_edicts + 1 + i;
|
||||||
|
if (!client->inuse)
|
||||||
|
continue;
|
||||||
|
// strip players of all keys between units
|
||||||
|
for (n = 0; n < MAX_ITEMS; n++)
|
||||||
|
{
|
||||||
|
if (itemlist[n].flags & IT_KEY)
|
||||||
|
client->client->pers.inventory[n] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!deathmatch->value)
|
||||||
|
{
|
||||||
|
level.exitintermission = 1; // go immediately to the next level
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
level.exitintermission = 0;
|
||||||
|
|
||||||
|
// find an intermission spot
|
||||||
|
ent = G_Find (NULL, FOFS(classname), "info_player_intermission");
|
||||||
|
if (!ent)
|
||||||
|
{ // the map creator forgot to put in an intermission point...
|
||||||
|
ent = G_Find (NULL, FOFS(classname), "info_player_start");
|
||||||
|
if (!ent)
|
||||||
|
ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // chose one of four spots
|
||||||
|
i = rand() & 3;
|
||||||
|
while (i--)
|
||||||
|
{
|
||||||
|
ent = G_Find (ent, FOFS(classname), "info_player_intermission");
|
||||||
|
if (!ent) // wrap around the list
|
||||||
|
ent = G_Find (ent, FOFS(classname), "info_player_intermission");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VectorCopy (ent->s.origin, level.intermission_origin);
|
||||||
|
VectorCopy (ent->s.angles, level.intermission_angle);
|
||||||
|
|
||||||
|
// move all clients to the intermission point
|
||||||
|
for (i=0 ; i<maxclients->value ; i++)
|
||||||
|
{
|
||||||
|
client = g_edicts + 1 + i;
|
||||||
|
if (!client->inuse)
|
||||||
|
continue;
|
||||||
|
MoveClientToIntermission (client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==================
|
||||||
|
DeathmatchScoreboardMessage
|
||||||
|
|
||||||
|
==================
|
||||||
|
*/
|
||||||
|
void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer)
|
||||||
|
{
|
||||||
|
char entry[1024];
|
||||||
|
char string[1400];
|
||||||
|
int stringlength;
|
||||||
|
int i, j, k;
|
||||||
|
int sorted[MAX_CLIENTS];
|
||||||
|
int sortedscores[MAX_CLIENTS];
|
||||||
|
int score, total;
|
||||||
|
int picnum;
|
||||||
|
int x, y;
|
||||||
|
gclient_t *cl;
|
||||||
|
edict_t *cl_ent;
|
||||||
|
char *tag;
|
||||||
|
|
||||||
|
//ZOID
|
||||||
|
if (ctf->value) {
|
||||||
|
CTFScoreboardMessage (ent, killer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//ZOID
|
||||||
|
|
||||||
|
// sort the clients by score
|
||||||
|
total = 0;
|
||||||
|
for (i=0 ; i<game.maxclients ; i++)
|
||||||
|
{
|
||||||
|
cl_ent = g_edicts + 1 + i;
|
||||||
|
if (!cl_ent->inuse)
|
||||||
|
continue;
|
||||||
|
score = game.clients[i].resp.score;
|
||||||
|
for (j=0 ; j<total ; j++)
|
||||||
|
{
|
||||||
|
if (score > sortedscores[j])
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (k=total ; k>j ; k--)
|
||||||
|
{
|
||||||
|
sorted[k] = sorted[k-1];
|
||||||
|
sortedscores[k] = sortedscores[k-1];
|
||||||
|
}
|
||||||
|
sorted[j] = i;
|
||||||
|
sortedscores[j] = score;
|
||||||
|
total++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// print level name and exit rules
|
||||||
|
string[0] = 0;
|
||||||
|
|
||||||
|
stringlength = strlen(string);
|
||||||
|
|
||||||
|
// add the clients in sorted order
|
||||||
|
if (total > 12)
|
||||||
|
total = 12;
|
||||||
|
|
||||||
|
for (i=0 ; i<total ; i++)
|
||||||
|
{
|
||||||
|
cl = &game.clients[sorted[i]];
|
||||||
|
cl_ent = g_edicts + 1 + sorted[i];
|
||||||
|
|
||||||
|
picnum = gi.imageindex ("i_fixme");
|
||||||
|
x = (i>=6) ? 160 : 0;
|
||||||
|
y = 32 + 32 * (i%6);
|
||||||
|
|
||||||
|
// add a dogtag
|
||||||
|
if (cl_ent == ent)
|
||||||
|
tag = "tag1";
|
||||||
|
else if (cl_ent == killer)
|
||||||
|
tag = "tag2";
|
||||||
|
else
|
||||||
|
tag = NULL;
|
||||||
|
if (tag)
|
||||||
|
{
|
||||||
|
Com_sprintf (entry, sizeof(entry),
|
||||||
|
"xv %i yv %i picn %s ",x+32, y, tag);
|
||||||
|
j = strlen(entry);
|
||||||
|
if (stringlength + j > 1024)
|
||||||
|
break;
|
||||||
|
strcpy (string + stringlength, entry);
|
||||||
|
stringlength += j;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the layout
|
||||||
|
Com_sprintf (entry, sizeof(entry),
|
||||||
|
"client %i %i %i %i %i %i ",
|
||||||
|
x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600);
|
||||||
|
j = strlen(entry);
|
||||||
|
if (stringlength + j > 1024)
|
||||||
|
break;
|
||||||
|
strcpy (string + stringlength, entry);
|
||||||
|
stringlength += j;
|
||||||
|
}
|
||||||
|
|
||||||
|
gi.WriteByte (svc_layout);
|
||||||
|
gi.WriteString (string);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==================
|
||||||
|
DeathmatchScoreboard
|
||||||
|
|
||||||
|
Draw instead of help message.
|
||||||
|
Note that it isn't that hard to overflow the 1400 byte message limit!
|
||||||
|
==================
|
||||||
|
*/
|
||||||
|
void DeathmatchScoreboard (edict_t *ent)
|
||||||
|
{
|
||||||
|
DeathmatchScoreboardMessage (ent, ent->enemy);
|
||||||
|
gi.unicast (ent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==================
|
||||||
|
Cmd_Score_f
|
||||||
|
|
||||||
|
Display the scoreboard
|
||||||
|
==================
|
||||||
|
*/
|
||||||
|
void Cmd_Score_f (edict_t *ent)
|
||||||
|
{
|
||||||
|
ent->client->showinventory = false;
|
||||||
|
ent->client->showhelp = false;
|
||||||
|
//ZOID
|
||||||
|
if (ent->client->menu)
|
||||||
|
PMenu_Close(ent);
|
||||||
|
//ZOID
|
||||||
|
|
||||||
|
if (!deathmatch->value && !coop->value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ent->client->showscores)
|
||||||
|
{
|
||||||
|
ent->client->showscores = false;
|
||||||
|
ent->client->update_chase = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ent->client->showscores = true;
|
||||||
|
|
||||||
|
DeathmatchScoreboard (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==================
|
||||||
|
HelpComputer
|
||||||
|
|
||||||
|
Draw help computer.
|
||||||
|
==================
|
||||||
|
*/
|
||||||
|
void HelpComputer (edict_t *ent)
|
||||||
|
{
|
||||||
|
char string[1024];
|
||||||
|
char *sk;
|
||||||
|
|
||||||
|
if (skill->value == 0)
|
||||||
|
sk = "easy";
|
||||||
|
else if (skill->value == 1)
|
||||||
|
sk = "medium";
|
||||||
|
else if (skill->value == 2)
|
||||||
|
sk = "hard";
|
||||||
|
else
|
||||||
|
sk = "hard+";
|
||||||
|
|
||||||
|
// send the layout
|
||||||
|
Com_sprintf (string, sizeof(string),
|
||||||
|
"xv 32 yv 8 picn help " // background
|
||||||
|
"xv 202 yv 12 string2 \"%s\" " // skill
|
||||||
|
"xv 0 yv 24 cstring2 \"%s\" " // level name
|
||||||
|
"xv 0 yv 54 cstring2 \"%s\" " // help 1
|
||||||
|
"xv 0 yv 110 cstring2 \"%s\" " // help 2
|
||||||
|
"xv 50 yv 164 string2 \" kills goals secrets\" "
|
||||||
|
"xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ",
|
||||||
|
sk,
|
||||||
|
level.level_name,
|
||||||
|
game.helpmessage1,
|
||||||
|
game.helpmessage2,
|
||||||
|
level.killed_monsters, level.total_monsters,
|
||||||
|
level.found_goals, level.total_goals,
|
||||||
|
level.found_secrets, level.total_secrets);
|
||||||
|
|
||||||
|
gi.WriteByte (svc_layout);
|
||||||
|
gi.WriteString (string);
|
||||||
|
gi.unicast (ent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==================
|
||||||
|
Cmd_Help_f
|
||||||
|
|
||||||
|
Display the current help message
|
||||||
|
==================
|
||||||
|
*/
|
||||||
|
void Cmd_Help_f (edict_t *ent)
|
||||||
|
{
|
||||||
|
// this is for backwards compatability
|
||||||
|
if (deathmatch->value)
|
||||||
|
{
|
||||||
|
Cmd_Score_f (ent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ent->client->showinventory = false;
|
||||||
|
ent->client->showscores = false;
|
||||||
|
|
||||||
|
if (ent->client->showhelp && (ent->client->resp.game_helpchanged == game.helpchanged))
|
||||||
|
{
|
||||||
|
ent->client->showhelp = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ent->client->showhelp = true;
|
||||||
|
ent->client->resp.helpchanged = 0;
|
||||||
|
HelpComputer (ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=======================================================================
|
||||||
|
|
||||||
|
/*
|
||||||
|
===============
|
||||||
|
G_SetStats
|
||||||
|
===============
|
||||||
|
*/
|
||||||
|
void G_SetStats (edict_t *ent)
|
||||||
|
{
|
||||||
|
gitem_t *item;
|
||||||
|
int index, cells;
|
||||||
|
int power_armor_type;
|
||||||
|
|
||||||
|
//
|
||||||
|
// health
|
||||||
|
//
|
||||||
|
ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
|
||||||
|
ent->client->ps.stats[STAT_HEALTH] = ent->health;
|
||||||
|
|
||||||
|
//
|
||||||
|
// ammo
|
||||||
|
//
|
||||||
|
if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */)
|
||||||
|
{
|
||||||
|
ent->client->ps.stats[STAT_AMMO_ICON] = 0;
|
||||||
|
ent->client->ps.stats[STAT_AMMO] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item = &itemlist[ent->client->ammo_index];
|
||||||
|
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon);
|
||||||
|
ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
cells = 0;
|
||||||
|
|
||||||
|
//
|
||||||
|
// armor
|
||||||
|
//
|
||||||
|
power_armor_type = PowerArmorType (ent);
|
||||||
|
if (power_armor_type)
|
||||||
|
{
|
||||||
|
cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
|
||||||
|
if (cells == 0)
|
||||||
|
{ // ran out of cells for power armor
|
||||||
|
ent->flags &= ~FL_POWER_ARMOR;
|
||||||
|
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
|
||||||
|
power_armor_type = 0;;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index = ArmorIndex (ent);
|
||||||
|
if (power_armor_type && (!index || (level.framenum & 8) ) )
|
||||||
|
{ // flash between power armor and other armor icon
|
||||||
|
ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield");
|
||||||
|
ent->client->ps.stats[STAT_ARMOR] = cells;
|
||||||
|
}
|
||||||
|
else if (index)
|
||||||
|
{
|
||||||
|
item = GetItemByIndex (index);
|
||||||
|
ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon);
|
||||||
|
ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
|
||||||
|
ent->client->ps.stats[STAT_ARMOR] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// pickup message
|
||||||
|
//
|
||||||
|
if (level.time > ent->client->pickup_msg_time)
|
||||||
|
{
|
||||||
|
ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
|
||||||
|
ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// timers
|
||||||
|
//
|
||||||
|
if (ent->client->quad_framenum > level.framenum)
|
||||||
|
{
|
||||||
|
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad");
|
||||||
|
ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10;
|
||||||
|
}
|
||||||
|
else if (ent->client->invincible_framenum > level.framenum)
|
||||||
|
{
|
||||||
|
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability");
|
||||||
|
ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10;
|
||||||
|
}
|
||||||
|
else if (ent->client->enviro_framenum > level.framenum)
|
||||||
|
{
|
||||||
|
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit");
|
||||||
|
ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10;
|
||||||
|
}
|
||||||
|
else if (ent->client->breather_framenum > level.framenum)
|
||||||
|
{
|
||||||
|
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather");
|
||||||
|
ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ent->client->ps.stats[STAT_TIMER_ICON] = 0;
|
||||||
|
ent->client->ps.stats[STAT_TIMER] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// selected item
|
||||||
|
//
|
||||||
|
if (ent->client->pers.selected_item == -1)
|
||||||
|
ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
|
||||||
|
else
|
||||||
|
ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon);
|
||||||
|
|
||||||
|
ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
|
||||||
|
|
||||||
|
//
|
||||||
|
// layouts
|
||||||
|
//
|
||||||
|
ent->client->ps.stats[STAT_LAYOUTS] = 0;
|
||||||
|
|
||||||
|
if (deathmatch->value)
|
||||||
|
{
|
||||||
|
if (ent->client->pers.health <= 0 || level.intermissiontime
|
||||||
|
|| ent->client->showscores)
|
||||||
|
ent->client->ps.stats[STAT_LAYOUTS] |= 1;
|
||||||
|
if (ent->client->showinventory && ent->client->pers.health > 0)
|
||||||
|
ent->client->ps.stats[STAT_LAYOUTS] |= 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ent->client->showscores || ent->client->showhelp)
|
||||||
|
ent->client->ps.stats[STAT_LAYOUTS] |= 1;
|
||||||
|
if (ent->client->showinventory && ent->client->pers.health > 0)
|
||||||
|
ent->client->ps.stats[STAT_LAYOUTS] |= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// frags
|
||||||
|
//
|
||||||
|
ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
|
||||||
|
|
||||||
|
//
|
||||||
|
// help icon / current weapon if not shown
|
||||||
|
//
|
||||||
|
if (ent->client->resp.helpchanged && (level.framenum&8) )
|
||||||
|
ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help");
|
||||||
|
else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91)
|
||||||
|
&& ent->client->pers.weapon)
|
||||||
|
{
|
||||||
|
cvar_t *gun;
|
||||||
|
gun = gi.cvar("cl_gun", "2", 0);
|
||||||
|
|
||||||
|
if(gun->value != 2)
|
||||||
|
ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon);
|
||||||
|
else
|
||||||
|
ent->client->ps.stats[STAT_HELPICON] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ent->client->ps.stats[STAT_HELPICON] = 0;
|
||||||
|
|
||||||
|
//ZOID
|
||||||
|
SetCTFStats(ent);
|
||||||
|
//ZOID
|
||||||
|
}
|
||||||
|
|
257
src/p_menu.c
Normal file
257
src/p_menu.c
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
// Note that the pmenu entries are duplicated
|
||||||
|
// this is so that a static set of pmenu entries can be used
|
||||||
|
// for multiple clients and changed without interference
|
||||||
|
// note that arg will be freed when the menu is closed, it must be allocated memory
|
||||||
|
pmenuhnd_t *PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num, void *arg)
|
||||||
|
{
|
||||||
|
pmenuhnd_t *hnd;
|
||||||
|
pmenu_t *p;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!ent->client)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (ent->client->menu) {
|
||||||
|
gi.dprintf("warning, ent already has a menu\n");
|
||||||
|
PMenu_Close(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
hnd = malloc(sizeof(*hnd));
|
||||||
|
|
||||||
|
hnd->arg = arg;
|
||||||
|
hnd->entries = malloc(sizeof(pmenu_t) * num);
|
||||||
|
memcpy(hnd->entries, entries, sizeof(pmenu_t) * num);
|
||||||
|
// duplicate the strings since they may be from static memory
|
||||||
|
for (i = 0; i < num; i++)
|
||||||
|
if (entries[i].text)
|
||||||
|
hnd->entries[i].text = strdup(entries[i].text);
|
||||||
|
|
||||||
|
hnd->num = num;
|
||||||
|
|
||||||
|
if (cur < 0 || !entries[cur].SelectFunc) {
|
||||||
|
for (i = 0, p = entries; i < num; i++, p++)
|
||||||
|
if (p->SelectFunc)
|
||||||
|
break;
|
||||||
|
} else
|
||||||
|
i = cur;
|
||||||
|
|
||||||
|
if (i >= num)
|
||||||
|
hnd->cur = -1;
|
||||||
|
else
|
||||||
|
hnd->cur = i;
|
||||||
|
|
||||||
|
ent->client->showscores = true;
|
||||||
|
ent->client->inmenu = true;
|
||||||
|
ent->client->menu = hnd;
|
||||||
|
|
||||||
|
PMenu_Do_Update(ent);
|
||||||
|
gi.unicast (ent, true);
|
||||||
|
|
||||||
|
return hnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PMenu_Close(edict_t *ent)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
pmenuhnd_t *hnd;
|
||||||
|
|
||||||
|
if (!ent->client->menu)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hnd = ent->client->menu;
|
||||||
|
for (i = 0; i < hnd->num; i++)
|
||||||
|
if (hnd->entries[i].text)
|
||||||
|
free(hnd->entries[i].text);
|
||||||
|
free(hnd->entries);
|
||||||
|
if (hnd->arg)
|
||||||
|
free(hnd->arg);
|
||||||
|
free(hnd);
|
||||||
|
ent->client->menu = NULL;
|
||||||
|
ent->client->showscores = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only use on pmenu's that have been called with PMenu_Open
|
||||||
|
void PMenu_UpdateEntry(pmenu_t *entry, const char *text, int align, SelectFunc_t SelectFunc)
|
||||||
|
{
|
||||||
|
if (entry->text)
|
||||||
|
free(entry->text);
|
||||||
|
entry->text = strdup(text);
|
||||||
|
entry->align = align;
|
||||||
|
entry->SelectFunc = SelectFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PMenu_Do_Update(edict_t *ent)
|
||||||
|
{
|
||||||
|
char string[1400];
|
||||||
|
int i;
|
||||||
|
pmenu_t *p;
|
||||||
|
int x;
|
||||||
|
pmenuhnd_t *hnd;
|
||||||
|
char *t;
|
||||||
|
qboolean alt = false;
|
||||||
|
|
||||||
|
if (!ent->client->menu) {
|
||||||
|
gi.dprintf("warning: ent has no menu\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hnd = ent->client->menu;
|
||||||
|
|
||||||
|
strcpy(string, "xv 32 yv 8 picn inventory ");
|
||||||
|
|
||||||
|
for (i = 0, p = hnd->entries; i < hnd->num; i++, p++) {
|
||||||
|
if (!p->text || !*(p->text))
|
||||||
|
continue; // blank line
|
||||||
|
t = p->text;
|
||||||
|
if (*t == '*') {
|
||||||
|
alt = true;
|
||||||
|
t++;
|
||||||
|
}
|
||||||
|
sprintf(string + strlen(string), "yv %d ", 32 + i * 8);
|
||||||
|
if (p->align == PMENU_ALIGN_CENTER)
|
||||||
|
x = 196/2 - strlen(t)*4 + 64;
|
||||||
|
else if (p->align == PMENU_ALIGN_RIGHT)
|
||||||
|
x = 64 + (196 - strlen(t)*8);
|
||||||
|
else
|
||||||
|
x = 64;
|
||||||
|
|
||||||
|
sprintf(string + strlen(string), "xv %d ",
|
||||||
|
x - ((hnd->cur == i) ? 8 : 0));
|
||||||
|
|
||||||
|
if (hnd->cur == i)
|
||||||
|
sprintf(string + strlen(string), "string2 \"\x0d%s\" ", t);
|
||||||
|
else if (alt)
|
||||||
|
sprintf(string + strlen(string), "string2 \"%s\" ", t);
|
||||||
|
else
|
||||||
|
sprintf(string + strlen(string), "string \"%s\" ", t);
|
||||||
|
alt = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gi.WriteByte (svc_layout);
|
||||||
|
gi.WriteString (string);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PMenu_Update(edict_t *ent)
|
||||||
|
{
|
||||||
|
if (!ent->client->menu) {
|
||||||
|
gi.dprintf("warning: ent has no menu\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level.time - ent->client->menutime >= 1.0) {
|
||||||
|
// been a second or more since last update, update now
|
||||||
|
PMenu_Do_Update(ent);
|
||||||
|
gi.unicast (ent, true);
|
||||||
|
ent->client->menutime = level.time;
|
||||||
|
ent->client->menudirty = false;
|
||||||
|
}
|
||||||
|
ent->client->menutime = level.time + 0.2;
|
||||||
|
ent->client->menudirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PMenu_Next(edict_t *ent)
|
||||||
|
{
|
||||||
|
pmenuhnd_t *hnd;
|
||||||
|
int i;
|
||||||
|
pmenu_t *p;
|
||||||
|
|
||||||
|
if (!ent->client->menu) {
|
||||||
|
gi.dprintf("warning: ent has no menu\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hnd = ent->client->menu;
|
||||||
|
|
||||||
|
if (hnd->cur < 0)
|
||||||
|
return; // no selectable entries
|
||||||
|
|
||||||
|
i = hnd->cur;
|
||||||
|
p = hnd->entries + hnd->cur;
|
||||||
|
do {
|
||||||
|
i++, p++;
|
||||||
|
if (i == hnd->num)
|
||||||
|
i = 0, p = hnd->entries;
|
||||||
|
if (p->SelectFunc)
|
||||||
|
break;
|
||||||
|
} while (i != hnd->cur);
|
||||||
|
|
||||||
|
hnd->cur = i;
|
||||||
|
|
||||||
|
PMenu_Update(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PMenu_Prev(edict_t *ent)
|
||||||
|
{
|
||||||
|
pmenuhnd_t *hnd;
|
||||||
|
int i;
|
||||||
|
pmenu_t *p;
|
||||||
|
|
||||||
|
if (!ent->client->menu) {
|
||||||
|
gi.dprintf("warning: ent has no menu\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hnd = ent->client->menu;
|
||||||
|
|
||||||
|
if (hnd->cur < 0)
|
||||||
|
return; // no selectable entries
|
||||||
|
|
||||||
|
i = hnd->cur;
|
||||||
|
p = hnd->entries + hnd->cur;
|
||||||
|
do {
|
||||||
|
if (i == 0) {
|
||||||
|
i = hnd->num - 1;
|
||||||
|
p = hnd->entries + i;
|
||||||
|
} else
|
||||||
|
i--, p--;
|
||||||
|
if (p->SelectFunc)
|
||||||
|
break;
|
||||||
|
} while (i != hnd->cur);
|
||||||
|
|
||||||
|
hnd->cur = i;
|
||||||
|
|
||||||
|
PMenu_Update(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PMenu_Select(edict_t *ent)
|
||||||
|
{
|
||||||
|
pmenuhnd_t *hnd;
|
||||||
|
pmenu_t *p;
|
||||||
|
|
||||||
|
if (!ent->client->menu) {
|
||||||
|
gi.dprintf("warning: ent has no menu\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hnd = ent->client->menu;
|
||||||
|
|
||||||
|
if (hnd->cur < 0)
|
||||||
|
return; // no selectable entries
|
||||||
|
|
||||||
|
p = hnd->entries + hnd->cur;
|
||||||
|
|
||||||
|
if (p->SelectFunc)
|
||||||
|
p->SelectFunc(ent, hnd);
|
||||||
|
}
|
||||||
|
|
50
src/p_menu.h
Normal file
50
src/p_menu.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PMENU_ALIGN_LEFT,
|
||||||
|
PMENU_ALIGN_CENTER,
|
||||||
|
PMENU_ALIGN_RIGHT
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct pmenuhnd_s {
|
||||||
|
struct pmenu_s *entries;
|
||||||
|
int cur;
|
||||||
|
int num;
|
||||||
|
void *arg;
|
||||||
|
} pmenuhnd_t;
|
||||||
|
|
||||||
|
typedef void (*SelectFunc_t)(edict_t *ent, pmenuhnd_t *hnd);
|
||||||
|
|
||||||
|
typedef struct pmenu_s {
|
||||||
|
char *text;
|
||||||
|
int align;
|
||||||
|
SelectFunc_t SelectFunc;
|
||||||
|
} pmenu_t;
|
||||||
|
|
||||||
|
pmenuhnd_t *PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num, void *arg);
|
||||||
|
void PMenu_Close(edict_t *ent);
|
||||||
|
void PMenu_UpdateEntry(pmenu_t *entry, const char *text, int align, SelectFunc_t SelectFunc);
|
||||||
|
void PMenu_Do_Update(edict_t *ent);
|
||||||
|
void PMenu_Update(edict_t *ent);
|
||||||
|
void PMenu_Next(edict_t *ent);
|
||||||
|
void PMenu_Prev(edict_t *ent);
|
||||||
|
void PMenu_Select(edict_t *ent);
|
||||||
|
|
147
src/p_trail.c
Normal file
147
src/p_trail.c
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 1997-2001 Id Software, 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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
#include "g_local.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
PLAYER TRAIL
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
This is a circular list containing the a list of points of where
|
||||||
|
the player has been recently. It is used by monsters for pursuit.
|
||||||
|
|
||||||
|
.origin the spot
|
||||||
|
.owner forward link
|
||||||
|
.aiment backward link
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#define TRAIL_LENGTH 8
|
||||||
|
|
||||||
|
edict_t *trail[TRAIL_LENGTH];
|
||||||
|
int trail_head;
|
||||||
|
qboolean trail_active = false;
|
||||||
|
|
||||||
|
#define NEXT(n) (((n) + 1) & (TRAIL_LENGTH - 1))
|
||||||
|
#define PREV(n) (((n) - 1) & (TRAIL_LENGTH - 1))
|
||||||
|
|
||||||
|
|
||||||
|
void PlayerTrail_Init (void)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if (deathmatch->value /* FIXME || coop */)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (n = 0; n < TRAIL_LENGTH; n++)
|
||||||
|
{
|
||||||
|
trail[n] = G_Spawn();
|
||||||
|
trail[n]->classname = "player_trail";
|
||||||
|
}
|
||||||
|
|
||||||
|
trail_head = 0;
|
||||||
|
trail_active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PlayerTrail_Add (vec3_t spot)
|
||||||
|
{
|
||||||
|
vec3_t temp;
|
||||||
|
|
||||||
|
if (!trail_active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
VectorCopy (spot, trail[trail_head]->s.origin);
|
||||||
|
|
||||||
|
trail[trail_head]->timestamp = level.time;
|
||||||
|
|
||||||
|
VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp);
|
||||||
|
trail[trail_head]->s.angles[1] = vectoyaw (temp);
|
||||||
|
|
||||||
|
trail_head = NEXT(trail_head);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PlayerTrail_New (vec3_t spot)
|
||||||
|
{
|
||||||
|
if (!trail_active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PlayerTrail_Init ();
|
||||||
|
PlayerTrail_Add (spot);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
edict_t *PlayerTrail_PickFirst (edict_t *self)
|
||||||
|
{
|
||||||
|
int marker;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if (!trail_active)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
|
||||||
|
{
|
||||||
|
if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
|
||||||
|
marker = NEXT(marker);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visible(self, trail[marker]))
|
||||||
|
{
|
||||||
|
return trail[marker];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visible(self, trail[PREV(marker)]))
|
||||||
|
{
|
||||||
|
return trail[PREV(marker)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return trail[marker];
|
||||||
|
}
|
||||||
|
|
||||||
|
edict_t *PlayerTrail_PickNext (edict_t *self)
|
||||||
|
{
|
||||||
|
int marker;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if (!trail_active)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
|
||||||
|
{
|
||||||
|
if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
|
||||||
|
marker = NEXT(marker);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trail[marker];
|
||||||
|
}
|
||||||
|
|
||||||
|
edict_t *PlayerTrail_LastSpot (void)
|
||||||
|
{
|
||||||
|
return trail[PREV(trail_head)];
|
||||||
|
}
|
||||||
|
|
1125
src/p_view.c
Normal file
1125
src/p_view.c
Normal file
File diff suppressed because it is too large
Load diff
1466
src/p_weapon.c
Normal file
1466
src/p_weapon.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue