This commit is contained in:
Literally A Penguin
2024-09-25 15:37:26 -05:00
committed by GitHub
parent e58d83b4a8
commit b585e51a81
100 changed files with 65768 additions and 0 deletions

1117
game/g_ai.c Normal file

File diff suppressed because it is too large Load Diff

175
game/g_chase.c Normal file
View File

@ -0,0 +1,175 @@
/*
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->client->resp.spectator) {
edict_t *old = ent->client->chase_target;
ChaseNext(ent);
if (ent->client->chase_target == old) {
ent->client->chase_target = NULL;
ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
return;
}
}
targ = ent->client->chase_target;
VectorCopy(targ->s.origin, ownerv);
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;
}
if (targ->deadflag)
ent->client->ps.pmove.pm_type = PM_DEAD;
else
ent->client->ps.pmove.pm_type = PM_FREEZE;
VectorCopy(goal, ent->s.origin);
for (i=0 ; i<3 ; i++)
ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]);
if (targ->deadflag) {
ent->client->ps.viewangles[ROLL] = 40;
ent->client->ps.viewangles[PITCH] = -15;
ent->client->ps.viewangles[YAW] = targ->client->killer_yaw;
} else {
VectorCopy(targ->client->v_angle, ent->client->ps.viewangles);
VectorCopy(targ->client->v_angle, ent->client->v_angle);
}
ent->viewheight = 0;
ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
gi.linkentity(ent);
}
void ChaseNext(edict_t *ent)
{
int i;
edict_t *e;
if (!ent->client->chase_target)
return;
i = ent->client->chase_target - g_edicts;
do {
i++;
if (i > maxclients->value)
i = 1;
e = g_edicts + i;
if (!e->inuse)
continue;
if (!e->client->resp.spectator)
break;
} while (e != ent->client->chase_target);
ent->client->chase_target = e;
ent->client->update_chase = true;
}
void ChasePrev(edict_t *ent)
{
int i;
edict_t *e;
if (!ent->client->chase_target)
return;
i = ent->client->chase_target - g_edicts;
do {
i--;
if (i < 1)
i = maxclients->value;
e = g_edicts + i;
if (!e->inuse)
continue;
if (!e->client->resp.spectator)
break;
} while (e != ent->client->chase_target);
ent->client->chase_target = e;
ent->client->update_chase = true;
}
void GetChaseTarget(edict_t *ent)
{
int i;
edict_t *other;
for (i = 1; i <= maxclients->value; i++) {
other = g_edicts + i;
if (other->inuse && !other->client->resp.spectator) {
ent->client->chase_target = other;
ent->client->update_chase = true;
UpdateChaseCam(ent);
return;
}
}
gi.centerprintf(ent, "No other players to chase.");
}

992
game/g_cmds.c Normal file
View File

@ -0,0 +1,992 @@
/*
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"
#include "m_player.h"
char *ClientTeam (edict_t *ent)
{
char *p;
static char value[512];
value[0] = 0;
if (!ent->client)
return value;
strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin"));
p = strchr(value, '/');
if (!p)
return value;
if ((int)(dmflags->value) & DF_MODELTEAMS)
{
*p = 0;
return value;
}
// if ((int)(dmflags->value) & DF_SKINTEAMS)
return ++p;
}
qboolean OnSameTeam (edict_t *ent1, edict_t *ent2)
{
char ent1Team [512];
char ent2Team [512];
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
return false;
strcpy (ent1Team, ClientTeam (ent1));
strcpy (ent2Team, ClientTeam (ent2));
if (strcmp(ent1Team, ent2Team) == 0)
return true;
return false;
}
void SelectNextItem (edict_t *ent, int itflags)
{
gclient_t *cl;
int i, index;
gitem_t *it;
cl = ent->client;
if (cl->chase_target) {
ChaseNext(ent);
return;
}
// scan for the next valid one
for (i=1 ; i<=MAX_ITEMS ; i++)
{
index = (cl->pers.selected_item + i)%MAX_ITEMS;
if (!cl->pers.inventory[index])
continue;
it = &itemlist[index];
if (!it->use)
continue;
if (!(it->flags & itflags))
continue;
cl->pers.selected_item = index;
return;
}
cl->pers.selected_item = -1;
}
void SelectPrevItem (edict_t *ent, int itflags)
{
gclient_t *cl;
int i, index;
gitem_t *it;
cl = ent->client;
if (cl->chase_target) {
ChasePrev(ent);
return;
}
// scan for the next valid one
for (i=1 ; i<=MAX_ITEMS ; i++)
{
index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS;
if (!cl->pers.inventory[index])
continue;
it = &itemlist[index];
if (!it->use)
continue;
if (!(it->flags & itflags))
continue;
cl->pers.selected_item = index;
return;
}
cl->pers.selected_item = -1;
}
void ValidateSelectedItem (edict_t *ent)
{
gclient_t *cl;
cl = ent->client;
if (cl->pers.inventory[cl->pers.selected_item])
return; // valid
SelectNextItem (ent, -1);
}
//=================================================================================
/*
==================
Cmd_Give_f
Give items to a client
==================
*/
void Cmd_Give_f (edict_t *ent)
{
char *name;
gitem_t *it;
int index;
int i;
qboolean give_all;
edict_t *it_ent;
if (deathmatch->value && !sv_cheats->value)
{
gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
return;
}
name = gi.args();
if (Q_stricmp(name, "all") == 0)
give_all = true;
else
give_all = false;
if (give_all || Q_stricmp(gi.argv(1), "health") == 0)
{
if (gi.argc() == 3)
ent->health = atoi(gi.argv(2));
else
ent->health = ent->max_health;
if (!give_all)
return;
}
if (give_all || Q_stricmp(name, "weapons") == 0)
{
for (i=0 ; i<game.num_items ; i++)
{
it = itemlist + i;
if (!it->pickup)
continue;
if (!(it->flags & IT_WEAPON))
continue;
ent->client->pers.inventory[i] += 1;
}
if (!give_all)
return;
}
if (give_all || Q_stricmp(name, "ammo") == 0)
{
for (i=0 ; i<game.num_items ; i++)
{
it = itemlist + i;
if (!it->pickup)
continue;
if (!(it->flags & IT_AMMO))
continue;
Add_Ammo (ent, it, 1000);
}
if (!give_all)
return;
}
if (give_all || Q_stricmp(name, "armor") == 0)
{
gitem_armor_t *info;
it = FindItem("Jacket Armor");
ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
it = FindItem("Combat Armor");
ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
it = FindItem("Body Armor");
info = (gitem_armor_t *)it->info;
ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count;
if (!give_all)
return;
}
if (give_all || Q_stricmp(name, "Power Shield") == 0)
{
it = FindItem("Power Shield");
it_ent = G_Spawn();
it_ent->classname = it->classname;
SpawnItem (it_ent, it);
Touch_Item (it_ent, ent, NULL, NULL);
if (it_ent->inuse)
G_FreeEdict(it_ent);
if (!give_all)
return;
}
if (give_all)
{
for (i=0 ; i<game.num_items ; i++)
{
it = itemlist + i;
if (!it->pickup)
continue;
if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO))
continue;
ent->client->pers.inventory[i] = 1;
}
return;
}
it = FindItem (name);
if (!it)
{
name = gi.argv(1);
it = FindItem (name);
if (!it)
{
gi.cprintf (ent, PRINT_HIGH, "unknown item\n");
return;
}
}
if (!it->pickup)
{
gi.cprintf (ent, PRINT_HIGH, "non-pickup item\n");
return;
}
index = ITEM_INDEX(it);
if (it->flags & IT_AMMO)
{
if (gi.argc() == 3)
ent->client->pers.inventory[index] = atoi(gi.argv(2));
else
ent->client->pers.inventory[index] += it->quantity;
}
else
{
it_ent = G_Spawn();
it_ent->classname = it->classname;
SpawnItem (it_ent, it);
Touch_Item (it_ent, ent, NULL, NULL);
if (it_ent->inuse)
G_FreeEdict(it_ent);
}
}
/*
==================
Cmd_God_f
Sets client to godmode
argv(0) god
==================
*/
void Cmd_God_f (edict_t *ent)
{
char *msg;
if (deathmatch->value && !sv_cheats->value)
{
gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
return;
}
ent->flags ^= FL_GODMODE;
if (!(ent->flags & FL_GODMODE) )
msg = "godmode OFF\n";
else
msg = "godmode ON\n";
gi.cprintf (ent, PRINT_HIGH, msg);
}
/*
==================
Cmd_Notarget_f
Sets client to notarget
argv(0) notarget
==================
*/
void Cmd_Notarget_f (edict_t *ent)
{
char *msg;
if (deathmatch->value && !sv_cheats->value)
{
gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
return;
}
ent->flags ^= FL_NOTARGET;
if (!(ent->flags & FL_NOTARGET) )
msg = "notarget OFF\n";
else
msg = "notarget ON\n";
gi.cprintf (ent, PRINT_HIGH, msg);
}
/*
==================
Cmd_Noclip_f
argv(0) noclip
==================
*/
void Cmd_Noclip_f (edict_t *ent)
{
char *msg;
if (deathmatch->value && !sv_cheats->value)
{
gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
return;
}
if (ent->movetype == MOVETYPE_NOCLIP)
{
ent->movetype = MOVETYPE_WALK;
msg = "noclip OFF\n";
}
else
{
ent->movetype = MOVETYPE_NOCLIP;
msg = "noclip ON\n";
}
gi.cprintf (ent, PRINT_HIGH, msg);
}
/*
==================
Cmd_Use_f
Use an inventory item
==================
*/
void Cmd_Use_f (edict_t *ent)
{
int index;
gitem_t *it;
char *s;
s = gi.args();
it = FindItem (s);
if (!it)
{
gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s);
return;
}
if (!it->use)
{
gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n");
return;
}
index = ITEM_INDEX(it);
if (!ent->client->pers.inventory[index])
{
gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
return;
}
it->use (ent, it);
}
/*
==================
Cmd_Drop_f
Drop an inventory item
==================
*/
void Cmd_Drop_f (edict_t *ent)
{
int index;
gitem_t *it;
char *s;
s = gi.args();
it = FindItem (s);
if (!it)
{
gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s);
return;
}
if (!it->drop)
{
gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n");
return;
}
index = ITEM_INDEX(it);
if (!ent->client->pers.inventory[index])
{
gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
return;
}
it->drop (ent, it);
}
/*
=================
Cmd_Inven_f
=================
*/
void Cmd_Inven_f (edict_t *ent)
{
int i;
gclient_t *cl;
cl = ent->client;
cl->showscores = false;
cl->showhelp = false;
if (cl->showinventory)
{
cl->showinventory = false;
return;
}
cl->showinventory = true;
gi.WriteByte (svc_inventory);
for (i=0 ; i<MAX_ITEMS ; i++)
{
gi.WriteShort (cl->pers.inventory[i]);
}
gi.unicast (ent, true);
}
/*
=================
Cmd_InvUse_f
=================
*/
void Cmd_InvUse_f (edict_t *ent)
{
gitem_t *it;
ValidateSelectedItem (ent);
if (ent->client->pers.selected_item == -1)
{
gi.cprintf (ent, PRINT_HIGH, "No item to use.\n");
return;
}
it = &itemlist[ent->client->pers.selected_item];
if (!it->use)
{
gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n");
return;
}
it->use (ent, it);
}
/*
=================
Cmd_WeapPrev_f
=================
*/
void Cmd_WeapPrev_f (edict_t *ent)
{
gclient_t *cl;
int i, index;
gitem_t *it;
int selected_weapon;
cl = ent->client;
if (!cl->pers.weapon)
return;
selected_weapon = ITEM_INDEX(cl->pers.weapon);
// scan for the next valid one
for (i=1 ; i<=MAX_ITEMS ; i++)
{
index = (selected_weapon + i)%MAX_ITEMS;
if (!cl->pers.inventory[index])
continue;
it = &itemlist[index];
if (!it->use)
continue;
if (! (it->flags & IT_WEAPON) )
continue;
it->use (ent, it);
if (cl->pers.weapon == it)
return; // successful
}
}
/*
=================
Cmd_WeapNext_f
=================
*/
void Cmd_WeapNext_f (edict_t *ent)
{
gclient_t *cl;
int i, index;
gitem_t *it;
int selected_weapon;
cl = ent->client;
if (!cl->pers.weapon)
return;
selected_weapon = ITEM_INDEX(cl->pers.weapon);
// scan for the next valid one
for (i=1 ; i<=MAX_ITEMS ; i++)
{
index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS;
if (!cl->pers.inventory[index])
continue;
it = &itemlist[index];
if (!it->use)
continue;
if (! (it->flags & IT_WEAPON) )
continue;
it->use (ent, it);
if (cl->pers.weapon == it)
return; // successful
}
}
/*
=================
Cmd_WeapLast_f
=================
*/
void Cmd_WeapLast_f (edict_t *ent)
{
gclient_t *cl;
int index;
gitem_t *it;
cl = ent->client;
if (!cl->pers.weapon || !cl->pers.lastweapon)
return;
index = ITEM_INDEX(cl->pers.lastweapon);
if (!cl->pers.inventory[index])
return;
it = &itemlist[index];
if (!it->use)
return;
if (! (it->flags & IT_WEAPON) )
return;
it->use (ent, it);
}
/*
=================
Cmd_InvDrop_f
=================
*/
void Cmd_InvDrop_f (edict_t *ent)
{
gitem_t *it;
ValidateSelectedItem (ent);
if (ent->client->pers.selected_item == -1)
{
gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n");
return;
}
it = &itemlist[ent->client->pers.selected_item];
if (!it->drop)
{
gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n");
return;
}
it->drop (ent, it);
}
/*
=================
Cmd_Kill_f
=================
*/
void Cmd_Kill_f (edict_t *ent)
{
if((level.time - ent->client->respawn_time) < 5)
return;
ent->flags &= ~FL_GODMODE;
ent->health = 0;
meansOfDeath = MOD_SUICIDE;
player_die (ent, ent, ent, 100000, vec3_origin);
}
/*
=================
Cmd_PutAway_f
=================
*/
void Cmd_PutAway_f (edict_t *ent)
{
ent->client->showscores = false;
ent->client->showhelp = false;
ent->client->showinventory = false;
}
int PlayerSort (void const *a, void const *b)
{
int anum, bnum;
anum = *(int *)a;
bnum = *(int *)b;
anum = game.clients[anum].ps.stats[STAT_FRAGS];
bnum = game.clients[bnum].ps.stats[STAT_FRAGS];
if (anum < bnum)
return -1;
if (anum > bnum)
return 1;
return 0;
}
/*
=================
Cmd_Players_f
=================
*/
void Cmd_Players_f (edict_t *ent)
{
int i;
int count;
char small[64];
char large[1280];
int index[256];
count = 0;
for (i = 0 ; i < maxclients->value ; i++)
if (game.clients[i].pers.connected)
{
index[count] = i;
count++;
}
// sort by frags
qsort (index, count, sizeof(index[0]), PlayerSort);
// print information
large[0] = 0;
for (i = 0 ; i < count ; i++)
{
Com_sprintf (small, sizeof(small), "%3i %s\n",
game.clients[index[i]].ps.stats[STAT_FRAGS],
game.clients[index[i]].pers.netname);
if (strlen (small) + strlen(large) > sizeof(large) - 100 )
{ // can't print all of them in one packet
strcat (large, "...\n");
break;
}
strcat (large, small);
}
gi.cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count);
}
/*
=================
Cmd_Wave_f
=================
*/
void Cmd_Wave_f (edict_t *ent)
{
int i;
i = atoi (gi.argv(1));
// can't wave when ducked
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
return;
if (ent->client->anim_priority > ANIM_WAVE)
return;
ent->client->anim_priority = ANIM_WAVE;
switch (i)
{
case 0:
gi.cprintf (ent, PRINT_HIGH, "flipoff\n");
ent->s.frame = FRAME_flip01-1;
ent->client->anim_end = FRAME_flip12;
break;
case 1:
gi.cprintf (ent, PRINT_HIGH, "salute\n");
ent->s.frame = FRAME_salute01-1;
ent->client->anim_end = FRAME_salute11;
break;
case 2:
gi.cprintf (ent, PRINT_HIGH, "taunt\n");
ent->s.frame = FRAME_taunt01-1;
ent->client->anim_end = FRAME_taunt17;
break;
case 3:
gi.cprintf (ent, PRINT_HIGH, "wave\n");
ent->s.frame = FRAME_wave01-1;
ent->client->anim_end = FRAME_wave11;
break;
case 4:
default:
gi.cprintf (ent, PRINT_HIGH, "point\n");
ent->s.frame = FRAME_point01-1;
ent->client->anim_end = FRAME_point12;
break;
}
}
/*
==================
Cmd_Say_f
==================
*/
void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0)
{
int i, j;
edict_t *other;
char *p;
char text[2048];
gclient_t *cl;
if (gi.argc () < 2 && !arg0)
return;
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
team = false;
if (team)
Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname);
else
Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname);
if (arg0)
{
strcat (text, gi.argv(0));
strcat (text, " ");
strcat (text, gi.args());
}
else
{
p = gi.args();
if (*p == '"')
{
p++;
p[strlen(p)-1] = 0;
}
strcat(text, p);
}
// don't let text be too long for malicious reasons
if (strlen(text) > 150)
text[150] = 0;
strcat(text, "\n");
if (flood_msgs->value) {
cl = ent->client;
if (level.time < cl->flood_locktill) {
gi.cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds\n",
(int)(cl->flood_locktill - level.time));
return;
}
i = cl->flood_whenhead - flood_msgs->value + 1;
if (i < 0)
i = (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])) + i;
if (cl->flood_when[i] &&
level.time - cl->flood_when[i] < flood_persecond->value) {
cl->flood_locktill = level.time + flood_waitdelay->value;
gi.cprintf(ent, PRINT_CHAT, "Flood protection: You can't talk for %d seconds.\n",
(int)flood_waitdelay->value);
return;
}
cl->flood_whenhead = (cl->flood_whenhead + 1) %
(sizeof(cl->flood_when)/sizeof(cl->flood_when[0]));
cl->flood_when[cl->flood_whenhead] = level.time;
}
if (dedicated->value)
gi.cprintf(NULL, PRINT_CHAT, "%s", text);
for (j = 1; j <= game.maxclients; j++)
{
other = &g_edicts[j];
if (!other->inuse)
continue;
if (!other->client)
continue;
if (team)
{
if (!OnSameTeam(ent, other))
continue;
}
gi.cprintf(other, PRINT_CHAT, "%s", text);
}
}
void Cmd_PlayerList_f(edict_t *ent)
{
int i;
char st[80];
char text[1400];
edict_t *e2;
// connect time, ping, score, name
*text = 0;
for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++) {
if (!e2->inuse)
continue;
sprintf(st, "%02d:%02d %4d %3d %s%s\n",
(level.framenum - e2->client->resp.enterframe) / 600,
((level.framenum - e2->client->resp.enterframe) % 600)/10,
e2->client->ping,
e2->client->resp.score,
e2->client->pers.netname,
e2->client->resp.spectator ? " (spectator)" : "");
if (strlen(text) + strlen(st) > sizeof(text) - 50) {
sprintf(text+strlen(text), "And more...\n");
gi.cprintf(ent, PRINT_HIGH, "%s", text);
return;
}
strcat(text, st);
}
gi.cprintf(ent, PRINT_HIGH, "%s", text);
}
/*
=================
ClientCommand
=================
*/
void ClientCommand (edict_t *ent)
{
char *cmd;
if (!ent->client)
return; // not fully in game yet
cmd = gi.argv(0);
if (Q_stricmp (cmd, "players") == 0)
{
Cmd_Players_f (ent);
return;
}
if (Q_stricmp (cmd, "say") == 0)
{
Cmd_Say_f (ent, false, false);
return;
}
if (Q_stricmp (cmd, "say_team") == 0)
{
Cmd_Say_f (ent, true, false);
return;
}
if (Q_stricmp (cmd, "score") == 0)
{
Cmd_Score_f (ent);
return;
}
if (Q_stricmp (cmd, "help") == 0)
{
Cmd_Help_f (ent);
return;
}
if (level.intermissiontime)
return;
if (Q_stricmp (cmd, "use") == 0)
Cmd_Use_f (ent);
else if (Q_stricmp (cmd, "drop") == 0)
Cmd_Drop_f (ent);
else if (Q_stricmp (cmd, "give") == 0)
Cmd_Give_f (ent);
else if (Q_stricmp (cmd, "god") == 0)
Cmd_God_f (ent);
else if (Q_stricmp (cmd, "notarget") == 0)
Cmd_Notarget_f (ent);
else if (Q_stricmp (cmd, "noclip") == 0)
Cmd_Noclip_f (ent);
else if (Q_stricmp (cmd, "inven") == 0)
Cmd_Inven_f (ent);
else if (Q_stricmp (cmd, "invnext") == 0)
SelectNextItem (ent, -1);
else if (Q_stricmp (cmd, "invprev") == 0)
SelectPrevItem (ent, -1);
else if (Q_stricmp (cmd, "invnextw") == 0)
SelectNextItem (ent, IT_WEAPON);
else if (Q_stricmp (cmd, "invprevw") == 0)
SelectPrevItem (ent, IT_WEAPON);
else if (Q_stricmp (cmd, "invnextp") == 0)
SelectNextItem (ent, IT_POWERUP);
else if (Q_stricmp (cmd, "invprevp") == 0)
SelectPrevItem (ent, IT_POWERUP);
else if (Q_stricmp (cmd, "invuse") == 0)
Cmd_InvUse_f (ent);
else if (Q_stricmp (cmd, "invdrop") == 0)
Cmd_InvDrop_f (ent);
else if (Q_stricmp (cmd, "weapprev") == 0)
Cmd_WeapPrev_f (ent);
else if (Q_stricmp (cmd, "weapnext") == 0)
Cmd_WeapNext_f (ent);
else if (Q_stricmp (cmd, "weaplast") == 0)
Cmd_WeapLast_f (ent);
else if (Q_stricmp (cmd, "kill") == 0)
Cmd_Kill_f (ent);
else if (Q_stricmp (cmd, "putaway") == 0)
Cmd_PutAway_f (ent);
else if (Q_stricmp (cmd, "wave") == 0)
Cmd_Wave_f (ent);
else if (Q_stricmp(cmd, "playerlist") == 0)
Cmd_PlayerList_f(ent);
else // anything that doesn't match a command will be a chat
Cmd_Say_f (ent, false, true);
}

576
game/g_combat.c Normal file
View File

@ -0,0 +1,576 @@
/*
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))
{
// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type
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.WriteByte (damage);
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;
int damagePerCell;
int pa_te_type;
int power;
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;
}
else
return 0;
if (power_armor_type == POWER_ARMOR_NONE)
return 0;
if (!power)
return 0;
if (power_armor_type == POWER_ARMOR_SCREEN)
{
vec3_t vec;
float dot;
vec3_t forward;
// only works if damage point is in front
AngleVectors (ent->s.angles, forward, NULL, NULL);
VectorSubtract (point, ent->s.origin, vec);
VectorNormalize (vec);
dot = DotProduct (vec, forward);
if (dot <= 0.3)
return 0;
damagePerCell = 1;
pa_te_type = TE_SCREEN_SPARKS;
damage = damage / 3;
}
else
{
damagePerCell = 2;
pa_te_type = TE_SHIELD_SPARKS;
damage = (2 * damage) / 3;
}
save = power * damagePerCell;
if (!save)
return 0;
if (save > damage)
save = damage;
SpawnDamage (pa_te_type, point, normal, 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)
{
targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
// this can only happen in coop (both new and old enemies are clients)
// only switch if can't see the current enemy
if (targ->enemy && targ->enemy->client)
{
if (visible(targ, targ->enemy))
{
targ->oldenemy = attacker;
return;
}
targ->oldenemy = targ->enemy;
}
targ->enemy = attacker;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
FoundTarget (targ);
return;
}
// it's the same base (walk/swim/fly) type and a different classname and it's not a tank
// (they spray too much), get mad at them
if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) &&
(strcmp (targ->classname, attacker->classname) != 0) &&
(strcmp(attacker->classname, "monster_tank") != 0) &&
(strcmp(attacker->classname, "monster_supertank") != 0) &&
(strcmp(attacker->classname, "monster_makron") != 0) &&
(strcmp(attacker->classname, "monster_jorg") != 0) )
{
if (targ->enemy && targ->enemy->client)
targ->oldenemy = targ->enemy;
targ->enemy = attacker;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
FoundTarget (targ);
}
// if they *meant* to shoot us, then shoot back
else if (attacker->enemy == targ)
{
if (targ->enemy && targ->enemy->client)
targ->oldenemy = targ->enemy;
targ->enemy = attacker;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
FoundTarget (targ);
}
// otherwise get mad at whoever they are mad at (help our buddy) unless it is us!
else if (attacker->enemy && attacker->enemy != targ)
{
if (targ->enemy && targ->enemy->client)
targ->oldenemy = targ->enemy;
targ->enemy = attacker->enemy;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
FoundTarget (targ);
}
}
qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker)
{
//FIXME make the next line real and uncomment this block
// if ((ability to damage a teammate == OFF) && (targ's team == attacker's team))
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;
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;
}
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;
// team damage avoidance
if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
return;
// do the damage
if (take)
{
if ((targ->svflags & SVF_MONSTER) || (client))
SpawnDamage (TE_BLOOD, point, normal, take);
else
SpawnDamage (te_sparks, point, normal, take);
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))
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);
}
}
}
}

2048
game/g_func.c Normal file

File diff suppressed because it is too large Load Diff

2216
game/g_items.c Normal file

File diff suppressed because it is too large Load Diff

1113
game/g_local.h Normal file

File diff suppressed because it is too large Load Diff

411
game/g_main.c Normal file
View File

@ -0,0 +1,411 @@
/*
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;
cvar_t *password;
cvar_t *spectator_password;
cvar_t *maxclients;
cvar_t *maxspectators;
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;
}
// 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;
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];
Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap);
gi.AddCommandString (command);
level.changemap = NULL;
level.exitintermission = 0;
level.intermissiontime = 0;
ClientEndServerFrames ();
// 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 ();
}

1876
game/g_misc.c Normal file

File diff suppressed because it is too large Load Diff

740
game/g_monster.c Normal file
View File

@ -0,0 +1,740 @@
/*
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;
}
// ent->groundentity = trace.ent;
// ent->groundentity_linkcount = trace.ent->linkcount;
// if (!trace.startsolid && !trace.allsolid)
// VectorCopy (trace.endpos, ent->s.origin);
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;
// gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin));
}
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);
}

961
game/g_phys.c Normal file
View File

@ -0,0 +1,961 @@
/*
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) && !VectorCompare (planes[i], planes[j]))
{
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)
{
// gi.dprintf ("clip velocity, numplanes == %i\n",numplanes);
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
//retry:
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);
#if 0
// if the pushed entity went away and the pusher is still there
if (!obstacle->inuse && part->inuse)
goto retry;
#endif
}
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);
}
}
// if (ent->touch)
// ent->touch (ent, trace.ent, &trace.plane, trace.surface);
}
// 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->inuse)
return;
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);
}
}