as released 1998-11-29

This commit is contained in:
archive 1998-11-29 00:00:00 +00:00
commit e3ace8e28d
95 changed files with 72059 additions and 0 deletions

298
Makefile Normal file
View file

@ -0,0 +1,298 @@
BUILD_DEBUG_DIR=debug
BUILD_RELEASE_DIR=release
CC=gcc
BASE_CFLAGS=-Dstricmp=strcasecmp
RELEASE_CFLAGS=$(BASE_CFLAGS) -ffast-math -funroll-loops \
-fomit-frame-pointer -fexpensive-optimizations
DEBUG_CFLAGS=$(BASE_CFLAGS) -g
LDFLAGS=-ldl -lm
SHLIBEXT=so
SHLIBCFLAGS=-fPIC
SHLIBLDFLAGS=-shared
DO_CC=$(CC) $(CFLAGS) -o $@ -c $<
DO_SHLIB_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $<
TARGETS=$(BUILDDIR)/game$(ARCH).$(SHLIBEXT) \
build_debug:
@-mkdir $(BUILD_DEBUG_DIR)
$(MAKE) targets BUILDDIR=$(BUILD_DEBUG_DIR) CFLAGS="$(DEBUG_CFLAGS)"
build_release:
@-mkdir $(BUILD_RELEASE_DIR)
$(MAKE) targets BUILDDIR=$(BUILD_RELEASE_DIR) CFLAGS="$(RELEASE_CFLAGS)"
all: build_debug build_release
targets: $(TARGETS)
ROGUE_OBJS = \
$(BUILDDIR)/dm_ball.o \
$(BUILDDIR)/dm_tag.o \
$(BUILDDIR)/g_ai.o \
$(BUILDDIR)/g_chase.o \
$(BUILDDIR)/g_cmds.o \
$(BUILDDIR)/g_combat.o \
$(BUILDDIR)/g_func.o \
$(BUILDDIR)/g_items.o \
$(BUILDDIR)/g_main.o \
$(BUILDDIR)/g_misc.o \
$(BUILDDIR)/g_monster.o \
$(BUILDDIR)/g_newai.o \
$(BUILDDIR)/g_newdm.o \
$(BUILDDIR)/g_newfnc.o \
$(BUILDDIR)/g_newtarg.o \
$(BUILDDIR)/g_newtrig.o \
$(BUILDDIR)/g_newweap.o \
$(BUILDDIR)/g_phys.o \
$(BUILDDIR)/g_save.o \
$(BUILDDIR)/g_spawn.o \
$(BUILDDIR)/g_sphere.o \
$(BUILDDIR)/g_svcmds.o \
$(BUILDDIR)/g_target.o \
$(BUILDDIR)/g_trigger.o \
$(BUILDDIR)/g_turret.o \
$(BUILDDIR)/g_utils.o \
$(BUILDDIR)/g_weapon.o \
$(BUILDDIR)/m_actor.o \
$(BUILDDIR)/m_berserk.o \
$(BUILDDIR)/m_boss2.o \
$(BUILDDIR)/m_boss3.o \
$(BUILDDIR)/m_boss31.o \
$(BUILDDIR)/m_boss32.o \
$(BUILDDIR)/m_brain.o \
$(BUILDDIR)/m_carrier.o \
$(BUILDDIR)/m_chick.o \
$(BUILDDIR)/m_flash.o \
$(BUILDDIR)/m_flipper.o \
$(BUILDDIR)/m_float.o \
$(BUILDDIR)/m_flyer.o \
$(BUILDDIR)/m_gladiator.o \
$(BUILDDIR)/m_gunner.o \
$(BUILDDIR)/m_hover.o \
$(BUILDDIR)/m_infantry.o \
$(BUILDDIR)/m_insane.o \
$(BUILDDIR)/m_medic.o \
$(BUILDDIR)/m_move.o \
$(BUILDDIR)/m_mutant.o \
$(BUILDDIR)/m_parasite.o \
$(BUILDDIR)/m_soldier.o \
$(BUILDDIR)/m_stalker.o \
$(BUILDDIR)/m_supertank.o \
$(BUILDDIR)/m_tank.o \
$(BUILDDIR)/m_turret.o \
$(BUILDDIR)/m_widow.o \
$(BUILDDIR)/m_widow2.o \
$(BUILDDIR)/p_client.o \
$(BUILDDIR)/p_hud.o \
$(BUILDDIR)/p_trail.o \
$(BUILDDIR)/p_view.o \
$(BUILDDIR)/p_weapon.o \
$(BUILDDIR)/q_shared.o
$(BUILDDIR)/game$(ARCH).$(SHLIBEXT) : $(ROGUE_OBJS)
$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(ROGUE_OBJS)
$(BUILDDIR)/dm_ball.o : dm_ball.c
$(DO_SHLIB_CC)
$(BUILDDIR)/dm_tag.o : dm_tag.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_ai.o : g_ai.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_chase.o : g_chase.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_cmds.o : g_cmds.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_combat.o : g_combat.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_func.o : g_func.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_items.o : g_items.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_main.o : g_main.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_misc.o : g_misc.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_monster.o : g_monster.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_newai.o : g_newai.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_newdm.o : g_newdm.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_newfnc.o : g_newfnc.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_newtarg.o : g_newtarg.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_newtrig.o : g_newtrig.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_newweap.o : g_newweap.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_phys.o : g_phys.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_save.o : g_save.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_spawn.o : g_spawn.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_sphere.o : g_sphere.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_svcmds.o : g_svcmds.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_target.o : g_target.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_trigger.o : g_trigger.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_turret.o : g_turret.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_utils.o : g_utils.c
$(DO_SHLIB_CC)
$(BUILDDIR)/g_weapon.o : g_weapon.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_actor.o : m_actor.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_berserk.o : m_berserk.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_boss2.o : m_boss2.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_boss3.o : m_boss3.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_boss31.o : m_boss31.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_boss32.o : m_boss32.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_brain.o : m_brain.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_carrier.o : m_carrier.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_chick.o : m_chick.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_flash.o : m_flash.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_flipper.o : m_flipper.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_float.o : m_float.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_flyer.o : m_flyer.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_gladiator.o : m_gladiator.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_gunner.o : m_gunner.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_hover.o : m_hover.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_infantry.o : m_infantry.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_insane.o : m_insane.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_medic.o : m_medic.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_move.o : m_move.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_mutant.o : m_mutant.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_parasite.o : m_parasite.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_soldier.o : m_soldier.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_stalker.o : m_stalker.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_supertank.o : m_supertank.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_tank.o : m_tank.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_turret.o : m_turret.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_widow.o : m_widow.c
$(DO_SHLIB_CC)
$(BUILDDIR)/m_widow2.o : m_widow2.c
$(DO_SHLIB_CC)
$(BUILDDIR)/p_client.o : p_client.c
$(DO_SHLIB_CC)
$(BUILDDIR)/p_hud.o : p_hud.c
$(DO_SHLIB_CC)
$(BUILDDIR)/p_trail.o : p_trail.c
$(DO_SHLIB_CC)
$(BUILDDIR)/p_view.o : p_view.c
$(DO_SHLIB_CC)
$(BUILDDIR)/p_weapon.o : p_weapon.c
$(DO_SHLIB_CC)
$(BUILDDIR)/q_shared.o : q_shared.c
$(DO_SHLIB_CC)
######################
clean: clean-debug clean-release
clean-debug:
$(MAKE) clean2 BUILDDIR=$(BUILD_DEBUG_DIR) CFLAGS="$(DEBUG_CFLAGS)"
clean-release:
$(MAKE) clean2 BUILDDIR=$(BUILD_RELEASE_DIR) CFLAGS="$(DEBUG_CFLAGS)"
clean2:
-rm -f $(ROGUE_OBJS)

678
dm_ball.c Normal file
View file

@ -0,0 +1,678 @@
// dm_ball.c
// pmack
// june 98
#include "g_local.h"
// defines
#define DBALL_GOAL_TEAM1 0x0001
#define DBALL_GOAL_TEAM2 0x0002
// globals
edict_t *dball_ball_entity = NULL;
int dball_ball_startpt_count;
int dball_team1_goalscore;
int dball_team2_goalscore;
cvar_t *dball_team1_skin;
cvar_t *dball_team2_skin;
cvar_t *goallimit;
// prototypes
extern void EndDMLevel (void);
extern void ClientUserinfoChanged (edict_t *ent, char *userinfo);
extern void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles);
extern float PlayersRangeFromSpot (edict_t *spot);
void DBall_BallDie (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
void DBall_BallRespawn (edict_t *self);
// **************************
// Game rules
// **************************
int DBall_CheckDMRules (void)
{
if(goallimit && goallimit->value)
{
if(dball_team1_goalscore >= goallimit->value)
gi.bprintf (PRINT_HIGH, "Team 1 Wins.\n");
else if(dball_team2_goalscore >= goallimit->value)
gi.bprintf (PRINT_HIGH, "Team 2 Wins.\n");
else
return 0;
EndDMLevel ();
return 1;
}
return 0;
}
//==================
//==================
void DBall_ClientBegin (edict_t *ent)
{
int team1, team2, unassigned;
edict_t *other;
char *p;
static char value[512];
int j;
team1 = 0;
team2 = 0;
unassigned = 0;
for (j = 1; j <= game.maxclients; j++)
{
other = &g_edicts[j];
if (!other->inuse)
continue;
if (!other->client)
continue;
if (other == ent) // don't count the new player
continue;
strcpy(value, Info_ValueForKey (other->client->pers.userinfo, "skin"));
p = strchr(value, '/');
if (p)
{
if(!strcmp(dball_team1_skin->string, value))
team1++;
else if(!strcmp(dball_team2_skin->string, value))
team2++;
else
unassigned++;
}
else
unassigned++;
}
if(team1 > team2)
{
gi.dprintf("assigned to team 2\n");
Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team2_skin->string);
}
else
{
gi.dprintf("assigned to team 1\n");
Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team1_skin->string);
}
ClientUserinfoChanged(ent, ent->client->pers.userinfo);
if(unassigned)
gi.dprintf("%d unassigned players present!\n", unassigned);
}
//==================
//==================
void DBall_SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles)
{
edict_t *bestspot;
float bestdistance, bestplayerdistance;
edict_t *spot;
char *spottype;
char skin[512];
strcpy(skin, Info_ValueForKey (ent->client->pers.userinfo, "skin"));
if(!strcmp(dball_team1_skin->string, skin))
spottype = "dm_dball_team1_start";
else if(!strcmp(dball_team2_skin->string, skin))
spottype = "dm_dball_team2_start";
else
spottype = "info_player_deathmatch";
spot = NULL;
bestspot = NULL;
bestdistance = 0;
while ((spot = G_Find (spot, FOFS(classname), spottype)) != NULL)
{
bestplayerdistance = PlayersRangeFromSpot (spot);
if (bestplayerdistance > bestdistance)
{
bestspot = spot;
bestdistance = bestplayerdistance;
}
}
if (bestspot)
{
VectorCopy (bestspot->s.origin, origin);
origin[2] += 9;
VectorCopy (bestspot->s.angles, angles);
return;
}
// if we didn't find an appropriate spawnpoint, just
// call the standard one.
SelectSpawnPoint(ent, origin, angles);
}
//==================
//==================
void DBall_GameInit (void)
{
// we don't want a minimum speed for friction to take effect.
// this will allow any knockback to move stuff.
sv_stopspeed->value = 0;
dball_team1_goalscore = 0;
dball_team2_goalscore = 0;
dmflags->value = (int)dmflags->value | DF_NO_MINES | DF_NO_NUKES | DF_NO_STACK_DOUBLE |
DF_NO_FRIENDLY_FIRE | DF_SKINTEAMS;
dball_team1_skin = gi.cvar ("dball_team1_skin", "male/ctf_r", 0);
dball_team2_skin = gi.cvar ("dball_team2_skin", "male/ctf_b", 0);
goallimit = gi.cvar ("goallimit", "0", 0);
}
//==================
//==================
void DBall_PostInitSetup (void)
{
edict_t *e;
e=NULL;
// turn teleporter destinations nonsolid.
while(e = G_Find (e, FOFS(classname), "misc_teleporter_dest"))
{
e->solid = SOLID_NOT;
gi.linkentity (e);
}
// count the ball start points
dball_ball_startpt_count = 0;
e=NULL;
while(e = G_Find (e, FOFS(classname), "dm_dball_ball_start"))
{
dball_ball_startpt_count++;
}
if(dball_ball_startpt_count == 0)
gi.dprintf("No Deathball start points!\n");
}
//==================
// DBall_ChangeDamage - half damage between players. full if it involves
// the ball entity
//==================
int DBall_ChangeDamage (edict_t *targ, edict_t *attacker, int damage, int mod)
{
// cut player -> ball damage to 1
if (targ == dball_ball_entity)
return 1;
// damage player -> player is halved
if (attacker != dball_ball_entity)
return damage / 2;
return damage;
}
//==================
//==================
int DBall_ChangeKnockback (edict_t *targ, edict_t *attacker, int knockback, int mod)
{
if(targ != dball_ball_entity)
return knockback;
if(knockback < 1)
{
// FIXME - these don't account for quad/double
if(mod == MOD_ROCKET) // rocket
knockback = 70;
else if(mod == MOD_BFG_EFFECT) // bfg
knockback = 90;
else
gi.dprintf ("zero knockback, mod %d\n", mod);
}
else
{
// FIXME - change this to an array?
switch(mod)
{
case MOD_BLASTER:
knockback *= 3;
break;
case MOD_SHOTGUN:
knockback = (knockback * 3) / 8;
break;
case MOD_SSHOTGUN:
knockback = knockback / 3;
break;
case MOD_MACHINEGUN:
knockback = (knockback * 3) / 2;
break;
case MOD_HYPERBLASTER:
knockback *= 4;
break;
case MOD_GRENADE:
case MOD_HANDGRENADE:
case MOD_PROX:
case MOD_G_SPLASH:
case MOD_HG_SPLASH:
case MOD_HELD_GRENADE:
case MOD_TRACKER:
case MOD_DISINTEGRATOR:
knockback /= 2;
break;
case MOD_R_SPLASH:
knockback = (knockback * 3) / 2;
break;
case MOD_RAILGUN:
case MOD_HEATBEAM:
knockback /= 3;
break;
}
}
// gi.dprintf("mod: %d knockback: %d\n", mod, knockback);
return knockback;
}
// **************************
// Goals
// **************************
void DBall_GoalTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
int team_score;
int scorechange;
int j;
char value[512];
char *p;
edict_t *ent;
if(other != dball_ball_entity)
return;
self->health = self->max_health;
// determine which team scored, and bump the team score
if(self->spawnflags & DBALL_GOAL_TEAM1)
{
dball_team1_goalscore += self->wait;
team_score = 1;
}
else
{
dball_team2_goalscore += self->wait;
team_score = 2;
}
// bump the score for everyone on the correct team.
for (j = 1; j <= game.maxclients; j++)
{
ent = &g_edicts[j];
if (!ent->inuse)
continue;
if (!ent->client)
continue;
if (ent == other->enemy)
scorechange = self->wait + 5;
else
scorechange = self->wait;
strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin"));
p = strchr(value, '/');
if (p)
{
if(!strcmp(dball_team1_skin->string, value))
{
if(team_score == 1)
ent->client->resp.score += scorechange;
else if(other->enemy == ent)
ent->client->resp.score -= scorechange;
}
else if(!strcmp(dball_team2_skin->string, value))
{
if(team_score == 2)
ent->client->resp.score += scorechange;
else if(other->enemy == ent)
ent->client->resp.score -= scorechange;
}
else
gi.dprintf("unassigned player!!!!\n");
}
}
if(other->enemy)
gi.dprintf("score for team %d by %s\n", team_score, other->enemy->client->pers.netname);
else
gi.dprintf("score for team %d by someone\n", team_score);
DBall_BallDie (other, other->enemy, other->enemy, 0, vec3_origin);
G_UseTargets (self, other);
}
// **************************
// Ball
// **************************
edict_t *PickBallStart (edict_t *ent)
{
int which, current;
edict_t *e;
which = ceil(random() * dball_ball_startpt_count);
e = NULL;
current = 0;
while(e = G_Find (e, FOFS(classname), "dm_dball_ball_start"))
{
current++;
if(current == which)
return e;
}
if(current == 0)
gi.dprintf("No ball start points found!\n");
return G_Find(NULL, FOFS(classname), "dm_dball_ball_start");
}
//==================
// DBall_BallTouch - if the ball hit another player, hurt them
//==================
void DBall_BallTouch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
vec3_t dir;
float dot;
float speed;
if(other->takedamage == DAMAGE_NO)
return;
// hit a player
if(other->client)
{
if(ent->velocity[0] || ent->velocity[1] || ent->velocity[2])
{
speed = VectorLength(ent->velocity);
VectorSubtract(ent->s.origin, other->s.origin, dir);
dot = DotProduct(dir, ent->velocity);
if(dot > 0.7)
{
T_Damage (other, ent, ent, vec3_origin, ent->s.origin, vec3_origin,
speed/10, speed/10, 0, MOD_DBALL_CRUSH);
}
}
}
}
//==================
// DBall_BallPain
//==================
void DBall_BallPain (edict_t *self, edict_t *other, float kick, int damage)
{
self->enemy = other;
self->health = self->max_health;
// if(other->classname)
// gi.dprintf("hurt by %s -- %d\n", other->classname, self->health);
}
void DBall_BallDie (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
// do the splash effect
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_DBALL_GOAL);
gi.WritePosition (self->s.origin);
gi.multicast (self->s.origin, MULTICAST_PVS);
VectorClear(self->s.angles);
VectorClear(self->velocity);
VectorClear(self->avelocity);
// make it invisible and desolid until respawn time
self->solid = SOLID_NOT;
// self->s.modelindex = 0;
self->think = DBall_BallRespawn;
self->nextthink = level.time + 2;
gi.linkentity(self);
}
void DBall_BallRespawn (edict_t *self)
{
edict_t *start;
// do the splash effect
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_DBALL_GOAL);
gi.WritePosition (self->s.origin);
gi.multicast (self->s.origin, MULTICAST_PVS);
// move the ball and stop it
start = PickBallStart(self);
if(start)
{
VectorCopy(start->s.origin, self->s.origin);
VectorCopy(start->s.origin, self->s.old_origin);
}
VectorClear(self->s.angles);
VectorClear(self->velocity);
VectorClear(self->avelocity);
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex ("models/objects/dball/tris.md2");
self->s.event = EV_PLAYER_TELEPORT;
self->groundentity = NULL;
// kill anything at the destination
KillBox (self);
gi.linkentity (self);
}
// ************************
// SPEED CHANGES
// ************************
#define DBALL_SPEED_ONEWAY 1
void DBall_SpeedTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
float dot;
vec3_t vel;
if(other != dball_ball_entity)
return;
if(self->timestamp >= level.time)
return;
if(VectorLength(other->velocity) < 1)
return;
if(self->spawnflags & DBALL_SPEED_ONEWAY)
{
VectorCopy (other->velocity, vel);
VectorNormalize (vel);
dot = DotProduct (vel, self->movedir);
if(dot < 0.8)
return;
}
self->timestamp = level.time + self->delay;
VectorScale (other->velocity, self->speed, other->velocity);
}
// ************************
// SPAWN FUNCTIONS
// ************************
/*QUAKED dm_dball_ball (1 .5 .5) (-48 -48 -48) (48 48 48)
Deathball Ball
*/
void SP_dm_dball_ball (edict_t *self)
{
if(!(deathmatch->value))
{
G_FreeEdict(self);
return;
}
if(gamerules && (gamerules->value != RDM_DEATHBALL))
{
G_FreeEdict(self);
return;
}
dball_ball_entity = self;
// VectorCopy (self->s.origin, dball_ball_startpt);
self->s.modelindex = gi.modelindex ("models/objects/dball/tris.md2");
VectorSet (self->mins, -32, -32, -32);
VectorSet (self->maxs, 32, 32, 32);
self->solid = SOLID_BBOX;
self->movetype = MOVETYPE_NEWTOSS;
self->clipmask = MASK_MONSTERSOLID;
self->takedamage = DAMAGE_YES;
self->mass = 50;
self->health = 50000;
self->max_health = 50000;
self->pain = DBall_BallPain;
self->die = DBall_BallDie;
self->touch = DBall_BallTouch;
gi.linkentity (self);
}
/*QUAKED dm_dball_team1_start (1 .5 .5) (-16 -16 -24) (16 16 32)
Deathball team 1 start point
*/
void SP_dm_dball_team1_start (edict_t *self)
{
if (!deathmatch->value)
{
G_FreeEdict (self);
return;
}
if(gamerules && (gamerules->value != RDM_DEATHBALL))
{
G_FreeEdict(self);
return;
}
}
/*QUAKED dm_dball_team2_start (1 .5 .5) (-16 -16 -24) (16 16 32)
Deathball team 2 start point
*/
void SP_dm_dball_team2_start (edict_t *self)
{
if (!deathmatch->value)
{
G_FreeEdict (self);
return;
}
if(gamerules && (gamerules->value != RDM_DEATHBALL))
{
G_FreeEdict(self);
return;
}
}
/*QUAKED dm_dball_ball_start (1 .5 .5) (-48 -48 -48) (48 48 48)
Deathball ball start point
*/
void SP_dm_dball_ball_start (edict_t *self)
{
if (!deathmatch->value)
{
G_FreeEdict (self);
return;
}
if(gamerules && (gamerules->value != RDM_DEATHBALL))
{
G_FreeEdict(self);
return;
}
}
/*QUAKED dm_dball_speed_change (1 .5 .5) ? ONEWAY
Deathball ball speed changing field.
speed: multiplier for speed (.5 = half, 2 = double, etc) (default = double)
angle: used with ONEWAY so speed change is only one way.
delay: time between speed changes (default: 0.2 sec)
*/
void SP_dm_dball_speed_change (edict_t *self)
{
if (!deathmatch->value)
{
G_FreeEdict (self);
return;
}
if(gamerules && (gamerules->value != RDM_DEATHBALL))
{
G_FreeEdict(self);
return;
}
if(!self->speed)
self->speed = 2;
if(!self->delay)
self->delay = 0.2;
self->touch = DBall_SpeedTouch;
self->solid = SOLID_TRIGGER;
self->movetype = MOVETYPE_NONE;
self->svflags |= SVF_NOCLIENT;
if (!VectorCompare(self->s.angles, vec3_origin))
G_SetMovedir (self->s.angles, self->movedir);
else
VectorSet (self->movedir, 1, 0, 0);
gi.setmodel (self, self->model);
gi.linkentity (self);
}
/*QUAKED dm_dball_goal (1 .5 .5) ? TEAM1 TEAM2
Deathball goal
Team1/Team2 - beneficiary of this goal. when the ball enters this goal, the beneficiary team will score.
"wait": score to be given for this goal (default 10) player gets score+5.
*/
void SP_dm_dball_goal (edict_t *self)
{
if(!(deathmatch->value))
{
G_FreeEdict(self);
return;
}
if(gamerules && (gamerules->value != RDM_DEATHBALL))
{
G_FreeEdict(self);
return;
}
if(!self->wait)
self->wait = 10;
self->touch = DBall_GoalTouch;
self->solid = SOLID_TRIGGER;
self->movetype = MOVETYPE_NONE;
self->svflags |= SVF_NOCLIENT;
if (!VectorCompare(self->s.angles, vec3_origin))
G_SetMovedir (self->s.angles, self->movedir);
gi.setmodel (self, self->model);
gi.linkentity (self);
}

333
dm_tag.c Normal file
View file

@ -0,0 +1,333 @@
// dm_tag
// pmack
// june 1998
#include "g_local.h"
extern edict_t *SelectFarthestDeathmatchSpawnPoint (void);
extern void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles);
void SP_dm_tag_token (edict_t *self);
// ***********************
// Tag Specific Stuff
// ***********************
edict_t *tag_token;
edict_t *tag_owner;
int tag_count;
//=================
//=================
void Tag_PlayerDeath(edict_t *targ, edict_t *inflictor, edict_t *attacker)
{
// gi.dprintf("%s killed %s\n", attacker->classname, targ->classname);
// gi.dprintf("%x killed %x\n", attacker, targ);
if(tag_token && targ && (targ == tag_owner))
{
// gi.dprintf("owner died/suicided. dropping\n");
Tag_DropToken(targ, FindItem( "Tag Token" ));
tag_owner = NULL;
tag_count = 0;
}
// else
// gi.dprintf("unrelated slaying\n");
}
//=================
//=================
void Tag_KillItBonus (edict_t *self)
{
edict_t *armor;
// if the player is hurt, boost them up to max.
if(self->health < self->max_health)
{
self->health += 200;
if(self->health > self->max_health)
self->health = self->max_health;
}
// give the player a body armor
armor = G_Spawn();
armor->spawnflags |= DROPPED_ITEM;
armor->item = FindItem("Body Armor");
Touch_Item(armor, self, NULL, NULL);
if(armor->inuse)
G_FreeEdict(armor);
}
//=================
//=================
void Tag_PlayerDisconnect (edict_t *self)
{
if(tag_token && self && (self == tag_owner))
{
// gi.dprintf("owner died/suicided. dropping\n");
Tag_DropToken(self, FindItem( "Tag Token" ));
tag_owner = NULL;
tag_count = 0;
}
}
//=================
//=================
void Tag_Score (edict_t *attacker, edict_t *victim, int scoreChange)
{
gitem_t *quad;
int mod;
mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
// gi.dprintf("%s killed %s\n", attacker->classname, victim->classname);
// gi.dprintf("%x killed %x\n", attacker, victim);
if(tag_token && tag_owner)
{
// owner killed somone else
if((scoreChange > 0) && tag_owner == attacker)
{
scoreChange = 3;
tag_count++;
// gi.dprintf("tag total: %d\n", tag_count);
if(tag_count == 5)
{
// gi.dprintf("going to quad\n");
quad = FindItem ("Quad Damage");
attacker->client->pers.inventory[ITEM_INDEX(quad)]++;
quad->use (attacker, quad);
tag_count = 0;
}
}
// owner got killed. 5 points and switch owners
else if(tag_owner == victim && tag_owner != attacker)
{
// gi.dprintf("owner killed by another player.\n");
scoreChange = 5;
if ((mod == MOD_HUNTER_SPHERE) || (mod == MOD_DOPPLE_EXPLODE) ||
(mod == MOD_DOPPLE_VENGEANCE) || (mod == MOD_DOPPLE_HUNTER) ||
(attacker->health <= 0))
{
Tag_DropToken(tag_owner, FindItem( "Tag Token" ));
tag_owner = NULL;
tag_count = 0;
}
else
{
Tag_KillItBonus(attacker);
tag_owner = attacker;
tag_count = 0;
}
}
// else
// gi.dprintf("unaffected slaying\n");
}
// else
// gi.dprintf("no tag token?\n");
attacker->client->resp.score += scoreChange;
}
//=================
//=================
qboolean Tag_PickupToken (edict_t *ent, edict_t *other)
{
if(gamerules && (gamerules->value != 2))
{
return false;
}
// gi.dprintf("tag token picked up by %x\n", other);
// sanity checking is good.
if(tag_token != ent)
tag_token = ent;
other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
tag_owner = other;
tag_count = 0;
Tag_KillItBonus (other);
return true;
}
//=================
//=================
void Tag_Respawn (edict_t *ent)
{
edict_t *spot;
spot = SelectFarthestDeathmatchSpawnPoint();
if(spot == NULL)
{
// gi.dprintf("No open spawn point, waiting...\n");
ent->nextthink = level.time + 1;
return;
}
// gi.dprintf("Relocating\n");
VectorCopy(spot->s.origin, ent->s.origin);
gi.linkentity(ent);
}
//=================
//=================
void Tag_MakeTouchable (edict_t *ent)
{
ent->touch = Touch_Item;
tag_token->think = Tag_Respawn;
// check here to see if it's in lava or slime. if so, do a respawn sooner
if(gi.pointcontents(ent->s.origin) & (CONTENTS_LAVA|CONTENTS_SLIME))
{
// gi.dprintf("spawned in slime or lava. quick relocate\n");
tag_token->nextthink = level.time + 3;
}
else
{
// gi.dprintf("spawned in the clear. regular relocate\n");
tag_token->nextthink = level.time + 30;
}
}
//=================
//=================
void Tag_DropToken (edict_t *ent, gitem_t *item)
{
trace_t trace;
vec3_t forward, right;
vec3_t offset;
// if(ent->client)
// gi.dprintf("%s dropped the tag token\n", ent->client->pers.netname);
// else
// gi.dprintf("non-client dropped the tag token (%s)\n", ent->classname);
// reset the score count for next player
tag_count = 0;
tag_owner = NULL;
tag_token = G_Spawn();
tag_token->classname = item->classname;
tag_token->item = item;
tag_token->spawnflags = DROPPED_ITEM;
tag_token->s.effects = EF_ROTATE | EF_TAGTRAIL;
tag_token->s.renderfx = RF_GLOW;
VectorSet (tag_token->mins, -15, -15, -15);
VectorSet (tag_token->maxs, 15, 15, 15);
gi.setmodel (tag_token, tag_token->item->world_model);
tag_token->solid = SOLID_TRIGGER;
tag_token->movetype = MOVETYPE_TOSS;
tag_token->touch = NULL;
tag_token->owner = ent;
AngleVectors (ent->client->v_angle, forward, right, NULL);
VectorSet(offset, 24, 0, -16);
G_ProjectSource (ent->s.origin, offset, forward, right, tag_token->s.origin);
trace = gi.trace (ent->s.origin, tag_token->mins, tag_token->maxs,
tag_token->s.origin, ent, CONTENTS_SOLID);
VectorCopy (trace.endpos, tag_token->s.origin);
VectorScale (forward, 100, tag_token->velocity);
tag_token->velocity[2] = 300;
tag_token->think = Tag_MakeTouchable;
tag_token->nextthink = level.time + 1;
gi.linkentity (tag_token);
// tag_token = Drop_Item (ent, item);
ent->client->pers.inventory[ITEM_INDEX(item)]--;
ValidateSelectedItem (ent);
}
//=================
//=================
void Tag_PlayerEffects (edict_t *ent)
{
if(ent == tag_owner)
ent->s.effects |= EF_TAGTRAIL;
}
//=================
//=================
void Tag_DogTag (edict_t *ent, edict_t *killer, char **pic)
{
if(ent == tag_owner)
(*pic)="tag3";
}
//=================
// Tag_ChangeDamage - damage done that does not involve the tag owner
// is at 75% original to encourage folks to go after the tag owner.
//=================
int Tag_ChangeDamage (edict_t *targ, edict_t *attacker, int damage, int mod)
{
if((targ != tag_owner) && (attacker != tag_owner))
return (damage * 3 / 4);
return damage;
}
//=================
//=================
void Tag_GameInit (void)
{
tag_token = NULL;
tag_owner = NULL;
tag_count = 0;
}
//=================
//=================
void Tag_PostInitSetup (void)
{
edict_t *e;
vec3_t origin, angles;
// automatic spawning of tag token if one is not present on map.
e = G_Find (NULL, FOFS(classname), "dm_tag_token");
if(e == NULL)
{
e = G_Spawn();
e->classname = "dm_tag_token";
SelectSpawnPoint (e, origin, angles);
VectorCopy(origin, e->s.origin);
VectorCopy(origin, e->s.old_origin);
VectorCopy(angles, e->s.angles);
SP_dm_tag_token (e);
}
}
/*QUAKED dm_tag_token (.3 .3 1) (-16 -16 -16) (16 16 16)
The tag token for deathmatch tag games.
*/
void SP_dm_tag_token (edict_t *self)
{
if(!(deathmatch->value))
{
G_FreeEdict(self);
return;
}
if(gamerules && (gamerules->value != 2))
{
G_FreeEdict(self);
return;
}
// store the tag token edict pointer for later use.
tag_token = self;
tag_count = 0;
self->classname = "dm_tag_token";
self->model = "models/items/tagtoken/tris.md2";
self->count = 1;
SpawnItem (self, FindItem ("Tag Token"));
}

1596
g_ai.c Normal file

File diff suppressed because it is too large Load diff

156
g_chase.c Normal file
View file

@ -0,0 +1,156 @@
#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.");
}

1036
g_cmds.c Normal file

File diff suppressed because it is too large Load diff

947
g_combat.c Normal file
View file

@ -0,0 +1,947 @@
// g_combat.c
#include "g_local.h"
void M_SetEffects (edict_t *self);
/*
ROGUE
clean up heal targets for medic
*/
void cleanupHealTarget (edict_t *ent)
{
ent->monsterinfo.healer = NULL;
ent->takedamage = DAMAGE_YES;
ent->monsterinfo.aiflags &= ~AI_RESURRECTING;
M_SetEffects (ent);
}
/*
============
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;
if (targ->monsterinfo.aiflags & AI_MEDIC)
{
if (targ->enemy) // god, I hope so
{
cleanupHealTarget (targ->enemy);
}
// clean up self
targ->monsterinfo.aiflags &= ~AI_MEDIC;
targ->enemy = attacker;
}
else
{
targ->enemy = attacker;
}
if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
{
// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type
//ROGUE - free up slot for spawned monster if it's spawned
if (targ->monsterinfo.aiflags & AI_SPAWNED_CARRIER)
{
if (targ->monsterinfo.commander && targ->monsterinfo.commander->inuse &&
!strcmp(targ->monsterinfo.commander->classname, "monster_carrier"))
{
targ->monsterinfo.commander->monsterinfo.monster_slots++;
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("g_combat: freeing up carrier slot - %d left\n", targ->monsterinfo.commander->monsterinfo.monster_slots);
}
}
if (targ->monsterinfo.aiflags & AI_SPAWNED_MEDIC_C)
{
if (targ->monsterinfo.commander)
{
if (targ->monsterinfo.commander->inuse && !strcmp(targ->monsterinfo.commander->classname, "monster_medic_commander"))
{
targ->monsterinfo.commander->monsterinfo.monster_slots++;
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("g_combat: freeing up medic slot - %d left\n", targ->monsterinfo.commander->monsterinfo.monster_slots);
}
// else
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("my commander is dead! he's a %s\n", targ->monsterinfo.commander->classname);
}
// else if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("My commander is GONE\n");
}
if (targ->monsterinfo.aiflags & AI_SPAWNED_WIDOW)
{
// need to check this because we can have variable numbers of coop players
if (targ->monsterinfo.commander && targ->monsterinfo.commander->inuse &&
!strncmp(targ->monsterinfo.commander->classname, "monster_widow", 13))
{
if (targ->monsterinfo.commander->monsterinfo.monster_used > 0)
targ->monsterinfo.commander->monsterinfo.monster_used--;
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("g_combat: freeing up black widow slot - %d used\n", targ->monsterinfo.commander->monsterinfo.monster_used);
}
}
//rogue
if ((!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) && (!(targ->monsterinfo.aiflags & AI_DO_NOT_COUNT)))
{
level.killed_monsters++;
if (coop->value && attacker->client)
attacker->client->resp.score++;
// medics won't heal monsters that they kill themselves
// PMM - now they will
// 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 | DAMAGE_NO_POWER_ARMOR)) // PGM
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;
}
// etf rifle
if (dflags & DAMAGE_NO_REG_ARMOR)
save = (power * damagePerCell) / 2;
else
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;
if (dflags & DAMAGE_NO_REG_ARMOR)
power_used = (save / damagePerCell) * 2;
else
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;
// ROGUE - added DAMAGE_NO_REG_ARMOR for atf rifle
if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_REG_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, edict_t *inflictor)
{
// pmm
qboolean new_tesla;
if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
return;
//=======
//ROGUE
// logic for tesla - if you are hit by a tesla, and can't see who you should be mad at (attacker)
// attack the tesla
// also, target the tesla if it's a "new" tesla
if ((inflictor) && (!strcmp(inflictor->classname, "tesla")))
{
new_tesla = MarkTeslaArea(targ, inflictor);
if (new_tesla)
TargetTesla (targ, inflictor);
return;
// FIXME - just ignore teslas when you're TARGET_ANGER or MEDIC
/* if (!(targ->enemy && (targ->monsterinfo.aiflags & (AI_TARGET_ANGER|AI_MEDIC))))
{
// FIXME - coop issues?
if ((!targ->enemy) || (!visible(targ, targ->enemy)))
{
gi.dprintf ("can't see player, switching to tesla\n");
TargetTesla (targ, inflictor);
return;
}
gi.dprintf ("can see player, ignoring tesla\n");
}
else if ((g_showlogic) && (g_showlogic->value))
gi.dprintf ("no enemy, or I'm doing other, more important things, than worrying about a damned tesla!\n");
*/
}
//ROGUE
//=======
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;
}
//PGM
// if we're currently mad at something a target_anger made us mad at, ignore
// damage
if (targ->enemy && targ->monsterinfo.aiflags & AI_TARGET_ANGER)
{
float percentHealth;
// make sure whatever we were pissed at is still around.
if(targ->enemy->inuse)
{
percentHealth = (float)(targ->health) / (float)(targ->max_health);
if( targ->enemy->inuse && percentHealth > 0.33)
return;
}
// remove the target anger flag
targ->monsterinfo.aiflags &= ~AI_TARGET_ANGER;
}
//PGM
// PMM
// if we're healing someone, do like above and try to stay with them
if ((targ->enemy) && (targ->monsterinfo.aiflags & AI_MEDIC))
{
float percentHealth;
percentHealth = (float)(targ->health) / (float)(targ->max_health);
// ignore it some of the time
if( targ->enemy->inuse && percentHealth > 0.25)
return;
// remove the medic flag
targ->monsterinfo.aiflags &= ~AI_MEDIC;
cleanupHealTarget (targ->enemy);
}
// PMM
// 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
// PMM
// added medics to this
// FIXME -
// this really should be turned into an AI flag marking appropriate monsters as "don't shoot me"
// this also leads to the problem of tanks and medics being able to, at will, kill monsters with
// no chance of retaliation. My vote is to make those monsters who are designed as "don't shoot me"
// such that they also ignore being shot by monsters as well
/*
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) &&
(strcmp(attacker->classname, "monster_carrier") != 0) &&
(strncmp(attacker->classname, "monster_medic", 12) != 0) ) // this should get medics & medic_commanders
*/
if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) &&
(strcmp (targ->classname, attacker->classname) != 0) &&
!(attacker->monsterinfo.aiflags & AI_IGNORE_SHOTS) &&
!(targ->monsterinfo.aiflags & AI_IGNORE_SHOTS) )
{
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;
int sphere_notified; // PGM
if (!targ->takedamage)
return;
sphere_notified = false; // PGM
// 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))
{
// PMM - nukes kill everyone
if (((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) && (mod != MOD_NUKE))
damage = 0;
else
mod |= MOD_FRIENDLY_FIRE;
}
}
meansOfDeath = mod;
//ROGUE
// allow the deathmatch game to change values
if (deathmatch->value && gamerules && gamerules->value)
{
if(DMGame.ChangeDamage)
damage = DMGame.ChangeDamage(targ, attacker, damage, mod);
if(DMGame.ChangeKnockback)
knockback = DMGame.ChangeKnockback(targ, attacker, knockback, mod);
if(!damage)
return;
}
//ROGUE
// easy mode takes half damage
if (skill->value == 0 && deathmatch->value == 0 && targ->client)
{
damage *= 0.5;
if (!damage)
damage = 1;
}
client = targ->client;
// PMM - defender sphere takes half damage
if ((client) && (client->owned_sphere) && (client->owned_sphere->spawnflags == 1))
{
damage *= 0.5;
if (!damage)
damage = 1;
}
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;
}
// ROGUE
// check for monster invincibility
if (((targ->svflags & SVF_MONSTER) && targ->monsterinfo.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;
}
// ROGUE
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;
// ROGUE - this option will do damage both to the armor and person. originally for DPU rounds
if (dflags & DAMAGE_DESTROY_ARMOR)
{
if(!(targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) &&
!(client && client->invincible_framenum > level.framenum))
{
take = damage;
}
}
// ROGUE
// do the damage
if (take)
{
//PGM need more blood for chainfist.
if(targ->flags & FL_MECHANICAL)
{
SpawnDamage ( TE_ELECTRIC_SPARKS, point, normal, take);
}
else if ((targ->svflags & SVF_MONSTER) || (client))
{
if(mod == MOD_CHAINFIST)
SpawnDamage (TE_MOREBLOOD, point, normal, 255);
else
SpawnDamage (TE_BLOOD, point, normal, take);
}
else
SpawnDamage (te_sparks, point, normal, take);
//PGM
targ->health = targ->health - take;
//PGM - spheres need to know who to shoot at
if(client && client->owned_sphere)
{
sphere_notified = true;
if(client->owned_sphere->pain)
client->owned_sphere->pain (client->owned_sphere, attacker, 0, 0);
}
//PGM
if (targ->health <= 0)
{
if ((targ->svflags & SVF_MONSTER) || (client))
targ->flags |= FL_NO_KNOCKBACK;
Killed (targ, inflictor, attacker, take, point);
return;
}
}
//PGM - spheres need to know who to shoot at
if (!sphere_notified)
{
if(client && client->owned_sphere)
{
sphere_notified = true;
if(client->owned_sphere->pain)
client->owned_sphere->pain (client->owned_sphere, attacker, 0, 0);
}
}
//PGM
if (targ->svflags & SVF_MONSTER)
{
M_ReactToDamage (targ, attacker, inflictor);
// PMM - fixme - if anyone else but the medic ever uses AI_MEDIC, check for it here instead
// of in the medic's pain function
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);
}
}
}
}
// **********************
// ROGUE
/*
============
T_RadiusNukeDamage
Like T_RadiusDamage, but ignores walls (skips CanDamage check, among others)
// up to KILLZONE radius, do 10,000 points
// after that, do damage linearly out to KILLZONE2 radius
============
*/
void T_RadiusNukeDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod)
{
float points;
edict_t *ent = NULL;
vec3_t v;
vec3_t dir;
float len;
float killzone, killzone2;
trace_t tr;
float dist;
killzone = radius;
killzone2 = radius*2.0;
while ((ent = findradius(ent, inflictor->s.origin, killzone2)) != NULL)
{
// ignore nobody
if (ent == ignore)
continue;
if (!ent->takedamage)
continue;
if (!ent->inuse)
continue;
if (!(ent->client || (ent->svflags & SVF_MONSTER) || (ent->svflags & SVF_DAMAGEABLE)))
continue;
VectorAdd (ent->mins, ent->maxs, v);
VectorMA (ent->s.origin, 0.5, v, v);
VectorSubtract (inflictor->s.origin, v, v);
len = VectorLength(v);
if (len <= killzone)
{
if (ent->client)
ent->flags |= FL_NOGIB;
points = 10000;
}
else if (len <= killzone2)
points = (damage/killzone)*(killzone2 - len);
else
points = 0;
// points = damage - 0.005 * len*len;
// if (ent == attacker)
// points = points * 0.5;
// if ((g_showlogic) && (g_showlogic->value))
// {
// if (!(strcmp(ent->classname, "player")))
// gi.dprintf ("dist = %2.2f doing %6.0f damage to %s\n", len, points, inflictor->teammaster->client->pers.netname);
// else
// gi.dprintf ("dist = %2.2f doing %6.0f damage to %s\n", len, points, ent->classname);
// }
if (points > 0)
{
// if (CanDamage (ent, inflictor))
// {
if (ent->client)
ent->client->nuke_framenum = level.framenum + 20;
VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
// }
}
}
ent = g_edicts+1; // skip the worldspawn
// cycle through players
while (ent)
{
if ((ent->client) && (ent->client->nuke_framenum != level.framenum+20) && (ent->inuse))
{
tr = gi.trace (inflictor->s.origin, NULL, NULL, ent->s.origin, inflictor, MASK_SOLID);
if (tr.fraction == 1.0)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("Undamaged player in LOS with nuke, flashing!\n");
ent->client->nuke_framenum = level.framenum + 20;
}
else
{
dist = realrange (ent, inflictor);
if (dist < 2048)
ent->client->nuke_framenum = max(ent->client->nuke_framenum,level.framenum + 15);
else
ent->client->nuke_framenum = max(ent->client->nuke_framenum,level.framenum + 10);
}
ent++;
}
else
ent = NULL;
}
}
/*
============
T_RadiusClassDamage
Like T_RadiusDamage, but ignores anything with classname=ignoreClass
============
*/
void T_RadiusClassDamage (edict_t *inflictor, edict_t *attacker, float damage, char *ignoreClass, float radius, int mod)
{
float points;
edict_t *ent = NULL;
vec3_t v;
vec3_t dir;
while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
{
if (ent->classname && !strcmp(ent->classname, ignoreClass))
continue;
if (!ent->takedamage)
continue;
VectorAdd (ent->mins, ent->maxs, v);
VectorMA (ent->s.origin, 0.5, v, v);
VectorSubtract (inflictor->s.origin, v, v);
points = damage - 0.5 * VectorLength (v);
if (ent == attacker)
points = points * 0.5;
if (points > 0)
{
if (CanDamage (ent, inflictor))
{
VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
}
}
}
}
// ROGUE
// ********************

2825
g_func.c Normal file

File diff suppressed because it is too large Load diff

3209
g_items.c Normal file

File diff suppressed because it is too large Load diff

1453
g_local.h Normal file

File diff suppressed because it is too large Load diff

412
g_main.c Normal file
View file

@ -0,0 +1,412 @@
#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;
cvar_t *sv_stopspeed; //PGM (this was a define in g_phys.c)
//ROGUE cvars
cvar_t *g_showlogic;
cvar_t *gamerules;
cvar_t *huntercam;
cvar_t *strong_mines;
cvar_t *randomrespawn;
//ROGUE
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;
//=======
//ROGUE
if (gamerules && gamerules->value && DMGame.CheckDMRules)
{
if(DMGame.CheckDMRules())
return;
}
//ROGUE
//=======
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 ();
}

1999
g_misc.c Normal file

File diff suppressed because it is too large Load diff

979
g_monster.c Normal file
View file

@ -0,0 +1,979 @@
#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);
}
//ROGUE
void monster_fire_blaster2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
{
fire_blaster2 (self, start, dir, damage, speed, effect, false);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
// FIXME -- add muzzle flash
void monster_fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy, int flashtype)
{
fire_tracker (self, start, dir, damage, speed, enemy);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_heat (edict_t *self, vec3_t start, vec3_t dir, vec3_t offset, int damage, int kick, int flashtype)
{
fire_heat (self, start, dir, offset, damage, kick, true);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
//ROGUE
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)
{
// PMM
if (!(gi.pointcontents (start) & MASK_SOLID))
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
//
void M_FliesOff (edict_t *self)
{
self->s.effects &= ~EF_FLIES;
self->s.sound = 0;
}
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;
#ifdef ROGUE_GRAVITY
if ((ent->velocity[2] * ent->gravityVector[2]) < -100) // PGM
#else
if (ent->velocity[2] > 100)
#endif
{
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];
#ifdef ROGUE_GRAVITY
point[2] = ent->s.origin[2] + (0.25 * ent->gravityVector[2]); //PGM
#else
point[2] = ent->s.origin[2] - 0.25;
#endif
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID);
// check steepness
#ifdef ROGUE_GRAVITY
//PGM
if ( ent->gravityVector[2] < 0) // normal gravity
{
if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
{
ent->groundentity = NULL;
return;
}
}
else // inverted gravity
{
if ( trace.plane.normal[2] > -0.7 && !trace.startsolid)
{
ent->groundentity = NULL;
return;
}
}
//PGM
#else
if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
{
ent->groundentity = NULL;
return;
}
#endif
// 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;
#ifdef ROGUE_GRAVITY
//PGM
if(ent->gravityVector[2] < 0)
{
ent->s.origin[2] += 1;
VectorCopy (ent->s.origin, end);
end[2] -= 256;
}
else
{
ent->s.origin[2] -= 1;
VectorCopy (ent->s.origin, end);
end[2] += 256;
}
//PGM
#else
ent->s.origin[2] += 1;
VectorCopy (ent->s.origin, end);
end[2] -= 256;
#endif
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)
{
int remaining;
ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN|EF_DOUBLE|EF_QUAD|EF_PENT);
ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE|RF_SHELL_DOUBLE);
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;
}
}
// PMM - new monster powerups
if (ent->monsterinfo.quad_framenum > level.framenum)
{
remaining = ent->monsterinfo.quad_framenum - level.framenum;
if (remaining > 30 || (remaining & 4) )
ent->s.effects |= EF_QUAD;
}
else
ent->s.effects &= ~EF_QUAD;
if (ent->monsterinfo.double_framenum > level.framenum)
{
remaining = ent->monsterinfo.double_framenum - level.framenum;
if (remaining > 30 || (remaining & 4) )
ent->s.effects |= EF_DOUBLE;
}
else
ent->s.effects &= ~EF_DOUBLE;
if (ent->monsterinfo.invincible_framenum > level.framenum)
{
remaining = ent->monsterinfo.invincible_framenum - level.framenum;
if (remaining > 30 || (remaining & 4) )
ent->s.effects |= EF_PENT;
}
else
ent->s.effects &= ~EF_PENT;
// PMM
// PMM - testing
// ent->s.effects |= EF_COLOR_SHELL;
// ent->s.renderfx |= RF_SHELL_HALF_DAM;
/*
if (fmod (level.time, 4.0) > 2.0)
{
gi.dprintf ("invulnerable ");
ent->s.renderfx |= RF_SHELL_RED;
}
else
ent->s.renderfx &= ~RF_SHELL_RED;
if (fmod (level.time, 8.0) > 4.0)
{
gi.dprintf ("shield ");
ent->s.renderfx |= RF_SHELL_GREEN;
}
else
ent->s.renderfx &= ~RF_SHELL_GREEN;
if (fmod (level.time, 16.0) > 8.0)
{
gi.dprintf ("quad ");
ent->s.renderfx |= RF_SHELL_BLUE;\
}
else
ent->s.renderfx &= ~RF_SHELL_BLUE;
if (fmod (level.time, 32.0) > 16.0)
{
gi.dprintf ("double ");
ent->s.renderfx |= RF_SHELL_DOUBLE;
}
else
ent->s.renderfx &= ~RF_SHELL_DOUBLE;
if (fmod (level.time, 64.0) > 32.0)
{
gi.dprintf ("half ");
ent->s.renderfx |= RF_SHELL_HALF_DAM;
}
else
ent->s.renderfx &= ~RF_SHELL_HALF_DAM;
gi.dprintf ("\n");
*/
}
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;
if (activator->flags & FL_DISGUISED) // PGM
return; // PGM
// 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))
{
if(!(self->enemy->flags & FL_DISGUISED)) // PGM
FoundTarget (self);
else // PMM - just in case, make sure to clear the enemy so FindTarget doesn't get confused
self->enemy = NULL;
}
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)) && (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT)))
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));
// PMM - get this so I don't have to do it in all of the monsters
self->monsterinfo.base_height = self->maxs[2];
// PMM - clear these
self->monsterinfo.quad_framenum = 0;
self->monsterinfo.double_framenum = 0;
self->monsterinfo.invincible_framenum = 0;
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;
// PMM - stalkers are too short for this
if (!(strcmp(self->classname, "monster_stalker")))
self->viewheight = 15;
else
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);
}
//ROGUE
void stationarymonster_start_go (edict_t *self);
void stationarymonster_triggered_spawn (edict_t *self)
{
KillBox (self);
self->solid = SOLID_BBOX;
self->movetype = MOVETYPE_NONE;
self->svflags &= ~SVF_NOCLIENT;
self->air_finished = level.time + 12;
gi.linkentity (self);
// FIXME - why doesn't this happen with real monsters?
self->spawnflags &= ~2;
stationarymonster_start_go (self);
if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
{
if(!(self->enemy->flags & FL_DISGUISED)) // PGM
FoundTarget (self);
else // PMM - just in case, make sure to clear the enemy so FindTarget doesn't get confused
self->enemy = NULL;
}
else
{
self->enemy = NULL;
}
}
void stationarymonster_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 = stationarymonster_triggered_spawn;
self->nextthink = level.time + FRAMETIME;
if (activator->client)
self->enemy = activator;
self->use = monster_use;
}
void stationarymonster_triggered_start (edict_t *self)
{
self->solid = SOLID_NOT;
self->movetype = MOVETYPE_NONE;
self->svflags |= SVF_NOCLIENT;
self->nextthink = 0;
self->use = stationarymonster_triggered_spawn_use;
}
void stationarymonster_start_go (edict_t *self)
{
// PGM - only turrets use this, so remove the error message. They're supposed to be in solid.
// 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)
stationarymonster_triggered_start (self);
}
void stationarymonster_start (edict_t *self)
{
self->think = stationarymonster_start_go;
monster_start (self);
}
void monster_done_dodge (edict_t *self)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("%s done dodging\n", self->classname);
self->monsterinfo.aiflags &= ~AI_DODGING;
}
//ROGUE

1998
g_newai.c Normal file

File diff suppressed because it is too large Load diff

384
g_newdm.c Normal file
View file

@ -0,0 +1,384 @@
// g_newdm.c
// pmack
// june 1998
#include "g_local.h"
#include "m_player.h"
dm_game_rt DMGame;
// ****************************
// General DM Stuff
// ****************************
void InitGameRules(void)
{
int gameNum;
// clear out the game rule structure before we start
memset(&DMGame, 0, sizeof(dm_game_rt));
if(gamerules && gamerules->value)
{
gameNum = gamerules->value;
switch(gameNum)
{
case RDM_TAG:
DMGame.GameInit = Tag_GameInit;
DMGame.PostInitSetup = Tag_PostInitSetup;
DMGame.PlayerDeath = Tag_PlayerDeath;
DMGame.Score = Tag_Score;
DMGame.PlayerEffects = Tag_PlayerEffects;
DMGame.DogTag = Tag_DogTag;
DMGame.PlayerDisconnect = Tag_PlayerDisconnect;
DMGame.ChangeDamage = Tag_ChangeDamage;
break;
/*
case RDM_DEATHBALL:
DMGame.GameInit = DBall_GameInit;
DMGame.ChangeKnockback = DBall_ChangeKnockback;
DMGame.ChangeDamage = DBall_ChangeDamage;
DMGame.ClientBegin = DBall_ClientBegin;
DMGame.SelectSpawnPoint = DBall_SelectSpawnPoint;
DMGame.PostInitSetup = DBall_PostInitSetup;
DMGame.CheckDMRules = DBall_CheckDMRules;
break;
*/
// reset gamerules if it's not a valid number
default:
gamerules->value = 0;
break;
}
}
// if we're set up to play, initialize the game as needed.
if(DMGame.GameInit)
DMGame.GameInit();
}
//=================
//=================
#define IT_TYPE_MASK (IT_WEAPON|IT_AMMO|IT_POWERUP|IT_ARMOR|IT_KEY)
extern void ED_CallSpawn (edict_t *ent);
extern qboolean Pickup_Health (edict_t *ent, edict_t *other);
extern qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other);
extern qboolean Pickup_Armor (edict_t *ent, edict_t *other);
extern qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other);
char *FindSubstituteItem (edict_t *ent)
{
int i;
int itflags, myflags;
float rnd;
int count;
int pick;
gitem_t *it;
// there are only two classes of power armor, and we don't want
// to give out power screens. therefore, power shields should
// remain power shields. (powerscreens shouldn't be there at all...)
if (ent->item->pickup == Pickup_PowerArmor)
return NULL;
// health is special case
if ((ent->item->pickup == Pickup_Health) || (ent->item->pickup == Pickup_Adrenaline))
{
// health pellets stay health pellets
if(!strcmp(ent->classname, "item_health_small"))
return NULL;
rnd = random();
if(rnd < 0.6)
return "item_health";
else if(rnd < 0.9)
return "item_health_large";
else if(rnd < 0.99)
return "item_adrenaline";
else
return "item_health_mega";
}
// armor is also special case
else if(ent->item->pickup == Pickup_Armor)
{
// armor shards stay armor shards
if (ent->item->tag == ARMOR_SHARD)
return NULL;
rnd = random();
if(rnd < 0.6)
return "item_armor_jacket";
else if(rnd < 0.9)
return "item_armor_combat";
else
return "item_armor_body";
}
// we want to stay within the item class
myflags = ent->item->flags & IT_TYPE_MASK;
if ((myflags & IT_AMMO) && (myflags & IT_WEAPON))
myflags = IT_AMMO;
count = 0;
// first pass, count the matching items
it = itemlist;
for (i=0 ; i<game.num_items ; i++, it++)
{
itflags = it->flags;
if (!itflags || (itflags & IT_NOT_GIVEABLE))
continue;
// prox,grenades,etc should count as ammo.
if ((itflags & IT_AMMO) && (itflags & IT_WEAPON))
itflags = IT_AMMO;
// don't respawn spheres if they're dmflag disabled.
if ( (int)dmflags->value & DF_NO_SPHERES )
{
if (!strcmp (ent->classname, "item_sphere_vengeance") ||
!strcmp (ent->classname, "item_sphere_hunter") ||
!strcmp (ent->classname, "item_spehre_defender"))
{
continue;
}
}
if ( ((int)dmflags->value & DF_NO_NUKES) && !strcmp(ent->classname, "ammo_nuke") )
continue;
if ( ((int)dmflags->value & DF_NO_MINES) &&
(!strcmp(ent->classname, "ammo_prox") || !strcmp(ent->classname, "ammo_tesla")))
continue;
if ((itflags & IT_TYPE_MASK) == (myflags & IT_TYPE_MASK))
count++;
}
if(!count)
return NULL;
pick = ceil(random() * count);
count = 0;
// second pass, pick one.
it = itemlist;
for (i=0 ; i<game.num_items ; i++, it++)
{
itflags = it->flags;
if (!itflags || (itflags & IT_NOT_GIVEABLE))
continue;
// prox,grenades,etc should count as ammo.
if ((itflags & IT_AMMO) && (itflags & IT_WEAPON))
itflags = IT_AMMO;
if ( ((int)dmflags->value & DF_NO_NUKES) && !strcmp(ent->classname, "ammo_nuke") )
continue;
if ( ((int)dmflags->value & DF_NO_MINES) &&
(!strcmp(ent->classname, "ammo_prox") || !strcmp(ent->classname, "ammo_tesla")))
continue;
if ((itflags & IT_TYPE_MASK) == (myflags & IT_TYPE_MASK))
{
count++;
if(pick == count)
return it->classname;
}
}
return NULL;
}
//=================
//=================
edict_t *DoRandomRespawn (edict_t *ent)
{
edict_t *newEnt;
char *classname;
classname = FindSubstituteItem (ent);
if (classname == NULL)
return NULL;
gi.unlinkentity (ent);
newEnt = G_Spawn();
newEnt->classname = classname;
VectorCopy (ent->s.origin, newEnt->s.origin);
VectorCopy (ent->s.old_origin, newEnt->s.old_origin);
VectorCopy (ent->mins, newEnt->mins);
VectorCopy (ent->maxs, newEnt->maxs);
VectorSet (newEnt->gravityVector, 0, 0, -1);
ED_CallSpawn (newEnt);
newEnt->s.renderfx |= RF_IR_VISIBLE;
return newEnt;
}
//=================
//=================
void PrecacheForRandomRespawn (void)
{
gitem_t *it;
int i;
int itflags;
it = itemlist;
for (i=0 ; i<game.num_items ; i++, it++)
{
itflags = it->flags;
if (!itflags || (itflags & IT_NOT_GIVEABLE))
continue;
PrecacheItem(it);
}
}
// ***************************
// DOPPLEGANGER
// ***************************
extern edict_t *Sphere_Spawn (edict_t *owner, int spawnflags);
void fire_doppleganger (edict_t *ent, vec3_t start, vec3_t aimdir);
void doppleganger_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
void doppleganger_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
edict_t *sphere;
float dist;
vec3_t dir;
if((self->enemy) && (self->enemy != self->teammaster))
{
VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
dist = VectorLength(dir);
if(dist > 768)
{
sphere = Sphere_Spawn (self, SPHERE_HUNTER | SPHERE_DOPPLEGANGER);
sphere->pain(sphere, attacker, 0, 0);
}
else //if(dist > 256)
{
sphere = Sphere_Spawn (self, SPHERE_VENGEANCE | SPHERE_DOPPLEGANGER);
sphere->pain(sphere, attacker, 0, 0);
}
// else
// {
// T_RadiusClassDamage (self, self->teammaster, 175, "doppleganger", 384, MOD_DOPPLE_EXPLODE);
// }
}
if(self->teamchain)
BecomeExplosion1(self->teamchain);
BecomeExplosion1(self);
}
void doppleganger_pain (edict_t *self, edict_t *other, float kick, int damage)
{
self->enemy = other;
}
void doppleganger_timeout (edict_t *self)
{
// T_RadiusClassDamage (self, self->teammaster, 140, "doppleganger", 256, MOD_DOPPLE_EXPLODE);
if(self->teamchain)
BecomeExplosion1(self->teamchain);
BecomeExplosion1(self);
}
void body_think (edict_t *self)
{
float r;
if(abs(self->ideal_yaw - anglemod(self->s.angles[YAW])) < 2)
{
if(self->timestamp < level.time)
{
r = random();
if(r < 0.10)
{
self->ideal_yaw = random() * 350.0;
self->timestamp = level.time + 1;
}
}
}
else
M_ChangeYaw(self);
self->s.frame ++;
if (self->s.frame > FRAME_stand40)
self->s.frame = FRAME_stand01;
self->nextthink = level.time + 0.1;
}
void fire_doppleganger (edict_t *ent, vec3_t start, vec3_t aimdir)
{
edict_t *base;
edict_t *body;
vec3_t dir;
vec3_t forward, right, up;
int number;
vectoangles2 (aimdir, dir);
AngleVectors (dir, forward, right, up);
base = G_Spawn();
VectorCopy (start, base->s.origin);
VectorCopy (dir, base->s.angles);
VectorClear (base->velocity);
VectorClear (base->avelocity);
base->movetype = MOVETYPE_TOSS;
base->solid = SOLID_BBOX;
base->s.renderfx |= RF_IR_VISIBLE;
base->s.angles[PITCH]=0;
VectorSet (base->mins, -16, -16, -24);
VectorSet (base->maxs, 16, 16, 32);
// base->s.modelindex = gi.modelindex ("models/objects/dopplebase/tris.md2");
base->s.modelindex = 0;
base->teammaster = ent;
base->svflags |= SVF_DAMAGEABLE;
base->takedamage = DAMAGE_AIM;
base->health = 30;
base->pain = doppleganger_pain;
base->die = doppleganger_die;
// FIXME - remove with style
base->nextthink = level.time + 30;
base->think = doppleganger_timeout;
base->classname = "doppleganger";
gi.linkentity (base);
body = G_Spawn();
number = body->s.number;
body->s = ent->s;
body->s.sound = 0;
body->s.event = 0;
// body->s.modelindex2 = 0; // no attached items (CTF flag, etc)
body->s.number = number;
body->yaw_speed = 30;
body->ideal_yaw = 0;
VectorCopy (start, body->s.origin);
body->s.origin[2] += 8;
body->think = body_think;
body->nextthink = level.time + FRAMETIME;
gi.linkentity (body);
base->teamchain = body;
body->teammaster = base;
}

347
g_newfnc.c Normal file
View file

@ -0,0 +1,347 @@
#include "g_local.h"
//void plat_CalcMove (edict_t *ent, vec3_t dest, void(*func)(edict_t*));
void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*));
void fd_secret_move1(edict_t *self);
void fd_secret_move2(edict_t *self);
void fd_secret_move3(edict_t *self);
void fd_secret_move4(edict_t *self);
void fd_secret_move5(edict_t *self);
void fd_secret_move6(edict_t *self);
void fd_secret_done(edict_t *self);
/*
=============================================================================
SECRET DOORS
=============================================================================
*/
#define SEC_OPEN_ONCE 1 // stays open
#define SEC_1ST_LEFT 2 // 1st move is left of arrow
#define SEC_1ST_DOWN 4 // 1st move is down from arrow
#define SEC_NO_SHOOT 8 // only opened by trigger
#define SEC_YES_SHOOT 16 // shootable even if targeted
#define SEC_MOVE_RIGHT 32
#define SEC_MOVE_FORWARD 64
void fd_secret_use (edict_t *self, edict_t *other, edict_t *activator)
{
edict_t *ent;
// gi.dprintf("fd_secret_use\n");
if (self->flags & FL_TEAMSLAVE)
return;
// trigger all paired doors
for (ent = self ; ent ; ent = ent->teamchain)
Move_Calc(ent, ent->moveinfo.start_origin, fd_secret_move1);
}
void fd_secret_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
// gi.dprintf("fd_secret_killed\n");
self->health = self->max_health;
self->takedamage = DAMAGE_NO;
if (self->flags & FL_TEAMSLAVE && self->teammaster && self->teammaster->takedamage != DAMAGE_NO)
fd_secret_killed (self->teammaster, inflictor, attacker, damage, point);
else
fd_secret_use (self, inflictor, attacker);
}
// Wait after first movement...
void fd_secret_move1(edict_t *self)
{
// gi.dprintf("fd_secret_move1\n");
self->nextthink = level.time + 1.0;
self->think = fd_secret_move2;
}
// Start moving sideways w/sound...
void fd_secret_move2(edict_t *self)
{
// gi.dprintf("fd_secret_move2\n");
Move_Calc(self, self->moveinfo.end_origin, fd_secret_move3);
}
// Wait here until time to go back...
void fd_secret_move3(edict_t *self)
{
// gi.dprintf("fd_secret_move3\n");
if (!(self->spawnflags & SEC_OPEN_ONCE))
{
self->nextthink = level.time + self->wait;
self->think = fd_secret_move4;
}
}
// Move backward...
void fd_secret_move4(edict_t *self)
{
// gi.dprintf("fd_secret_move4\n");
Move_Calc(self, self->moveinfo.start_origin, fd_secret_move5);
}
// Wait 1 second...
void fd_secret_move5(edict_t *self)
{
// gi.dprintf("fd_secret_move5\n");
self->nextthink = level.time + 1.0;
self->think = fd_secret_move6;
}
void fd_secret_move6(edict_t *self)
{
// gi.dprintf("fd_secret_move6\n");
Move_Calc(self, self->move_origin, fd_secret_done);
}
void fd_secret_done(edict_t *self)
{
// gi.dprintf("fd_secret_done\n");
if (!self->targetname || self->spawnflags & SEC_YES_SHOOT)
{
self->health = 1;
self->takedamage = DAMAGE_YES;
self->die = fd_secret_killed;
}
}
void secret_blocked(edict_t *self, edict_t *other)
{
if (!(self->flags & FL_TEAMSLAVE))
T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 0, 0, MOD_CRUSH);
// if (time < self->attack_finished)
// return;
// self->attack_finished = time + 0.5;
// T_Damage (other, self, self, self->dmg);
}
/*
================
secret_touch
Prints messages
================
*/
void secret_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
if (other->health <= 0)
return;
if (!(other->client))
return;
if (self->monsterinfo.attack_finished > level.time)
return;
self->monsterinfo.attack_finished = level.time + 2;
if (self->message)
{
gi.centerprintf (other, self->message);
// fixme - put this sound back??
// gi.sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
}
}
/*QUAKED func_door_secret2 (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot slide_right slide_forward
Basic secret door. Slides back, then to the left. Angle determines direction.
FLAGS:
open_once = not implemented yet
1st_left = 1st move is left/right of arrow
1st_down = 1st move is forwards/backwards
no_shoot = not implemented yet
always_shoot = even if targeted, keep shootable
reverse_left = the sideways move will be to right of arrow
reverse_back = the to/fro move will be forward
VALUES:
wait = # of seconds before coming back (5 default)
dmg = damage to inflict when blocked (2 default)
*/
void SP_func_door_secret2 (edict_t *ent)
{
vec3_t forward,right,up;
float lrSize, fbSize;
ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav");
ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav");
ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav");
if (!ent->dmg)
ent->dmg = 2;
AngleVectors(ent->s.angles, forward, right, up);
VectorCopy(ent->s.origin, ent->move_origin);
VectorCopy(ent->s.angles, ent->move_angles);
G_SetMovedir (ent->s.angles, ent->movedir);
ent->movetype = MOVETYPE_PUSH;
ent->solid = SOLID_BSP;
gi.setmodel (ent, ent->model);
if(ent->move_angles[1] == 0 || ent->move_angles[1] == 180)
{
lrSize = ent->size[1];
fbSize = ent->size[0];
}
else if(ent->move_angles[1] == 90 || ent->move_angles[1] == 270)
{
lrSize = ent->size[0];
fbSize = ent->size[1];
}
else
{
gi.dprintf("Secret door not at 0,90,180,270!\n");
}
if(ent->spawnflags & SEC_MOVE_FORWARD)
VectorScale(forward, fbSize, forward);
else
{
VectorScale(forward, fbSize * -1 , forward);
}
if(ent->spawnflags & SEC_MOVE_RIGHT)
VectorScale(right, lrSize, right);
else
{
VectorScale(right, lrSize * -1, right);
}
if(ent->spawnflags & SEC_1ST_DOWN)
{
VectorAdd(ent->s.origin, forward, ent->moveinfo.start_origin);
VectorAdd(ent->moveinfo.start_origin, right, ent->moveinfo.end_origin);
}
else
{
VectorAdd(ent->s.origin, right, ent->moveinfo.start_origin);
VectorAdd(ent->moveinfo.start_origin, forward, ent->moveinfo.end_origin);
}
ent->touch = secret_touch;
ent->blocked = secret_blocked;
ent->use = fd_secret_use;
ent->moveinfo.speed = 50;
ent->moveinfo.accel = 50;
ent->moveinfo.decel = 50;
if (!ent->targetname || ent->spawnflags & SEC_YES_SHOOT)
{
ent->health = 1;
ent->max_health = ent->health;
ent->takedamage = DAMAGE_YES;
ent->die = fd_secret_killed;
}
if (!ent->wait)
ent->wait = 5; // 5 seconds before closing
gi.linkentity(ent);
}
// ==================================================
#define FWALL_START_ON 1
void force_wall_think(edict_t *self)
{
if(!self->wait)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_FORCEWALL);
gi.WritePosition (self->pos1);
gi.WritePosition (self->pos2);
gi.WriteByte (self->style);
gi.multicast (self->offset, MULTICAST_PVS);
}
self->think = force_wall_think;
self->nextthink = level.time + 0.1;
}
void force_wall_use (edict_t *self, edict_t *other, edict_t *activator)
{
if(!self->wait)
{
self->wait = 1;
self->think = NULL;
self->nextthink = 0;
self->solid = SOLID_NOT;
gi.linkentity( self );
}
else
{
self->wait = 0;
self->think = force_wall_think;
self->nextthink = level.time + 0.1;
self->solid = SOLID_BSP;
KillBox(self); // Is this appropriate?
gi.linkentity (self);
}
}
/*QUAKED func_force_wall (1 0 1) ? start_on
A vertical particle force wall. Turns on and solid when triggered.
If someone is in the force wall when it turns on, they're telefragged.
start_on - forcewall begins activated. triggering will turn it off.
style - color of particles to use.
208: green, 240: red, 241: blue, 224: orange
*/
void SP_func_force_wall(edict_t *ent)
{
gi.setmodel (ent, ent->model);
ent->offset[0] = (ent->absmax[0] + ent->absmin[0]) / 2;
ent->offset[1] = (ent->absmax[1] + ent->absmin[1]) / 2;
ent->offset[2] = (ent->absmax[2] + ent->absmin[2]) / 2;
ent->pos1[2] = ent->absmax[2];
ent->pos2[2] = ent->absmax[2];
if(ent->size[0] > ent->size[1])
{
ent->pos1[0] = ent->absmin[0];
ent->pos2[0] = ent->absmax[0];
ent->pos1[1] = ent->offset[1];
ent->pos2[1] = ent->offset[1];
}
else
{
ent->pos1[0] = ent->offset[0];
ent->pos2[0] = ent->offset[0];
ent->pos1[1] = ent->absmin[1];
ent->pos2[1] = ent->absmax[1];
}
if(!ent->style)
ent->style = 208;
ent->movetype = MOVETYPE_NONE;
ent->wait = 1;
if(ent->spawnflags & FWALL_START_ON)
{
ent->solid = SOLID_BSP;
ent->think = force_wall_think;
ent->nextthink = level.time + 0.1;
}
else
ent->solid = SOLID_NOT;
ent->use = force_wall_use;
ent->svflags = SVF_NOCLIENT;
gi.linkentity(ent);
}

352
g_newtarg.c Normal file
View file

@ -0,0 +1,352 @@
#include "g_local.h"
//==========================================================
/*QUAKED target_steam (1 0 0) (-8 -8 -8) (8 8 8)
Creates a steam effect (particles w/ velocity in a line).
speed = velocity of particles (default 50)
count = number of particles (default 32)
sounds = color of particles (default 8 for steam)
the color range is from this color to this color + 6
wait = seconds to run before stopping (overrides default
value derived from func_timer)
best way to use this is to tie it to a func_timer that "pokes"
it every second (or however long you set the wait time, above)
note that the width of the base is proportional to the speed
good colors to use:
6-9 - varying whites (darker to brighter)
224 - sparks
176 - blue water
80 - brown water
208 - slime
232 - blood
*/
void use_target_steam (edict_t *self, edict_t *other, edict_t *activator)
{
// FIXME - this needs to be a global
static int nextid;
vec3_t point;
if (nextid > 20000)
nextid = nextid %20000;
nextid++;
// automagically set wait from func_timer unless they set it already, or
// default to 1000 if not called by a func_timer (eek!)
if (!self->wait)
if (other)
self->wait = other->wait * 1000;
else
self->wait = 1000;
if (self->enemy)
{
VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
VectorSubtract (point, self->s.origin, self->movedir);
VectorNormalize (self->movedir);
}
VectorMA (self->s.origin, self->plat2flags*0.5, self->movedir, point);
if (self->wait > 100)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_STEAM);
gi.WriteShort (nextid);
gi.WriteByte (self->count);
gi.WritePosition (self->s.origin);
gi.WriteDir (self->movedir);
gi.WriteByte (self->sounds&0xff);
gi.WriteShort ( (short int)(self->plat2flags) );
gi.WriteLong ( (int)(self->wait) );
gi.multicast (self->s.origin, MULTICAST_PVS);
}
else
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_STEAM);
gi.WriteShort ((short int)-1);
gi.WriteByte (self->count);
gi.WritePosition (self->s.origin);
gi.WriteDir (self->movedir);
gi.WriteByte (self->sounds&0xff);
gi.WriteShort ( (short int)(self->plat2flags) );
gi.multicast (self->s.origin, MULTICAST_PVS);
}
}
void target_steam_start (edict_t *self)
{
edict_t *ent;
self->use = use_target_steam;
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);
}
if (!self->count)
self->count = 32;
if (!self->plat2flags)
self->plat2flags = 75;
if (!self->sounds)
self->sounds = 8;
if (self->wait)
self->wait *= 1000; // we want it in milliseconds, not seconds
// paranoia is good
self->sounds &= 0xff;
self->count &= 0xff;
self->svflags = SVF_NOCLIENT;
gi.linkentity (self);
}
void SP_target_steam (edict_t *self)
{
self->plat2flags = self->speed;
if (self->target)
{
self->think = target_steam_start;
self->nextthink = level.time + 1;
}
else
target_steam_start (self);
}
//==========================================================
// target_anger
//==========================================================
void target_anger_use (edict_t *self, edict_t *other, edict_t *activator)
{
edict_t *target;
edict_t *t;
t = NULL;
target = G_Find (t, FOFS(targetname), self->killtarget);
if (target && self->target)
{
// Make whatever a "good guy" so the monster will try to kill it!
target->monsterinfo.aiflags |= AI_GOOD_GUY;
target->svflags |= SVF_MONSTER;
target->health = 300;
t = NULL;
while ((t = G_Find (t, FOFS(targetname), self->target)))
{
if (t == self)
{
gi.dprintf ("WARNING: entity used itself.\n");
}
else
{
if (t->use)
{
if (t->health < 0)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("target_anger with dead monster!\n");
return;
}
t->enemy = target;
t->monsterinfo.aiflags |= AI_TARGET_ANGER;
FoundTarget (t);
}
}
if (!self->inuse)
{
gi.dprintf("entity was removed while using targets\n");
return;
}
}
}
}
/*QUAKED target_anger (1 0 0) (-8 -8 -8) (8 8 8)
This trigger will cause an entity to be angry at another entity when a player touches it. Target the
entity you want to anger, and killtarget the entity you want it to be angry at.
target - entity to piss off
killtarget - entity to be pissed off at
*/
void SP_target_anger (edict_t *self)
{
if (!self->target)
{
gi.dprintf("target_anger without target!\n");
G_FreeEdict (self);
return;
}
if (!self->killtarget)
{
gi.dprintf("target_anger without killtarget!\n");
G_FreeEdict (self);
return;
}
self->use = target_anger_use;
self->svflags = SVF_NOCLIENT;
}
// ================
// target_spawn
// ================
/*
extern edict_t *CreateMonster(vec3_t origin, vec3_t angles, char *classname);
void target_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
{
edict_t *newEnt;
newEnt = CreateMonster (self->s.origin, self->s.angles, "monster_infantry");
if(newEnt)
newEnt->enemy = other;
}
*/
/*Q U AKED target_spawn (1 0 0) (-32 -32 -24) (32 32 72)
*/
/*
void SP_target_spawn (edict_t *self)
{
self->use = target_spawn_use;
self->svflags = SVF_NOCLIENT;
}
*/
// ***********************************
// target_killplayers
// ***********************************
void target_killplayers_use (edict_t *self, edict_t *other, edict_t *activator)
{
int i;
edict_t *ent, *player;
// kill the players
for (i=0 ; i<game.maxclients ; i++)
{
player = &g_edicts[1+i];
if (!player->inuse)
continue;
// nail it
T_Damage (player, self, self, vec3_origin, self->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
}
// kill any visible monsters
for (ent = g_edicts; ent < &g_edicts[globals.num_edicts] ; ent++)
{
if (!ent->inuse)
continue;
if (ent->health < 1)
continue;
if (!ent->takedamage)
continue;
for(i=0;i<game.maxclients ; i++)
{
player = &g_edicts[1+i];
if(!player->inuse)
continue;
if(visible(player, ent))
{
T_Damage (ent, self, self, vec3_origin, ent->s.origin, vec3_origin,
ent->health, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
break;
}
}
}
}
/*QUAKED target_killplayers (1 0 0) (-8 -8 -8) (8 8 8)
When triggered, this will kill all the players on the map.
*/
void SP_target_killplayers (edict_t *self)
{
self->use = target_killplayers_use;
self->svflags = SVF_NOCLIENT;
}
/*QUAKED target_blacklight (1 0 1) (-16 -16 -24) (16 16 24)
Pulsing black light with sphere in the center
*/
void blacklight_think (edict_t *self)
{
self->s.angles[0] = rand()%360;
self->s.angles[1] = rand()%360;
self->s.angles[2] = rand()%360;
self->nextthink = level.time + 0.1;
}
void SP_target_blacklight(edict_t *ent)
{
if (deathmatch->value)
{ // auto-remove for deathmatch
G_FreeEdict (ent);
return;
}
VectorClear (ent->mins);
VectorClear (ent->maxs);
ent->s.effects |= (EF_TRACKERTRAIL|EF_TRACKER);
ent->think = blacklight_think;
ent->s.modelindex = gi.modelindex ("models/items/spawngro2/tris.md2");
ent->s.frame = 1;
ent->nextthink = level.time + 0.1;
gi.linkentity (ent);
}
/*QUAKED target_orb (1 0 1) (-16 -16 -24) (16 16 24)
Translucent pulsing orb with speckles
*/
void orb_think (edict_t *self)
{
self->s.angles[0] = rand()%360;
self->s.angles[1] = rand()%360;
self->s.angles[2] = rand()%360;
// self->s.effects |= (EF_TRACKERTRAIL|EF_DOUBLE);
self->nextthink = level.time + 0.1;
}
void SP_target_orb(edict_t *ent)
{
if (deathmatch->value)
{ // auto-remove for deathmatch
G_FreeEdict (ent);
return;
}
VectorClear (ent->mins);
VectorClear (ent->maxs);
// ent->s.effects |= EF_TRACKERTRAIL;
ent->think = orb_think;
ent->nextthink = level.time + 0.1;
ent->s.modelindex = gi.modelindex ("models/items/spawngro2/tris.md2");
ent->s.frame = 2;
ent->s.effects |= EF_SPHERETRANS;
gi.linkentity (ent);
}

176
g_newtrig.c Normal file
View file

@ -0,0 +1,176 @@
// g_newtrig.c
// pmack
// october 1997
#include "g_local.h"
#define TELEPORT_PLAYER_ONLY 1
#define TELEPORT_SILENT 2
#define TELEPORT_CTF_ONLY 4
#define TELEPORT_START_ON 8
extern void TeleportEffect (vec3_t origin);
/*QUAKED info_teleport_destination (.5 .5 .5) (-16 -16 -24) (16 16 32)
Destination marker for a teleporter.
*/
void SP_info_teleport_destination (edict_t *self)
{
}
/*QUAKED trigger_teleport (.5 .5 .5) ? player_only silent ctf_only start_on
Any object touching this will be transported to the corresponding
info_teleport_destination entity. You must set the "target" field,
and create an object with a "targetname" field that matches.
If the trigger_teleport has a targetname, it will only teleport
entities when it has been fired.
player_only: only players are teleported
silent: <not used right now>
ctf_only: <not used right now>
start_on: when trigger has targetname, start active, deactivate when used.
*/
void trigger_teleport_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
edict_t *dest;
int i;
if(/*(self->spawnflags & TELEPORT_PLAYER_ONLY) &&*/ !(other->client))
return;
if(self->delay)
return;
dest = G_Find (NULL, FOFS(targetname), self->target);
if(!dest)
{
gi.dprintf("Teleport Destination not found!\n");
return;
}
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_TELEPORT_EFFECT);
gi.WritePosition (other->s.origin);
gi.multicast (other->s.origin, MULTICAST_PVS);
// unlink to make sure it can't possibly interfere with KillBox
gi.unlinkentity (other);
VectorCopy (dest->s.origin, other->s.origin);
VectorCopy (dest->s.origin, other->s.old_origin);
other->s.origin[2] += 10;
// clear the velocity and hold them in place briefly
VectorClear (other->velocity);
if(other->client)
{
other->client->ps.pmove.pm_time = 160>>3; // hold time
other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
// draw the teleport splash at source and on the player
// self->s.event = EV_PLAYER_TELEPORT;
other->s.event = EV_PLAYER_TELEPORT;
// set angles
for (i=0 ; i<3 ; i++)
other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
VectorClear (other->client->ps.viewangles);
VectorClear (other->client->v_angle);
}
VectorClear (other->s.angles);
// kill anything at the destination
KillBox (other);
gi.linkentity (other);
}
void trigger_teleport_use (edict_t *self, edict_t *other, edict_t *activator)
{
if(self->delay)
self->delay = 0;
else
self->delay = 1;
}
void SP_trigger_teleport(edict_t *self)
{
if (!self->wait)
self->wait = 0.2;
self->delay = 0;
if (self->targetname)
{
self->use = trigger_teleport_use;
if(!(self->spawnflags & TELEPORT_START_ON))
self->delay = 1;
}
self->touch = trigger_teleport_touch;
self->solid = SOLID_TRIGGER;
self->movetype = MOVETYPE_NONE;
// self->flags |= FL_NOCLIENT;
if (!VectorCompare(self->s.angles, vec3_origin))
G_SetMovedir (self->s.angles, self->movedir);
gi.setmodel (self, self->model);
gi.linkentity (self);
}
// ***************************
// TRIGGER_DISGUISE
// ***************************
/*QUAKED trigger_disguise (.5 .5 .5) ? TOGGLE START_ON REMOVE
Anything passing through this trigger when it is active will
be marked as disguised.
TOGGLE - field is turned off and on when used.
START_ON - field is active when spawned.
REMOVE - field removes the disguise
*/
void trigger_disguise_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
if (other->client)
{
if(self->spawnflags & 4)
other->flags &= ~FL_DISGUISED;
else
other->flags |= FL_DISGUISED;
}
}
void trigger_disguise_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);
}
void SP_trigger_disguise (edict_t *self)
{
if(self->spawnflags & 2)
self->solid = SOLID_TRIGGER;
else
self->solid = SOLID_NOT;
self->touch = trigger_disguise_touch;
self->use = trigger_disguise_use;
self->movetype = MOVETYPE_NONE;
self->svflags = SVF_NOCLIENT;
gi.setmodel (self, self->model);
gi.linkentity(self);
}

2281
g_newweap.c Normal file

File diff suppressed because it is too large Load diff

1122
g_phys.c Normal file

File diff suppressed because it is too large Load diff

798
g_save.c Normal file
View file

@ -0,0 +1,798 @@
#include "g_local.h"
#define Function(f) {#f, f}
mmove_t mmove_reloc;
field_t fields[] = {
{"classname", FOFS(classname), F_LSTRING},
{"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},
{"mass", FOFS(mass), F_INT},
{"volume", FOFS(volume), F_FLOAT},
{"attenuation", FOFS(attenuation), F_FLOAT},
{"map", FOFS(map), F_LSTRING},
{"origin", FOFS(s.origin), F_VECTOR},
{"angles", FOFS(s.angles), F_VECTOR},
{"angle", FOFS(s.angles), F_ANGLEHACK},
{"goalentity", FOFS(goalentity), F_EDICT, FFL_NOSPAWN},
{"movetarget", FOFS(movetarget), F_EDICT, FFL_NOSPAWN},
{"enemy", FOFS(enemy), F_EDICT, FFL_NOSPAWN},
{"oldenemy", FOFS(oldenemy), F_EDICT, FFL_NOSPAWN},
{"activator", FOFS(activator), F_EDICT, FFL_NOSPAWN},
{"groundentity", FOFS(groundentity), F_EDICT, FFL_NOSPAWN},
{"teamchain", FOFS(teamchain), F_EDICT, FFL_NOSPAWN},
{"teammaster", FOFS(teammaster), F_EDICT, FFL_NOSPAWN},
{"owner", FOFS(owner), F_EDICT, FFL_NOSPAWN},
{"mynoise", FOFS(mynoise), F_EDICT, FFL_NOSPAWN},
{"mynoise2", FOFS(mynoise2), F_EDICT, FFL_NOSPAWN},
{"target_ent", FOFS(target_ent), F_EDICT, FFL_NOSPAWN},
{"chain", FOFS(chain), F_EDICT, FFL_NOSPAWN},
{"prethink", FOFS(prethink), F_FUNCTION, FFL_NOSPAWN},
{"think", FOFS(think), F_FUNCTION, FFL_NOSPAWN},
{"blocked", FOFS(blocked), F_FUNCTION, FFL_NOSPAWN},
{"touch", FOFS(touch), F_FUNCTION, FFL_NOSPAWN},
{"use", FOFS(use), F_FUNCTION, FFL_NOSPAWN},
{"pain", FOFS(pain), F_FUNCTION, FFL_NOSPAWN},
{"die", FOFS(die), F_FUNCTION, FFL_NOSPAWN},
{"stand", FOFS(monsterinfo.stand), F_FUNCTION, FFL_NOSPAWN},
{"idle", FOFS(monsterinfo.idle), F_FUNCTION, FFL_NOSPAWN},
{"search", FOFS(monsterinfo.search), F_FUNCTION, FFL_NOSPAWN},
{"walk", FOFS(monsterinfo.walk), F_FUNCTION, FFL_NOSPAWN},
{"run", FOFS(monsterinfo.run), F_FUNCTION, FFL_NOSPAWN},
{"dodge", FOFS(monsterinfo.dodge), F_FUNCTION, FFL_NOSPAWN},
{"attack", FOFS(monsterinfo.attack), F_FUNCTION, FFL_NOSPAWN},
{"melee", FOFS(monsterinfo.melee), F_FUNCTION, FFL_NOSPAWN},
{"sight", FOFS(monsterinfo.sight), F_FUNCTION, FFL_NOSPAWN},
{"checkattack", FOFS(monsterinfo.checkattack), F_FUNCTION, FFL_NOSPAWN},
{"currentmove", FOFS(monsterinfo.currentmove), F_MMOVE, FFL_NOSPAWN},
{"endfunc", FOFS(moveinfo.endfunc), F_FUNCTION, FFL_NOSPAWN},
// 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},
//need for item field in edict struct, FFL_SPAWNTEMP item will be skipped on saves
{"item", FOFS(item), F_ITEM},
{"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},
// ROGUE
{"bad_area", FOFS(bad_area), F_EDICT},
// while the hint_path stuff could be reassembled on the fly, no reason to be different
{"hint_chain", FOFS(hint_chain), F_EDICT},
{"monster_hint_chain", FOFS(monster_hint_chain), F_EDICT},
{"target_hint_chain", FOFS(target_hint_chain), F_EDICT},
//
{"goal_hint", FOFS(monsterinfo.goal_hint), F_EDICT},
{"badMedic1", FOFS(monsterinfo.badMedic1), F_EDICT},
{"badMedic2", FOFS(monsterinfo.badMedic2), F_EDICT},
{"last_player_enemy", FOFS(monsterinfo.last_player_enemy), F_EDICT},
{"commander", FOFS(monsterinfo.commander), F_EDICT},
{"blocked", FOFS(monsterinfo.blocked), F_MMOVE, FFL_NOSPAWN},
{"duck", FOFS(monsterinfo.duck), F_MMOVE, FFL_NOSPAWN},
{"unduck", FOFS(monsterinfo.unduck), F_MMOVE, FFL_NOSPAWN},
{"sidestep", FOFS(monsterinfo.sidestep), F_MMOVE, FFL_NOSPAWN},
// ROGUE
{0, 0, 0, 0}
};
field_t levelfields[] =
{
{"changemap", LLOFS(changemap), F_LSTRING},
{"sight_client", LLOFS(sight_client), F_EDICT},
{"sight_entity", LLOFS(sight_entity), F_EDICT},
{"sound_entity", LLOFS(sound_entity), F_EDICT},
{"sound2_entity", LLOFS(sound2_entity), F_EDICT},
// ROGUE
{"disguise_violator", LLOFS(disguise_violator), F_EDICT},
// ROGUE
{NULL, 0, F_INT}
};
field_t clientfields[] =
{
{"pers.weapon", CLOFS(pers.weapon), F_ITEM},
{"pers.lastweapon", CLOFS(pers.lastweapon), F_ITEM},
{"newweapon", CLOFS(newweapon), F_ITEM},
// ROGUE
{"owned_sphere", CLOFS(owned_sphere), F_EDICT},
// ROGUE
{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 ("==== InitGame ====\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);
sv_stopspeed = gi.cvar ("sv_stopspeed", "100", 0); // PGM - was #define in g_phys.c
//ROGUE
g_showlogic = gi.cvar ("g_showlogic", "0", 0);
huntercam = gi.cvar ("huntercam", "1", CVAR_SERVERINFO|CVAR_LATCH);
strong_mines = gi.cvar ("strong_mines", "0", 0);
randomrespawn = gi.cvar ("randomrespawn", "0", 0);
//ROGUE
// 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);
maxspectators = gi.cvar ("maxspectators", "4", CVAR_SERVERINFO);
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);
gamerules = gi.cvar ("gamerules", "0", CVAR_LATCH); //PGM
// change anytime vars
dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO);
fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO);
timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO);
password = gi.cvar ("password", "", CVAR_USERINFO);
spectator_password = gi.cvar ("spectator_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;
//======
//ROGUE
if(gamerules)
{
InitGameRules(); // if there are game rules to set up, do so now.
}
//ROGUE
//======
}
//=========================================================
void WriteField1 (FILE *f, field_t *field, byte *base)
{
void *p;
int len;
int index;
if (field->flags & FFL_SPAWNTEMP)
return;
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;
//relative to code segment
case F_FUNCTION:
if (*(byte **)p == NULL)
index = 0;
else
index = *(byte **)p - ((byte *)InitGame);
*(int *)p = index;
break;
//relative to data segment
case F_MMOVE:
if (*(byte **)p == NULL)
index = 0;
else
index = *(byte **)p - (byte *)&mmove_reloc;
*(int *)p = index;
break;
default:
gi.error ("WriteEdict: unknown field type");
}
}
void WriteField2 (FILE *f, field_t *field, byte *base)
{
int len;
void *p;
if (field->flags & FFL_SPAWNTEMP)
return;
p = (void *)(base + field->ofs);
switch (field->type)
{
case F_LSTRING:
if ( *(char **)p )
{
len = strlen(*(char **)p) + 1;
fwrite (*(char **)p, len, 1, f);
}
break;
}
}
void ReadField (FILE *f, field_t *field, byte *base)
{
void *p;
int len;
int index;
if (field->flags & FFL_SPAWNTEMP)
return;
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_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;
//relative to code segment
case F_FUNCTION:
index = *(int *)p;
if ( index == 0 )
*(byte **)p = NULL;
else
*(byte **)p = ((byte *)InitGame) + index;
break;
//relative to data segment
case F_MMOVE:
index = *(int *)p;
if (index == 0)
*(byte **)p = NULL;
else
*(byte **)p = (byte *)&mmove_reloc + 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=fields ; 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=fields ; 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=fields ; 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 already 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);
#ifdef _WIN32
if (base != (void *)InitGame)
{
fclose (f);
gi.error ("ReadLevel: function pointers have moved");
}
#else
gi.dprintf("Function offsets %d\n", ((byte *)base) - ((byte *)InitGame));
#endif
// 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);
// PMM - rebuild the hint path chains
// InitHintPaths();
// pmm
// 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;
}
}

1774
g_spawn.c Normal file

File diff suppressed because it is too large Load diff

789
g_sphere.c Normal file
View file

@ -0,0 +1,789 @@
// g_sphere.c
// pmack
// april 1998
// defender - actively finds and shoots at enemies
// hunter - waits until < 25% health and vore ball tracks person who hurt you
// vengeance - kills person who killed you.
#include "g_local.h"
#define DEFENDER_LIFESPAN 30
#define HUNTER_LIFESPAN 30
#define VENGEANCE_LIFESPAN 30
#define MINIMUM_FLY_TIME 15
//#define MINIMUM_FLY_TIME 30
// FIXME - do we need to be calling ED_NewString at all?
extern char *ED_NewString (char *string);
void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker);
void defender_think (edict_t *self);
void hunter_think (edict_t *self);
void vengeance_think (edict_t *self);
void vengeance_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
void hunter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
// *************************
// General Sphere Code
// *************************
// =================
// =================
void sphere_think_explode (edict_t *self)
{
if(self->owner && self->owner->client && !(self->spawnflags & SPHERE_DOPPLEGANGER))
{
self->owner->client->owned_sphere = NULL;
}
BecomeExplosion1 (self);
}
// =================
// sphere_explode
// =================
void sphere_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
// if(self->owner && self->owner->client)
// gi.cprintf(self->owner, PRINT_HIGH, "Sphere timed out\n");
// gi.dprintf("player died, blowing up\n");
sphere_think_explode (self);
}
// =================
// sphere_if_idle_die - if the sphere is not currently attacking, blow up.
// =================
void sphere_if_idle_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
if(!self->enemy)
{
// gi.dprintf("player died, blowing up\n");
sphere_think_explode(self);
}
}
// *************************
// Sphere Movement
// *************************
// =================
// =================
void sphere_fly (edict_t *self)
{
vec3_t dest;
vec3_t dir;
if(level.time >= self->wait)
{
// gi.dprintf("fly: timed out\n");
sphere_think_explode(self);
return;
}
VectorCopy (self->owner->s.origin, dest);
dest[2] = self->owner->absmax[2] + 4;
if(level.time == (float)(int)level.time)
{
if(!visible(self, self->owner))
{
VectorCopy(dest, self->s.origin);
gi.linkentity(self);
return;
}
}
VectorSubtract (dest, self->s.origin, dir);
VectorScale (dir, 5, self->velocity);
}
// =================
// =================
void sphere_chase (edict_t *self, int stupidChase)
{
vec3_t dest;
vec3_t dir;
float dist;
if(level.time >= self->wait || (self->enemy && self->enemy->health < 1))
{
sphere_think_explode(self);
return;
}
VectorCopy (self->enemy->s.origin, dest);
if(self->enemy->client)
dest[2] += self->enemy->viewheight;
if(visible(self, self->enemy) || stupidChase)
{
// if moving, hunter sphere uses active sound
if(!stupidChase)
self->s.sound = gi.soundindex ("spheres/h_active.wav");
VectorSubtract (dest, self->s.origin, dir);
VectorNormalize (dir);
vectoangles2(dir, self->s.angles);
VectorScale (dir, 500, self->velocity);
VectorCopy(dest, self->monsterinfo.saved_goal);
}
else if (VectorCompare (self->monsterinfo.saved_goal, vec3_origin))
{
VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
dist = VectorNormalize(dir);
vectoangles2(dir, self->s.angles);
// if lurking, hunter sphere uses lurking sound
self->s.sound = gi.soundindex ("spheres/h_lurk.wav");
VectorClear (self->velocity);
}
else
{
VectorSubtract(self->monsterinfo.saved_goal, self->s.origin, dir);
dist = VectorNormalize(dir);
if(dist > 1)
{
vectoangles2(dir, self->s.angles);
if(dist > 500)
VectorScale(dir, 500, self->velocity);
else if (dist < 20)
VectorScale(dir, (dist / FRAMETIME), self->velocity);
else
VectorScale(dir, dist, self->velocity);
// if moving, hunter sphere uses active sound
if(!stupidChase)
self->s.sound = gi.soundindex ("spheres/h_active.wav");
}
else
{
VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
dist = VectorNormalize(dir);
vectoangles2(dir, self->s.angles);
// if not moving, hunter sphere uses lurk sound
if(!stupidChase)
self->s.sound = gi.soundindex ("spheres/h_lurk.wav");
VectorClear(self->velocity);
}
}
}
// *************************
// Attack related stuff
// *************************
// =================
// =================
void sphere_fire (edict_t *self, edict_t *enemy)
{
vec3_t dest;
vec3_t dir;
if(level.time >= self->wait || !enemy)
{
sphere_think_explode(self);
return;
}
VectorCopy (enemy->s.origin, dest);
self->s.effects |= EF_ROCKET;
VectorSubtract (dest, self->s.origin, dir);
VectorNormalize (dir);
vectoangles2 ( dir, self->s.angles );
VectorScale (dir, 1000, self->velocity);
self->touch = vengeance_touch;
self->think = sphere_think_explode;
self->nextthink = self->wait;
}
// =================
// =================
void sphere_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf, int mod)
{
if(self->spawnflags & SPHERE_DOPPLEGANGER)
{
if (other == self->teammaster)
return;
self->takedamage = DAMAGE_NO;
self->owner = self->teammaster;
self->teammaster = NULL;
}
else
{
if (other == self->owner)
return;
// PMM - don't blow up on bodies
if (!strcmp(other->classname, "bodyque"))
return;
}
if (surf && (surf->flags & SURF_SKY))
{
G_FreeEdict (self);
return;
}
if (other->takedamage)
{
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
10000, 1, DAMAGE_DESTROY_ARMOR, mod);
}
else
{
T_RadiusDamage (self, self->owner, 512, self->owner, 256, mod);
}
sphere_think_explode (self);
}
// =================
// =================
void vengeance_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
if(self->spawnflags & SPHERE_DOPPLEGANGER)
sphere_touch (self, other, plane, surf, MOD_DOPPLE_VENGEANCE);
else
sphere_touch (self, other, plane, surf, MOD_VENGEANCE_SPHERE);
}
// =================
// =================
void hunter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
edict_t *owner;
// don't blow up if you hit the world.... sheesh.
if(other==world)
return;
if(self->owner)
{
// if owner is flying with us, make sure they stop too.
owner=self->owner;
if(owner->flags & FL_SAM_RAIMI)
{
VectorClear(owner->velocity);
owner->movetype = MOVETYPE_NONE;
gi.linkentity(owner);
}
}
if(self->spawnflags & SPHERE_DOPPLEGANGER)
sphere_touch (self, other, plane, surf, MOD_DOPPLE_HUNTER);
else
sphere_touch (self, other, plane, surf, MOD_HUNTER_SPHERE);
}
// =================
// =================
void defender_shoot (edict_t *self, edict_t *enemy)
{
vec3_t dir;
vec3_t start;
if(!(enemy->inuse) || enemy->health <= 0)
return;
if(enemy == self->owner)
return;
VectorSubtract (enemy->s.origin, self->s.origin, dir);
VectorNormalize (dir);
if(self->monsterinfo.attack_finished > level.time)
return;
if(!visible(self, self->enemy))
return;
VectorCopy(self->s.origin, start);
start[2] += 2;
fire_blaster2 (self->owner, start, dir, 10, 1000, EF_BLASTER, 0);
self->monsterinfo.attack_finished = level.time + 0.4;
}
// *************************
// Activation Related Stuff
// *************************
// =================
// =================
void body_gib (edict_t *self)
{
int n;
gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
for (n= 0; n < 4; n++)
ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 50, GIB_ORGANIC);
ThrowGib (self, "models/objects/gibs/skull/tris.md2", 50, GIB_ORGANIC);
}
// =================
// =================
void hunter_pain (edict_t *self, edict_t *other, float kick, int damage)
{
edict_t *owner;
float dist;
vec3_t dir;
if(self->enemy)
return;
owner = self->owner;
if(!(self->spawnflags & SPHERE_DOPPLEGANGER))
{
if(owner && (owner->health > 0))
return;
//PMM
if(other == owner)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("hunter: won't get mad at my owner!\n");
return;
}
//pmm
}
else
{
// if fired by a doppleganger, set it to 10 second timeout
self->wait = level.time + MINIMUM_FLY_TIME;
}
if((self->wait - level.time) < MINIMUM_FLY_TIME)
self->wait = level.time + MINIMUM_FLY_TIME;
self->s.effects |= EF_BLASTER | EF_TRACKER;
self->touch = hunter_touch;
self->enemy = other;
// if(g_showlogic && g_showlogic->value)
// gi.dprintf("hunter_pain: mad at %s\n", other->classname);
// if we're not owned by a player, no sam raimi
// if we're spawned by a doppleganger, no sam raimi
if((self->spawnflags & SPHERE_DOPPLEGANGER) || !(owner && owner->client))
return;
// sam raimi cam is disabled if FORCE_RESPAWN is set.
// sam raimi cam is also disabled if huntercam->value is 0.
if(!((int)dmflags->value & DF_FORCE_RESPAWN) && (huntercam && (huntercam->value)))
{
VectorSubtract(other->s.origin, self->s.origin, dir);
dist=VectorLength(dir);
if(owner && (dist >= 192))
{
// detach owner from body and send him flying
owner->movetype = MOVETYPE_FLYMISSILE;
// gib like we just died, even though we didn't, really.
body_gib(owner);
// move the sphere to the owner's current viewpoint.
// we know it's a valid spot (or will be momentarily)
VectorCopy(owner->s.origin, self->s.origin);
self->s.origin[2] += owner->viewheight;
// move the player's origin to the sphere's new origin
VectorCopy(self->s.origin, owner->s.origin);
VectorCopy(self->s.angles, owner->s.angles);
VectorCopy(self->s.angles, owner->client->v_angle);
VectorClear(owner->mins);
VectorClear(owner->maxs);
VectorSet(owner->mins, -5, -5, -5);
VectorSet(owner->maxs, 5, 5, 5);
owner->client->ps.fov = 140;
owner->s.modelindex = 0;
owner->s.modelindex2 = 0;
owner->viewheight = 8;
owner->solid = SOLID_NOT;
owner->flags |= FL_SAM_RAIMI;
gi.linkentity(owner);
// PMM - set bounding box so we don't clip out of world
// VectorSet(self->mins, -5, -5, -5);
// VectorSet(self->maxs, 5, 5, 5);
self->solid = SOLID_BBOX;
gi.linkentity (self);
}
// else
// gi.dprintf("too close for sam raimi cam\n");
}
}
// =================
// =================
void defender_pain (edict_t *self, edict_t *other, float kick, int damage)
{
//PMM
if(other == self->owner)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("defender: won't get mad at my owner!\n");
return;
}
//pmm
self->enemy = other;
}
// =================
// =================
void vengeance_pain (edict_t *self, edict_t *other, float kick, int damage)
{
if(self->enemy)
return;
if(!(self->spawnflags & SPHERE_DOPPLEGANGER))
{
if(self->owner->health >= 25)
return;
//PMM
if(other == self->owner)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("vengeance: won't get mad at my owner!\n");
return;
}
//pmm
}
else
{
self->wait = level.time + MINIMUM_FLY_TIME;
}
if((self->wait - level.time) < MINIMUM_FLY_TIME)
self->wait = level.time + MINIMUM_FLY_TIME;
self->s.effects |= EF_ROCKET;
self->touch = vengeance_touch;
self->enemy = other;
}
// *************************
// Think Functions
// *************************
// ===================
// ===================
void defender_think (edict_t *self)
{
if(!self->owner)
{
// gi.dprintf("think: no owner\n");
G_FreeEdict(self);
return;
}
// if we've exited the level, just remove ourselves.
if (level.intermissiontime)
{
sphere_think_explode(self);
return;
}
if(self->owner->health <=0)
{
sphere_think_explode(self);
return;
}
// if(level.time - self->timestamp > 1)
// {
// gi.sound (self, CHAN_VOICE, gi.soundindex ("powerup/dsphere.wav"), 0.6, ATTN_NORM, 0);
// self->timestamp = level.time;
// }
self->s.frame++;
if(self->s.frame>19)
self->s.frame = 0;
if(self->enemy)
{
if(self->enemy->health > 0)
{
// gi.dprintf( "shooting at %s\n", self->enemy->classname);
defender_shoot (self, self->enemy);
}
else
self->enemy = NULL;
}
// else
// {
// self->ideal_yaw+=3;
// M_ChangeYaw (self);
// }
sphere_fly (self);
if(self->inuse)
self->nextthink = level.time + 0.1;
}
// =================
// =================
void hunter_think (edict_t *self)
{
edict_t *owner;
vec3_t dir, ang;
// if we've exited the level, just remove ourselves.
if (level.intermissiontime)
{
sphere_think_explode(self);
return;
}
owner = self->owner;
if(!owner && !(self->spawnflags & SPHERE_DOPPLEGANGER))
{
// gi.dprintf("think: no owner\n");
G_FreeEdict(self);
return;
}
if(owner)
self->ideal_yaw = owner->s.angles[YAW];
else if(self->enemy) // fired by doppleganger
{
VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
vectoangles2(dir, ang);
self->ideal_yaw = ang[YAW];
}
M_ChangeYaw(self);
// if(level.time - self->timestamp > 1)
// {
// gi.sound (self, CHAN_VOICE, gi.soundindex ("powerup/hsphere.wav"), 0.5, ATTN_NORM, 0);
// self->timestamp = level.time;
// }
if(self->enemy)
{
sphere_chase (self, 0);
// deal with sam raimi cam
if(owner && (owner->flags & FL_SAM_RAIMI))
{
if(self->inuse)
{
owner->movetype = MOVETYPE_FLYMISSILE;
// VectorCopy(self->s.angles, owner->s.angles);
// VectorCopy(self->s.angles, owner->client->v_angle);
LookAtKiller (owner, self, self->enemy);
// owner->viewheight = 22;
// owner->client->v_angle[YAW]+=5;
// owner is flying with us, move him too
owner->movetype = MOVETYPE_FLYMISSILE;
owner->viewheight = self->s.origin[2] - owner->s.origin[2];
// VectorCopy(self->s.angles, owner->s.angles);
// VectorCopy(self->s.angles, owner->client->v_angle);
VectorCopy(self->s.origin, owner->s.origin);
VectorCopy(self->velocity, owner->velocity);
VectorClear(owner->mins);
VectorClear(owner->maxs);
gi.linkentity(owner);
}
else // sphere timed out
{
VectorClear(owner->velocity);
owner->movetype = MOVETYPE_NONE;
gi.linkentity(owner);
}
}
}
else
{
// self->ideal_yaw+=3;
// M_ChangeYaw (self);
sphere_fly (self);
}
if(self->inuse)
self->nextthink = level.time + 0.1;
}
// =================
// =================
void vengeance_think (edict_t *self)
{
// if we've exited the level, just remove ourselves.
if (level.intermissiontime)
{
sphere_think_explode(self);
return;
}
if(!(self->owner) && !(self->spawnflags & SPHERE_DOPPLEGANGER))
{
// gi.dprintf("think: no owner\n");
G_FreeEdict(self);
return;
}
// if(level.time - self->timestamp > 1)
// {
// gi.sound (self, CHAN_VOICE, gi.soundindex ("powerup/vsphere.wav"), 0.5, ATTN_NORM, 0);
// self->timestamp = level.time;
// }
if(self->enemy)
{
// sphere_fire (self, self->owner->enemy);
sphere_chase (self, 1);
}
else
sphere_fly (self);
if(self->inuse)
self->nextthink = level.time + 0.1;
}
// *************************
// Spawning / Creation
// *************************
// monsterinfo_t
// =================
// =================
edict_t *Sphere_Spawn (edict_t *owner, int spawnflags)
{
edict_t *sphere;
sphere = G_Spawn();
VectorCopy(owner->s.origin, sphere->s.origin);
sphere->s.origin[2] = owner->absmax[2];
sphere->s.angles[YAW] = owner->s.angles[YAW];
sphere->solid = SOLID_BBOX;
sphere->clipmask = MASK_SHOT;
sphere->s.renderfx = RF_FULLBRIGHT | RF_IR_VISIBLE;
sphere->movetype = MOVETYPE_FLYMISSILE;
if(spawnflags & SPHERE_DOPPLEGANGER)
sphere->teammaster = owner->teammaster;
else
sphere->owner = owner;
sphere->classname = "sphere";
sphere->yaw_speed = 40;
sphere->monsterinfo.attack_finished = 0;
sphere->spawnflags = spawnflags; // need this for the HUD to recognize sphere
//PMM
sphere->takedamage = DAMAGE_NO;
switch(spawnflags & SPHERE_TYPE)
{
case SPHERE_DEFENDER:
sphere->s.modelindex = gi.modelindex("models/items/defender/tris.md2");
// PMM - this doesn't work, causes problems with other stuff
// sphere->s.modelindex2 = gi.modelindex("models/items/shell/tris.md2") | 0x80;
sphere->s.modelindex2 = gi.modelindex("models/items/shell/tris.md2");
sphere->s.sound = gi.soundindex ("spheres/d_idle.wav");
sphere->pain = defender_pain;
sphere->wait = level.time + DEFENDER_LIFESPAN;
sphere->die = sphere_explode;
sphere->think = defender_think;
break;
case SPHERE_HUNTER:
sphere->s.modelindex = gi.modelindex("models/items/hunter/tris.md2");
sphere->s.sound = gi.soundindex ("spheres/h_idle.wav");
sphere->wait = level.time + HUNTER_LIFESPAN;
sphere->pain = hunter_pain;
sphere->die = sphere_if_idle_die;
sphere->think = hunter_think;
break;
case SPHERE_VENGEANCE:
sphere->s.modelindex = gi.modelindex("models/items/vengnce/tris.md2");
sphere->s.sound = gi.soundindex ("spheres/v_idle.wav");
sphere->wait = level.time + VENGEANCE_LIFESPAN;
sphere->pain = vengeance_pain;
sphere->die = sphere_if_idle_die;
sphere->think = vengeance_think;
VectorSet (sphere->avelocity, 30, 30, 0);
break;
default:
gi.dprintf("Tried to create an invalid sphere\n");
G_FreeEdict(sphere);
return NULL;
}
sphere->nextthink = level.time + 0.1;
gi.linkentity (sphere);
return sphere;
}
// =================
// Own_Sphere - attach the sphere to the client so we can
// directly access it later
// =================
void Own_Sphere (edict_t *self, edict_t *sphere)
{
if(!sphere)
return;
// ownership only for players
if(self->client)
{
// if they don't have one
if(!(self->client->owned_sphere))
{
self->client->owned_sphere = sphere;
}
// they already have one, take care of the old one
else
{
if(self->client->owned_sphere->inuse)
{
G_FreeEdict(self->client->owned_sphere);
self->client->owned_sphere = sphere;
}
else
{
self->client->owned_sphere = sphere;
}
}
}
}
// =================
// =================
void Defender_Launch (edict_t *self)
{
edict_t *sphere;
sphere = Sphere_Spawn (self, SPHERE_DEFENDER);
Own_Sphere (self, sphere);
}
// =================
// =================
void Hunter_Launch (edict_t *self)
{
edict_t *sphere;
sphere = Sphere_Spawn (self, SPHERE_HUNTER);
Own_Sphere (self, sphere);
}
// =================
// =================
void Vengeance_Launch (edict_t *self)
{
edict_t *sphere;
sphere = Sphere_Spawn (self, SPHERE_VENGEANCE);
Own_Sphere (self, sphere);
}

281
g_svcmds.c Normal file
View file

@ -0,0 +1,281 @@
#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);
}

825
g_target.c Normal file
View file

@ -0,0 +1,825 @@
#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 always 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; // already 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);
ent->s.renderfx |= RF_IR_VISIBLE; //PGM
}
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 WINDOWSTOP
When triggered, fires a laser. You can either set a target
or a direction.
WINDOWSTOP - stops at CONTENTS_WINDOW
*/
//======
// PGM
#define LASER_ON 0x0001
#define LASER_RED 0x0002
#define LASER_GREEN 0x0004
#define LASER_BLUE 0x0008
#define LASER_YELLOW 0x0010
#define LASER_ORANGE 0x0020
#define LASER_FAT 0x0040
#define LASER_STOPWINDOW 0x0080
// PGM
//======
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)
{
//======
// PGM
if(self->spawnflags & LASER_STOPWINDOW)
tr = gi.trace (start, NULL, NULL, end, ignore, MASK_SHOT);
else
tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
// PGM
//======
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))
//PMM added SVF_DAMAGEABLE
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client) && !(tr.ent->svflags & SVF_DAMAGEABLE))
{
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) SILENT
When triggered, this initiates a level-wide earthquake.
All players and monsters are affected.
"speed" severity of the quake (default:200)
"count" duration of the quake (default:5)
*/
void target_earthquake_think (edict_t *self)
{
int i;
edict_t *e;
if(!(self->spawnflags & 1)) // PGM
{ // PGM
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;
}
} // PGM
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)
{
// PGM
// if(g_showlogic && g_showlogic->value)
// gi.dprintf("earthquake: %0.1f\n", self->speed);
// PGM
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;
if(!(