Initial commit...
This commit is contained in:
commit
51e076c7ac
66 changed files with 50218 additions and 0 deletions
125
Makefile
Normal file
125
Makefile
Normal file
|
@ -0,0 +1,125 @@
|
|||
#
|
||||
# Action makefile
|
||||
# Intended for gcc/Linux, may need modifying for other platforms
|
||||
#
|
||||
|
||||
CC=gcc
|
||||
BASE_CFLAGS=-Dstricmp=strcasecmp
|
||||
|
||||
#use these cflags to optimize it
|
||||
#CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \
|
||||
# -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \
|
||||
# -malign-jumps=2 -malign-functions=2
|
||||
#use these when debugging
|
||||
CFLAGS=$(BASE_CFLAGS) -I../src/
|
||||
|
||||
LDFLAGS=-ldl -lm
|
||||
SHLIBEXT=so
|
||||
SHLIBCFLAGS=-fPIC
|
||||
SHLIBLDFLAGS=-shared
|
||||
|
||||
DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $<
|
||||
|
||||
#############################################################################
|
||||
# SETUP AND BUILD
|
||||
# GAME
|
||||
#############################################################################
|
||||
|
||||
.c.o:
|
||||
$(DO_CC)
|
||||
|
||||
GAME_OBJS = \
|
||||
g_ai.o g_cmds.o g_combat.o g_func.o g_items.o g_main.o g_misc.o \
|
||||
g_monster.o g_phys.o g_save.o g_spawn.o g_svcmds.o \
|
||||
g_target.o g_trigger.o g_turret.o g_utils.o g_weapon.o g_chase.o \
|
||||
p_client.o p_hud.o p_trail.o p_view.o p_weapon.o q_shared.o \
|
||||
m_move.o a_team.o a_game.o a_items.o a_cmds.o a_radio.o a_menu.o \
|
||||
cgf_sfx_glass.o a_doorkick.o
|
||||
|
||||
game$(ARCH).$(SHLIBEXT) : $(GAME_OBJS)
|
||||
$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(GAME_OBJS)
|
||||
|
||||
|
||||
#############################################################################
|
||||
# MISC
|
||||
#############################################################################
|
||||
|
||||
clean:
|
||||
-rm -f $(GAME_OBJS)
|
||||
|
||||
depend:
|
||||
gcc -MM $(GAME_OBJS:.o=.c)
|
||||
|
||||
|
||||
install:
|
||||
cp gamei386.so ../quake2/action
|
||||
|
||||
#
|
||||
# From "make depend"
|
||||
#
|
||||
|
||||
g_ai.o: g_ai.c g_local.h q_shared.h game.h a_team.h a_game.h a_menu.h \
|
||||
a_radio.h
|
||||
g_cmds.o: g_cmds.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h m_player.h
|
||||
g_combat.o: g_combat.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h cgf_sfx_glass.h
|
||||
g_func.o: g_func.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
g_items.o: g_items.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
g_main.o: g_main.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
g_misc.o: g_misc.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h cgf_sfx_glass.h
|
||||
g_monster.o: g_monster.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
g_phys.o: g_phys.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h cgf_sfx_glass.h
|
||||
g_save.o: g_save.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h cgf_sfx_glass.h
|
||||
g_spawn.o: g_spawn.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
g_svcmds.o: g_svcmds.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
g_target.o: g_target.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
g_trigger.o: g_trigger.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
g_turret.o: g_turret.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
g_utils.o: g_utils.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
g_weapon.o: g_weapon.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h cgf_sfx_glass.h
|
||||
g_chase.o: g_chase.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
p_client.o: p_client.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h m_player.h cgf_sfx_glass.h
|
||||
p_hud.o: p_hud.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
p_trail.o: p_trail.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
p_view.o: p_view.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h m_player.h
|
||||
p_weapon.o: p_weapon.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h m_player.h
|
||||
q_shared.o: q_shared.c q_shared.h
|
||||
m_move.o: m_move.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
a_team.o: a_team.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h cgf_sfx_glass.h
|
||||
a_game.o: a_game.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h cgf_sfx_glass.h
|
||||
a_items.o: a_items.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
a_cmds.o: a_cmds.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
a_radio.o: a_radio.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
a_menu.o: a_menu.c g_local.h q_shared.h game.h a_team.h a_game.h \
|
||||
a_menu.h a_radio.h
|
||||
cgf_sfx_glass.o: cgf_sfx_glass.c g_local.h q_shared.h game.h a_team.h \
|
||||
a_game.h a_menu.h a_radio.h cgf_sfx_glass.h
|
||||
a_doorkick.o: a_doorkick.c g_local.h q_shared.h game.h a_team.h \
|
||||
a_game.h a_menu.h a_radio.h
|
770
a_cmds.c
Normal file
770
a_cmds.c
Normal file
|
@ -0,0 +1,770 @@
|
|||
// zucc
|
||||
// File for our new commands.
|
||||
|
||||
// laser sight patch, by Geza Beladi
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
|
||||
|
||||
extern void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result);
|
||||
|
||||
|
||||
/*----------------------------------------
|
||||
SP_LaserSight
|
||||
|
||||
Create/remove the laser sight entity
|
||||
-----------------------------------------*/
|
||||
|
||||
#define lss self->lasersight
|
||||
|
||||
void SP_LaserSight(edict_t *self, gitem_t *item ) {
|
||||
|
||||
vec3_t start,forward,right,end;
|
||||
int laser_on = 0;
|
||||
gitem_t *temp_item;
|
||||
|
||||
|
||||
|
||||
// safe_cprintf(self, PRINT_HIGH, "Calling lasersight function have_laser %d.\n", self->client->have_laser);
|
||||
|
||||
temp_item = FindItem(LASER_NAME);
|
||||
if ( !(self->client->pers.inventory[ITEM_INDEX(temp_item)]) )
|
||||
{
|
||||
if ( lss ) // laser is on
|
||||
{
|
||||
G_FreeEdict(lss);
|
||||
lss = NULL;
|
||||
}
|
||||
// safe_cprintf(self, PRINT_HIGH, "didn't have laser sight.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// zucc code to make it be used with the right weapons
|
||||
|
||||
|
||||
// safe_cprintf(self, PRINT_HIGH, "curr_weap = %d.\n", self->client->curr_weap);
|
||||
|
||||
switch ( self->client->curr_weap )
|
||||
{
|
||||
case MK23_NUM:
|
||||
case MP5_NUM:
|
||||
case M4_NUM:
|
||||
{
|
||||
laser_on = 1;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
laser_on = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// safe_cprintf(self, PRINT_HIGH, "laser_on is %d.\n", laser_on);
|
||||
|
||||
// laser is on but we want it off
|
||||
if ( lss && !laser_on ) {
|
||||
// safe_cprintf(self, PRINT_HIGH, "trying to free the laser sight\n");
|
||||
G_FreeEdict(lss);
|
||||
lss = NULL;
|
||||
//gi.bprintf (PRINT_HIGH, "lasersight off.");
|
||||
return;
|
||||
}
|
||||
|
||||
// safe_cprintf(self, PRINT_HIGH, "laser wasn't lss is %p.\n", lss);
|
||||
|
||||
// off and we want it to stay that way
|
||||
if ( !laser_on )
|
||||
return;
|
||||
|
||||
//gi.bprintf (PRINT_HIGH, "lasersight on.");
|
||||
|
||||
AngleVectors (self->client->v_angle, forward, right, NULL);
|
||||
|
||||
VectorSet(end,100 , 0, 0);
|
||||
G_ProjectSource (self->s.origin, end, forward, right, start);
|
||||
|
||||
lss = G_Spawn ();
|
||||
lss->owner = self;
|
||||
lss->movetype = MOVETYPE_NOCLIP;
|
||||
lss->solid = SOLID_NOT;
|
||||
lss->classname = "lasersight";
|
||||
lss->s.modelindex = gi.modelindex ("sprites/lsight.sp2");
|
||||
lss->s.renderfx = RF_TRANSLUCENT;
|
||||
lss->think = LaserSightThink;
|
||||
lss->nextthink = level.time + 0.01;
|
||||
}
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
LaserSightThink
|
||||
|
||||
Updates the sights position, angle, and shape
|
||||
is the lasersight entity
|
||||
---------------------------------------------*/
|
||||
|
||||
void LaserSightThink (edict_t *self)
|
||||
{
|
||||
vec3_t start,end,endp,offset;
|
||||
vec3_t forward,right,up;
|
||||
vec3_t angles;
|
||||
trace_t tr;
|
||||
int height = 0;
|
||||
|
||||
// zucc compensate for weapon ride up
|
||||
VectorAdd (self->owner->client->v_angle, self->owner->client->kick_angles, angles);
|
||||
AngleVectors (/*self->owner->client->v_angle*/angles, forward, right, up);
|
||||
|
||||
|
||||
if ( self->owner->lasersight != self )
|
||||
{
|
||||
self->think = G_FreeEdict;
|
||||
}
|
||||
|
||||
if (self->owner->client->pers.firing_style == ACTION_FIRING_CLASSIC)
|
||||
height = 8;
|
||||
|
||||
|
||||
VectorSet(offset,24 , 8, self->owner->viewheight- height);
|
||||
|
||||
P_ProjectSource (self->owner->client, self->owner->s.origin, offset, forward, right, start);
|
||||
VectorMA(start,8192,forward,end);
|
||||
|
||||
PRETRACE();
|
||||
tr = gi.trace (start,NULL,NULL, end,self->owner,CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
|
||||
POSTTRACE();
|
||||
|
||||
if (tr.fraction != 1) {
|
||||
VectorMA(tr.endpos,-4,forward,endp);
|
||||
VectorCopy(endp,tr.endpos);
|
||||
}
|
||||
|
||||
vectoangles(tr.plane.normal,self->s.angles);
|
||||
VectorCopy(tr.endpos,self->s.origin);
|
||||
|
||||
gi.linkentity (self);
|
||||
self->nextthink = level.time + 0.1;
|
||||
}
|
||||
|
||||
|
||||
void Cmd_New_Reload_f( edict_t *ent )
|
||||
{
|
||||
//FB 6/1/99 - refuse to reload during LCA
|
||||
if ((int)teamplay->value && lights_camera_action)
|
||||
return;
|
||||
//FB 6/1/99
|
||||
|
||||
ent->client->reload_attempts++;
|
||||
}
|
||||
|
||||
|
||||
//+BD ENTIRE CODE BLOCK NEW
|
||||
// Cmd_Reload_f()
|
||||
// Handles weapon reload requests
|
||||
void Cmd_Reload_f (edict_t *ent)
|
||||
{
|
||||
// int rds_left; //+BD - Variable to handle rounds left
|
||||
|
||||
|
||||
//+BD - If the player is dead, don't bother
|
||||
if(ent->deadflag == DEAD_DEAD)
|
||||
{
|
||||
//safe_centerprintf(ent, "I know you're a hard ass,\nBUT YOU'RE FUCKING DEAD!!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if(ent->client->weaponstate == WEAPON_BANDAGING
|
||||
|| ent->client->bandaging == 1
|
||||
|| ent->client->bandage_stopped == 1
|
||||
|| ent->client->weaponstate == WEAPON_ACTIVATING
|
||||
|| ent->client->weaponstate == WEAPON_DROPPING
|
||||
|| ent->client->weaponstate == WEAPON_FIRING )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ent->client->fast_reload)
|
||||
ent->client->reload_attempts--;
|
||||
if ( ent->client->reload_attempts < 0 )
|
||||
ent->client->reload_attempts = 0;
|
||||
|
||||
//First, grab the current magazine max count...
|
||||
if ( ( ent->client->curr_weap == MK23_NUM )
|
||||
|| ( ent->client->curr_weap == MP5_NUM )
|
||||
|| ( ent->client->curr_weap == M4_NUM )
|
||||
|| ( ent->client->curr_weap == M3_NUM )
|
||||
|| ( ent->client->curr_weap == HC_NUM )
|
||||
|| ( ent->client->curr_weap == SNIPER_NUM )
|
||||
|| ( ent->client->curr_weap == DUAL_NUM ) )
|
||||
{
|
||||
}
|
||||
else //We should never get here, but...
|
||||
//BD 5/26 - Actually we get here quite often right now. Just exit for weaps that we
|
||||
// don't want reloaded or that never reload (grenades)
|
||||
{
|
||||
//safe_centerprintf(ent,"Where'd you train?\nYou can't reload that!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if(ent->client->pers.inventory[ent->client->ammo_index])
|
||||
{
|
||||
//Set the weaponstate...
|
||||
if ( ent->client->curr_weap == M3_NUM )
|
||||
{
|
||||
if (ent->client->shot_rds >= ent->client->shot_max)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// already in the process of reloading!
|
||||
if ( ent->client->weaponstate == WEAPON_RELOADING && (ent->client->shot_rds < (ent->client->shot_max -1)) && !(ent->client->fast_reload) && ((ent->client->pers.inventory[ent->client->ammo_index] -1) > 0 ))
|
||||
{
|
||||
// don't let them start fast reloading until far enough into the firing sequence
|
||||
// this gives them a chance to break off from reloading to fire the weapon - zucc
|
||||
if ( ent->client->ps.gunframe >= 48 )
|
||||
{
|
||||
ent->client->fast_reload = 1;
|
||||
(ent->client->pers.inventory[ent->client->ammo_index])--;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->client->reload_attempts++;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( ent->client->curr_weap == HC_NUM )
|
||||
{
|
||||
if (ent->client->cannon_rds >= ent->client->cannon_max)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(ent->client->pers.inventory[ent->client->ammo_index] >= 2))
|
||||
return;
|
||||
}
|
||||
if ( ent->client->curr_weap == SNIPER_NUM )
|
||||
{
|
||||
if (ent->client->sniper_rds >= ent->client->sniper_max)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// already in the process of reloading!
|
||||
if ( ent->client->weaponstate == WEAPON_RELOADING && (ent->client->sniper_rds < (ent->client->sniper_max -1)) && !(ent->client->fast_reload) && ((ent->client->pers.inventory[ent->client->ammo_index] -1) > 0 ) )
|
||||
{
|
||||
// don't let them start fast reloading until far enough into the firing sequence
|
||||
// this gives them a chance to break off from reloading to fire the weapon - zucc
|
||||
if ( ent->client->ps.gunframe >= 72 )
|
||||
{
|
||||
ent->client->fast_reload = 1;
|
||||
(ent->client->pers.inventory[ent->client->ammo_index])--;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->client->reload_attempts++;
|
||||
}
|
||||
}
|
||||
ent->client->ps.fov = 90;
|
||||
if ( ent->client->pers.weapon )
|
||||
ent->client->ps.gunindex = gi.modelindex( ent->client->pers.weapon->view_model );
|
||||
}
|
||||
if ( ent->client->curr_weap == DUAL_NUM )
|
||||
{
|
||||
if (!(ent->client->pers.inventory[ent->client->ammo_index] >= 2))
|
||||
return;
|
||||
//FIREBLADE 7/11/1999 - stop reloading when weapon already full
|
||||
if (ent->client->dual_rds == ent->client->dual_max)
|
||||
return;
|
||||
}
|
||||
if (ent->client->curr_weap == MP5_NUM)
|
||||
{
|
||||
if (ent->client->mp5_rds == ent->client->mp5_max)
|
||||
return;
|
||||
}
|
||||
if (ent->client->curr_weap == M4_NUM)
|
||||
{
|
||||
if (ent->client->m4_rds == ent->client->m4_max)
|
||||
return;
|
||||
}
|
||||
if (ent->client->curr_weap == MK23_NUM)
|
||||
{
|
||||
if (ent->client->mk23_rds == ent->client->mk23_max)
|
||||
return;
|
||||
}
|
||||
//FIREBLADE
|
||||
|
||||
ent->client->weaponstate = WEAPON_RELOADING;
|
||||
//(ent->client->pers.inventory[ent->client->ammo_index])--;
|
||||
}
|
||||
else
|
||||
safe_cprintf (ent, PRINT_HIGH, "Out of ammo\n");
|
||||
//safe_centerprintf(ent,"Pull your head out-\nYou've got NO AMMO!\n");
|
||||
}
|
||||
//+BD END CODE BLOCK
|
||||
|
||||
void Cmd_New_Weapon_f( edict_t *ent )
|
||||
{
|
||||
ent->client->weapon_attempts++;
|
||||
if ( ent->client->weapon_attempts == 1 )
|
||||
Cmd_Weapon_f(ent);
|
||||
}
|
||||
|
||||
|
||||
// function to change the firing mode of weapons (when appropriate)
|
||||
|
||||
void Cmd_Weapon_f ( edict_t *ent )
|
||||
{
|
||||
int dead;
|
||||
|
||||
dead = (ent->solid == SOLID_NOT || ent->deadflag == DEAD_DEAD);
|
||||
|
||||
ent->client->weapon_attempts--;
|
||||
if ( ent->client->weapon_attempts < 0 )
|
||||
ent->client->weapon_attempts = 0;
|
||||
|
||||
if ( ent->client->bandaging || ent->client->bandage_stopped )
|
||||
{
|
||||
|
||||
safe_cprintf(ent, PRINT_HIGH, "You'll get to your weapon when your done bandaging!\n");
|
||||
ent->client->weapon_attempts++;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ent->client->weaponstate == WEAPON_FIRING || ent->client->weaponstate == WEAPON_BUSY )
|
||||
{
|
||||
//safe_cprintf(ent, PRINT_HIGH, "Try again when you aren't using your weapon.\n");
|
||||
ent->client->weapon_attempts++;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ent->client->curr_weap == MK23_NUM )
|
||||
{
|
||||
if (!dead)
|
||||
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/click.wav"), 1, ATTN_NORM, 0);
|
||||
ent->client->resp.mk23_mode = !(ent->client->resp.mk23_mode);
|
||||
if ( ent->client->resp.mk23_mode )
|
||||
safe_cprintf (ent, PRINT_HIGH, "MK23 Pistol set for semi-automatic action\n");
|
||||
else
|
||||
safe_cprintf (ent, PRINT_HIGH, "MK23 Pistol set for automatic action\n");
|
||||
}
|
||||
if ( ent->client->curr_weap == MP5_NUM )
|
||||
{
|
||||
if (!dead)
|
||||
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/click.wav"), 1, ATTN_NORM, 0);
|
||||
ent->client->resp.mp5_mode = !(ent->client->resp.mp5_mode);
|
||||
if ( ent->client->resp.mp5_mode )
|
||||
safe_cprintf (ent, PRINT_HIGH, "MP5 set to 3 Round Burst mode\n");
|
||||
else
|
||||
safe_cprintf (ent, PRINT_HIGH, "MP5 set to Full Automatic mode\n");
|
||||
}
|
||||
if ( ent->client->curr_weap == M4_NUM )
|
||||
{
|
||||
if (!dead)
|
||||
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/click.wav"), 1, ATTN_NORM, 0);
|
||||
ent->client->resp.m4_mode = !(ent->client->resp.m4_mode);
|
||||
if ( ent->client->resp.m4_mode )
|
||||
safe_cprintf (ent, PRINT_HIGH, "M4 set to 3 Round Burst mode\n");
|
||||
else
|
||||
safe_cprintf (ent, PRINT_HIGH, "M4 set to Full Automatic mode\n");
|
||||
}
|
||||
|
||||
if ( ent->client->curr_weap == SNIPER_NUM )
|
||||
{
|
||||
if (dead)
|
||||
return;
|
||||
if ( ent->client->resp.sniper_mode == SNIPER_1X )
|
||||
{
|
||||
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/lensflik.wav"), 1, ATTN_NORM, 0);
|
||||
ent->client->resp.sniper_mode = SNIPER_2X;
|
||||
ent->client->desired_fov = 45;
|
||||
if ( ent->client->weaponstate != WEAPON_RELOADING )
|
||||
{
|
||||
ent->client->idle_weapon = 6; // 6 frames of idleness
|
||||
ent->client->ps.gunframe = 22;
|
||||
ent->client->weaponstate = WEAPON_BUSY;
|
||||
}
|
||||
}
|
||||
else if ( ent->client->resp.sniper_mode == SNIPER_2X )
|
||||
{
|
||||
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/lensflik.wav"), 1, ATTN_NORM, 0);
|
||||
ent->client->resp.sniper_mode = SNIPER_4X;
|
||||
ent->client->desired_fov = 20;
|
||||
}
|
||||
else if ( ent->client->resp.sniper_mode == SNIPER_4X )
|
||||
{
|
||||
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/lensflik.wav"), 1, ATTN_NORM, 0);
|
||||
ent->client->resp.sniper_mode = SNIPER_6X;
|
||||
ent->client->desired_fov = 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/lensflik.wav"), 1, ATTN_NORM, 0);
|
||||
ent->client->resp.sniper_mode = SNIPER_1X;
|
||||
ent->client->desired_fov = 90;
|
||||
if ( ent->client->pers.weapon )
|
||||
ent->client->ps.gunindex = gi.modelindex( ent->client->pers.weapon->view_model );
|
||||
}
|
||||
|
||||
}
|
||||
if ( ent->client->curr_weap == KNIFE_NUM )
|
||||
{
|
||||
if (dead)
|
||||
return;
|
||||
if ( ent->client->weaponstate == WEAPON_READY )
|
||||
{
|
||||
ent->client->resp.knife_mode = !(ent->client->resp.knife_mode);
|
||||
ent->client->weaponstate = WEAPON_ACTIVATING;
|
||||
if ( ent->client->resp.knife_mode )
|
||||
{
|
||||
safe_cprintf(ent, PRINT_HIGH, "Switching to throwing\n");
|
||||
ent->client->ps.gunframe = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_cprintf(ent, PRINT_HIGH, "Switching to slashing\n");
|
||||
ent->client->ps.gunframe = 106;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if ( ent->client->curr_weap == GRENADE_NUM )
|
||||
{
|
||||
if ( ent->client->resp.grenade_mode == 0 )
|
||||
{
|
||||
safe_cprintf (ent, PRINT_HIGH, "Prepared to make a medium range throw\n");
|
||||
ent->client->resp.grenade_mode = 1;
|
||||
}
|
||||
else if ( ent->client->resp.grenade_mode == 1 )
|
||||
{
|
||||
safe_cprintf (ent, PRINT_HIGH, "Prepared to make a long range throw\n");
|
||||
ent->client->resp.grenade_mode = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_cprintf (ent, PRINT_HIGH, "Prepared to make a short range throw\n");
|
||||
ent->client->resp.grenade_mode = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// sets variable to toggle nearby door status
|
||||
void Cmd_OpenDoor_f (edict_t *ent )
|
||||
{
|
||||
|
||||
ent->client->doortoggle = 1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void Cmd_Bandage_f ( edict_t *ent )
|
||||
{
|
||||
gitem_t *item;
|
||||
|
||||
|
||||
if ( (ent->client->bleeding != 0 || ent->client->leg_damage != 0) && ent->client->bandaging != 1 )
|
||||
ent->client->reload_attempts = 0; // prevent any further reloading
|
||||
|
||||
|
||||
|
||||
if ( (ent->client->weaponstate == WEAPON_READY || ent->client->weaponstate == WEAPON_END_MAG )
|
||||
&& (ent->client->bleeding != 0 || ent->client->leg_damage != 0 )
|
||||
&& ent->client->bandaging != 1 )
|
||||
{
|
||||
|
||||
// zucc - check if they have a primed grenade
|
||||
|
||||
if ( ent->client->curr_weap == GRENADE_NUM
|
||||
&& ( ( ent->client->ps.gunframe >= GRENADE_IDLE_FIRST
|
||||
&& ent->client->ps.gunframe <= GRENADE_IDLE_LAST )
|
||||
|| ( ent->client->ps.gunframe >= GRENADE_THROW_FIRST
|
||||
&& ent->client->ps.gunframe <= GRENADE_THROW_LAST ) ) )
|
||||
{
|
||||
ent->client->ps.gunframe = 0;
|
||||
fire_grenade2 (ent, ent->s.origin, tv(0,0,0), GRENADE_DAMRAD, 0, 2, GRENADE_DAMRAD*2, false);
|
||||
item = FindItem(GRENADE_NAME);
|
||||
ent->client->pers.inventory[ITEM_INDEX(item)]--;
|
||||
if ( ent->client->pers.inventory[ITEM_INDEX(item)] <= 0 )
|
||||
{
|
||||
ent->client->newweapon = FindItem( MK23_NAME );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ent->client->bandaging = 1;
|
||||
ent->client->resp.sniper_mode = SNIPER_1X;
|
||||
ent->client->ps.fov = 90;
|
||||
ent->client->desired_fov = 90;
|
||||
if ( ent->client->pers.weapon )
|
||||
ent->client->ps.gunindex = gi.modelindex( ent->client->pers.weapon->view_model );
|
||||
|
||||
}
|
||||
else if ( ent->client->bandaging == 1 )
|
||||
safe_cprintf (ent, PRINT_HIGH, "Already bandaging\n");
|
||||
//FIREBLADE 12/26/98 - fix inappropriate message
|
||||
else if (ent->client->bleeding == 0 && ent->client->leg_damage == 0)
|
||||
safe_cprintf (ent, PRINT_HIGH, "No need to bandage\n");
|
||||
else
|
||||
safe_cprintf(ent, PRINT_HIGH, "Can't bandage now\n");
|
||||
//FIREBLADE
|
||||
}
|
||||
// function called in generic_weapon function that does the bandaging
|
||||
|
||||
void Bandage( edict_t* ent )
|
||||
{
|
||||
ent->client->leg_noise = 0;
|
||||
ent->client->leg_damage = 0;
|
||||
ent->client->leghits = 0;
|
||||
ent->client->bleeding = 0;
|
||||
ent->client->bleed_remain = 0;
|
||||
// ent->client->bleedcount = 0;
|
||||
// ent->client->bleeddelay = 0;
|
||||
ent->client->bandaging = 0;
|
||||
ent->client->leg_dam_count = 0;
|
||||
ent->client->attacker = NULL;
|
||||
ent->client->bandage_stopped = 1;
|
||||
ent->client->idle_weapon = BANDAGE_TIME;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Cmd_ID_f (edict_t *ent )
|
||||
{
|
||||
|
||||
if (!ent->client->resp.id) {
|
||||
safe_cprintf(ent, PRINT_HIGH, "Disabling player identification display.\n");
|
||||
ent->client->resp.id = 1;
|
||||
} else {
|
||||
safe_cprintf(ent, PRINT_HIGH, "Activating player identification display.\n");
|
||||
ent->client->resp.id = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static void loc_buildboxpoints(vec3_t p[8], vec3_t org, vec3_t mins, vec3_t maxs)
|
||||
{
|
||||
VectorAdd(org, mins, p[0]);
|
||||
VectorCopy(p[0], p[1]);
|
||||
p[1][0] -= mins[0];
|
||||
VectorCopy(p[0], p[2]);
|
||||
p[2][1] -= mins[1];
|
||||
VectorCopy(p[0], p[3]);
|
||||
p[3][0] -= mins[0];
|
||||
p[3][1] -= mins[1];
|
||||
VectorAdd(org, maxs, p[4]);
|
||||
VectorCopy(p[4], p[5]);
|
||||
p[5][0] -= maxs[0];
|
||||
VectorCopy(p[0], p[6]);
|
||||
p[6][1] -= maxs[1];
|
||||
VectorCopy(p[0], p[7]);
|
||||
p[7][0] -= maxs[0];
|
||||
p[7][1] -= maxs[1];
|
||||
}
|
||||
|
||||
qboolean loc_CanSee (edict_t *targ, edict_t *inflictor)
|
||||
{
|
||||
trace_t trace;
|
||||
vec3_t targpoints[8];
|
||||
int i;
|
||||
vec3_t viewpoint;
|
||||
|
||||
// bmodels need special checking because their origin is 0,0,0
|
||||
if (targ->movetype == MOVETYPE_PUSH)
|
||||
return false; // bmodels not supported
|
||||
|
||||
loc_buildboxpoints(targpoints, targ->s.origin, targ->mins, targ->maxs);
|
||||
|
||||
VectorCopy(inflictor->s.origin, viewpoint);
|
||||
viewpoint[2] += inflictor->viewheight;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
PRETRACE();
|
||||
trace = gi.trace (viewpoint, vec3_origin, vec3_origin, targpoints[i], inflictor, MASK_SOLID);
|
||||
POSTTRACE();
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// originally from Zoid's CTF
|
||||
void SetIDView(edict_t *ent)
|
||||
{
|
||||
vec3_t forward, dir;
|
||||
trace_t tr;
|
||||
edict_t *who, *best;
|
||||
//FIREBLADE, suggested by hal[9k] 3/11/1999
|
||||
float bd = 0.9;
|
||||
//FIREBLADE
|
||||
float d;
|
||||
int i;
|
||||
|
||||
ent->client->ps.stats[STAT_ID_VIEW] = 0;
|
||||
|
||||
//FIREBLADE
|
||||
if (ent->solid != SOLID_NOT && !teamplay->value)
|
||||
{
|
||||
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
|
||||
return; // won't ever work in non-teams so don't run the code...
|
||||
}
|
||||
|
||||
if (ent->client->chase_mode)
|
||||
{
|
||||
if (ent->client->chase_target &&
|
||||
ent->client->chase_target->inuse)
|
||||
{
|
||||
ent->client->ps.stats[STAT_ID_VIEW] =
|
||||
CS_PLAYERSKINS +
|
||||
(ent->client->chase_target - g_edicts - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
//FIREBLADE
|
||||
|
||||
if ( ent->client->resp.id == 1 )
|
||||
return;
|
||||
|
||||
AngleVectors(ent->client->v_angle, forward, NULL, NULL);
|
||||
VectorScale(forward, 8192, forward);
|
||||
VectorAdd(ent->s.origin, forward, forward);
|
||||
PRETRACE();
|
||||
tr = gi.trace(ent->s.origin, NULL, NULL, forward, ent, MASK_SOLID);
|
||||
POSTTRACE();
|
||||
if (tr.fraction < 1 && tr.ent && tr.ent->client) {
|
||||
ent->client->ps.stats[STAT_ID_VIEW] =
|
||||
CS_PLAYERSKINS + (ent - g_edicts - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
AngleVectors(ent->client->v_angle, forward, NULL, NULL);
|
||||
best = NULL;
|
||||
for (i = 1; i <= maxclients->value; i++) {
|
||||
who = g_edicts + i;
|
||||
if (!who->inuse)
|
||||
continue;
|
||||
VectorSubtract(who->s.origin, ent->s.origin, dir);
|
||||
VectorNormalize(dir);
|
||||
d = DotProduct(forward, dir);
|
||||
if (d > bd && loc_CanSee(ent, who) &&
|
||||
//FIREBLADE
|
||||
(who->solid != SOLID_NOT || who->deadflag == DEAD_DEAD) &&
|
||||
(ent->solid == SOLID_NOT || OnSameTeam(ent, who)))
|
||||
{
|
||||
//FIREBLADE
|
||||
bd = d;
|
||||
best = who;
|
||||
}
|
||||
}
|
||||
if (best != NULL && bd > 0.90)
|
||||
{
|
||||
ent->client->ps.stats[STAT_ID_VIEW] =
|
||||
CS_PLAYERSKINS + (best - g_edicts - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Cmd_IR_f (edict_t *ent )
|
||||
{
|
||||
int band = 0;
|
||||
if ( ir->value )
|
||||
{
|
||||
if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(BAND_NAME))] )
|
||||
{
|
||||
band = 1;
|
||||
}
|
||||
if ( ent->client->resp.ir == 0 )
|
||||
{
|
||||
ent->client->resp.ir = 1;
|
||||
if ( band )
|
||||
safe_cprintf(ent, PRINT_HIGH, "IR vision disabled.\n");
|
||||
else
|
||||
safe_cprintf(ent, PRINT_HIGH, "IR vision will be disabled when you get a bandolier.\n");
|
||||
// if ( ent->client->ps.rdflags & RDF_IRGOGGLES )
|
||||
// ent->client->ps.rdflags &= ~RDF_IRGOGGLES;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->client->resp.ir = 0;
|
||||
if ( band )
|
||||
safe_cprintf(ent, PRINT_HIGH, "IR vision enabled.\n");
|
||||
else
|
||||
safe_cprintf(ent, PRINT_HIGH, "IR vision will be enabled when you get a bandolier.\n");
|
||||
// if ( !(ent->client->ps.rdflags & RDF_IRGOGGLES) )
|
||||
// ent->client->ps.rdflags |= RDF_IRGOGGLES;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_cprintf(ent, PRINT_HIGH, "IR vision not enabled on this server.\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// zucc choose command, avoids using the menus in teamplay
|
||||
|
||||
void Cmd_Choose_f (edict_t *ent)
|
||||
{
|
||||
|
||||
char *s;
|
||||
|
||||
s = gi.args();
|
||||
|
||||
// only works in teamplay
|
||||
if (!teamplay->value)
|
||||
return;
|
||||
|
||||
// convert names a player might try
|
||||
if (!stricmp(s, "A 2nd pistol") || !stricmp(s, "railgun"))
|
||||
s = DUAL_NAME;
|
||||
if (!stricmp(s, "shotgun"))
|
||||
s = M3_NAME;
|
||||
if (!stricmp(s, "machinegun"))
|
||||
s = HC_NAME;
|
||||
if (!stricmp(s, "super shotgun"))
|
||||
s = MP5_NAME;
|
||||
if (!stricmp(s, "chaingun"))
|
||||
s = SNIPER_NAME;
|
||||
if (!stricmp(s, "bfg10k"))
|
||||
s = KNIFE_NAME;
|
||||
if (!stricmp(s, "grenade launcher"))
|
||||
s = M4_NAME;
|
||||
|
||||
if ( stricmp(s, MP5_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(MP5_NAME);
|
||||
else if ( stricmp(s, M3_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(M3_NAME);
|
||||
else if ( stricmp(s, M4_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(M4_NAME);
|
||||
else if ( stricmp(s, HC_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(HC_NAME);
|
||||
else if ( stricmp(s, SNIPER_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(SNIPER_NAME);
|
||||
else if ( stricmp(s, KNIFE_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(KNIFE_NAME);
|
||||
else if ( stricmp(s, DUAL_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(DUAL_NAME);
|
||||
else if ( stricmp(s, KEV_NAME) == 0 )
|
||||
ent->client->resp.item = FindItem(KEV_NAME);
|
||||
else if ( stricmp(s, LASER_NAME) == 0 )
|
||||
ent->client->resp.item = FindItem(LASER_NAME);
|
||||
else if ( stricmp(s, SLIP_NAME) == 0 )
|
||||
ent->client->resp.item = FindItem(SLIP_NAME);
|
||||
else if ( stricmp(s, SIL_NAME) == 0 )
|
||||
ent->client->resp.item = FindItem(SIL_NAME);
|
||||
else if ( stricmp(s, BAND_NAME) == 0 )
|
||||
ent->client->resp.item = FindItem(BAND_NAME);
|
||||
else
|
||||
{
|
||||
safe_cprintf(ent, PRINT_HIGH, "Invalid weapon or item choice.\n");
|
||||
return;
|
||||
}
|
||||
safe_cprintf(ent, PRINT_HIGH, "Weapon selected: %s\nItem selected: %s\n", (ent->client->resp.weapon)->pickup_name, (ent->client->resp.item)->pickup_name);
|
||||
|
||||
}
|
||||
|
135
a_doorkick.c
Normal file
135
a_doorkick.c
Normal file
|
@ -0,0 +1,135 @@
|
|||
/* a_doorkick.c
|
||||
* Door kicking code by hal[9k]
|
||||
* originally for AQ:Espionage (http://aqdt.fear.net/)
|
||||
* email: hal9000@telefragged.com
|
||||
* Assembled here by Homer (homer@fear.net)
|
||||
*/
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
#define STATE_TOP 0
|
||||
#define STATE_BOTTOM 1
|
||||
#define STATE_UP 2
|
||||
#define STATE_DOWN 3
|
||||
|
||||
#define DOOR_START_OPEN 1
|
||||
#define DOOR_REVERSE 2
|
||||
|
||||
extern void door_use (edict_t *self, edict_t *other, edict_t *activator);
|
||||
|
||||
// needed for KickDoor
|
||||
void VectorRotate(vec3_t in, vec3_t angles, vec3_t out)
|
||||
{
|
||||
float cv, sv, angle, tv;
|
||||
|
||||
VectorCopy(in, out);
|
||||
|
||||
angle = (-angles[PITCH]) * M_PI / 180;
|
||||
cv = cos(angle);
|
||||
sv = sin(angle);
|
||||
tv = (out[0] * cv) - (out[2] * sv);
|
||||
|
||||
out[2] = (out[2] * cv) + (out[0] * sv);
|
||||
out[0] = tv;
|
||||
|
||||
angle = (angles[YAW]) * M_PI / 180;
|
||||
|
||||
cv = cos(angle);
|
||||
sv = sin(angle);
|
||||
tv = (out[0] * cv) - (out[1] * sv);
|
||||
|
||||
out[1] = (out[1] * cv) + (out[0] * sv);
|
||||
out[0] = tv;
|
||||
angle = (angles[ROLL]) * M_PI / 180;
|
||||
|
||||
cv = cos(angle);
|
||||
sv = sin(angle);
|
||||
tv = (out[1] * cv) - (out[2] * sv);
|
||||
out[2] = (out[2] * cv) + (out[1] * sv);
|
||||
out[1] = tv;
|
||||
}
|
||||
|
||||
int KickDoor( trace_t *tr_old, edict_t *ent, vec3_t forward )
|
||||
{
|
||||
trace_t tr;
|
||||
|
||||
vec3_t d_forward, right, end;
|
||||
float d;
|
||||
if ( !Q_strcasecmp( tr_old->ent->classname, "func_door_rotating" ) )
|
||||
{
|
||||
// Make that the door is closed
|
||||
|
||||
tr = *tr_old;
|
||||
#if 1
|
||||
if ( (!(tr.ent->spawnflags & DOOR_START_OPEN) &&
|
||||
!(tr.ent->moveinfo.state == STATE_TOP)) ||
|
||||
( (tr.ent->spawnflags & DOOR_START_OPEN) &&
|
||||
!(tr.ent->moveinfo.state == STATE_BOTTOM)) )
|
||||
#else
|
||||
if ( (!(tr.ent->spawnflags & DOOR_START_OPEN) &&
|
||||
((tr.ent->moveinfo.state == STATE_BOTTOM) ||
|
||||
(tr.ent->moveinfo.state == STATE_DOWN))) ||
|
||||
((tr.ent->spawnflags & DOOR_START_OPEN) &&
|
||||
((tr.ent->moveinfo.state == STATE_TOP) ||
|
||||
(tr.ent->moveinfo.state == STATE_UP))) )
|
||||
#endif
|
||||
{
|
||||
//gi.dprintf( "Kicking a closed door\n" );
|
||||
|
||||
// Find out if we are on the "outside"
|
||||
|
||||
#if 0
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_RAILTRAIL);
|
||||
gi.WritePosition (tr.ent->s.origin);
|
||||
gi.WritePosition (tr.endpos);
|
||||
gi.multicast (tr.ent->s.origin, MULTICAST_PHS);
|
||||
#endif
|
||||
VectorSubtract( tr.endpos, tr.ent->s.origin, d_forward );
|
||||
|
||||
forward[2] = 0;
|
||||
d_forward[2] = 0;
|
||||
VectorNormalize( forward );
|
||||
VectorNormalize( d_forward );
|
||||
VectorSet( right, 0, 90, 0 );
|
||||
VectorRotate( d_forward, right, d_forward );
|
||||
|
||||
d = DotProduct( forward, d_forward );
|
||||
if ( tr.ent->spawnflags & DOOR_REVERSE )
|
||||
d = -d;
|
||||
// d = sin( acos( d ) );
|
||||
if ( d > 0.0 )
|
||||
{
|
||||
// gi.dprintf( "we think we are on the outside\n" );
|
||||
//if ( tr.ent->spawnflags & DOOR_REVERSE )
|
||||
// gi.dprintf( "but DOOR_REVERSE is set\n" );
|
||||
// Only use the door if it's not already opening
|
||||
if ( (!( tr.ent->spawnflags & DOOR_START_OPEN ) &&
|
||||
!( tr.ent->moveinfo.state == STATE_UP )) ||
|
||||
((tr.ent->spawnflags & DOOR_START_OPEN ) &&
|
||||
(tr.ent->moveinfo.state == STATE_DOWN) ) )
|
||||
door_use( tr.ent, ent, ent );
|
||||
// Find out if someone else is on the other side
|
||||
VectorMA( tr.endpos, 25, forward, end );
|
||||
PRETRACE();
|
||||
tr = gi.trace (tr.endpos, NULL, NULL, end, tr.ent, MASK_SHOT);
|
||||
POSTTRACE();
|
||||
if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
|
||||
{
|
||||
if (tr.fraction < 1.0)
|
||||
{
|
||||
if (tr.ent->client)
|
||||
{
|
||||
//gi.dprintf("we found a client on the other side\n");
|
||||
*tr_old = tr;
|
||||
return( 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return( 0 );
|
||||
}
|
39
a_game.h
Normal file
39
a_game.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Include for base Action game-related things
|
||||
*/
|
||||
|
||||
#define ACTION_VERSION "1.52"
|
||||
|
||||
extern char team1_name[];
|
||||
extern char team2_name[];
|
||||
extern char team1_skin[];
|
||||
extern char team2_skin[];
|
||||
extern char team1_skin_index[];
|
||||
extern char team2_skin_index[];
|
||||
extern char *map_rotation[];
|
||||
extern int num_maps, cur_map;
|
||||
extern char *tnames[];
|
||||
extern int *took_damage;
|
||||
|
||||
void ReadConfigFile();
|
||||
void ReadMOTDFile();
|
||||
void PrintMOTD(edict_t *);
|
||||
void stuffcmd(edict_t *, char *);
|
||||
int KickDoor( trace_t *tr_old, edict_t *ent, vec3_t forward );
|
||||
|
||||
// Prototypes of base Q2 functions that weren't included in any Q2 header
|
||||
qboolean loc_CanSee(edict_t *, edict_t *);
|
||||
qboolean IsNeutral(edict_t *);
|
||||
qboolean IsFemale(edict_t *);
|
||||
void ParseSayText(edict_t *, char *);
|
||||
|
||||
// Firing styles (where shots originate from)
|
||||
#define ACTION_FIRING_CENTER 0
|
||||
#define ACTION_FIRING_CLASSIC 1
|
||||
#define ACTION_FIRING_CLASSIC_HIGH 2
|
||||
|
||||
// maxs[2] of a player when crouching (we modify it from the normal 4)
|
||||
// ...also the modified viewheight -FB 7/18/99
|
||||
#define CROUCHING_MAXS2 16
|
||||
#define CROUCHING_VIEWHEIGHT 8
|
||||
|
202
a_items.c
Normal file
202
a_items.c
Normal file
|
@ -0,0 +1,202 @@
|
|||
/************************************************************************
|
||||
* Special item spawning/management code. Mainly hacked from CTF, thanks
|
||||
* Zoid.
|
||||
* - zucc
|
||||
*/
|
||||
#include "g_local.h"
|
||||
|
||||
|
||||
|
||||
// time too wait between failures to respawn?
|
||||
#define SPEC_RESPAWN_TIME 60
|
||||
// time before they will get respawned
|
||||
#define SPEC_TECH_TIMEOUT 60
|
||||
|
||||
char *tnames[] = {
|
||||
"item_quiet", "item_slippers", "item_vest", "item_band", "item_lasersight",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
void SpecThink(edict_t *spec);
|
||||
|
||||
|
||||
static edict_t *FindSpecSpawn(void)
|
||||
{
|
||||
edict_t *spot = NULL;
|
||||
int i = rand() % 16;
|
||||
|
||||
while (i--)
|
||||
spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
|
||||
if (!spot)
|
||||
spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
|
||||
|
||||
if (spot == NULL)
|
||||
{
|
||||
/* FB 6/24/99
|
||||
gi.dprintf("Warning: failed to find special item spawn point!\n");
|
||||
*/
|
||||
}
|
||||
|
||||
return spot;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void SpawnSpec(gitem_t *item, edict_t *spot)
|
||||
{
|
||||
edict_t *ent;
|
||||
vec3_t forward, right;
|
||||
vec3_t angles;
|
||||
|
||||
ent = G_Spawn();
|
||||
|
||||
ent->classname = item->classname;
|
||||
ent->item = item;
|
||||
ent->spawnflags = DROPPED_ITEM;
|
||||
ent->s.effects = item->world_model_flags;
|
||||
ent->s.renderfx = RF_GLOW;
|
||||
VectorSet (ent->mins, -15, -15, -15);
|
||||
VectorSet (ent->maxs, 15, 15, 15);
|
||||
// zucc dumb hack to make laser look like it is on the ground
|
||||
if (stricmp(item->pickup_name, LASER_NAME) == 0)
|
||||
{
|
||||
VectorSet (ent->mins, -15, -15, -1);
|
||||
VectorSet (ent->maxs, 15, 15, 1);
|
||||
}
|
||||
gi.setmodel (ent, ent->item->world_model);
|
||||
ent->solid = SOLID_TRIGGER;
|
||||
ent->movetype = MOVETYPE_TOSS;
|
||||
ent->touch = Touch_Item;
|
||||
ent->owner = ent;
|
||||
|
||||
angles[0] = 0;
|
||||
angles[1] = rand() % 360;
|
||||
angles[2] = 0;
|
||||
|
||||
AngleVectors (angles, forward, right, NULL);
|
||||
VectorCopy (spot->s.origin, ent->s.origin);
|
||||
ent->s.origin[2] += 16;
|
||||
VectorScale (forward, 100, ent->velocity);
|
||||
ent->velocity[2] = 300;
|
||||
|
||||
ent->nextthink = level.time + SPEC_RESPAWN_TIME;
|
||||
ent->think = SpecThink;
|
||||
|
||||
gi.linkentity (ent);
|
||||
}
|
||||
|
||||
void SpawnSpecs(edict_t *ent)
|
||||
{
|
||||
gitem_t *spec;
|
||||
edict_t *spot;
|
||||
int i;
|
||||
i = 0;
|
||||
while (tnames[i]) {
|
||||
if ((spec = FindItemByClassname(tnames[i])) != NULL &&
|
||||
(spot = FindSpecSpawn()) != NULL)
|
||||
SpawnSpec(spec, spot);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SpecThink(edict_t *spec)
|
||||
{
|
||||
edict_t *spot;
|
||||
|
||||
if ((spot = FindSpecSpawn()) != NULL) {
|
||||
SpawnSpec(spec->item, spot);
|
||||
G_FreeEdict(spec);
|
||||
} else {
|
||||
spec->nextthink = level.time + SPEC_RESPAWN_TIME;
|
||||
spec->think = SpecThink;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void MakeTouchSpecThink (edict_t *ent)
|
||||
{
|
||||
ent->touch = Touch_Item;
|
||||
|
||||
if ( deathmatch->value && !teamplay->value && !allitem->value )
|
||||
{
|
||||
ent->nextthink = level.time + SPEC_RESPAWN_TIME - 1;
|
||||
ent->think = SpecThink;
|
||||
}
|
||||
else if ( teamplay->value && !allitem->value )
|
||||
{
|
||||
ent->nextthink = level.time + 60;
|
||||
ent->think = G_FreeEdict;
|
||||
}
|
||||
else // allitem->value is set
|
||||
{
|
||||
ent->nextthink = level.time + 1;
|
||||
ent->think = G_FreeEdict;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Drop_Spec(edict_t *ent, gitem_t *item)
|
||||
{
|
||||
edict_t *spec;
|
||||
|
||||
spec = Drop_Item(ent, item);
|
||||
//gi.cprintf(ent, PRINT_HIGH, "Dropping special item.\n");
|
||||
spec->nextthink = level.time + 1;
|
||||
spec->think = MakeTouchSpecThink;
|
||||
//zucc this and the one below should probably be -- not = 0, if
|
||||
// a server turns on multiple item pickup.
|
||||
ent->client->pers.inventory[ITEM_INDEX(item)]--;
|
||||
}
|
||||
|
||||
void DeadDropSpec(edict_t *ent)
|
||||
{
|
||||
gitem_t *spec;
|
||||
edict_t *dropped;
|
||||
int i;
|
||||
|
||||
i = 0;
|
||||
while (tnames[i]) {
|
||||
if ((spec = FindItemByClassname(tnames[i])) != NULL &&
|
||||
ent->client->pers.inventory[ITEM_INDEX(spec)]) {
|
||||
dropped = Drop_Item(ent, spec);
|
||||
// hack the velocity to make it bounce random
|
||||
dropped->velocity[0] = (rand() % 600) - 300;
|
||||
dropped->velocity[1] = (rand() % 600) - 300;
|
||||
dropped->nextthink = level.time + 1;
|
||||
dropped->think = MakeTouchSpecThink;
|
||||
dropped->owner = NULL;
|
||||
ent->client->pers.inventory[ITEM_INDEX(spec)] = 0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// frees the passed edict!
|
||||
void RespawnSpec(edict_t *ent)
|
||||
{
|
||||
edict_t *spot;
|
||||
|
||||
if ((spot = FindSpecSpawn()) != NULL)
|
||||
SpawnSpec(ent->item, spot);
|
||||
G_FreeEdict(ent);
|
||||
}
|
||||
|
||||
void SetupSpecSpawn(void)
|
||||
{
|
||||
edict_t *ent;
|
||||
|
||||
if (level.specspawn)
|
||||
return;
|
||||
|
||||
//gi.bprintf (PRINT_HIGH, "got into the setup\n");
|
||||
|
||||
ent = G_Spawn();
|
||||
ent->nextthink = level.time + 4;
|
||||
ent->think = SpawnSpecs;
|
||||
level.specspawn = 1;
|
||||
}
|
194
a_menu.c
Normal file
194
a_menu.c
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Action (formerly Axshun) menu code
|
||||
* This is directly from Zoid's CTF code. Thanks to Zoid again.
|
||||
* -Fireblade
|
||||
*/
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
void PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num)
|
||||
{
|
||||
pmenuhnd_t *hnd;
|
||||
pmenu_t *p;
|
||||
int i;
|
||||
|
||||
if (!ent->client)
|
||||
return;
|
||||
|
||||
if (ent->client->menu) {
|
||||
gi.dprintf("warning, ent already has a menu\n");
|
||||
PMenu_Close(ent);
|
||||
}
|
||||
|
||||
hnd = gi.TagMalloc(sizeof(*hnd), TAG_GAME);
|
||||
|
||||
hnd->entries = entries;
|
||||
hnd->num = num;
|
||||
|
||||
if (cur < 0 || !entries[cur].SelectFunc) {
|
||||
for (i = 0, p = entries; i < num; i++, p++)
|
||||
if (p->SelectFunc)
|
||||
break;
|
||||
} else
|
||||
i = cur;
|
||||
|
||||
if (i >= num)
|
||||
hnd->cur = -1;
|
||||
else
|
||||
hnd->cur = i;
|
||||
|
||||
ent->client->showscores = true;
|
||||
ent->client->inmenu = true;
|
||||
ent->client->menu = hnd;
|
||||
|
||||
PMenu_Update(ent);
|
||||
gi.unicast (ent, true);
|
||||
}
|
||||
|
||||
void PMenu_Close(edict_t *ent)
|
||||
{
|
||||
if (!ent->client->menu)
|
||||
return;
|
||||
|
||||
gi.TagFree(ent->client->menu);
|
||||
ent->client->menu = NULL;
|
||||
ent->client->showscores = false;
|
||||
}
|
||||
|
||||
void PMenu_Update(edict_t *ent)
|
||||
{
|
||||
char string[1400];
|
||||
int i;
|
||||
pmenu_t *p;
|
||||
int x;
|
||||
pmenuhnd_t *hnd;
|
||||
char *t;
|
||||
qboolean alt = false;
|
||||
|
||||
if (!ent->client->menu) {
|
||||
gi.dprintf("warning: ent has no menu\n");
|
||||
return;
|
||||
}
|
||||
|
||||
hnd = ent->client->menu;
|
||||
|
||||
strcpy(string, "xv 32 yv 8 ");
|
||||
|
||||
for (i = 0, p = hnd->entries; i < hnd->num; i++, p++) {
|
||||
if (!p->text || !*(p->text))
|
||||
continue; // blank line
|
||||
t = p->text;
|
||||
if (*t == '*') {
|
||||
alt = true;
|
||||
t++;
|
||||
}
|
||||
sprintf(string + strlen(string), "yv %d ", 32 + i * 8);
|
||||
if (p->align == PMENU_ALIGN_CENTER)
|
||||
x = 196/2 - strlen(t)*4 + 64;
|
||||
else if (p->align == PMENU_ALIGN_RIGHT)
|
||||
x = 64 + (196 - strlen(t)*8);
|
||||
else
|
||||
x = 64;
|
||||
|
||||
sprintf(string + strlen(string), "xv %d ",
|
||||
x - ((hnd->cur == i) ? 8 : 0));
|
||||
|
||||
if (hnd->cur == i)
|
||||
sprintf(string + strlen(string), "string2 \"\x0d%s\" ", t);
|
||||
else if (alt)
|
||||
sprintf(string + strlen(string), "string2 \"%s\" ", t);
|
||||
else
|
||||
sprintf(string + strlen(string), "string \"%s\" ", t);
|
||||
alt = false;
|
||||
}
|
||||
|
||||
gi.WriteByte (svc_layout);
|
||||
gi.WriteString (string);
|
||||
}
|
||||
|
||||
void PMenu_Next(edict_t *ent)
|
||||
{
|
||||
pmenuhnd_t *hnd;
|
||||
int i;
|
||||
pmenu_t *p;
|
||||
|
||||
if (!ent->client->menu) {
|
||||
gi.dprintf("warning: ent has no menu\n");
|
||||
return;
|
||||
}
|
||||
|
||||
hnd = ent->client->menu;
|
||||
|
||||
if (hnd->cur < 0)
|
||||
return; // no selectable entries
|
||||
|
||||
i = hnd->cur;
|
||||
p = hnd->entries + hnd->cur;
|
||||
do {
|
||||
i++, p++;
|
||||
if (i == hnd->num)
|
||||
i = 0, p = hnd->entries;
|
||||
if (p->SelectFunc)
|
||||
break;
|
||||
} while (i != hnd->cur);
|
||||
|
||||
hnd->cur = i;
|
||||
|
||||
PMenu_Update(ent);
|
||||
gi.unicast (ent, true);
|
||||
}
|
||||
|
||||
void PMenu_Prev(edict_t *ent)
|
||||
{
|
||||
pmenuhnd_t *hnd;
|
||||
int i;
|
||||
pmenu_t *p;
|
||||
|
||||
if (!ent->client->menu) {
|
||||
gi.dprintf("warning: ent has no menu\n");
|
||||
return;
|
||||
}
|
||||
|
||||
hnd = ent->client->menu;
|
||||
|
||||
if (hnd->cur < 0)
|
||||
return; // no selectable entries
|
||||
|
||||
i = hnd->cur;
|
||||
p = hnd->entries + hnd->cur;
|
||||
do {
|
||||
if (i == 0) {
|
||||
i = hnd->num - 1;
|
||||
p = hnd->entries + i;
|
||||
} else
|
||||
i--, p--;
|
||||
if (p->SelectFunc)
|
||||
break;
|
||||
} while (i != hnd->cur);
|
||||
|
||||
hnd->cur = i;
|
||||
|
||||
PMenu_Update(ent);
|
||||
gi.unicast (ent, true);
|
||||
}
|
||||
|
||||
void PMenu_Select(edict_t *ent)
|
||||
{
|
||||
pmenuhnd_t *hnd;
|
||||
pmenu_t *p;
|
||||
|
||||
if (!ent->client->menu) {
|
||||
gi.dprintf("warning: ent has no menu\n");
|
||||
return;
|
||||
}
|
||||
|
||||
hnd = ent->client->menu;
|
||||
|
||||
if (hnd->cur < 0)
|
||||
return; // no selectable entries
|
||||
|
||||
p = hnd->entries + hnd->cur;
|
||||
|
||||
if (p->SelectFunc)
|
||||
p->SelectFunc(ent, p);
|
||||
}
|
30
a_menu.h
Normal file
30
a_menu.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Action (formerly Axshun) menus
|
||||
* From Zoid's CTF.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PMENU_ALIGN_LEFT,
|
||||
PMENU_ALIGN_CENTER,
|
||||
PMENU_ALIGN_RIGHT
|
||||
};
|
||||
|
||||
typedef struct pmenuhnd_s {
|
||||
struct pmenu_s *entries;
|
||||
int cur;
|
||||
int num;
|
||||
} pmenuhnd_t;
|
||||
|
||||
typedef struct pmenu_s {
|
||||
char *text;
|
||||
int align;
|
||||
void *arg;
|
||||
void (*SelectFunc)(edict_t *ent, struct pmenu_s *entry);
|
||||
} pmenu_t;
|
||||
|
||||
void PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num);
|
||||
void PMenu_Close(edict_t *ent);
|
||||
void PMenu_Update(edict_t *ent);
|
||||
void PMenu_Next(edict_t *ent);
|
||||
void PMenu_Prev(edict_t *ent);
|
||||
void PMenu_Select(edict_t *ent);
|
703
a_radio.c
Normal file
703
a_radio.c
Normal file
|
@ -0,0 +1,703 @@
|
|||
/*
|
||||
* Radio-related code for Action (formerly Axshun)
|
||||
*
|
||||
* -Fireblade
|
||||
*/
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0, qboolean partner_msg);
|
||||
|
||||
// Each of the possible radio messages and their length
|
||||
|
||||
radio_msg_t male_radio_msgs[] = {
|
||||
{ "1", 6 },
|
||||
{ "2", 6 },
|
||||
{ "3", 8 },
|
||||
{ "4", 7 },
|
||||
{ "5", 8 },
|
||||
{ "6", 9 },
|
||||
{ "7", 8 },
|
||||
{ "8", 7 },
|
||||
{ "9", 7 },
|
||||
{ "10", 6 },
|
||||
{ "back", 6 },
|
||||
{ "cover", 7 },
|
||||
{ "down", 13 },
|
||||
{ "enemyd", 10 },
|
||||
{ "enemys", 9 },
|
||||
{ "forward", 6 },
|
||||
{ "go", 6 },
|
||||
{ "im_hit", 7 },
|
||||
{ "left", 7 },
|
||||
{ "reportin", 9 },
|
||||
{ "right", 6 },
|
||||
{ "taking_f", 22 },
|
||||
{ "teamdown", 13 },
|
||||
{ "treport", 12 },
|
||||
{ "up", 4 },
|
||||
{ "END", 0 }, // end of list delimiter
|
||||
};
|
||||
|
||||
radio_msg_t female_radio_msgs[] = {
|
||||
{ "1", 5 },
|
||||
{ "2", 5 },
|
||||
{ "3", 5 },
|
||||
{ "4", 5 },
|
||||
{ "5", 5 },
|
||||
{ "6", 8 },
|
||||
{ "7", 7 },
|
||||
{ "8", 5 },
|
||||
{ "9", 5 },
|
||||
{ "10", 5 },
|
||||
{ "back", 6 },
|
||||
{ "cover", 5 },
|
||||
{ "down", 6 },
|
||||
{ "enemyd", 9 },
|
||||
{ "enemys", 9 },
|
||||
{ "forward", 8 },
|
||||
{ "go", 6 },
|
||||
{ "im_hit", 7 },
|
||||
{ "left", 8 },
|
||||
{ "reportin", 9 },
|
||||
{ "right", 5 },
|
||||
{ "taking_f", 22 },
|
||||
{ "teamdown", 10 },
|
||||
{ "treport", 12 },
|
||||
{ "up", 6 },
|
||||
{ "END", 0 }, // end of list delimiter
|
||||
};
|
||||
|
||||
void PrecacheRadioMsgSet(radio_msg_t *msgs, char *base_path)
|
||||
{
|
||||
char msg_fullpath[2048];
|
||||
|
||||
while (strcmp(msgs->msg, "END"))
|
||||
{
|
||||
sprintf(msg_fullpath, "%s%s.wav", base_path, msgs->msg);
|
||||
gi.soundindex(msg_fullpath);
|
||||
msgs++;
|
||||
}
|
||||
}
|
||||
|
||||
void PrecacheRadioSounds()
|
||||
{
|
||||
gi.soundindex(RADIO_CLICK);
|
||||
gi.soundindex(RADIO_DEATH_MALE);
|
||||
gi.soundindex(RADIO_DEATH_FEMALE);
|
||||
|
||||
PrecacheRadioMsgSet(male_radio_msgs, "radio/male/");
|
||||
PrecacheRadioMsgSet(female_radio_msgs, "radio/female/");
|
||||
}
|
||||
|
||||
void DeleteFirstRadioQueueEntry(edict_t *ent)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (ent->client->resp.radio_queue_size <= 0)
|
||||
{
|
||||
gi.dprintf("DeleteFirstRadioQueueEntry: attempt to delete without any entries\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 1; i < ent->client->resp.radio_queue_size; i++)
|
||||
{
|
||||
memcpy(&(ent->client->resp.radio_queue[i - 1]),
|
||||
&(ent->client->resp.radio_queue[i]),
|
||||
sizeof(radio_queue_entry_t));
|
||||
}
|
||||
|
||||
ent->client->resp.radio_queue_size--;
|
||||
}
|
||||
|
||||
void DeleteRadioQueueEntry(edict_t *ent, int entry_num)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (ent->client->resp.radio_queue_size <= entry_num)
|
||||
{
|
||||
gi.dprintf("DeleteRadioQueueEntry: attempt to delete out of range queue entry\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = entry_num + 1; i < ent->client->resp.radio_queue_size; i++)
|
||||
{
|
||||
memcpy(&(ent->client->resp.radio_queue[i - 1]),
|
||||
&(ent->client->resp.radio_queue[i]),
|
||||
sizeof(radio_queue_entry_t));
|
||||
}
|
||||
|
||||
ent->client->resp.radio_queue_size--;
|
||||
}
|
||||
|
||||
// RadioThink should be called once on each player per server frame.
|
||||
void RadioThink(edict_t *ent)
|
||||
{
|
||||
// Try to clean things up, a bit....
|
||||
if (ent->client->resp.radio_partner)
|
||||
{
|
||||
if (!ent->client->resp.radio_partner->inuse ||
|
||||
ent->client->resp.radio_partner->client->resp.radio_partner != ent)
|
||||
{
|
||||
ent->client->resp.radio_partner = NULL;
|
||||
}
|
||||
}
|
||||
if (ent->client->resp.partner_last_offered_to)
|
||||
{
|
||||
if (!ent->client->resp.partner_last_offered_to->inuse ||
|
||||
ent->client->resp.partner_last_offered_to->solid == SOLID_NOT)
|
||||
{
|
||||
ent->client->resp.partner_last_offered_to = NULL;
|
||||
}
|
||||
}
|
||||
if (ent->client->resp.partner_last_denied_from)
|
||||
{
|
||||
if (!ent->client->resp.partner_last_denied_from->inuse ||
|
||||
ent->client->resp.partner_last_denied_from->solid == SOLID_NOT)
|
||||
{
|
||||
ent->client->resp.partner_last_denied_from = NULL;
|
||||
}
|
||||
}
|
||||
// ................................
|
||||
|
||||
if (ent->client->resp.radio_power_off)
|
||||
{
|
||||
ent->client->resp.radio_queue_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ent->client->resp.radio_delay > 1)
|
||||
{
|
||||
ent->client->resp.radio_delay--;
|
||||
return;
|
||||
}
|
||||
else if (ent->client->resp.radio_delay == 1)
|
||||
{
|
||||
DeleteFirstRadioQueueEntry(ent);
|
||||
ent->client->resp.radio_delay = 0;
|
||||
}
|
||||
|
||||
if (ent->client->resp.radio_queue_size)
|
||||
{
|
||||
char snd_play_cmd[512];
|
||||
edict_t *from;
|
||||
int check;
|
||||
|
||||
from = ent->client->resp.radio_queue[0].from_player;
|
||||
|
||||
if (!ent->client->resp.radio_queue[0].click &&
|
||||
(!from->inuse || from->solid == SOLID_NOT || from->deadflag == DEAD_DEAD))
|
||||
{
|
||||
if (ent->client->resp.radio_queue[0].from_gender)
|
||||
{
|
||||
strcpy(ent->client->resp.radio_queue[0].soundfile, RADIO_DEATH_FEMALE);
|
||||
ent->client->resp.radio_queue[0].length = RADIO_DEATH_FEMALE_LEN;
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(ent->client->resp.radio_queue[0].soundfile, RADIO_DEATH_MALE);
|
||||
ent->client->resp.radio_queue[0].length = RADIO_DEATH_MALE_LEN;
|
||||
}
|
||||
|
||||
for (check = 1; check < ent->client->resp.radio_queue_size; check++)
|
||||
{
|
||||
if (ent->client->resp.radio_queue[check].from_player == from)
|
||||
{
|
||||
DeleteRadioQueueEntry(ent, check);
|
||||
check--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sprintf(snd_play_cmd, "play %s", ent->client->resp.radio_queue[0].soundfile);
|
||||
stuffcmd(ent, snd_play_cmd);
|
||||
ent->client->resp.radio_queue[0].now_playing = 1;
|
||||
ent->client->resp.radio_delay = ent->client->resp.radio_queue[0].length;
|
||||
}
|
||||
}
|
||||
|
||||
int TotalNonClickMessagesInQueue(edict_t *ent)
|
||||
{
|
||||
int i, count = 0;
|
||||
|
||||
for (i = 0; i < ent->client->resp.radio_queue_size; i++)
|
||||
{
|
||||
if (!ent->client->resp.radio_queue[i].click)
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void AppendRadioMsgToQueue(edict_t *ent, char *msg, int len, int click, edict_t *from_player)
|
||||
{
|
||||
radio_queue_entry_t *newentry;
|
||||
|
||||
if (ent->client->resp.radio_queue_size >= MAX_RADIO_QUEUE_SIZE)
|
||||
{
|
||||
gi.dprintf("AppendRadioMsgToQueue: Maximum radio queue size exceeded\n");
|
||||
return;
|
||||
}
|
||||
|
||||
newentry = &(ent->client->resp.radio_queue[ent->client->resp.radio_queue_size]);
|
||||
|
||||
if (strlen(msg) + 1 > MAX_SOUNDFILE_PATH_LEN)
|
||||
{
|
||||
gi.dprintf("Radio sound file path (%s) exceeded maximum length\n", msg);
|
||||
*(msg + MAX_SOUNDFILE_PATH_LEN - 1) = 0;
|
||||
}
|
||||
strcpy(newentry->soundfile, msg);
|
||||
newentry->from_player = from_player;
|
||||
newentry->from_gender = from_player->client->resp.radio_gender;
|
||||
newentry->now_playing = 0;
|
||||
newentry->length = len;
|
||||
newentry->click = click;
|
||||
|
||||
ent->client->resp.radio_queue_size++;
|
||||
}
|
||||
|
||||
void InsertRadioMsgInQueueBeforeClick(edict_t *ent, char *msg, int len, edict_t *from_player)
|
||||
{
|
||||
radio_queue_entry_t *newentry;
|
||||
|
||||
if (ent->client->resp.radio_queue_size >= MAX_RADIO_QUEUE_SIZE)
|
||||
{
|
||||
gi.dprintf("InsertRadioMsgInQueueBeforeClick: Maximum radio queue size exceeded\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&(ent->client->resp.radio_queue[ent->client->resp.radio_queue_size]),
|
||||
&(ent->client->resp.radio_queue[ent->client->resp.radio_queue_size - 1]),
|
||||
sizeof(radio_queue_entry_t));
|
||||
|
||||
newentry = &(ent->client->resp.radio_queue[ent->client->resp.radio_queue_size - 1]);
|
||||
|
||||
if (strlen(msg) + 1 > MAX_SOUNDFILE_PATH_LEN)
|
||||
{
|
||||
gi.dprintf("Radio sound file path (%s) exceeded maximum length\n", msg);
|
||||
*(msg + MAX_SOUNDFILE_PATH_LEN - 1) = 0;
|
||||
}
|
||||
strcpy(newentry->soundfile, msg);
|
||||
newentry->from_player = from_player;
|
||||
newentry->from_gender = from_player->client->resp.radio_gender;
|
||||
newentry->now_playing = 0;
|
||||
newentry->length = len;
|
||||
newentry->click = 0;
|
||||
|
||||
ent->client->resp.radio_queue_size++;
|
||||
}
|
||||
|
||||
void AddRadioMsg(edict_t *ent, char *msg, int len, edict_t *from_player)
|
||||
{
|
||||
if (ent->client->resp.radio_queue_size == 0 ||
|
||||
(ent->client->resp.radio_queue[0].click &&
|
||||
ent->client->resp.radio_queue_size == 1))
|
||||
{
|
||||
AppendRadioMsgToQueue(ent, RADIO_CLICK, RADIO_CLICK_LEN, 1, from_player);
|
||||
AppendRadioMsgToQueue(ent, msg, len, 0, from_player);
|
||||
AppendRadioMsgToQueue(ent, RADIO_CLICK, RADIO_CLICK_LEN, 1, from_player);
|
||||
}
|
||||
else // we have some msgs in it already...
|
||||
{
|
||||
if (TotalNonClickMessagesInQueue(ent) < MAX_RADIO_MSG_QUEUE_SIZE)
|
||||
InsertRadioMsgInQueueBeforeClick(ent, msg, len, from_player);
|
||||
// else ignore the message...
|
||||
}
|
||||
}
|
||||
|
||||
void RadioBroadcast(edict_t *ent, int partner, char *msg)
|
||||
{
|
||||
int j, i, msg_len, found;
|
||||
edict_t *other;
|
||||
radio_msg_t *radio_msgs;
|
||||
char msg_fullpath[2048], *base_path;
|
||||
|
||||
if (ent->deadflag == DEAD_DEAD || ent->solid == SOLID_NOT)
|
||||
return;
|
||||
|
||||
if (!teamplay->value)
|
||||
{
|
||||
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
|
||||
return; // don't allow in a non-team setup...
|
||||
}
|
||||
|
||||
if (ent->client->resp.radio_power_off)
|
||||
{
|
||||
safe_centerprintf(ent, "Your radio is off!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (partner && ent->client->resp.radio_partner == NULL)
|
||||
{
|
||||
safe_cprintf(ent, PRINT_HIGH, "You don't have a partner.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ent->client->resp.radio_gender)
|
||||
{
|
||||
radio_msgs = female_radio_msgs;
|
||||
base_path = "radio/female/";
|
||||
}
|
||||
else
|
||||
{
|
||||
radio_msgs = male_radio_msgs;
|
||||
base_path = "radio/male/";
|
||||
}
|
||||
|
||||
i = found = 0;
|
||||
while (strcmp(radio_msgs[i].msg, "END"))
|
||||
{
|
||||
if (!Q_stricmp(radio_msgs[i].msg, msg))
|
||||
{
|
||||
found = 1;
|
||||
sprintf(msg_fullpath, "%s%s.wav", base_path, radio_msgs[i].msg);
|
||||
msg_len = radio_msgs[i].length;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
safe_centerprintf(ent, "'%s' is not a valid radio message\n", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (radiolog->value)
|
||||
{
|
||||
safe_cprintf(NULL, PRINT_CHAT, "[%s RADIO] %s: %s\n",
|
||||
partner ? "PARTNER" : "TEAM",
|
||||
ent->client->pers.netname,
|
||||
msg);
|
||||
}
|
||||
|
||||
for (j = 1; j <= game.maxclients; j++)
|
||||
{
|
||||
other = &g_edicts[j];
|
||||
if (!other->inuse)
|
||||
continue;
|
||||
if (!other->client)
|
||||
continue;
|
||||
if (!OnSameTeam(ent, other))
|
||||
continue;
|
||||
if (partner && other != ent->client->resp.radio_partner)
|
||||
continue;
|
||||
AddRadioMsg(other, msg_fullpath, msg_len, ent);
|
||||
}
|
||||
}
|
||||
|
||||
void Cmd_Radio_f(edict_t *ent)
|
||||
{
|
||||
RadioBroadcast(ent, ent->client->resp.radio_partner_mode, gi.args());
|
||||
}
|
||||
|
||||
void Cmd_Radiopartner_f(edict_t *ent)
|
||||
{
|
||||
RadioBroadcast(ent, 1, gi.args());
|
||||
}
|
||||
|
||||
void Cmd_Radioteam_f(edict_t *ent)
|
||||
{
|
||||
RadioBroadcast(ent, 0, gi.args());
|
||||
}
|
||||
|
||||
void Cmd_Radiogender_f(edict_t *ent)
|
||||
{
|
||||
char *arg;
|
||||
|
||||
if (!teamplay->value)
|
||||
{
|
||||
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
|
||||
return; // don't allow in a non-team setup...
|
||||
}
|
||||
|
||||
arg = gi.args();
|
||||
if (arg == NULL || !strlen(arg))
|
||||
{
|
||||
if (ent->client->resp.radio_gender)
|
||||
safe_cprintf(ent, PRINT_HIGH, "Radio gender currently set to female\n");
|
||||
else
|
||||
safe_cprintf(ent, PRINT_HIGH, "Radio gender currently set to male\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Q_stricmp(arg, "male"))
|
||||
{
|
||||
safe_cprintf(ent, PRINT_HIGH, "Radio gender set to male\n");
|
||||
ent->client->resp.radio_gender = 0;
|
||||
}
|
||||
else if (!Q_stricmp(arg, "female"))
|
||||
{
|
||||
safe_cprintf(ent, PRINT_HIGH, "Radio gender set to female\n");
|
||||
ent->client->resp.radio_gender = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_cprintf(ent, PRINT_HIGH, "Invalid gender selection, try 'male' or 'female'\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Cmd_Radio_power_f(edict_t *ent)
|
||||
{
|
||||
if (!teamplay->value)
|
||||
{
|
||||
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
|
||||
return; // don't allow in a non-team setup...
|
||||
}
|
||||
|
||||
ent->client->resp.radio_power_off = !ent->client->resp.radio_power_off;
|
||||
|
||||
if (ent->client->resp.radio_power_off)
|
||||
{
|
||||
safe_centerprintf(ent, "Radio switched off\n");
|
||||
stuffcmd(ent, "play " RADIO_CLICK);
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_centerprintf(ent, "Radio switched on\n");
|
||||
stuffcmd(ent, "play " RADIO_CLICK);
|
||||
}
|
||||
}
|
||||
|
||||
void Cmd_Channel_f(edict_t *ent)
|
||||
{
|
||||
if (!teamplay->value)
|
||||
{
|
||||
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
|
||||
return; // don't allow in a non-team setup...
|
||||
}
|
||||
|
||||
ent->client->resp.radio_partner_mode = !ent->client->resp.radio_partner_mode;
|
||||
if (ent->client->resp.radio_partner_mode)
|
||||
{
|
||||
safe_centerprintf(ent, "Channel set to 1, partner channel\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_centerprintf(ent, "Channel set to 0, team channel\n");
|
||||
}
|
||||
}
|
||||
|
||||
// DetermineViewedTeammate: determine the current player you're viewing (only looks for live teammates)
|
||||
// Modified from SetIDView (which was used from Zoid's CTF)
|
||||
edict_t *DetermineViewedTeammate(edict_t *ent)
|
||||
{
|
||||
vec3_t forward, dir;
|
||||
trace_t tr;
|
||||
edict_t *who, *best;
|
||||
//FIREBLADE, suggested by hal[9k] 3/11/1999
|
||||
float bd = 0.9;
|
||||
//FIREBLADE
|
||||
float d;
|
||||
int i;
|
||||
|
||||
AngleVectors(ent->client->v_angle, forward, NULL, NULL);
|
||||
VectorScale(forward, 8192, forward);
|
||||
VectorAdd(ent->s.origin, forward, forward);
|
||||
PRETRACE();
|
||||
tr = gi.trace(ent->s.origin, NULL, NULL, forward, ent, MASK_SOLID);
|
||||
POSTTRACE();
|
||||
if (tr.fraction < 1 && tr.ent && tr.ent->client) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AngleVectors(ent->client->v_angle, forward, NULL, NULL);
|
||||
best = NULL;
|
||||
for (i = 1; i <= maxclients->value; i++) {
|
||||
who = g_edicts + i;
|
||||
if (!who->inuse)
|
||||
continue;
|
||||
VectorSubtract(who->s.origin, ent->s.origin, dir);
|
||||
VectorNormalize(dir);
|
||||
d = DotProduct(forward, dir);
|
||||
if (d > bd && loc_CanSee(ent, who) &&
|
||||
who->solid != SOLID_NOT &&
|
||||
who->deadflag != DEAD_DEAD &&
|
||||
OnSameTeam(who, ent)) {
|
||||
bd = d;
|
||||
best = who;
|
||||
}
|
||||
}
|
||||
|
||||
if (bd > 0.90)
|
||||
{
|
||||
return best;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void Cmd_Partner_f(edict_t *ent)
|
||||
{
|
||||
edict_t *target;
|
||||
char *genderstr;
|
||||
|
||||
if (!teamplay->value)
|
||||
{
|
||||
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
|
||||
return; // don't allow in a non-team setup...
|
||||
}
|
||||
|
||||
if (ent->deadflag == DEAD_DEAD || ent->solid == SOLID_NOT)
|
||||
return;
|
||||
|
||||
if (ent->client->resp.radio_partner &&
|
||||
!ent->client->resp.radio_partner->inuse)
|
||||
{ // just in case RadioThink hasn't caught it yet... avoid any problems
|
||||
ent->client->resp.radio_partner = NULL;
|
||||
}
|
||||
|
||||
if (ent->client->resp.radio_partner)
|
||||
{
|
||||
safe_centerprintf(ent, "You already have a partner, %s\n",
|
||||
ent->client->resp.radio_partner->client->pers.netname);
|
||||
return;
|
||||
}
|
||||
|
||||
target = DetermineViewedTeammate(ent);
|
||||
|
||||
if (target == NULL)
|
||||
{
|
||||
safe_centerprintf(ent, "No potential partner selected\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (target->client->resp.radio_partner)
|
||||
{
|
||||
safe_centerprintf(ent, "%s already has a partner\n", target->client->pers.netname);
|
||||
return;
|
||||
}
|
||||
|
||||
if (target->client->resp.partner_last_offered_to == ent &&
|
||||
ent->client->resp.partner_last_offered_from == target)
|
||||
{
|
||||
safe_centerprintf(ent, "%s is now your partner\n", target->client->pers.netname);
|
||||
safe_centerprintf(target, "%s is now your partner\n", ent->client->pers.netname);
|
||||
ent->client->resp.radio_partner = target;
|
||||
target->client->resp.radio_partner = ent;
|
||||
ent->client->resp.partner_last_offered_from = NULL;
|
||||
target->client->resp.partner_last_offered_to = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (target->client->resp.partner_last_denied_from == ent)
|
||||
{
|
||||
safe_centerprintf(ent, "%s has already denied you\n",
|
||||
target->client->pers.netname);
|
||||
return;
|
||||
}
|
||||
|
||||
if (target == ent->client->resp.partner_last_offered_to)
|
||||
{
|
||||
if (IsFemale(target))
|
||||
genderstr = "her";
|
||||
else if (IsNeutral(target))
|
||||
genderstr = "it";
|
||||
else
|
||||
genderstr = "him";
|
||||
|
||||
safe_centerprintf(ent, "Already awaiting confirmation from %s\n", genderstr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsFemale(ent))
|
||||
genderstr = "her";
|
||||
else if (IsNeutral(ent))
|
||||
genderstr = "it";
|
||||
else
|
||||
genderstr = "him";
|
||||
|
||||
safe_centerprintf(ent, "Awaiting confirmation from %s\n", target->client->pers.netname);
|
||||
safe_centerprintf(target, "%s offers to be your partner\n"
|
||||
"To accept:\nView %s and use the 'partner' command\n"
|
||||
"To deny:\nUse the 'deny' command\n",
|
||||
ent->client->pers.netname, genderstr);
|
||||
|
||||
ent->client->resp.partner_last_offered_to = target;
|
||||
target->client->resp.partner_last_offered_from = ent;
|
||||
}
|
||||
|
||||
void Cmd_Unpartner_f(edict_t *ent)
|
||||
{
|
||||
edict_t *target;
|
||||
|
||||
if (!teamplay->value)
|
||||
{
|
||||
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
|
||||
return; // don't allow in a non-team setup...
|
||||
}
|
||||
|
||||
if (ent->client->resp.radio_partner &&
|
||||
!ent->client->resp.radio_partner->inuse)
|
||||
{ // just in case RadioThink hasn't caught it yet... avoid any problems
|
||||
ent->client->resp.radio_partner = NULL;
|
||||
}
|
||||
|
||||
target = ent->client->resp.radio_partner;
|
||||
|
||||
if (target == NULL)
|
||||
{
|
||||
safe_centerprintf(ent, "You don't have a partner\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (target->client->resp.radio_partner == ent)
|
||||
{
|
||||
safe_centerprintf(target, "%s broke your partnership\n",
|
||||
ent->client->pers.netname);
|
||||
target->client->resp.radio_partner = NULL;
|
||||
}
|
||||
|
||||
safe_centerprintf(ent, "You broke your partnership with %s\n",
|
||||
target->client->pers.netname);
|
||||
ent->client->resp.radio_partner = NULL;
|
||||
}
|
||||
|
||||
void Cmd_Deny_f(edict_t *ent)
|
||||
{
|
||||
edict_t *target;
|
||||
|
||||
if (!teamplay->value)
|
||||
{
|
||||
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
|
||||
return; // don't allow in a non-team setup...
|
||||
}
|
||||
|
||||
if (ent->deadflag == DEAD_DEAD || ent->solid == SOLID_NOT)
|
||||
return;
|
||||
|
||||
target = ent->client->resp.partner_last_offered_from;
|
||||
if (target && target->inuse)
|
||||
{
|
||||
safe_centerprintf(ent, "You denied %s\n",
|
||||
target->client->pers.netname);
|
||||
safe_centerprintf(target, "%s has denied you\n",
|
||||
ent->client->pers.netname);
|
||||
ent->client->resp.partner_last_denied_from = target;
|
||||
ent->client->resp.partner_last_offered_from = NULL;
|
||||
if (target->client->resp.partner_last_offered_to == ent)
|
||||
target->client->resp.partner_last_offered_to = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_centerprintf(ent, "No one has offered to be your partner\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Cmd_Say_partner_f(edict_t *ent)
|
||||
{
|
||||
if (!teamplay->value)
|
||||
{
|
||||
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
|
||||
return; // don't allow in a non-team setup...
|
||||
}
|
||||
|
||||
if (ent->client->resp.radio_partner == NULL)
|
||||
{
|
||||
safe_cprintf(ent, PRINT_HIGH, "You don't have a partner.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Cmd_Say_f(ent, false, false, true);
|
||||
}
|
49
a_radio.h
Normal file
49
a_radio.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* a_radio.h
|
||||
*
|
||||
* Include file for use with radio stuff
|
||||
* -Fireblade
|
||||
*/
|
||||
|
||||
typedef struct radio_msg_s
|
||||
{
|
||||
char *msg; // the msg name
|
||||
int length; // length in server frames (ie tenths of a second), rounded up
|
||||
} radio_msg_t;
|
||||
|
||||
#define RADIO_CLICK "radio/click.wav"
|
||||
#define RADIO_CLICK_LEN 2
|
||||
#define RADIO_DEATH_MALE "radio/male/rdeath.wav"
|
||||
#define RADIO_DEATH_MALE_LEN 27
|
||||
#define RADIO_DEATH_FEMALE "radio/female/rdeath.wav"
|
||||
#define RADIO_DEATH_FEMALE_LEN 30
|
||||
|
||||
#define MAX_SOUNDFILE_PATH_LEN 32 // max length of a sound file path
|
||||
#define MAX_RADIO_MSG_QUEUE_SIZE 4
|
||||
#define MAX_RADIO_QUEUE_SIZE 6 // this must be at least 2 greater than the above
|
||||
|
||||
extern radio_msg_t male_radio_msgs[];
|
||||
extern radio_msg_t female_radio_msgs[];
|
||||
|
||||
typedef struct radio_queue_entry_s
|
||||
{
|
||||
char soundfile[MAX_SOUNDFILE_PATH_LEN];
|
||||
edict_t *from_player;
|
||||
int from_gender; // true if female
|
||||
qboolean now_playing;
|
||||
int length;
|
||||
qboolean click;
|
||||
} radio_queue_entry_t;
|
||||
|
||||
void RadioThink(edict_t *);
|
||||
void Cmd_Radio_f(edict_t *);
|
||||
void Cmd_Radiogender_f(edict_t *);
|
||||
void Cmd_Radio_power_f(edict_t *);
|
||||
void Cmd_Radiopartner_f(edict_t *);
|
||||
void Cmd_Radioteam_f(edict_t *);
|
||||
void Cmd_Channel_f(edict_t *);
|
||||
void Cmd_Say_partner_f(edict_t *);
|
||||
void Cmd_Partner_f(edict_t *);
|
||||
void Cmd_Deny_f(edict_t *);
|
||||
void Cmd_Unpartner_f(edict_t *);
|
||||
void PrecacheRadioSounds();
|
81
a_team.h
Normal file
81
a_team.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Include for Action team-related things
|
||||
*/
|
||||
|
||||
#define NOTEAM 0
|
||||
#define TEAM1 1
|
||||
#define TEAM2 2
|
||||
|
||||
#define MAX_TEAMS 2
|
||||
#define TEAM_TOP (MAX_TEAMS+1)
|
||||
|
||||
#define WINNER_NONE 0
|
||||
#define WINNER_TEAM1 1
|
||||
#define WINNER_TEAM2 2
|
||||
#define WINNER_TIE 3
|
||||
|
||||
// Pre- and post-trace code for our teamplay anti-stick stuff. If there are
|
||||
// still "transparent" (SOLID_TRIGGER) players, they need to be set to
|
||||
// SOLID_BBOX before a trace is performed, then changed back again
|
||||
// afterwards. PRETRACE() and POSTTRACE() should be called before and after
|
||||
// traces in all places where combat is taking place (ie "transparent" players
|
||||
// should be detected), ie shots being traced etc.
|
||||
// FB 6/1/99: Now crouching players will have their bounding box adjusted here
|
||||
// too, for better shot areas. (there has to be a better way to do this?)
|
||||
|
||||
#define PRETRACE() \
|
||||
if (transparent_list && (int)teamplay->value && !lights_camera_action) \
|
||||
TransparentListSet(SOLID_BBOX)
|
||||
|
||||
#define POSTTRACE() \
|
||||
if (transparent_list && (int)teamplay->value && !lights_camera_action) \
|
||||
TransparentListSet(SOLID_TRIGGER)
|
||||
|
||||
edict_t *SelectTeamplaySpawnPoint(edict_t *);
|
||||
qboolean FallingDamageAmnesty(edict_t *targ);
|
||||
void OpenJoinMenu(edict_t *);
|
||||
void OpenWeaponMenu(edict_t *);
|
||||
void OpenItemMenu(edict_t *ent);
|
||||
void JoinTeam(edict_t *ent, int desired_team, int skip_menuclose);
|
||||
edict_t *FindOverlap(edict_t *ent, edict_t *last_overlap);
|
||||
void CheckTeamRules(void);
|
||||
void A_Scoreboard(edict_t *ent);
|
||||
void Team_f(edict_t *ent);
|
||||
qboolean StartClient(edict_t *ent);
|
||||
void AssignSkin(edict_t *, char *);
|
||||
void TallyEndOfLevelTeamScores(void);
|
||||
void CheckForUnevenTeams(void);
|
||||
void SetupTeamSpawnPoints();
|
||||
void GetSpawnPoints();
|
||||
void CleanBodies(); // from p_client.c, removes all current dead bodies from map
|
||||
void LeaveTeam(edict_t *);
|
||||
int newrand(int top);
|
||||
void InitTransparentList();
|
||||
void AddToTransparentList(edict_t *);
|
||||
void RemoveFromTransparentList(edict_t *);
|
||||
void PrintTransparentList();
|
||||
|
||||
typedef struct spawn_distances_s
|
||||
{
|
||||
float distance;
|
||||
edict_t *s;
|
||||
} spawn_distances_t;
|
||||
|
||||
typedef struct transparent_list_s
|
||||
{
|
||||
edict_t *ent;
|
||||
struct transparent_list_s *next;
|
||||
} transparent_list_t;
|
||||
|
||||
|
||||
extern qboolean team_game_going;
|
||||
extern qboolean team_round_going;
|
||||
extern int team1_score;
|
||||
extern int team2_score;
|
||||
extern int team1_total;
|
||||
extern int team2_total;
|
||||
extern int lights_camera_action;
|
||||
extern int holding_on_tie_check;
|
||||
extern int team_round_countdown;
|
||||
extern transparent_list_t *transparent_list;
|
||||
extern trace_t trace_t_temp;
|
339
acesrc/acebot.h
Normal file
339
acesrc/acebot.h
Normal file
|
@ -0,0 +1,339 @@
|
|||
///////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// ACE - Quake II Bot Base Code
|
||||
//
|
||||
// Version 1.0
|
||||
//
|
||||
// Original file is Copyright(c), Steve Yeager 1998, All Rights Reserved
|
||||
//
|
||||
//
|
||||
// All other files are Copyright(c) Id Software, Inc.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* $Header: /LicenseToKill/src/acesrc/acebot.h 14 26/11/99 0:58 Riever $
|
||||
*
|
||||
* $Log: /LicenseToKill/src/acesrc/acebot.h $
|
||||
*
|
||||
* 14 26/11/99 0:58 Riever
|
||||
* Max nodes up to 1200
|
||||
*
|
||||
* 13 21/10/99 8:19 Riever
|
||||
* ltk_showpath cvar enables display of bot paths.
|
||||
*
|
||||
* 12 16/10/99 9:11 Riever
|
||||
* Declared new config functions.
|
||||
*
|
||||
* 11 16/10/99 8:50 Riever
|
||||
* Added ltk_chat CVAR
|
||||
*
|
||||
* 10 6/10/99 17:40 Riever
|
||||
* Added TeamPlay state STATE_POSITION to enable bots to seperate and
|
||||
* avoid centipede formations.
|
||||
*
|
||||
* 9 29/09/99 13:32 Riever
|
||||
* Chnaged DrawPath declaration to accept edict_t *self so it can use the
|
||||
* AntPath SLL code.
|
||||
*
|
||||
* 8 27/09/99 16:01 Riever
|
||||
* Added "self" to ACEND_UpdateNodeEdge calls
|
||||
*
|
||||
* 7 27/09/99 14:51 Riever
|
||||
* Increased node density by lowering node_gap to 96.
|
||||
*
|
||||
* 6 21/09/99 13:10 Riever
|
||||
* defined ITEMLIST entries for AQ2 ammo
|
||||
*
|
||||
* 5 17/09/99 17:11 Riever
|
||||
* New Node structure
|
||||
* Link structure added
|
||||
* MAXLINKS defined
|
||||
* NODE_DOOR definition added
|
||||
*
|
||||
* 4 14/09/99 8:05 Riever
|
||||
* Added ltk_skill cvar
|
||||
*
|
||||
* 3 13/09/99 19:50 Riever
|
||||
* Added header
|
||||
*
|
||||
* 2 13/09/99 19:49 Riever
|
||||
* Initial check in at 0.02alpha
|
||||
*
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// acebot.h - Main header file for ACEBOT
|
||||
//
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _ACEBOT_H
|
||||
#define _ACEBOT_H
|
||||
|
||||
// Only 100 allowed for now (probably never be enough edicts for 'em
|
||||
#define MAX_BOTS 100
|
||||
|
||||
// Platform states
|
||||
#define STATE_TOP 0
|
||||
#define STATE_BOTTOM 1
|
||||
#define STATE_UP 2
|
||||
#define STATE_DOWN 3
|
||||
|
||||
// Maximum nodes
|
||||
#define MAX_NODES 1200
|
||||
|
||||
// Link types
|
||||
#define INVALID -1
|
||||
|
||||
// Node types
|
||||
#define NODE_MOVE 0
|
||||
#define NODE_LADDER 1
|
||||
#define NODE_PLATFORM 2
|
||||
#define NODE_TELEPORTER 3
|
||||
#define NODE_ITEM 4
|
||||
#define NODE_WATER 5
|
||||
#define NODE_GRAPPLE 6
|
||||
#define NODE_JUMP 7
|
||||
#define NODE_DOOR 8 // - RiEvEr
|
||||
#define NODE_ALL 99 // For selecting all nodes
|
||||
|
||||
// Density setting for nodes
|
||||
#define NODE_DENSITY 96
|
||||
|
||||
// Maximum links per node
|
||||
#define MAXLINKS 12
|
||||
|
||||
//AQ2 ADD
|
||||
extern cvar_t *ltk_skill; // Skill setting for bots, range 0-10
|
||||
extern cvar_t *ltk_showpath; // Toggles display of bot paths in debug mode
|
||||
extern cvar_t *ltk_chat; // Chat setting for bots, off or on (0,1)
|
||||
|
||||
//AQ2 END
|
||||
|
||||
// Bot state types
|
||||
#define STATE_STAND 0
|
||||
#define STATE_MOVE 1
|
||||
#define STATE_ATTACK 2
|
||||
#define STATE_WANDER 3
|
||||
#define STATE_FLEE 4
|
||||
#define STATE_POSITION 5
|
||||
|
||||
#define MOVE_LEFT 0
|
||||
#define MOVE_RIGHT 1
|
||||
#define MOVE_FORWARD 2
|
||||
#define MOVE_BACK 3
|
||||
|
||||
// Item defines (got this list from somewhere??....so thanks to whoever created it)
|
||||
#define ITEMLIST_NULLINDEX 0
|
||||
#define ITEMLIST_BODYARMOR 1
|
||||
#define ITEMLIST_COMBATARMOR 2
|
||||
#define ITEMLIST_JACKETARMOR 3
|
||||
#define ITEMLIST_ARMORSHARD 4
|
||||
#define ITEMLIST_POWERSCREEN 5
|
||||
#define ITEMLIST_POWERSHIELD 6
|
||||
|
||||
#define ITEMLIST_GRAPPLE 7
|
||||
|
||||
#define ITEMLIST_BLASTER 8
|
||||
#define ITEMLIST_SHOTGUN 9
|
||||
#define ITEMLIST_SUPERSHOTGUN 10
|
||||
#define ITEMLIST_MACHINEGUN 11
|
||||
#define ITEMLIST_CHAINGUN 12
|
||||
#define ITEMLIST_GRENADES 13
|
||||
#define ITEMLIST_GRENADELAUNCHER 14
|
||||
#define ITEMLIST_ROCKETLAUNCHER 15
|
||||
#define ITEMLIST_HYPERBLASTER 16
|
||||
#define ITEMLIST_RAILGUN 17
|
||||
#define ITEMLIST_BFG10K 18
|
||||
|
||||
#define ITEMLIST_SHELLS 19
|
||||
#define ITEMLIST_BULLETS 20
|
||||
#define ITEMLIST_CELLS 21
|
||||
#define ITEMLIST_ROCKETS 22
|
||||
#define ITEMLIST_SLUGS 23
|
||||
#define ITEMLIST_QUADDAMAGE 24
|
||||
#define ITEMLIST_INVULNERABILITY 25
|
||||
#define ITEMLIST_SILENCER 26
|
||||
#define ITEMLIST_REBREATHER 27
|
||||
#define ITEMLIST_ENVIRONMENTSUIT 28
|
||||
#define ITEMLIST_ANCIENTHEAD 29
|
||||
#define ITEMLIST_ADRENALINE 30
|
||||
#define ITEMLIST_BANDOLIER 31
|
||||
#define ITEMLIST_AMMOPACK 32
|
||||
#define ITEMLIST_DATACD 33
|
||||
#define ITEMLIST_POWERCUBE 34
|
||||
#define ITEMLIST_PYRAMIDKEY 35
|
||||
#define ITEMLIST_DATASPINNER 36
|
||||
#define ITEMLIST_SECURITYPASS 37
|
||||
#define ITEMLIST_BLUEKEY 38
|
||||
#define ITEMLIST_REDKEY 39
|
||||
#define ITEMLIST_COMMANDERSHEAD 40
|
||||
#define ITEMLIST_AIRSTRIKEMARKER 41
|
||||
#define ITEMLIST_HEALTH 42
|
||||
|
||||
// new for ctf
|
||||
#define ITEMLIST_FLAG1 43
|
||||
#define ITEMLIST_FLAG2 44
|
||||
#define ITEMLIST_RESISTANCETECH 45
|
||||
#define ITEMLIST_STRENGTHTECH 46
|
||||
#define ITEMLIST_HASTETECH 47
|
||||
#define ITEMLIST_REGENERATIONTECH 48
|
||||
|
||||
// my additions
|
||||
#define ITEMLIST_HEALTH_SMALL 49
|
||||
#define ITEMLIST_HEALTH_MEDIUM 50
|
||||
#define ITEMLIST_HEALTH_LARGE 51
|
||||
#define ITEMLIST_BOT 52
|
||||
#define ITEMLIST_PLAYER 53
|
||||
#define ITEMLIST_HEALTH_MEGA 54
|
||||
|
||||
//AQ2 ADD
|
||||
#define ITEMLIST_MK23 55 //"MK23 Pistol"
|
||||
#define ITEMLIST_MP5 56 //"MP5/10 Submachinegun"
|
||||
#define ITEMLIST_M4 57 //"M4 Assault Rifle"
|
||||
#define ITEMLIST_M3 58 //"M3 Super 90 Assault Shotgun"
|
||||
#define ITEMLIST_HC 59 // "Handcannon"
|
||||
#define ITEMLIST_SNIPER 60 // "Sniper Rifle"
|
||||
#define ITEMLIST_DUAL 61 // "Dual MK23 Pistols"
|
||||
#define ITEMLIST_KNIFE 62 // "Combat Knife"
|
||||
#define ITEMLIST_GRENADE 63 // "M26 Fragmentation Grenade"
|
||||
|
||||
#define ITEMLIST_SIL 64 // "Silencer"
|
||||
#define ITEMLIST_SLIP 65 // "Stealth Slippers"
|
||||
#define ITEMLIST_BAND 66 // "Bandolier"
|
||||
#define ITEMLIST_KEV 67 // "Kevlar Vest"
|
||||
#define ITEMLIST_LASER 68 // "Lasersight"
|
||||
|
||||
#define ITEMLIST_AMMO_CLIP 69
|
||||
#define ITEMLIST_AMMO_M3 70
|
||||
#define ITEMLIST_AMMO_M4 71
|
||||
#define ITEMLIST_AMMO_MAG 72
|
||||
#define ITEMLIST_AMMO_SNIPER 73
|
||||
|
||||
//AQ2 END
|
||||
|
||||
typedef struct nodelink_s
|
||||
{
|
||||
short int targetNode;
|
||||
float cost; // updated for pathsearch algorithm
|
||||
|
||||
}nodelink_t; // RiEvEr
|
||||
|
||||
|
||||
// Node structure
|
||||
typedef struct node_s
|
||||
{
|
||||
vec3_t origin; // Using Id's representation
|
||||
int type; // type of node
|
||||
short int nodenum; // node number - RiEvEr
|
||||
// short int lightlevel; // obvious... - RiEvEr
|
||||
nodelink_t links[MAXLINKS]; // store all links. - RiEvEr
|
||||
|
||||
} node_t;
|
||||
|
||||
typedef struct item_table_s
|
||||
{
|
||||
int item;
|
||||
float weight;
|
||||
edict_t *ent;
|
||||
int node;
|
||||
|
||||
} item_table_t;
|
||||
|
||||
extern int num_players;
|
||||
extern edict_t *players[MAX_CLIENTS]; // pointers to all players in the game
|
||||
|
||||
// extern decs
|
||||
extern node_t nodes[MAX_NODES];
|
||||
extern item_table_t item_table[MAX_EDICTS];
|
||||
extern qboolean debug_mode;
|
||||
extern int numnodes;
|
||||
extern int num_items;
|
||||
|
||||
// id Function Protos I need
|
||||
void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker);
|
||||
void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker);
|
||||
void TossClientWeapon (edict_t *self);
|
||||
void ClientThink (edict_t *ent, usercmd_t *ucmd);
|
||||
void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles);
|
||||
void ClientUserinfoChanged (edict_t *ent, char *userinfo);
|
||||
void CopyToBodyQue (edict_t *ent);
|
||||
qboolean ClientConnect (edict_t *ent, char *userinfo);
|
||||
void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator);
|
||||
|
||||
// acebot_ai.c protos
|
||||
void ACEAI_Think (edict_t *self);
|
||||
void ACEAI_PickLongRangeGoal(edict_t *self);
|
||||
void ACEAI_PickShortRangeGoal(edict_t *self);
|
||||
qboolean ACEAI_FindEnemy(edict_t *self);
|
||||
void ACEAI_ChooseWeapon(edict_t *self);
|
||||
|
||||
// acebot_cmds.c protos
|
||||
qboolean ACECM_Commands(edict_t *ent);
|
||||
void ACECM_Store();
|
||||
|
||||
// acebot_items.c protos
|
||||
void ACEIT_PlayerAdded(edict_t *ent);
|
||||
void ACEIT_PlayerRemoved(edict_t *ent);
|
||||
qboolean ACEIT_IsVisible(edict_t *self, vec3_t goal);
|
||||
qboolean ACEIT_IsReachable(edict_t *self,vec3_t goal);
|
||||
qboolean ACEIT_ChangeWeapon (edict_t *ent, gitem_t *item);
|
||||
//AQ2 ADD
|
||||
qboolean ACEIT_ChangeMK23SpecialWeapon (edict_t *ent, gitem_t *item);
|
||||
qboolean ACEIT_ChangeHCSpecialWeapon (edict_t *ent, gitem_t *item);
|
||||
qboolean ACEIT_ChangeSniperSpecialWeapon (edict_t *ent, gitem_t *item);
|
||||
qboolean ACEIT_ChangeM4SpecialWeapon (edict_t *ent, gitem_t *item);
|
||||
qboolean ACEIT_ChangeM3SpecialWeapon (edict_t *ent, gitem_t *item);
|
||||
qboolean ACEIT_ChangeMP5SpecialWeapon (edict_t *ent, gitem_t *item);
|
||||
qboolean ACEIT_ChangeDualSpecialWeapon (edict_t *ent, gitem_t *item);
|
||||
//AQ2 END
|
||||
qboolean ACEIT_CanUseArmor (gitem_t *item, edict_t *other);
|
||||
float ACEIT_ItemNeed(edict_t *self, int item);
|
||||
int ACEIT_ClassnameToIndex(char *classname);
|
||||
void ACEIT_BuildItemNodeTable (qboolean rebuild);
|
||||
|
||||
// acebot_movement.c protos
|
||||
qboolean ACEMV_SpecialMove(edict_t *self,usercmd_t *ucmd);
|
||||
void ACEMV_Move(edict_t *self, usercmd_t *ucmd);
|
||||
void ACEMV_Attack (edict_t *self, usercmd_t *ucmd);
|
||||
void ACEMV_Wander (edict_t *self, usercmd_t *ucmd);
|
||||
|
||||
// acebot_nodes.c protos
|
||||
int ACEND_FindCost(int from, int to);
|
||||
int ACEND_FindCloseReachableNode(edict_t *self, int dist, int type);
|
||||
int ACEND_FindClosestReachableNode(edict_t *self, int range, int type);
|
||||
void ACEND_SetGoal(edict_t *self, int goal_node);
|
||||
qboolean ACEND_FollowPath(edict_t *self);
|
||||
void ACEND_GrapFired(edict_t *self);
|
||||
qboolean ACEND_CheckForLadder(edict_t *self);
|
||||
void ACEND_PathMap(edict_t *self);
|
||||
void ACEND_InitNodes(void);
|
||||
void ACEND_ShowNode(int node);
|
||||
void ACEND_DrawPath(edict_t *self);
|
||||
void ACEND_ShowPath(edict_t *self, int goal_node);
|
||||
int ACEND_AddNode(edict_t *self, int type);
|
||||
void ACEND_UpdateNodeEdge(edict_t *self, int from, int to);
|
||||
void ACEND_RemoveNodeEdge(edict_t *self, int from, int to);
|
||||
void ACEND_ResolveAllPaths();
|
||||
void ACEND_SaveNodes();
|
||||
void ACEND_LoadNodes();
|
||||
|
||||
// acebot_spawn.c protos
|
||||
void ACESP_SaveBots();
|
||||
void ACESP_LoadBots();
|
||||
void ACESP_LoadBotConfig();
|
||||
void ACESP_SpawnBotFromConfig( char *inString );
|
||||
void ACESP_HoldSpawn(edict_t *self);
|
||||
void ACESP_PutClientInServer (edict_t *bot, qboolean respawn, int team);
|
||||
void ACESP_Respawn (edict_t *self);
|
||||
edict_t *ACESP_FindFreeClient (void);
|
||||
void ACESP_SetName(edict_t *bot, char *name, char *skin, char *team);
|
||||
void ACESP_SpawnBot (char *team, char *name, char *skin, char *userinfo);
|
||||
void ACESP_ReAddBots();
|
||||
void ACESP_RemoveBot(char *name);
|
||||
void safe_cprintf (edict_t *ent, int printlevel, char *fmt, ...);
|
||||
void safe_centerprintf (edict_t *ent, char *fmt, ...);
|
||||
void safe_bprintf (int printlevel, char *fmt, ...);
|
||||
void debug_printf (char *fmt, ...);
|
||||
|
||||
#endif
|
751
acesrc/acebot_ai.c
Normal file
751
acesrc/acebot_ai.c
Normal file
|
@ -0,0 +1,751 @@
|
|||
///////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// ACE - Quake II Bot Base Code
|
||||
//
|
||||
// Version 1.0
|
||||
//
|
||||
// Original file is Copyright(c), Steve Yeager 1998, All Rights Reserved
|
||||
//
|
||||
//
|
||||
// All other files are Copyright(c) Id Software, Inc.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* $Header: /LicenseToKill/src/acesrc/acebot_ai.c 25 2/11/99 17:02 Riever $
|
||||
*
|
||||
* $Log: /LicenseToKill/src/acesrc/acebot_ai.c $
|
||||
*
|
||||
* 25 2/11/99 17:02 Riever
|
||||
* Fog support added for bot vision and target selection.
|
||||
*
|
||||
* 24 27/10/99 18:49 Riever
|
||||
* Added lightlevel check for enemy selection
|
||||
*
|
||||
* 23 22/10/99 7:36 Riever
|
||||
* Commented checkshot for future development.
|
||||
*
|
||||
* 22 22/10/99 7:08 Riever
|
||||
* Removed distance from weightings.
|
||||
*
|
||||
* 21 22/10/99 6:22 Riever
|
||||
* Modified LR goal item selection weightings.
|
||||
*
|
||||
* 20 16/10/99 12:06 Riever
|
||||
* Changed checkshot code to use a single pixel beam for now.
|
||||
*
|
||||
* 19 11/10/99 7:47 Riever
|
||||
* Fixed SP respawn bug (There was no respawn!)
|
||||
*
|
||||
* 18 6/10/99 20:28 Riever
|
||||
* Stopped targetting spectators for long range goals.
|
||||
*
|
||||
* 17 6/10/99 18:15 Riever
|
||||
* ACEAI_Cmd_Choose() for Teamplay equipment.
|
||||
*
|
||||
* 16 6/10/99 17:51 Riever
|
||||
* Teamplay: Bot spectators don't think.
|
||||
*
|
||||
* 15 6/10/99 17:40 Riever
|
||||
* Added TeamPlay state STATE_POSITION to enable bots to seperate and
|
||||
* avoid centipede formations.
|
||||
*
|
||||
* 14 27/09/99 20:47 Riever
|
||||
* Removed "current enemy" check
|
||||
* Added line of sight check for "hurt by enemy"
|
||||
*
|
||||
* 13 27/09/99 14:27 Riever
|
||||
* Removed cost weighting from item selection on long range goals
|
||||
*
|
||||
* 12 27/09/99 7:05 Riever
|
||||
* Added more weapon selection groups based on range
|
||||
*
|
||||
* 11 26/09/99 15:34 Riever
|
||||
* Look for nearby enemies even behind us
|
||||
*
|
||||
* 10 26/09/99 7:34 Riever
|
||||
* Bot vision restricted to FOV
|
||||
* Enemey selection now chooses closest target
|
||||
*
|
||||
* 9 21/09/99 20:20 Riever
|
||||
* Take teamPauseTime into account when choosing long range goals.
|
||||
*
|
||||
* 8 21/09/99 17:22 Riever
|
||||
* Put a timer in to stop the bots going centipede style in
|
||||
* TeamPlay.(teamPauseTime)
|
||||
*
|
||||
* 7 21/09/99 12:20 Riever
|
||||
*
|
||||
* 6 18/09/99 19:47 Riever
|
||||
* If range is over 300, HC and Knife will not be chosen
|
||||
*
|
||||
* 5 18/09/99 19:23 Riever
|
||||
* Increased random door opening time to 5 seconds
|
||||
*
|
||||
* 4 18/09/99 10:16 Riever
|
||||
* changed door opening attempt time to be 3 + random
|
||||
*
|
||||
* 3 18/09/99 9:49 Riever
|
||||
* Stopped the centipede effect in teamplay
|
||||
*
|
||||
* 2 13/09/99 19:52 Riever
|
||||
* Added headers
|
||||
*
|
||||
*/
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// acebot_ai.c - This file contains all of the
|
||||
// AI routines for the ACE II bot.
|
||||
//
|
||||
//
|
||||
// NOTE: I went back and pulled out most of the brains from
|
||||
// a number of these functions. They can be expanded on
|
||||
// to provide a "higher" level of AI.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "..\g_local.h"
|
||||
#include "..\m_player.h"
|
||||
|
||||
#include "acebot.h"
|
||||
// CGF_FOG ADD
|
||||
#include "cgf_sfx_fog.h"
|
||||
// CGF_FOG END
|
||||
void ACEAI_Cmd_Choose( edict_t *ent, char *s);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Main Think function for bot
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
void ACEAI_Think (edict_t *self)
|
||||
{
|
||||
usercmd_t ucmd;
|
||||
|
||||
// Set up client movement
|
||||
VectorCopy(self->client->ps.viewangles,self->s.angles);
|
||||
VectorSet (self->client->ps.pmove.delta_angles, 0, 0, 0);
|
||||
memset (&ucmd, 0, sizeof (ucmd));
|
||||
self->enemy = NULL;
|
||||
self->movetarget = NULL;
|
||||
|
||||
// Force respawn
|
||||
if (self->deadflag)
|
||||
{
|
||||
self->client->buttons = 0;
|
||||
ucmd.buttons = BUTTON_ATTACK;
|
||||
}
|
||||
|
||||
// Teamplay spectator?
|
||||
// if( self->solid == SOLID_NOT)
|
||||
// return;
|
||||
|
||||
if(self->state == STATE_WANDER &&
|
||||
(self->wander_timeout < level.time)
|
||||
)
|
||||
ACEAI_PickLongRangeGoal(self); // pick a new long range goal
|
||||
|
||||
// In teamplay pick a random node
|
||||
if( self->state == STATE_POSITION )
|
||||
{
|
||||
if( level.time >= self->teamPauseTime)
|
||||
{
|
||||
// We've waited long enough - let's go kick some ass!
|
||||
self->state = STATE_WANDER;
|
||||
}
|
||||
// Don't go here too often
|
||||
if( self->goal_node == INVALID || self->wander_timeout < level.time )
|
||||
ACEAI_PickLongRangeGoal(self);
|
||||
}
|
||||
|
||||
// Kill the bot if completely stuck somewhere
|
||||
if(VectorLength(self->velocity) > 37) //
|
||||
self->suicide_timeout = level.time + 10.0;
|
||||
|
||||
if(self->suicide_timeout < level.time && !teamplay->value)
|
||||
{
|
||||
self->health = 0;
|
||||
player_die (self, self, self, 100000, vec3_origin);
|
||||
}
|
||||
|
||||
// Find any short range goal
|
||||
ACEAI_PickShortRangeGoal(self);
|
||||
|
||||
// Look for enemies
|
||||
if(ACEAI_FindEnemy(self))
|
||||
{
|
||||
ACEAI_ChooseWeapon(self);
|
||||
ACEMV_Attack (self, &ucmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Are we hurt?
|
||||
if( self->health < 100)
|
||||
{
|
||||
Cmd_Bandage_f ( self );
|
||||
}
|
||||
// Execute the move, or wander
|
||||
if(self->state == STATE_WANDER)
|
||||
ACEMV_Wander(self,&ucmd);
|
||||
else if( (self->state == STATE_MOVE) || (self->state == STATE_POSITION) )
|
||||
ACEMV_Move(self,&ucmd);
|
||||
}
|
||||
|
||||
//AQ2 ADD
|
||||
if(self->last_door_time < (level.time - 5.0 - random()) )
|
||||
{
|
||||
// Toggle any door that may be nearby
|
||||
//@@ Temporary until I get better code in! Needs to trace for the door
|
||||
Cmd_OpenDoor_f ( self );
|
||||
self->last_door_time = level.time;
|
||||
}
|
||||
//AQ2 END
|
||||
|
||||
//debug_printf("State: %d\n",self->state);
|
||||
|
||||
// set approximate ping
|
||||
ucmd.msec = 75 + floor (random () * 25) + 1;
|
||||
|
||||
// show random ping values in scoreboard
|
||||
self->client->ping = ucmd.msec;
|
||||
|
||||
// set bot's view angle
|
||||
ucmd.angles[PITCH] = ANGLE2SHORT(self->s.angles[PITCH]);
|
||||
ucmd.angles[YAW] = ANGLE2SHORT(self->s.angles[YAW]);
|
||||
ucmd.angles[ROLL] = ANGLE2SHORT(self->s.angles[ROLL]);
|
||||
|
||||
// send command through id's code
|
||||
ClientThink (self, &ucmd);
|
||||
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Evaluate the best long range goal and send the bot on
|
||||
// its way. This is a good time waster, so use it sparingly.
|
||||
// Do not call it for every think cycle.
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
void ACEAI_PickLongRangeGoal(edict_t *self)
|
||||
{
|
||||
|
||||
int i;
|
||||
int node;
|
||||
float weight,best_weight=0.0;
|
||||
int current_node,goal_node;
|
||||
edict_t *goal_ent;
|
||||
float cost;
|
||||
|
||||
// look for a target
|
||||
current_node = ACEND_FindClosestReachableNode(self,NODE_DENSITY,NODE_ALL);
|
||||
|
||||
self->current_node = current_node;
|
||||
|
||||
// Even in teamplay, we wander if no valid node
|
||||
if(current_node == -1)
|
||||
{
|
||||
self->state = STATE_WANDER;
|
||||
self->wander_timeout = level.time + 1.0;
|
||||
self->goal_node = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
//======================
|
||||
// Teamplay POSITION state
|
||||
//======================
|
||||
if( self->state == STATE_POSITION )
|
||||
{
|
||||
int counter = 0;
|
||||
cost = INVALID;
|
||||
self->goal_node = INVALID;
|
||||
|
||||
// Pick a random node to go to
|
||||
while( cost == INVALID && counter < 10) // Don't look for too many
|
||||
{
|
||||
counter++;
|
||||
i = (int)(random() * numnodes -1); // Any of the current nodes will do
|
||||
cost = ACEND_FindCost(current_node, i);
|
||||
|
||||
if(cost == INVALID || cost < 2) // ignore invalid and very short hops
|
||||
{
|
||||
cost = INVALID;
|
||||
i = INVALID;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// We have a target node - just go there!
|
||||
if( i != INVALID )
|
||||
{
|
||||
self->state = STATE_MOVE;
|
||||
self->tries = 0; // Reset the count of how many times we tried this goal
|
||||
ACEND_SetGoal(self,i);
|
||||
self->wander_timeout = level.time + 1.0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// Items
|
||||
///////////////////////////////////////////////////////
|
||||
for(i=0;i<num_items;i++)
|
||||
{
|
||||
if(item_table[i].ent == NULL || item_table[i].ent->solid == SOLID_NOT) // ignore items that are not there.
|
||||
continue;
|
||||
|
||||
cost = ACEND_FindCost(current_node,item_table[i].node);
|
||||
|
||||
if(cost == INVALID || cost < 2) // ignore invalid and very short hops
|
||||
continue;
|
||||
|
||||
weight = ACEIT_ItemNeed(self, item_table[i].item);
|
||||
|
||||
/* // If I am on team one and I have the flag for the other team....return it
|
||||
if(ctf->value && (item_table[i].item == ITEMLIST_FLAG2 || item_table[i].item == ITEMLIST_FLAG1) &&
|
||||
(self->client->resp.ctf_team == CTF_TEAM1 && self->client->pers.inventory[ITEMLIST_FLAG2] ||
|
||||
self->client->resp.ctf_team == CTF_TEAM2 && self->client->pers.inventory[ITEMLIST_FLAG1]))
|
||||
weight = 10.0;*/
|
||||
|
||||
weight *= ( (rand()%5) +1 ); // Allow random variations
|
||||
// weight /= cost; // Check against cost of getting there
|
||||
|
||||
if(weight > best_weight && item_table[i].node != INVALID)
|
||||
{
|
||||
best_weight = weight;
|
||||
goal_node = item_table[i].node;
|
||||
goal_ent = item_table[i].ent;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// Players
|
||||
///////////////////////////////////////////////////////
|
||||
// This should be its own function and is for now just
|
||||
// finds a player to set as the goal.
|
||||
for(i=0;i<num_players;i++)
|
||||
{
|
||||
if( (players[i] == self) || (players[i]->solid == SOLID_NOT) )
|
||||
continue;
|
||||
|
||||
// If it's dark and he's not already our enemy, ignore him
|
||||
if( self->enemy && players[i] != self->enemy)
|
||||
{
|
||||
if( players[i]->light_level < 30)
|
||||
continue;
|
||||
// CGF_FOG ADD
|
||||
// Check for FOG!
|
||||
if( CGF_SFX_IsFogEnabled() )
|
||||
{
|
||||
vec3_t v;
|
||||
float range;
|
||||
|
||||
// Get distance to enemy
|
||||
VectorSubtract (self->s.origin, players[i]->s.origin, v);
|
||||
range = VectorLength(v);
|
||||
// If fog index is < 0.1 we can't see him
|
||||
if( CGF_SFX_GetFogForDistance(range) < 0.1)
|
||||
continue;
|
||||
}
|
||||
// CGF_FOG END
|
||||
}
|
||||
|
||||
node = ACEND_FindClosestReachableNode(players[i],NODE_DENSITY,NODE_ALL);
|
||||
// RiEvEr - bug fixing
|
||||
if( node == INVALID)
|
||||
cost = INVALID;
|
||||
else
|
||||
cost = ACEND_FindCost(current_node, node);
|
||||
|
||||
if(cost == INVALID || cost < 3) // ignore invalid and very short hops
|
||||
continue;
|
||||
|
||||
/* // Player carrying the flag?
|
||||
if(ctf->value && (players[i]->client->pers.inventory[ITEMLIST_FLAG2] || players[i]->client->pers.inventory[ITEMLIST_FLAG1]))
|
||||
weight = 2.0;
|
||||
else*/
|
||||
// Stop the centipede effect in teamplay
|
||||
if( teamplay->value )
|
||||
{
|
||||
// Check it's an enemy
|
||||
// If not an enemy, don't follow him
|
||||
if( OnSameTeam( self, players[i]))
|
||||
weight = 0.0;
|
||||
else
|
||||
weight = 0.3;
|
||||
}
|
||||
else
|
||||
weight = 0.3;
|
||||
|
||||
weight *= ( (rand()%5) +1 ); // Allow random variations
|
||||
// weight /= cost; // Check against cost of getting there
|
||||
|
||||
if(weight > best_weight && node != INVALID)
|
||||
{
|
||||
best_weight = weight;
|
||||
goal_node = node;
|
||||
goal_ent = players[i];
|
||||
}
|
||||
}
|
||||
|
||||
// If do not find a goal, go wandering....
|
||||
if(best_weight == 0.0 || goal_node == INVALID )
|
||||
{
|
||||
self->goal_node = INVALID;
|
||||
self->state = STATE_WANDER;
|
||||
self->wander_timeout = level.time + 1.0;
|
||||
if(debug_mode)
|
||||
debug_printf("%s did not find a LR goal, wandering.\n",self->client->pers.netname);
|
||||
return; // no path?
|
||||
}
|
||||
|
||||
// OK, everything valid, let's start moving to our goal.
|
||||
self->state = STATE_MOVE;
|
||||
self->tries = 0; // Reset the count of how many times we tried this goal
|
||||
|
||||
if(goal_ent != NULL && debug_mode)
|
||||
debug_printf("%s selected a %s at node %d for LR goal.\n",self->client->pers.netname, goal_ent->classname, goal_node);
|
||||
|
||||
ACEND_SetGoal(self,goal_node);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Pick best goal based on importance and range. This function
|
||||
// overrides the long range goal selection for items that
|
||||
// are very close to the bot and are reachable.
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
void ACEAI_PickShortRangeGoal(edict_t *self)
|
||||
{
|
||||
edict_t *target;
|
||||
float weight,best_weight=0.0;
|
||||
edict_t *best;
|
||||
int index;
|
||||
|
||||
// look for a target (should make more efficient later)
|
||||
target = findradius(NULL, self->s.origin, 200);
|
||||
|
||||
while(target)
|
||||
{
|
||||
if(target->classname == NULL)
|
||||
return;
|
||||
|
||||
// Missle avoidance code
|
||||
// Set our movetarget to be the rocket or grenade fired at us.
|
||||
if(strcmp(target->classname,"rocket")==0 || strcmp(target->classname,"grenade")==0)
|
||||
{
|
||||
if(debug_mode)
|
||||
debug_printf("ROCKET ALERT!\n");
|
||||
|
||||
self->movetarget = target;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ACEIT_IsReachable(self,target->s.origin))
|
||||
{
|
||||
if (infront(self, target))
|
||||
{
|
||||
index = ACEIT_ClassnameToIndex(target->classname);
|
||||
weight = ACEIT_ItemNeed(self, index);
|
||||
|
||||
if(weight > best_weight)
|
||||
{
|
||||
best_weight = weight;
|
||||
best = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// next target
|
||||
target = findradius(target, self->s.origin, 200);
|
||||
}
|
||||
|
||||
if(best_weight)
|
||||
{
|
||||
self->movetarget = best;
|
||||
|
||||
if(debug_mode && self->goalentity != self->movetarget)
|
||||
debug_printf("%s selected a %s for SR goal.\n",self->client->pers.netname, self->movetarget->classname);
|
||||
|
||||
self->goalentity = best;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Scan for enemy (simplifed for now to just pick any visible enemy)
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Modified by RiEvEr
|
||||
// Chooses nearest enemy or last person to shoot us
|
||||
//
|
||||
qboolean ACEAI_FindEnemy(edict_t *self)
|
||||
{
|
||||
int i;
|
||||
edict_t *bestenemy = NULL;
|
||||
float bestweight = 99999;
|
||||
float weight;
|
||||
vec3_t dist;
|
||||
|
||||
/* // If we already have an enemy and it is the last enemy to hurt us
|
||||
if (self->enemy &&
|
||||
(self->enemy == self->client->attacker) &&
|
||||
(!self->enemy->deadflag) &&
|
||||
(self->enemy->solid != SOLID_NOT)
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
for(i=0;i<=num_players;i++)
|
||||
{
|
||||
if(players[i] == NULL || players[i] == self ||
|
||||
players[i]->solid == SOLID_NOT)
|
||||
continue;
|
||||
|
||||
// If it's dark and he's not already our enemy, ignore him
|
||||
if( self->enemy && players[i] != self->enemy)
|
||||
{
|
||||
if( players[i]->light_level < 30)
|
||||
continue;
|
||||
// CGF_FOG ADD
|
||||
// Check for FOG!
|
||||
if( CGF_SFX_IsFogEnabled() )
|
||||
{
|
||||
vec3_t v;
|
||||
float range;
|
||||
|
||||
// Get distance to enemy
|
||||
VectorSubtract (self->s.origin, players[i]->s.origin, v);
|
||||
range = VectorLength(v);
|
||||
// If fog index is < 0.1 we can't see him
|
||||
if( CGF_SFX_GetFogForDistance(range) < 0.1)
|
||||
continue;
|
||||
}
|
||||
// CGF_FOG END
|
||||
}
|
||||
|
||||
/* if(ctf->value &&
|
||||
self->client->resp.ctf_team == players[i]->client->resp.ctf_team)
|
||||
continue;*/
|
||||
// AQ2 ADD
|
||||
if(teamplay->value && OnSameTeam( self, players[i]) )
|
||||
continue;
|
||||
// AQ2 END
|
||||
|
||||
if(!players[i]->deadflag && visible(self, players[i]) &&
|
||||
gi.inPVS(self->s.origin, players[i]->s.origin) )
|
||||
{
|
||||
// RiEvEr
|
||||
// Now we assess this enemy
|
||||
VectorSubtract(self->s.origin, players[i]->s.origin, dist);
|
||||
weight = VectorLength( dist );
|
||||
|
||||
// Can we see this enemy, or is it so close that we should not ignore it!
|
||||
if( infront( self, players[i] ) ||
|
||||
(weight < 300 ) )
|
||||
{
|
||||
// See if it's better than what we have already
|
||||
if (weight < bestweight)
|
||||
{
|
||||
bestweight = weight;
|
||||
bestenemy = players[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we found a good enemy set it up
|
||||
if( bestenemy)
|
||||
{
|
||||
self->enemy = bestenemy;
|
||||
return true;
|
||||
}
|
||||
// Check if we've been shot from behind or out of view
|
||||
if( self->client->attacker )
|
||||
{
|
||||
// Check if it was recent
|
||||
if( self->client->push_timeout > 0)
|
||||
{
|
||||
if(!self->client->attacker->deadflag && visible(self, self->client->attacker) &&
|
||||
gi.inPVS(self->s.origin, self->client->attacker->s.origin) )
|
||||
{
|
||||
self->enemy = self->client->attacker;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
//R
|
||||
// Otherwise signal "no enemy available"
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Hold fire with RL/BFG?
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
//@@ Modify this to check for hitting teammates in teamplay games.
|
||||
qboolean ACEAI_CheckShot(edict_t *self)
|
||||
{
|
||||
trace_t tr;
|
||||
|
||||
//AQ2 tr = gi.trace (self->s.origin, tv(-8,-8,-8), tv(8,8,8), self->enemy->s.origin, self, MASK_OPAQUE);
|
||||
// tr = gi.trace (self->s.origin, tv(-8,-8,-8), tv(8,8,8), self->enemy->s.origin, self, MASK_SOLID|MASK_OPAQUE);
|
||||
tr = gi.trace (self->s.origin, vec3_origin, vec3_origin, self->enemy->s.origin, self, MASK_SOLID|MASK_OPAQUE);
|
||||
|
||||
// Blocked, do not shoot
|
||||
if (tr.fraction < 0.9)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Choose the best weapon for bot (simplified)
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
void ACEAI_ChooseWeapon(edict_t *self)
|
||||
{
|
||||
float range;
|
||||
vec3_t v;
|
||||
|
||||
// if no enemy, then what are we doing here?
|
||||
if(!self->enemy)
|
||||
return;
|
||||
//AQ2 CHANGE
|
||||
// Currently always favor the dual pistols!
|
||||
//@@ This will become the "bot choice" weapon
|
||||
// if(ACEIT_ChangeDualSpecialWeapon(self,FindItem(DUAL_NAME)))
|
||||
// return;
|
||||
//AQ2 END
|
||||
|
||||
// Base selection on distance.
|
||||
VectorSubtract (self->s.origin, self->enemy->s.origin, v);
|
||||
range = VectorLength(v);
|
||||
|
||||
// Longer range
|
||||
if(range > 1000)
|
||||
{
|
||||
if(ACEIT_ChangeSniperSpecialWeapon(self,FindItem(SNIPER_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeM3SpecialWeapon(self,FindItem(M3_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeM4SpecialWeapon(self,FindItem(M4_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeMP5SpecialWeapon(self,FindItem(MP5_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeMK23SpecialWeapon(self,FindItem(MK23_NAME)))
|
||||
return;
|
||||
}
|
||||
|
||||
// Longer range
|
||||
if(range > 700)
|
||||
{
|
||||
if(ACEIT_ChangeM3SpecialWeapon(self,FindItem(M3_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeM4SpecialWeapon(self,FindItem(M4_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeMP5SpecialWeapon(self,FindItem(MP5_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeSniperSpecialWeapon(self,FindItem(SNIPER_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeMK23SpecialWeapon(self,FindItem(MK23_NAME)))
|
||||
return;
|
||||
}
|
||||
|
||||
// Longer range
|
||||
if(range > 500)
|
||||
{
|
||||
if(ACEIT_ChangeMP5SpecialWeapon(self,FindItem(MP5_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeM3SpecialWeapon(self,FindItem(M3_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeM4SpecialWeapon(self,FindItem(M4_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeSniperSpecialWeapon(self,FindItem(SNIPER_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeMK23SpecialWeapon(self,FindItem(MK23_NAME)))
|
||||
return;
|
||||
}
|
||||
|
||||
// Longer range
|
||||
if(range > 300)
|
||||
{
|
||||
if(ACEIT_ChangeM4SpecialWeapon(self,FindItem(M4_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeMP5SpecialWeapon(self,FindItem(MP5_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeM3SpecialWeapon(self,FindItem(M3_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeSniperSpecialWeapon(self,FindItem(SNIPER_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeMK23SpecialWeapon(self,FindItem(MK23_NAME)))
|
||||
return;
|
||||
}
|
||||
|
||||
// Short range
|
||||
if(ACEIT_ChangeHCSpecialWeapon(self,FindItem(HC_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeSniperSpecialWeapon(self,FindItem(SNIPER_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeM3SpecialWeapon(self,FindItem(M3_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeM4SpecialWeapon(self,FindItem(M4_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeMP5SpecialWeapon(self,FindItem(MP5_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeDualSpecialWeapon(self,FindItem(DUAL_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeMK23SpecialWeapon(self,FindItem(MK23_NAME)))
|
||||
return;
|
||||
|
||||
if(ACEIT_ChangeWeapon(self,FindItem(KNIFE_NAME)))
|
||||
return;
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
void ACEAI_Cmd_Choose (edict_t *ent, char *s)
|
||||
{
|
||||
// only works in teamplay
|
||||
if (!teamplay->value)
|
||||
return;
|
||||
|
||||
if ( stricmp(s, MP5_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(MP5_NAME);
|
||||
else if ( stricmp(s, M3_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(M3_NAME);
|
||||
else if ( stricmp(s, M4_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(M4_NAME);
|
||||
else if ( stricmp(s, HC_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(HC_NAME);
|
||||
else if ( stricmp(s, SNIPER_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(SNIPER_NAME);
|
||||
else if ( stricmp(s, KNIFE_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(KNIFE_NAME);
|
||||
else if ( stricmp(s, DUAL_NAME) == 0 )
|
||||
ent->client->resp.weapon = FindItem(DUAL_NAME);
|
||||
else if ( stricmp(s, KEV_NAME) == 0 )
|
||||
ent->client->resp.item = FindItem(KEV_NAME);
|
||||
else if ( stricmp(s, LASER_NAME) == 0 )
|
||||
ent->client->resp.item = FindItem(LASER_NAME);
|
||||
else if ( stricmp(s, SLIP_NAME) == 0 )
|
||||
ent->client->resp.item = FindItem(SLIP_NAME);
|
||||
else if ( stricmp(s, SIL_NAME) == 0 )
|
||||
ent->client->resp.item = FindItem(SIL_NAME);
|
||||
else if ( stricmp(s, BAND_NAME) == 0 )
|
||||
ent->client->resp.item = FindItem(BAND_NAME);
|
||||
}
|
210
acesrc/acebot_cmds.c
Normal file
210
acesrc/acebot_cmds.c
Normal file
|
@ -0,0 +1,210 @@
|
|||
///////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// ACE - Quake II Bot Base Code
|
||||
//
|
||||
// Version 1.0
|
||||
//
|
||||
// Original file is Copyright(c), Steve Yeager 1998, All Rights Reserved
|
||||
//
|
||||
//
|
||||
// All other files are Copyright(c) Id Software, Inc.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* $Header: /LicenseToKill/src/acesrc/acebot_cmds.c 5 27/09/99 16:01 Riever $
|
||||
*
|
||||
* $Log: /LicenseToKill/src/acesrc/acebot_cmds.c $
|
||||
*
|
||||
* 5 27/09/99 16:01 Riever
|
||||
* Added "self" to ACEND_UpdateNodeEdge calls
|
||||
*
|
||||
* 4 21/09/99 14:23 Riever
|
||||
* "ltkversion" console command added to identify version being played.
|
||||
*
|
||||
* 3 18/09/99 8:05 Riever
|
||||
* shownodes NN command created
|
||||
*
|
||||
* 2 13/09/99 19:52 Riever
|
||||
* Added headers
|
||||
*
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// acebot_cmds.c - Main internal command processor
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "..\g_local.h"
|
||||
#include "acebot.h"
|
||||
|
||||
qboolean debug_mode=false;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Special command processor
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
qboolean ACECM_Commands(edict_t *ent)
|
||||
{
|
||||
char *cmd;
|
||||
int node;
|
||||
|
||||
cmd = gi.argv(0);
|
||||
|
||||
if(Q_stricmp (cmd, "addnode") == 0 && debug_mode)
|
||||
ent->last_node = ACEND_AddNode(ent,atoi(gi.argv(1)));
|
||||
|
||||
else if(Q_stricmp (cmd, "removelink") == 0 && debug_mode)
|
||||
ACEND_RemoveNodeEdge(ent,atoi(gi.argv(1)), atoi(gi.argv(2)));
|
||||
|
||||
else if(Q_stricmp (cmd, "addlink") == 0 && debug_mode)
|
||||
ACEND_UpdateNodeEdge(ent, atoi(gi.argv(1)), atoi(gi.argv(2)));
|
||||
|
||||
else if(Q_stricmp (cmd, "showpath") == 0 && debug_mode)
|
||||
ACEND_ShowPath(ent,atoi(gi.argv(1)));
|
||||
|
||||
else if(Q_stricmp (cmd, "shownode") == 0 && debug_mode)
|
||||
ACEND_ShowNode(atoi(gi.argv(1)));
|
||||
|
||||
else if(Q_stricmp (cmd, "findnode") == 0 && debug_mode)
|
||||
{
|
||||
node = ACEND_FindClosestReachableNode(ent,NODE_DENSITY, NODE_ALL);
|
||||
safe_bprintf(PRINT_MEDIUM,"node: %d type: %d x: %f y: %f z %f\n",node,nodes[node].type,nodes[node].origin[0],nodes[node].origin[1],nodes[node].origin[2]);
|
||||
}
|
||||
|
||||
else if(Q_stricmp (cmd, "movenode") == 0 && debug_mode)
|
||||
{
|
||||
node = atoi(gi.argv(1));
|
||||
nodes[node].origin[0] = atof(gi.argv(2));
|
||||
nodes[node].origin[1] = atof(gi.argv(3));
|
||||
nodes[node].origin[2] = atof(gi.argv(4));
|
||||
safe_bprintf(PRINT_MEDIUM,"node: %d moved to x: %f y: %f z %f\n",node, nodes[node].origin[0],nodes[node].origin[1],nodes[node].origin[2]);
|
||||
}
|
||||
// RiEvEr - LTKVERSION number
|
||||
else if(Q_stricmp (cmd, "ltkversion") == 0 )
|
||||
{
|
||||
safe_bprintf(PRINT_HIGH,"Current version is %s\n", LTKVERSION);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Called when the level changes, store maps and bots (disconnected)
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
void ACECM_Store()
|
||||
{
|
||||
ACEND_SaveNodes();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// These routines are bot safe print routines, all id code needs to be
|
||||
// changed to these so the bots do not blow up on messages sent to them.
|
||||
// Do a find and replace on all code that matches the below criteria.
|
||||
//
|
||||
// (Got the basic idea from Ridah)
|
||||
//
|
||||
// change: gi.cprintf to safe_cprintf
|
||||
// change: gi.bprintf to safe_bprintf
|
||||
// change: gi.centerprintf to safe_centerprintf
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Debug print, could add a "logging" feature to print to a file
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
void debug_printf(char *fmt, ...)
|
||||
{
|
||||
int i;
|
||||
char bigbuffer[0x10000];
|
||||
int len;
|
||||
va_list argptr;
|
||||
edict_t *cl_ent;
|
||||
|
||||
va_start (argptr,fmt);
|
||||
len = vsprintf (bigbuffer,fmt,argptr);
|
||||
va_end (argptr);
|
||||
|
||||
if (dedicated->value)
|
||||
gi.cprintf(NULL, PRINT_MEDIUM, bigbuffer);
|
||||
|
||||
for (i=0 ; i<maxclients->value ; i++)
|
||||
{
|
||||
cl_ent = g_edicts + 1 + i;
|
||||
if (!cl_ent->inuse || cl_ent->is_bot)
|
||||
continue;
|
||||
|
||||
gi.cprintf(cl_ent, PRINT_MEDIUM, bigbuffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// botsafe cprintf
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
void safe_cprintf (edict_t *ent, int printlevel, char *fmt, ...)
|
||||
{
|
||||
char bigbuffer[0x10000];
|
||||
va_list argptr;
|
||||
int len;
|
||||
|
||||
if (ent && (!ent->inuse || ent->is_bot))
|
||||
return;
|
||||
|
||||
va_start (argptr,fmt);
|
||||
len = vsprintf (bigbuffer,fmt,argptr);
|
||||
va_end (argptr);
|
||||
|
||||
gi.cprintf(ent, printlevel, bigbuffer);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// botsafe centerprintf
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
void safe_centerprintf (edict_t *ent, char *fmt, ...)
|
||||
{
|
||||
char bigbuffer[0x10000];
|
||||
va_list argptr;
|
||||
int len;
|
||||
|
||||
if (!ent->inuse || ent->is_bot)
|
||||
return;
|
||||
|
||||
va_start (argptr,fmt);
|
||||
len = vsprintf (bigbuffer,fmt,argptr);
|
||||
va_end (argptr);
|
||||
|
||||
gi.centerprintf(ent, bigbuffer);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// botsafe bprintf
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
void safe_bprintf (int printlevel, char *fmt, ...)
|
||||
{
|
||||
int i;
|
||||
char bigbuffer[0x10000];
|
||||
int len;
|
||||
va_list argptr;
|
||||
edict_t *cl_ent;
|
||||
|
||||
va_start (argptr,fmt);
|
||||
len = vsprintf (bigbuffer,fmt,argptr);
|
||||
va_end (argptr);
|
||||
|
||||
if (dedicated->value)
|
||||
gi.cprintf(NULL, printlevel, bigbuffer);
|
||||
|
||||
for (i=0 ; i<maxclients->value ; i++)
|
||||
{
|
||||
cl_ent = g_edicts + 1 + i;
|
||||
if (!cl_ent->inuse || cl_ent->is_bot)
|
||||
continue;
|
||||
|
||||
gi.cprintf(cl_ent, printlevel, bigbuffer);
|
||||
}
|
||||
}
|
||||
|
303
acesrc/acebot_compress.c
Normal file
303
acesrc/acebot_compress.c
Normal file
|
@ -0,0 +1,303 @@
|
|||
///////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// ACE - Quake II Bot Base Code
|
||||
//
|
||||
// Version 1.0
|
||||
//
|
||||
// Original file is Copyright(c), Steve Yeager 1998, All Rights Reserved
|
||||
//
|
||||
//
|
||||
// All other files are Copyright(c) Id Software, Inc.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* $Header: /LicenseToKill/src/acesrc/acebot_compress.c 2 13/09/99 19:52 Riever $
|
||||
*
|
||||
* $Log: /LicenseToKill/src/acesrc/acebot_compress.c $
|
||||
*
|
||||
* 2 13/09/99 19:52 Riever
|
||||
* Added headers
|
||||
*
|
||||
*/
|
||||
//
|
||||
// acebot_compress.c - Data compress based on LZSS
|
||||
//
|
||||
// Not sure where I got this code, but thanks go to the
|
||||
// author. I just rewote it to allow the use of buffers
|
||||
// instead of files.
|
||||
//
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#define N 4096 /* size of ring buffer */
|
||||
#define F 18 /* upper limit for match_length */
|
||||
#define THRESHOLD 2 /* encode string into position and length
|
||||
if match_length is greater than this */
|
||||
#define NIL N /* index for root of binary search trees */
|
||||
|
||||
unsigned long int
|
||||
textsize = 0, /* text size counter */
|
||||
codesize = 0, /* code size counter */
|
||||
printcount = 0; /* counter for reporting progress every 1K bytes */
|
||||
unsigned char
|
||||
text_buf[N + F - 1]; /* ring buffer of size N,
|
||||
with extra F-1 bytes to facilitate string comparison */
|
||||
int match_position, match_length, /* of longest match. These are
|
||||
set by the InsertNode() procedure. */
|
||||
lson[N + 1], rson[N + 257], dad[N + 1]; /* left & right children &
|
||||
parents -- These constitute binary search trees. */
|
||||
|
||||
void InitTree(void) /* initialize trees */
|
||||
{
|
||||
int i;
|
||||
|
||||
/* For i = 0 to N - 1, rson[i] and lson[i] will be the right and
|
||||
left children of node i. These nodes need not be initialized.
|
||||
Also, dad[i] is the parent of node i. These are initialized to
|
||||
NIL (= N), which stands for 'not used.'
|
||||
For i = 0 to 255, rson[N + i + 1] is the root of the tree
|
||||
for strings that begin with character i. These are initialized
|
||||
to NIL. Note there are 256 trees. */
|
||||
|
||||
for (i = N + 1; i <= N + 256; i++) rson[i] = NIL;
|
||||
for (i = 0; i < N; i++) dad[i] = NIL;
|
||||
}
|
||||
|
||||
void InsertNode(int r)
|
||||
/* Inserts string of length F, text_buf[r..r+F-1], into one of the
|
||||
trees (text_buf[r]'th tree) and returns the longest-match position
|
||||
and length via the global variables match_position and match_length.
|
||||
If match_length = F, then removes the old node in favor of the new
|
||||
one, because the old one will be deleted sooner.
|
||||
Note r plays double role, as tree node and position in buffer. */
|
||||
{
|
||||
int i, p, cmp;
|
||||
unsigned char *key;
|
||||
|
||||
cmp = 1; key = &text_buf[r]; p = N + 1 + key[0];
|
||||
rson[r] = lson[r] = NIL; match_length = 0;
|
||||
for ( ; ; ) {
|
||||
if (cmp >= 0) {
|
||||
if (rson[p] != NIL) p = rson[p];
|
||||
else { rson[p] = r; dad[r] = p; return; }
|
||||
} else {
|
||||
if (lson[p] != NIL) p = lson[p];
|
||||
else { lson[p] = r; dad[r] = p; return; }
|
||||
}
|
||||
for (i = 1; i < F; i++)
|
||||
if ((cmp = key[i] - text_buf[p + i]) != 0) break;
|
||||
if (i > match_length) {
|
||||
match_position = p;
|
||||
if ((match_length = i) >= F) break;
|
||||
}
|
||||
}
|
||||
dad[r] = dad[p]; lson[r] = lson[p]; rson[r] = rson[p];
|
||||
dad[lson[p]] = r; dad[rson[p]] = r;
|
||||
if (rson[dad[p]] == p) rson[dad[p]] = r;
|
||||
else lson[dad[p]] = r;
|
||||
dad[p] = NIL; /* remove p */
|
||||
}
|
||||
|
||||
void DeleteNode(int p) /* deletes node p from tree */
|
||||
{
|
||||
int q;
|
||||
|
||||
if (dad[p] == NIL) return; /* not in tree */
|
||||
if (rson[p] == NIL) q = lson[p];
|
||||
else if (lson[p] == NIL) q = rson[p];
|
||||
else {
|
||||
q = lson[p];
|
||||
if (rson[q] != NIL) {
|
||||
do { q = rson[q]; } while (rson[q] != NIL);
|
||||
rson[dad[q]] = lson[q]; dad[lson[q]] = dad[q];
|
||||
lson[q] = lson[p]; dad[lson[p]] = q;
|
||||
}
|
||||
rson[q] = rson[p]; dad[rson[p]] = q;
|
||||
}
|
||||
dad[q] = dad[p];
|
||||
if (rson[dad[p]] == p) rson[dad[p]] = q; else lson[dad[p]] = q;
|
||||
dad[p] = NIL;
|
||||
}
|
||||
|
||||
int Encode(char *filename, unsigned char *buffer, int bufsize, int version)
|
||||
{
|
||||
int i, c, len, r, s, last_match_length, code_buf_ptr;
|
||||
unsigned char code_buf[17], mask;
|
||||
int bufptr = 0;
|
||||
FILE *pOut;
|
||||
//int version;
|
||||
|
||||
pOut = fopen(filename, "wb");
|
||||
if(pOut == NULL)
|
||||
return -1; // bail
|
||||
|
||||
//version = 2; // compressed version
|
||||
|
||||
// Read version info (uneeded here, but moves file ptr)
|
||||
fwrite(&version,sizeof(int),1,pOut); // write version
|
||||
fwrite(&bufsize,sizeof(int),1,pOut); // write out the size of the buffer
|
||||
|
||||
InitTree(); /* initialize trees */
|
||||
code_buf[0] = 0; /* code_buf[1..16] saves eight units of code, and
|
||||
code_buf[0] works as eight flags, "1" representing that the unit
|
||||
is an unencoded letter (1 byte), "0" a position-and-length pair
|
||||
(2 bytes). Thus, eight units require at most 16 bytes of code. */
|
||||
code_buf_ptr = mask = 1;
|
||||
s = 0; r = N - F;
|
||||
for (i = s; i < r; i++) text_buf[i] = ' '; /* Clear the buffer with
|
||||
any character that will appear often. */
|
||||
for (len = 0; len < F && bufptr < bufsize; len++)
|
||||
{
|
||||
c = buffer[bufptr++];
|
||||
text_buf[r + len] = c; /* Read F bytes into the last F bytes of
|
||||
the buffer */
|
||||
}
|
||||
if ((textsize = len) == 0) return -1; /* text of size zero */
|
||||
for (i = 1; i <= F; i++) InsertNode(r - i); /* Insert the F strings,
|
||||
each of which begins with one or more 'space' characters. Note
|
||||
the order in which these strings are inserted. This way,
|
||||
degenerate trees will be less likely to occur. */
|
||||
InsertNode(r); /* Finally, insert the whole string just read. The
|
||||
global variables match_length and match_position are set. */
|
||||
do {
|
||||
if (match_length > len) match_length = len; /* match_length
|
||||
may be spuriously long near the end of text. */
|
||||
if (match_length <= THRESHOLD) {
|
||||
match_length = 1; /* Not long enough match. Send one byte. */
|
||||
code_buf[0] |= mask; /* 'send one byte' flag */
|
||||
code_buf[code_buf_ptr++] = text_buf[r]; /* Send uncoded. */
|
||||
} else {
|
||||
code_buf[code_buf_ptr++] = (unsigned char) match_position;
|
||||
code_buf[code_buf_ptr++] = (unsigned char)
|
||||
(((match_position >> 4) & 0xf0)
|
||||
| (match_length - (THRESHOLD + 1))); /* Send position and
|
||||
length pair. Note match_length > THRESHOLD. */
|
||||
}
|
||||
if ((mask <<= 1) == 0) { /* Shift mask left one bit. */
|
||||
for (i = 0; i < code_buf_ptr; i++) /* Send at most 8 units of */
|
||||
putc(code_buf[i], pOut); /* code together */
|
||||
codesize += code_buf_ptr;
|
||||
code_buf[0] = 0; code_buf_ptr = mask = 1;
|
||||
}
|
||||
last_match_length = match_length;
|
||||
for (i = 0; i < last_match_length &&
|
||||
bufptr < bufsize; i++)
|
||||
{
|
||||
c = buffer[bufptr++];
|
||||
DeleteNode(s); /* Delete old strings and */
|
||||
text_buf[s] = c; /* read new bytes */
|
||||
if (s < F - 1) text_buf[s + N] = c; /* If the position is
|
||||
near the end of buffer, extend the buffer to make
|
||||
string comparison easier. */
|
||||
s = (s + 1) & (N - 1); r = (r + 1) & (N - 1);
|
||||
/* Since this is a ring buffer, increment the position
|
||||
modulo N. */
|
||||
InsertNode(r); /* Register the string in text_buf[r..r+F-1] */
|
||||
}
|
||||
// if ((textsize += i) > printcount) {
|
||||
// printf("%12ld\r", textsize); printcount += 1024;
|
||||
// /* Reports progress each time the textsize exceeds
|
||||
// multiples of 1024. */
|
||||
// }
|
||||
while (i++ < last_match_length) { /* After the end of text, */
|
||||
DeleteNode(s); /* no need to read, but */
|
||||
s = (s + 1) & (N - 1); r = (r + 1) & (N - 1);
|
||||
if (--len) InsertNode(r); /* buffer may not be empty. */
|
||||
}
|
||||
} while (len > 0); /* until length of string to be processed is zero */
|
||||
if (code_buf_ptr > 1) { /* Send remaining code. */
|
||||
for (i = 0; i < code_buf_ptr; i++) putc(code_buf[i], pOut);
|
||||
codesize += code_buf_ptr;
|
||||
}
|
||||
// printf("In : %ld bytes\n", textsize); /* Encoding is done. */
|
||||
// printf("Out: %ld bytes\n", codesize);
|
||||
// printf("Out/In: %.3f\n", (double)codesize / textsize);
|
||||
fclose(pOut);
|
||||
|
||||
return codesize;
|
||||
}
|
||||
|
||||
// Be careful with your buffersize, will return an exit of -1 if failure
|
||||
int Decode(char *filename, unsigned char *buffer, int bufsize) /* Just the reverse of Encode(). */
|
||||
{
|
||||
int i, j, k, r, c;
|
||||
unsigned int flags;
|
||||
int bufptr=0;
|
||||
FILE *pIn;
|
||||
int version;
|
||||
|
||||
pIn = fopen(filename, "rb");
|
||||
if(pIn == NULL)
|
||||
return -1; // bail
|
||||
|
||||
// Read version info (uneeded here, but moves file ptr)
|
||||
fread(&version,sizeof(int),1,pIn); // read version
|
||||
fread(&version,sizeof(int),1,pIn); // read buffersize (not needed, so repeat into version)
|
||||
|
||||
|
||||
for (i = 0; i < N - F; i++) text_buf[i] = ' ';
|
||||
r = N - F; flags = 0;
|
||||
for ( ; ; ) {
|
||||
if (((flags >>= 1) & 256) == 0) {
|
||||
if ((c = getc(pIn)) == EOF) break;
|
||||
flags = c | 0xff00; /* uses higher byte cleverly */
|
||||
} /* to count eight */
|
||||
if (flags & 1) {
|
||||
if ((c = getc(pIn)) == EOF) break;
|
||||
buffer[bufptr++] = c;
|
||||
if(bufptr > bufsize)
|
||||
return -1; // check for overflow
|
||||
text_buf[r++] = c;
|
||||
r &= (N - 1);
|
||||
} else {
|
||||
if ((i = getc(pIn)) == EOF) break;
|
||||
if ((j = getc(pIn)) == EOF) break;
|
||||
i |= ((j & 0xf0) << 4); j = (j & 0x0f) + THRESHOLD;
|
||||
for (k = 0; k <= j; k++) {
|
||||
c = text_buf[(i + k) & (N - 1)];
|
||||
buffer[bufptr++] = c;
|
||||
if(bufptr > bufsize)
|
||||
return -1; // check for overflow
|
||||
text_buf[r++] = c;
|
||||
r &= (N - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(pIn);
|
||||
return bufptr; // return uncompressed size
|
||||
}
|
||||
/*
|
||||
// tester
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
//char *s;
|
||||
unsigned char i;
|
||||
int csize,ucsize;
|
||||
unsigned char *buffer1;
|
||||
int bufsize = 20000;
|
||||
|
||||
buffer1 = (unsigned char *)malloc(bufsize);
|
||||
|
||||
buffer1[500] = 'c';
|
||||
buffer1[785] = 's';
|
||||
|
||||
csize = Encode("testbuf.bin", buffer1, bufsize);//sizeof(buffer1));
|
||||
|
||||
buffer1[500] = 0;
|
||||
buffer1[785] = 0;
|
||||
|
||||
ucsize = Decode("testbuf.bin", buffer1, bufsize);
|
||||
|
||||
printf("Output: %c %c\n",buffer1[500],buffer1[785]);
|
||||
printf("Compressed: %d Uncompressed:%d\n",csize,ucsize);
|
||||
|
||||
free(buffer1);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
*/
|
1359
acesrc/acebot_items.c
Normal file
1359
acesrc/acebot_items.c
Normal file
File diff suppressed because it is too large
Load diff
1038
acesrc/acebot_movement.c
Normal file
1038
acesrc/acebot_movement.c
Normal file
File diff suppressed because it is too large
Load diff
1245
acesrc/acebot_nodes.c
Normal file
1245
acesrc/acebot_nodes.c
Normal file
File diff suppressed because it is too large
Load diff
1214
acesrc/acebot_spawn.c
Normal file
1214
acesrc/acebot_spawn.c
Normal file
File diff suppressed because it is too large
Load diff
162
acesrc/botchat.c
Normal file
162
acesrc/botchat.c
Normal file
|
@ -0,0 +1,162 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// $Logfile: /LicenseToKill/src/acesrc/botchat.c $
|
||||
|
||||
// $Revision: 3 $
|
||||
// $Author: Riever $
|
||||
// $Date: 30/09/99 8:01 $
|
||||
/*
|
||||
* $Log: /LicenseToKill/src/acesrc/botchat.c $
|
||||
*
|
||||
* 3 30/09/99 8:01 Riever
|
||||
* changed some text
|
||||
*
|
||||
* 2 28/09/99 7:31 Riever
|
||||
* Altered function names for LTK
|
||||
* Added more chat prints to all three categories
|
||||
* Integrated with LTK
|
||||
*
|
||||
* 1 28/09/99 6:57 Riever
|
||||
* Initial import of chat files
|
||||
*/
|
||||
//
|
||||
/*
|
||||
botchat.c
|
||||
*/
|
||||
|
||||
#include "../g_local.h" // Also includes g_local.h
|
||||
#include "botchat.h"
|
||||
|
||||
|
||||
/*
|
||||
* In each of the following strings:
|
||||
* - the first %s is the attacker/enemy which the string is
|
||||
* directed to
|
||||
*/
|
||||
|
||||
#define DBC_WELCOMES 13
|
||||
char *ltk_welcomes[DBC_WELCOMES] =
|
||||
{
|
||||
"Greetings all!",
|
||||
"Hello %s! Prepare to die!!",
|
||||
"%s? Who the hell is that?",
|
||||
"I say, %s, have you got a license for that face?",
|
||||
"%s, how do you see where you're going with those b*ll*cks in your eyes?",
|
||||
"Give your arse a rest, %s. try talking through your mouth",
|
||||
"Damn! I hoped Thresh would be here...",
|
||||
"Hey Mom, they gave me a gun!",
|
||||
"I gotta find a better server...",
|
||||
"Hey, any of you guys played before?",
|
||||
"Hi %s, I wondered if you'd show your face around here again.",
|
||||
"Nice :-) :-) :-)",
|
||||
"OK %s, let's get this over with"
|
||||
};
|
||||
|
||||
#define DBC_KILLEDS 13
|
||||
char *ltk_killeds[DBC_KILLEDS] =
|
||||
{
|
||||
"B*stard! %s messed up my hair.",
|
||||
"All right, %s. Now I'm feeling put out!",
|
||||
"Hey! Go easy on me! I'm a newbie!",
|
||||
"Ooooh, %s, that smarts!",
|
||||
"%s's mother cooks socks in hell!",
|
||||
"Hey, %s, how about a match - your face and my arse!",
|
||||
"Was %s talking to me, or chewing a brick?",
|
||||
"Aw, %s doesn't like me...",
|
||||
"It's clobberin' time, %s",
|
||||
"Hey, I was tying my shoelace!",
|
||||
"Oh - now I know how strawberry jam feels...",
|
||||
"laaaaaaaaaaaaaaag!",
|
||||
"One feels like chicken tonight...."
|
||||
};
|
||||
|
||||
#define DBC_INSULTS 16
|
||||
char *ltk_insults[DBC_INSULTS] =
|
||||
{
|
||||
"Hey, %s. Your mother was a hamster...!",
|
||||
"%s; Eat my dust!",
|
||||
"Hahaha! Hook, Line and Sinker, %s!",
|
||||
"I'm sorry, %s, did I break your concentration?",
|
||||
"Unlike certain other bots, %s, I can kill with an English accent..",
|
||||
"Get used to disappointment, %s",
|
||||
"You couldn't organise a p*ss-up in a brewery, %s",
|
||||
"%s, does your mother know you're out?",
|
||||
"Hey, %s, one guy wins, the other prick loses...",
|
||||
"Oh %s, ever thought of taking up croquet instead?",
|
||||
"Yuck! I've got some %s on my shoe!",
|
||||
"Mmmmm... %s chunks!",
|
||||
"Hey everyone, %s was better than the Pirates of Penzance",
|
||||
"Oh - good play %s ... hehehe",
|
||||
"Errm, %s, have you ever thought of taking up croquet instead?",
|
||||
"Ooooooh - I'm sooooo scared %s"
|
||||
};
|
||||
|
||||
|
||||
void LTK_Chat (edict_t *bot, edict_t *object, int speech)
|
||||
{
|
||||
char final[150];
|
||||
char *text;
|
||||
|
||||
if ((!object) || (!object->client))
|
||||
return;
|
||||
|
||||
if (speech == DBC_WELCOME)
|
||||
text = ltk_welcomes[rand()%DBC_WELCOMES];
|
||||
else if (speech == DBC_KILLED)
|
||||
text = ltk_killeds[rand()%DBC_KILLEDS];
|
||||
else if (speech = DBC_INSULT)
|
||||
text = ltk_insults[rand()%DBC_INSULTS];
|
||||
else if( debug_mode )
|
||||
{
|
||||
gi.bprintf (PRINT_HIGH, "LTK_Chat: Unknown speech type attempted!(out of range)");
|
||||
return;
|
||||
}
|
||||
|
||||
sprintf (final, text, object->client->pers.netname);
|
||||
|
||||
LTK_Say (bot, final);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
Bot_Say
|
||||
==================
|
||||
*/
|
||||
void LTK_Say (edict_t *ent, char *what)
|
||||
{
|
||||
int j;
|
||||
edict_t *other;
|
||||
char text[2048];
|
||||
|
||||
Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname);
|
||||
|
||||
if (*what == '"')
|
||||
{
|
||||
what++;
|
||||
what[strlen(what)-1] = 0;
|
||||
}
|
||||
strcat(text, what);
|
||||
|
||||
// don't let text be too long for malicious reasons
|
||||
if (strlen(text) > 200)
|
||||
text[200] = '\0';
|
||||
|
||||
strcat(text, "\n");
|
||||
|
||||
if (dedicated->value)
|
||||
gi.cprintf(NULL, PRINT_CHAT, "%s", text);
|
||||
|
||||
for (j = 1; j <= game.maxclients; j++)
|
||||
{
|
||||
other = &g_edicts[j];
|
||||
if (!other->inuse)
|
||||
continue;
|
||||
if (!other->client)
|
||||
continue;
|
||||
if (Q_stricmp(other->classname, "bot") == 0)
|
||||
continue;
|
||||
gi.cprintf(other, PRINT_CHAT, "%s", text);
|
||||
}
|
||||
}
|
||||
|
40
acesrc/botchat.h
Normal file
40
acesrc/botchat.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// $Logfile: /LicenseToKill/src/acesrc/botchat.h $
|
||||
|
||||
// $Revision: 3 $
|
||||
// $Author: Riever $
|
||||
// $Date: 28/09/99 7:30 $
|
||||
/*
|
||||
* $Log: /LicenseToKill/src/acesrc/botchat.h $
|
||||
*
|
||||
* 3 28/09/99 7:30 Riever
|
||||
* Altered function names for LTK
|
||||
*
|
||||
* 2 28/09/99 7:10 Riever
|
||||
* Modified for LTK
|
||||
*
|
||||
* 1 28/09/99 6:57 Riever
|
||||
* Initial import of chat files
|
||||
*/
|
||||
//
|
||||
/*
|
||||
botchat.h
|
||||
*/
|
||||
|
||||
#ifndef __BOTCHAT_H__
|
||||
#define __BOTCHAT_H__
|
||||
|
||||
void LTK_Chat (edict_t *bot, edict_t *object, int speech);
|
||||
void LTK_Say (edict_t *ent, char *what);
|
||||
|
||||
/*
|
||||
* NOTE: DBC = DroneBot Chat
|
||||
*/
|
||||
|
||||
#define DBC_WELCOME 0 // When a bot is created
|
||||
#define DBC_KILLED 1 // When a bot is killed ;-)
|
||||
#define DBC_INSULT 2 // When a bot kills someone (player/bot)
|
||||
|
||||
|
||||
#endif // __DB_CHAT_H__
|
469
acesrc/botnav.c
Normal file
469
acesrc/botnav.c
Normal file
|
@ -0,0 +1,469 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// $Logfile:: /LicenseToKill/src/acesrc/botnav.c $
|
||||
// $Revision:: 5 $
|
||||
// $Author:: Riever $
|
||||
// $Date:: 27/09/99 14:31 $
|
||||
//
|
||||
// Copyright (C) 1999 by Connor "RiEvEr" Caple
|
||||
// All rights reserved.
|
||||
//
|
||||
// This file contains the searchpath algorithm files for use by the bots
|
||||
// The code in this file, or variants of it, may be used in any non-commercial
|
||||
// FPS mod as long as you credit me as the author.
|
||||
//
|
||||
// Commercial permission can be obtained from me via my current e-mail
|
||||
// address. (connor@botgod.org.uk as of September 1999)
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* $Log: /LicenseToKill/src/acesrc/botnav.c $
|
||||
*
|
||||
* 5 27/09/99 14:31 Riever
|
||||
* Safety checks needed for ACE use of this code have been added since ACE
|
||||
* is not too particular about calling pathing code with an INVALID node..
|
||||
* :)
|
||||
*
|
||||
* 4 27/09/99 13:23 Riever
|
||||
* Added bugfixes and error traps to the SLL code.
|
||||
*
|
||||
* 3 27/09/99 7:52 Riever
|
||||
* Tagged all memory allocations as TAG_LEVEL to prevent leaks
|
||||
*
|
||||
* 2 25/09/99 11:23 Riever
|
||||
* All linked list functions designed and implemented.
|
||||
*
|
||||
* 1 25/09/99 9:44 Riever
|
||||
*
|
||||
*/
|
||||
|
||||
#include "..\g_local.h"
|
||||
#include "botnav.h"
|
||||
|
||||
//== GLOBAL SEMAPHORE ==
|
||||
int antSearch;
|
||||
|
||||
// The nodes array
|
||||
extern node_t nodes[MAX_NODES];
|
||||
extern short path_table[MAX_NODES][MAX_NODES]; // Quick pathsearch array [from][to]
|
||||
|
||||
qboolean nodeused[MAX_NODES]; // This is used for a FAST check if the node has been used
|
||||
short int nodefrom[MAX_NODES]; // Stores how we got here once the node is closed
|
||||
|
||||
/* =========================================================
|
||||
The basic system works by using a single linked list and accessing information from the node array
|
||||
|
||||
1) The current node is found
|
||||
2) All links from it are propogated - if not already done by another node
|
||||
3) If we haven't found the target node then we get the next open node and go to 1
|
||||
4) If we have the target node we return the path to it
|
||||
5) If we run out of nodes then we return INVALID
|
||||
|
||||
This idea is based on "Path Finding Via 'Ant Races' (a floodfill algorithm)"
|
||||
by Richard Wesson. It is easy to optimise this code to make it even more efficient.
|
||||
|
||||
============================================================= */
|
||||
|
||||
|
||||
//=========================
|
||||
// Init the search path variables
|
||||
//=========================
|
||||
void AntInitSearch(edict_t *ent)
|
||||
{
|
||||
//Make sure the lists and arrays used are all set to the correct values and EMPTY!
|
||||
memset(nodeused, 0, sizeof(nodeused) );
|
||||
memset(nodefrom, INVALID, sizeof(nodefrom) );
|
||||
while( !SLLempty(&ent->pathList) )
|
||||
{
|
||||
SLLpop_front(&ent->pathList);
|
||||
}
|
||||
}
|
||||
|
||||
//=========================
|
||||
// StartSearch
|
||||
//=========================
|
||||
//
|
||||
// returns true if a path is found
|
||||
// false otherwise
|
||||
//
|
||||
qboolean AntStartSearch(edict_t *ent, int from, int to )
|
||||
{
|
||||
// Safety first!
|
||||
if( from==INVALID || to==INVALID)
|
||||
return false;
|
||||
|
||||
//@@ TESTING ONLY!! antSearch always available
|
||||
antSearch = 1;
|
||||
// Check we're allowed to search - if so, do it
|
||||
if( (antSearch > 0) && (ent->antLastCallTime < level.time - ANT_FREQ) )
|
||||
{
|
||||
// Decrement the semaphore to limit calls to this function
|
||||
//@@ If we ever get multithreading then we can increment later
|
||||
antSearch--;
|
||||
// make a note of when this bot last made a path call
|
||||
ent->antLastCallTime = level.time;
|
||||
// Set up the lists
|
||||
AntInitSearch(ent);
|
||||
// If we found a path
|
||||
if( AntFindPath( ent, from, to) )
|
||||
{
|
||||
// pathList now contains the links in reverse order
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// We can use the quick node search method here to get our path and put it in the pathList
|
||||
// the same way we do with the AntSearch mode. This will have the side effect of finding
|
||||
// bad paths and removing them.
|
||||
if( AntQuickPath(ent, from, to) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// If not allowed to search and no path
|
||||
AntInitSearch(ent); // Clear out the path storage
|
||||
return false;
|
||||
}
|
||||
|
||||
//=================================
|
||||
// QuickPath
|
||||
//=================================
|
||||
//
|
||||
// Uses the old path array to get a quick answer and removes bad paths
|
||||
//
|
||||
|
||||
qboolean AntQuickPath(edict_t *ent, int from, int to)
|
||||
{
|
||||
int newNode = from;
|
||||
int oldNode;
|
||||
|
||||
// Clean out the arrays, etc.
|
||||
AntInitSearch(ent);
|
||||
nodeused[from] = true;
|
||||
// Check we can get from->to and that the path is complete
|
||||
while( newNode != INVALID )
|
||||
{
|
||||
oldNode = newNode;
|
||||
// get next node
|
||||
newNode = path_table[newNode][to];
|
||||
if( newNode == to )
|
||||
{
|
||||
// We're there - store it then build the path
|
||||
nodeused[newNode] = true;
|
||||
nodefrom[newNode] = oldNode;
|
||||
break;
|
||||
}
|
||||
else if( newNode == INVALID )
|
||||
{
|
||||
// We have a bad path
|
||||
break;
|
||||
}
|
||||
else if( !nodeused[newNode] )
|
||||
{
|
||||
// Not been here yet - store it!
|
||||
nodeused[newNode] = true;
|
||||
nodefrom[newNode] = oldNode;
|
||||
}
|
||||
else
|
||||
break; // LOOP encountered
|
||||
}
|
||||
|
||||
// If successful, build the pathList
|
||||
if( newNode == to )
|
||||
{
|
||||
SLLpush_front(&ent->pathList, to );
|
||||
while( newNode != from)
|
||||
{
|
||||
// Push the
|
||||
SLLpush_front(&ent->pathList, nodefrom[newNode]);
|
||||
newNode = nodefrom[newNode];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// else wipe out the bad path!
|
||||
else
|
||||
{
|
||||
newNode = oldNode;
|
||||
while( newNode != from)
|
||||
{
|
||||
path_table[ nodefrom[newNode] ][ to ] = INVALID;
|
||||
}
|
||||
path_table[ from ][ to ] = INVALID;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//=======================
|
||||
// FindPath
|
||||
//=======================
|
||||
//
|
||||
// Uses OPEN and CLOSED lists to conduct a search
|
||||
// Many refinements planned
|
||||
//
|
||||
qboolean AntFindPath( edict_t *ent, int from, int to)
|
||||
{
|
||||
int counter = 0;
|
||||
int newNode = INVALID; // Stores the node being tested
|
||||
node_t *tempNode = NULL; // Pointer to a real NODE
|
||||
int workingNode,atNode; // Structures for search
|
||||
|
||||
// Locally declared OPEN list
|
||||
ltklist_t openList;
|
||||
openList.head = openList.tail = NULL; // MUST do this!!
|
||||
|
||||
// Safety first again - we don't want crashes!
|
||||
if( from==INVALID || to==INVALID )
|
||||
return false;
|
||||
|
||||
// Put startnode on the OPEN list
|
||||
atNode = from;
|
||||
nodefrom[atNode] = INVALID;
|
||||
SLLpush_back(&openList, from );
|
||||
nodeused[from] = true;
|
||||
|
||||
// While there are nodes on the OPEN list AND we are not at destNode
|
||||
while( !SLLempty(&openList) && newNode != to )
|
||||
{
|
||||
counter = 0;
|
||||
|
||||
// Where we are
|
||||
atNode = SLLfront(&openList);
|
||||
|
||||
// Safety check
|
||||
assert( atNode > INVALID);
|
||||
|
||||
// Get a pointer to all the node information
|
||||
tempNode = &nodes[atNode];
|
||||
// Using an array for FAST access to the path ratrher than a CLOSED list
|
||||
newNode = tempNode->links[counter].targetNode;
|
||||
|
||||
// Process this node putting linked nodes on the OPEN list
|
||||
while( newNode != INVALID)
|
||||
{
|
||||
// If newNode NOT on open or closed list
|
||||
if( !nodeused[newNode])
|
||||
{
|
||||
// Mark node as used
|
||||
nodeused[newNode] = true;
|
||||
// Set up working node for storage on OPEN list
|
||||
workingNode = newNode;
|
||||
nodefrom[newNode] = atNode;
|
||||
// Store it
|
||||
SLLpush_back(&openList, workingNode );
|
||||
}
|
||||
// If node being linked is destNode then quit
|
||||
if( newNode == to)
|
||||
{
|
||||
break;
|
||||
}
|
||||
// Check we aren't trying to read out of range
|
||||
if( ++counter >= MAXLINKS )
|
||||
break;
|
||||
else
|
||||
newNode = tempNode->links[counter].targetNode;
|
||||
}
|
||||
|
||||
// ... and remove atNode from the OPEN List
|
||||
SLLpop_front(&openList);
|
||||
} // END While
|
||||
|
||||
// Free up the memory we allocated
|
||||
SLLdelete (&openList);
|
||||
|
||||
// Optimise stored path with this new information
|
||||
if( newNode == to)
|
||||
{
|
||||
// Make the path using the fromnode array pushing node numbers on in reverse order
|
||||
// so we can SLLpop_front them back later
|
||||
SLLpush_front(&ent->pathList, newNode );
|
||||
// Set the to path in node array because this is shortest path
|
||||
path_table[ nodefrom[to] ][ to ] = to;
|
||||
// We earlier set our start node to INVALID to set up the termination
|
||||
while(
|
||||
(newNode=nodefrom[newNode])!=INVALID // there is a path and
|
||||
&& (newNode != from) // it's not the node we're standing on (safety check)
|
||||
)
|
||||
{
|
||||
// Push it onto the pathlist
|
||||
SLLpush_front(&ent->pathList, newNode );
|
||||
// Set the path in the node array to match this shortest path
|
||||
path_table[ nodefrom[newNode] ][ to ] = newNode;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// else
|
||||
return false;
|
||||
}
|
||||
|
||||
//=============================
|
||||
// LinkExists
|
||||
//=============================
|
||||
//
|
||||
// Used to check we haven't wandered off path!
|
||||
//
|
||||
qboolean AntLinkExists( int from, int to)
|
||||
{
|
||||
int counter =0;
|
||||
int testnode;
|
||||
node_t *tempNode = &nodes[from];
|
||||
|
||||
if( from==INVALID || to==INVALID )
|
||||
return false;
|
||||
// Check if the link exists
|
||||
while( counter < MAXLINKS)
|
||||
{
|
||||
testnode = tempNode->links[counter].targetNode;
|
||||
if( testnode == to)
|
||||
{
|
||||
// A path exists from,to
|
||||
return true;
|
||||
}
|
||||
else if( testnode == INVALID )
|
||||
{
|
||||
// No more links and no path found
|
||||
return false;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
// Didn't find it!
|
||||
return false;
|
||||
}
|
||||
|
||||
// ****************************************************** //
|
||||
//=======================================================
|
||||
// SLL functions used by search code
|
||||
//=======================================================
|
||||
|
||||
//==============================
|
||||
// SLLpush_front
|
||||
//==============================
|
||||
// Add to the front of the list
|
||||
//
|
||||
void SLLpush_front( ltklist_t *thelist, int nodedata )
|
||||
{
|
||||
slint_t *temp;
|
||||
|
||||
// Store the current head pointer
|
||||
temp = thelist->head;
|
||||
// allocate memory for the new data (LEVEL tagged)
|
||||
thelist->head = gi.TagMalloc( sizeof(slint_t), TAG_LEVEL);
|
||||
// Set up the data and pointer
|
||||
thelist->head->nodedata = nodedata;
|
||||
thelist->head->next = temp;
|
||||
// Check if there;'s a next item
|
||||
if( !thelist->head->next)
|
||||
{
|
||||
// Set the tail pointer = head
|
||||
thelist->tail = thelist->head;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================
|
||||
// SLLpop_front
|
||||
//==============================
|
||||
// Remove the iten from the front of the list
|
||||
//
|
||||
void SLLpop_front( ltklist_t *thelist )
|
||||
{
|
||||
slint_t *temp;
|
||||
|
||||
// Store the head pointer
|
||||
temp = thelist->head;
|
||||
// Check if there's a next item
|
||||
if( thelist && thelist->head )
|
||||
{
|
||||
if( thelist->head == thelist->tail )
|
||||
{
|
||||
// List is now emptying
|
||||
thelist->tail = thelist->head = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move head to point to next item
|
||||
thelist->head = thelist->head->next;
|
||||
}
|
||||
// Free the memory (LEVEL tagged)
|
||||
gi.TagFree( temp );
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_bprintf( PRINT_HIGH, "Attempting to POP an empty list!\n");
|
||||
}
|
||||
}
|
||||
|
||||
//==============================
|
||||
// SLLfront
|
||||
//==============================
|
||||
// Get the integer value from the front of the list
|
||||
// without removing the item (Query the list)
|
||||
//
|
||||
int SLLfront( ltklist_t *thelist )
|
||||
{
|
||||
if( thelist && !SLLempty( thelist) )
|
||||
return( thelist->head->nodedata);
|
||||
else
|
||||
return INVALID;
|
||||
}
|
||||
|
||||
//==============================
|
||||
// SLLpush_front
|
||||
//==============================
|
||||
// Add to the back of the list
|
||||
//
|
||||
void SLLpush_back( ltklist_t *thelist, int nodedata )
|
||||
{
|
||||
slint_t *temp;
|
||||
|
||||
// Allocate memory for the new item (LEVEL tagged)
|
||||
temp = (slint_t *)gi.TagMalloc( sizeof(slint_t), TAG_LEVEL);
|
||||
// Store the data
|
||||
temp->nodedata = nodedata;
|
||||
temp->next = NULL; // End of the list
|
||||
// Store the new item in the list
|
||||
// Is the list empty?
|
||||
if( !thelist->head )
|
||||
{
|
||||
// Yes - add as a new item
|
||||
thelist->head = temp;
|
||||
thelist->tail = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No make this the new tail item
|
||||
thelist->tail->next = temp;
|
||||
thelist->tail = temp;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================
|
||||
// SLLempty
|
||||
//==============================
|
||||
// See if the list is empty (false if not empty)
|
||||
//
|
||||
qboolean SLLempty( ltklist_t *thelist )
|
||||
{
|
||||
// If there is any item in the list then it is NOT empty...
|
||||
// If there is a list
|
||||
if(thelist)
|
||||
return (thelist->head == NULL);
|
||||
else // No list so return empty
|
||||
return (true);
|
||||
}
|
||||
|
||||
//===============================
|
||||
// Delete the list
|
||||
//===============================
|
||||
// Avoids memory leaks
|
||||
//
|
||||
void SLLdelete( ltklist_t *thelist )
|
||||
{
|
||||
slint_t *temp;
|
||||
|
||||
while( !SLLempty( thelist ))
|
||||
{
|
||||
temp = thelist->head;
|
||||
thelist->head = thelist->head->next;
|
||||
gi.TagFree( temp );
|
||||
}
|
||||
}
|
||||
|
||||
|
81
acesrc/botnav.h
Normal file
81
acesrc/botnav.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
#ifndef BOTNAV_H
|
||||
#define BOTNAV_H
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// $Logfile:: /LicenseToKill/src/acesrc/botnav.h $
|
||||
// $Revision:: 6 $
|
||||
// $Author:: Riever $
|
||||
// $Date:: 1/10/99 18:20 $
|
||||
//
|
||||
// Copyright (C) 1999 by Connor Caple
|
||||
// All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
/*
|
||||
* $Log: /LicenseToKill/src/acesrc/botnav.h $
|
||||
*
|
||||
* 6 1/10/99 18:20 Riever
|
||||
* Reduced intensive searches to once every 0.5 secs.
|
||||
*
|
||||
* 5 27/09/99 14:33 Riever
|
||||
* Added SLLdelete function to free up memory and help prevent any leaks.
|
||||
* There may still be a necessity to free up bot pathLists at respawn -
|
||||
* awaiting views from those who know how to spot memory leaks!
|
||||
*
|
||||
* 4 25/09/99 11:20 Riever
|
||||
* Tidied up errors in declarations
|
||||
*
|
||||
* 3 25/09/99 9:36 Riever
|
||||
* Added all SLL definitions
|
||||
*
|
||||
* 2 25/09/99 8:24 Riever
|
||||
*
|
||||
*/
|
||||
|
||||
#define ANT_FREQ 0.5 // Time gap between calls to the processor intensive search
|
||||
|
||||
// ----------- new Pathing Algorithm stuff -----
|
||||
qboolean AntPathMove( edict_t *ent ); // Called in item and enemy route functions
|
||||
void AntInitSearch( edict_t *ent ); // Resets all the path lists etc.
|
||||
qboolean AntStartSearch( edict_t *ent, int from, int to); // main entry to path algorithms
|
||||
qboolean AntQuickPath( edict_t *ent, int from, int to ); // backup path system
|
||||
qboolean AntFindPath( edict_t *ent, int from, int to); // Optimised path system
|
||||
qboolean AntLinkExists( int from, int to); // Detects if we are off the path
|
||||
|
||||
// --------- AI Tactics Values -------------
|
||||
enum{
|
||||
AIRoam, // Basic item collection AI
|
||||
AIAttack, // Basic Attack Enemy AI
|
||||
AIAttackCollect,// Attack Enemy while collecting Item
|
||||
AICamp, // Camp at a suitable location and collect the item on respawn
|
||||
AISnipe,
|
||||
AIAmbush
|
||||
};
|
||||
|
||||
// ** Single Linked List (SLL) implementation **
|
||||
|
||||
// ** slint_t is a list member ie: one item on the list
|
||||
typedef struct slint{
|
||||
struct slint *next; // pointer to the next list member
|
||||
int nodedata; // The node number we're storing
|
||||
} slint_t;
|
||||
|
||||
// ** ltklist_t is the actual list and contains a number of slint_t members
|
||||
// It is a double-ended singly linked list (ie, we can add things to the front or back)
|
||||
// We need head and tail pointers to speed up push_back operations
|
||||
typedef struct{
|
||||
slint_t *head; // Front of the list
|
||||
slint_t *tail; // Back of the list
|
||||
} ltklist_t;
|
||||
|
||||
// Now we have to define the operations that can happen on the list
|
||||
// All will be prefixed with "SLL" so we know what they are working on when we read the code
|
||||
|
||||
void SLLpush_front( ltklist_t *thelist, int nodedata );// Add to the front of the list
|
||||
void SLLpop_front( ltklist_t *thelist ); // Remove the iten from the front of the list
|
||||
int SLLfront( ltklist_t *thelist ); // Get the integer value from the front of the list..
|
||||
// ..without removing the item (Query the list)
|
||||
void SLLpush_back( ltklist_t *thelist, int nodedata ); // Add to the back of the list
|
||||
qboolean SLLempty( ltklist_t *thelist ); // See if the list is empty (false if not empty)
|
||||
void SLLdelete( ltklist_t *thelist ); // Free all memory from a list
|
||||
|
||||
#endif
|
316
acesrc/botscan.c
Normal file
316
acesrc/botscan.c
Normal file
|
@ -0,0 +1,316 @@
|
|||
/*
|
||||
* $Header: /LicenseToKill/src/acesrc/botscan.c 1 16/10/99 8:41 Riever $
|
||||
*
|
||||
* $Log: /LicenseToKill/src/acesrc/botscan.c $
|
||||
*
|
||||
* 1 16/10/99 8:41 Riever
|
||||
* Initial import to LTK
|
||||
*
|
||||
* 4 15/10/99 8:40 Riever
|
||||
* Added carriage return '\r' to whitespace definition.
|
||||
*
|
||||
* 3 15/10/99 8:11 Riever
|
||||
* Forgot to step over last quote in STRLIT - ok now.
|
||||
*
|
||||
* 2 15/10/99 7:01 Riever
|
||||
* Fixed a bug in INTLIT parsing
|
||||
*
|
||||
* 1 14/10/99 8:26 Riever
|
||||
*
|
||||
* 4 14/10/99 7:21 Riever
|
||||
* Defined all other operators and symbols that will be used.
|
||||
* Left out the 'e' notation for REAL numbers since we don't use that.
|
||||
*
|
||||
* 3 14/10/99 7:02 Riever
|
||||
* Changed filename to "botscan.c" to make it clear where it belongs in
|
||||
* this project.
|
||||
* Added a test parser function in comment block at end of file.
|
||||
*
|
||||
* 2 14/10/99 6:50 Riever
|
||||
* First version - just a compilation check.
|
||||
*
|
||||
*/
|
||||
//=================================================================
|
||||
// botscan.c
|
||||
//
|
||||
// Connor Caple 14th October 1999
|
||||
//
|
||||
// A lexical scanner module to allow much more complex configuration files
|
||||
// Original code idea : Lee & Mark Atkinson, "Using C", pub. Que
|
||||
//
|
||||
// Sample driver included at bottom of file.
|
||||
//
|
||||
//=================================================================
|
||||
|
||||
#include "botscan.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
//==============================
|
||||
// nmtoken
|
||||
//==============================
|
||||
// This is only used in testing. It returns a string that matches
|
||||
// the type of token returned in the scanner
|
||||
// Very useful when debugging new configuration files
|
||||
//
|
||||
// I may write a stand alone config file tester to ensure that
|
||||
// people use the instructions correctly.
|
||||
//
|
||||
char *nmtoken( int ttype)
|
||||
{
|
||||
static char *tokenNames[] = {
|
||||
"LEXERR",
|
||||
"SYMBOL",
|
||||
"INTLIT",
|
||||
"REALLIT",
|
||||
"STRLIT",
|
||||
"LPAREN",
|
||||
"RPAREN",
|
||||
"SEMIC",
|
||||
"COLON",
|
||||
"COMMA",
|
||||
"PERIOD",
|
||||
"APOST",
|
||||
"PLUSOP",
|
||||
"MINUSOP",
|
||||
"MUXOP",
|
||||
"DIVOP",
|
||||
"POWOP",
|
||||
"ASSIGNOP",
|
||||
"HASH",
|
||||
"BANG",
|
||||
"EOL",
|
||||
"UNDEF"
|
||||
};
|
||||
return( tokenNames[ttype] );
|
||||
}
|
||||
|
||||
//=================================
|
||||
// scanner
|
||||
//=================================
|
||||
// This is the actual scanner. It searches for tokens it can
|
||||
// recognise and returns them in a string with the
|
||||
// tokentype defined in an integer.
|
||||
//
|
||||
// text is the address of the string being worked on
|
||||
// token is the returned value of the token
|
||||
// ttype is the integer value of the token type
|
||||
//
|
||||
|
||||
void scanner( char **text, char *token, int *ttype)
|
||||
{
|
||||
// Skip all whitespace
|
||||
for ( ; **text == ' ' || **text == '\t' || **text == '\n' || **text == '\r'; (*text)++ );
|
||||
|
||||
// If the string terminates return EOL
|
||||
if( **text == '\0' )
|
||||
{
|
||||
*ttype = EOL;
|
||||
return;
|
||||
}
|
||||
|
||||
// SYMBOLS
|
||||
if( (**text >='A' && **text <='Z') || (**text >='a' && **text <='z') )
|
||||
{
|
||||
*ttype = SYMBOL;
|
||||
|
||||
while(
|
||||
(**text >='A' && **text <='Z') || (**text >='a' && **text <='z')
|
||||
|| (**text >='0' && **text <='9')
|
||||
)
|
||||
{
|
||||
*token++ = *(*text)++;
|
||||
}
|
||||
*token = '\0'; // Terminate the string.
|
||||
return;
|
||||
}
|
||||
|
||||
// STRING LITERALS
|
||||
if( **text == '"' )
|
||||
{
|
||||
*ttype = STRLIT;
|
||||
(*text)++; // Skip first quote.
|
||||
while( **text != '"' && **text )
|
||||
{
|
||||
*token++ = *(*text)++;
|
||||
}
|
||||
(*text)++; // Skip last quote.
|
||||
*token = '\0'; // Terminate the string.
|
||||
return;
|
||||
}
|
||||
|
||||
// NUMERICS
|
||||
if( **text >= '0' && **text <= '9' )
|
||||
{
|
||||
*ttype = INTLIT;
|
||||
while( **text >= '0' && **text <= '9' )
|
||||
{
|
||||
*token++ = *(*text)++;
|
||||
if( **text == '.' )
|
||||
{
|
||||
*ttype = REALLIT;
|
||||
*token++ = *(*text)++;
|
||||
}
|
||||
// I left out the 'e' notation part - we don't need it.
|
||||
}
|
||||
*token = '\0'; // Terminate the string.
|
||||
return;
|
||||
}
|
||||
|
||||
// PUNCTUATION SECTION
|
||||
if( **text == '(' )
|
||||
{
|
||||
*ttype = LPAREN;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == ')' )
|
||||
{
|
||||
*ttype = RPAREN;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == ';' )
|
||||
{
|
||||
*ttype = SEMIC;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == ':' )
|
||||
{
|
||||
*ttype = COLON;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == ',' )
|
||||
{
|
||||
*ttype = COMMA;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == '.' )
|
||||
{
|
||||
*ttype = PERIOD;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == '\'' )
|
||||
{
|
||||
*ttype = APOST;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == '+' )
|
||||
{
|
||||
*ttype = PLUSOP;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == '-' )
|
||||
{
|
||||
*ttype = MINUSOP;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == '*' )
|
||||
{
|
||||
*ttype = MUXOP;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == '/' )
|
||||
{
|
||||
*ttype = DIVOP;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == '^' )
|
||||
{
|
||||
*ttype = POWOP;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == '=' )
|
||||
{
|
||||
*ttype = ASSIGNOP;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == '#' )
|
||||
{
|
||||
*ttype = HASH;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if( **text == '!' )
|
||||
{
|
||||
*ttype = BANG;
|
||||
*token++ = *(*text)++;
|
||||
*token = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Nothing matched - it must be an error
|
||||
*ttype = LEXERR;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
//*************************************************************************
|
||||
// Sample driver to test this file with
|
||||
//
|
||||
|
||||
void parseString( char *test )
|
||||
{
|
||||
char *sp, *tp;
|
||||
int ttype;
|
||||
char token[81];
|
||||
|
||||
sp = test; // Set up a pointer to the string;
|
||||
ttype = UNDEF; // Signal "no match found yet"
|
||||
|
||||
// Now scan the string and report each token type and value found
|
||||
while( ttype != EOL && ttype != LEXERR )
|
||||
{
|
||||
// Set tp to point to our return string location
|
||||
tp = token;
|
||||
// Pass in the correct values to scanner()
|
||||
scanner( &sp, tp, &ttype );
|
||||
// If we are not done, print what we found.
|
||||
if( ttype != EOL )
|
||||
{
|
||||
printf( "Token type = %s, token = %s\n", nmtoken(ttype), token );
|
||||
}
|
||||
}
|
||||
}
|
||||
//**************************************************************************
|
||||
*/
|
53
acesrc/botscan.h
Normal file
53
acesrc/botscan.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* $Header: /LicenseToKill/src/acesrc/botscan.h 1 16/10/99 8:41 Riever $
|
||||
*
|
||||
* $Log: /LicenseToKill/src/acesrc/botscan.h $
|
||||
*
|
||||
* 1 16/10/99 8:41 Riever
|
||||
* Initial import to LTK
|
||||
*
|
||||
* 1 14/10/99 8:26 Riever
|
||||
*
|
||||
* 3 14/10/99 6:53 Riever
|
||||
* Changed file name to botscan.h to make it clear where it belongs in the
|
||||
* project.
|
||||
*
|
||||
* 2 14/10/99 6:51 Riever
|
||||
* First version.
|
||||
*
|
||||
*/
|
||||
//=================================================================
|
||||
// botscan.h
|
||||
//
|
||||
// Connor Caple 14th October 1999
|
||||
//
|
||||
// A lexical scanner module to allow much more complex configuration files
|
||||
// Original code idea : Lee & Mark Atkinson, "Using C", pub. Que
|
||||
//
|
||||
//=================================================================
|
||||
|
||||
#define LEXERR 0
|
||||
#define SYMBOL 1
|
||||
#define INTLIT 2
|
||||
#define REALLIT 3
|
||||
#define STRLIT 4
|
||||
#define LPAREN 5
|
||||
#define RPAREN 6
|
||||
#define SEMIC 7
|
||||
#define COLON 8
|
||||
#define COMMA 9
|
||||
#define PERIOD 10
|
||||
#define APOST 11
|
||||
#define PLUSOP 12
|
||||
#define MINUSOP 13
|
||||
#define MUXOP 14
|
||||
#define DIVOP 15
|
||||
#define POWOP 16
|
||||
#define ASSIGNOP 17
|
||||
#define HASH 18
|
||||
#define BANG 19
|
||||
#define EOL 20
|
||||
#define UNDEF 255
|
||||
|
||||
void scanner( char **text, char *token, int *ttype);
|
||||
char *nmtoken( int ttype );
|
472
acesrc/cgf_sfx_fog.c
Normal file
472
acesrc/cgf_sfx_fog.c
Normal file
|
@ -0,0 +1,472 @@
|
|||
/****************************************************************************/
|
||||
/* */
|
||||
/* project : CGF (c) 1999 William van der Sterren */
|
||||
/* */
|
||||
/* file : cgf_sfx_fog.h "fog openGL stuff" */
|
||||
/* author(s): William van der Sterren */
|
||||
/* version : 0.5 */
|
||||
/* */
|
||||
/* date (last revision): Aug 31, 99 */
|
||||
/* date (creation) : Aug 31, 99 */
|
||||
/* */
|
||||
/* */
|
||||
/* revision history */
|
||||
/* -- date ---- | -- revision ---------------------- | -- revisor -- */
|
||||
/* | | */
|
||||
/* */
|
||||
/****************************************************************************/
|
||||
|
||||
//#ifdef FOG
|
||||
|
||||
|
||||
#include <cmath> // prevent problems between C and STL
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "cgf_sfx_fog.h"
|
||||
#include "../g_local.h"
|
||||
//#include "sabin_debug.h"
|
||||
#include <windows.h>
|
||||
#include <GL/gl.h>
|
||||
|
||||
/*
|
||||
#ifdef CGF
|
||||
// AI update related to fog
|
||||
#include "cgf_tactics_visibility.h"
|
||||
#endif
|
||||
*/
|
||||
}
|
||||
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <strstream>
|
||||
using namespace std;
|
||||
|
||||
// defaults
|
||||
static vec_t m_FogDensity = 0.1000;
|
||||
static int m_FogEnabled = false;
|
||||
static int m_FogType = 2; // linear
|
||||
static int m_FogStart = 200;
|
||||
static int m_FogEnd = 1600;
|
||||
static vec_t m_FogColor[4] = {0.5, 0.5, 0.5, 1};
|
||||
static char m_Settings[64];
|
||||
|
||||
|
||||
const int kMaxViewingDistance = 8192;
|
||||
const int kViewingDistanceStep = 64;
|
||||
//static vec_t m_FogViewReduction[kMaxViewingDistance / kViewingDistanceStep];
|
||||
static vec_t m_FogViewReduction[128];
|
||||
|
||||
// cvar for fog and video mode
|
||||
static cvar_t* fog = 0;
|
||||
static cvar_t* vid_ref = 0;
|
||||
|
||||
|
||||
// forward decls
|
||||
void CGF_SFX_ActivateFog(vec_t aDensity,
|
||||
int aType,
|
||||
vec3_t aColor,
|
||||
vec_t aBegin,
|
||||
vec_t anEnd
|
||||
);
|
||||
void CGF_SFX_DeactivateFog();
|
||||
qboolean CGF_SFX_FindFogPreferences(const char* aMapName,
|
||||
vec_t* aDensity,
|
||||
int* aType,
|
||||
vec_t* aRed,
|
||||
vec_t* aGreen,
|
||||
vec_t* aBlue,
|
||||
vec_t* aBegin,
|
||||
vec_t* aEnd
|
||||
);
|
||||
void CGF_SFX_InitFogForDistance(int aFogMode, vec_t aFogDensity, vec_t aFogBegin, vec_t aFogEnd);
|
||||
|
||||
|
||||
void CGF_SFX_InstallFogSupport()
|
||||
{
|
||||
// reset settings
|
||||
m_Settings[0] = 0;//char(0);
|
||||
|
||||
// if fog > 0, then enabled
|
||||
fog = gi.cvar("fog", "0", 0);
|
||||
vid_ref = gi.cvar("vid_ref", "", 0);
|
||||
|
||||
// prevent issueing gl calls if no opengl present
|
||||
m_FogEnabled = ( (fog->value > 0)
|
||||
&& (0 != stricmp(vid_ref->string, "soft"))
|
||||
);
|
||||
|
||||
// if not fog enabled, act upon it
|
||||
if (!m_FogEnabled)
|
||||
{
|
||||
if (fog->value == 0)
|
||||
{
|
||||
strcpy(m_Settings, "cvar fog is 0");
|
||||
// CGF_Debug_LogMessage("fog disabled: cvar fog is 0");
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(m_Settings, "using software rendering");
|
||||
// CGF_Debug_LogMessage("fog disabled: using software rendering");
|
||||
}
|
||||
gi.cvar_set("fog", "0");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CGF_SFX_AdjustFogForMap(const char* aMapName)
|
||||
{
|
||||
// verify fog if m_FogEnabled, and reset if required
|
||||
fog = gi.cvar("fog", "0", 0);
|
||||
vid_ref = gi.cvar("vid_ref", "", 0);
|
||||
|
||||
if (m_FogEnabled)
|
||||
{
|
||||
if (!(m_FogEnabled = (fog->value != 0)))
|
||||
{
|
||||
#ifdef CGF
|
||||
CGF_Debug_LogMessage("fog disabled: fog set to 0 or video mode changed");
|
||||
// change visibility info for AI
|
||||
CGF_Tactics_IncorporateChangedFogSettings();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_FogEnabled = ( (fog->value > 0)
|
||||
&& (0 != stricmp(vid_ref->string, "soft"))
|
||||
);
|
||||
if (m_FogEnabled)
|
||||
{
|
||||
#ifdef CGF
|
||||
CGF_Debug_LogMessage("fog enabled : fog set to 1 or video mode changed");
|
||||
// change visibility info for AI
|
||||
CGF_Tactics_IncorporateChangedFogSettings();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// process fog setting
|
||||
if (m_FogEnabled)
|
||||
{
|
||||
vec_t d;
|
||||
int t;
|
||||
vec_t s;
|
||||
vec_t e;
|
||||
vec3_t rgb;
|
||||
if ( (!aMapName)
|
||||
|| (!CGF_SFX_FindFogPreferences(aMapName, &d, &t, &rgb[0], &rgb[1], &rgb[2], &s, &e))
|
||||
)
|
||||
{
|
||||
if (!CGF_SFX_FindFogPreferences("default", &d, &t, &rgb[0], &rgb[1], &rgb[2], &s, &e))
|
||||
{
|
||||
d = m_FogDensity;
|
||||
t = m_FogType;
|
||||
VectorCopy(m_FogColor, rgb);
|
||||
s = m_FogStart;
|
||||
e = m_FogEnd;
|
||||
}
|
||||
}
|
||||
// convert d to value usable for gl lib
|
||||
d /= 100;
|
||||
// convert fog type
|
||||
// 0 == GL_EXP, 1 == GL_EXP2, 2 == GL_LINEAR
|
||||
if ((t < 0) || (t > 2))
|
||||
t = m_FogType;
|
||||
if (t < 2)
|
||||
t = t + 0x800;
|
||||
else
|
||||
t = GL_LINEAR;
|
||||
|
||||
if (d > 0)
|
||||
CGF_SFX_ActivateFog(d, t, rgb, s, e);
|
||||
else
|
||||
CGF_SFX_DeactivateFog();
|
||||
}
|
||||
else
|
||||
{
|
||||
CGF_SFX_DeactivateFog();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
qboolean CGF_SFX_FindFogPreferences(const char* aMapName,
|
||||
vec_t* aDensity,
|
||||
int* aType,
|
||||
vec_t* aRed,
|
||||
vec_t* aGreen,
|
||||
vec_t* aBlue,
|
||||
vec_t* aBegin,
|
||||
vec_t* aEnd
|
||||
)
|
||||
{
|
||||
*aDensity = 0;
|
||||
|
||||
// try to read preferences from file action\cgf\fog\<mapname>.fog
|
||||
// format for file is:
|
||||
// // format: mapname density fogtype red green blue
|
||||
|
||||
char buf_filename[256];
|
||||
|
||||
ostrstream filename(buf_filename, 255);
|
||||
|
||||
cvar_t* gamedir;
|
||||
cvar_t* basedir;
|
||||
|
||||
gamedir = gi.cvar ("game", "", 0);
|
||||
basedir = gi.cvar("basedir", ".", 0);
|
||||
|
||||
// obtain file location
|
||||
filename << basedir->string << '\\' << gamedir->string
|
||||
<< '\\' << "cgf\\fog\\" << aMapName << ".fog" << char(0);
|
||||
|
||||
// open file, append mode
|
||||
ifstream preferences(buf_filename, ios_base::in);
|
||||
if (preferences.good())
|
||||
{
|
||||
char name[128];
|
||||
memset(name,0, sizeof(name));
|
||||
preferences >> name;
|
||||
|
||||
// skip comment lines if any
|
||||
while ( (strlen(name) >= 2)
|
||||
&& (name[0] == '/')
|
||||
&& ( (name[1] == '/')
|
||||
|| (name[1] == '*')
|
||||
)
|
||||
)
|
||||
{
|
||||
preferences.getline(name, 128);
|
||||
memset(name,0, sizeof(name));
|
||||
preferences >> name;
|
||||
}
|
||||
|
||||
/* test for name similarity removed
|
||||
if ( (strlen(name))
|
||||
&& ( 0 == stricmp(name, aMapName))
|
||||
)
|
||||
*/
|
||||
{
|
||||
preferences >> *aDensity;
|
||||
if (*aDensity > 0)
|
||||
{
|
||||
preferences >> *aType;
|
||||
preferences >> *aRed;
|
||||
preferences >> *aGreen;
|
||||
preferences >> *aBlue;
|
||||
preferences >> *aBegin;
|
||||
preferences >> *aEnd;
|
||||
}
|
||||
// adjust settings info
|
||||
ostrstream settings(buf_filename, 63);
|
||||
settings << "cgf\\fog\\" << aMapName << ".fog" << char(0);
|
||||
strcpy(m_Settings, buf_filename);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void CGF_SFX_ActivateFog(vec_t aDensity,
|
||||
int aType,
|
||||
vec3_t aColor,
|
||||
vec_t aBegin,
|
||||
vec_t anEnd
|
||||
)
|
||||
{
|
||||
#ifdef CGF
|
||||
if (CGF_Debug_IsLogging())
|
||||
{
|
||||
char message[256];
|
||||
if (aDensity)
|
||||
sprintf(message,
|
||||
"fog settings: mode %d, density %.2f, rgb=[%.2f, %.2f, %.2f], begin %d, end %d",
|
||||
(aType == GL_LINEAR ? 2 : aType - GL_FOG_START),
|
||||
aDensity, aColor[0], aColor[1], aColor[2], (int) aBegin, (int) anEnd
|
||||
);
|
||||
else
|
||||
sprintf(message,
|
||||
"fog settings: mode %d, density %.2f == disabled",
|
||||
(aType == GL_LINEAR ? 2 : aType - GL_FOG_START),
|
||||
aDensity
|
||||
);
|
||||
CGF_Debug_LogMessage(message);
|
||||
}
|
||||
#endif
|
||||
CGF_SFX_InitFogForDistance(aType, aDensity, aBegin, anEnd);
|
||||
|
||||
glEnable (GL_FOG); // turn on fog, otherwise you won't see any
|
||||
|
||||
glFogi (GL_FOG_MODE, aType);
|
||||
if (aType == GL_LINEAR)
|
||||
{
|
||||
glFogf (GL_FOG_START, aBegin);
|
||||
glFogf (GL_FOG_END, anEnd);
|
||||
}
|
||||
|
||||
glFogfv (GL_FOG_COLOR, aColor);
|
||||
glFogf (GL_FOG_DENSITY, aDensity);
|
||||
glHint (GL_FOG_HINT, GL_NICEST);
|
||||
}
|
||||
|
||||
|
||||
void CGF_SFX_DeactivateFog()
|
||||
{
|
||||
CGF_SFX_InitFogForDistance(0, 0, 0, 0);
|
||||
|
||||
// prevent opengl calls when no opengl
|
||||
if ( (vid_ref)
|
||||
&& (0 == stricmp(vid_ref->string, "soft"))
|
||||
)
|
||||
return;
|
||||
|
||||
glDisable (GL_FOG); // turn off fog
|
||||
}
|
||||
|
||||
|
||||
int CGF_SFX_IsFogEnabled()
|
||||
{
|
||||
return m_FogEnabled;
|
||||
}
|
||||
|
||||
|
||||
void CGF_SFX_InitFogForDistance(int aFogMode, vec_t aFogDensity, vec_t aFogBegin, vec_t aFogEnd)
|
||||
{
|
||||
// determine how many cells
|
||||
int cells;
|
||||
cells = kMaxViewingDistance / kViewingDistanceStep;
|
||||
|
||||
vec_t dist;
|
||||
vec_t maxdist;
|
||||
vec_t mindist;
|
||||
|
||||
if (aFogDensity == 0.0)
|
||||
aFogMode = 0;
|
||||
|
||||
// adjust fog density value to our values
|
||||
aFogDensity *= 100;
|
||||
|
||||
switch (aFogMode)
|
||||
{
|
||||
case GL_LINEAR:
|
||||
maxdist = aFogEnd;
|
||||
mindist = aFogBegin;
|
||||
break;
|
||||
case GL_EXP:
|
||||
maxdist = 400.0 / sqrt(aFogDensity);
|
||||
break;
|
||||
case GL_EXP2:
|
||||
maxdist = 130.0 / aFogDensity;
|
||||
break;
|
||||
default :
|
||||
maxdist = kMaxViewingDistance;
|
||||
}
|
||||
|
||||
dist = 0;
|
||||
for (int cell = 0; cell < cells; cell++)
|
||||
{
|
||||
vec_t reduction;
|
||||
switch (aFogMode)
|
||||
{
|
||||
case GL_LINEAR:
|
||||
if (dist < mindist)
|
||||
reduction = 1;
|
||||
else
|
||||
if (dist > maxdist)
|
||||
reduction = 0;
|
||||
else
|
||||
reduction = 1 - (dist - mindist) / (maxdist - mindist);
|
||||
break;
|
||||
case GL_EXP:
|
||||
reduction = 1 - dist / maxdist;
|
||||
break;
|
||||
case GL_EXP2:
|
||||
reduction = 1 - (dist / maxdist) * (dist / maxdist);
|
||||
break;
|
||||
default :
|
||||
reduction = 1.0;
|
||||
}
|
||||
if (reduction < 0)
|
||||
reduction = 0;
|
||||
m_FogViewReduction[cell] = reduction;
|
||||
|
||||
dist += kViewingDistanceStep;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char* CGF_SFX_FogSettingsInfo()
|
||||
{
|
||||
return (const char*) m_Settings;
|
||||
}
|
||||
|
||||
|
||||
vec_t CGF_SFX_GetFogForDistance(vec_t dist)
|
||||
{
|
||||
int index;
|
||||
index = ((int) dist) / (int) kViewingDistanceStep;
|
||||
assert( (index < (kMaxViewingDistance / kViewingDistanceStep))
|
||||
);
|
||||
return m_FogViewReduction[index];
|
||||
}
|
||||
|
||||
|
||||
vec_t CGF_SFX_GetViewingDistanceUpToReduction(vec_t aReduction)
|
||||
{
|
||||
int index;
|
||||
index = (kMaxViewingDistance / kViewingDistanceStep) - 1;
|
||||
while ( (index >= 0)
|
||||
&& (m_FogViewReduction[index] < aReduction)
|
||||
)
|
||||
index--;
|
||||
|
||||
return (vec_t) (index * kViewingDistanceStep);
|
||||
}
|
||||
|
||||
/*
|
||||
#ifdef _DEBUG
|
||||
extern "C" void CGF_SFX_Fog_DisplayRods(edict_t* aClient);
|
||||
|
||||
void CGF_SFX_Fog_DisplayRods(edict_t* aClient)
|
||||
{
|
||||
vec3_t forward;
|
||||
vec3_t v1;
|
||||
vec3_t v2;
|
||||
bool half;
|
||||
bool ten;
|
||||
|
||||
half = false;
|
||||
ten = false;
|
||||
|
||||
AngleVectors(aClient->s.angles, forward, 0, 0);
|
||||
forward[2] = 0;
|
||||
VectorNormalize(forward);
|
||||
|
||||
for (int rod = 0; rod < 20; rod ++)
|
||||
{
|
||||
VectorMA(aClient->s.origin, 100 * ((float) rod), forward, v1);
|
||||
VectorCopy(v1, v2);
|
||||
v2[2] -= 1000;
|
||||
SABIN_Debug_DisplayLine(v1, v2, 60);
|
||||
|
||||
if ( (!half)
|
||||
&& (half = (CGF_SFX_GetFogForDistance(100 * ((float) rod)) < 0.5))
|
||||
)
|
||||
{
|
||||
SABIN_Debug_DisplayPoint(v1, RF_SHELL_RED, 60);
|
||||
}
|
||||
|
||||
if ( (!ten)
|
||||
&& (ten = (CGF_SFX_GetFogForDistance(100 * ((float) rod)) < 0.1))
|
||||
)
|
||||
{
|
||||
SABIN_Debug_DisplayPoint(v1, RF_SHELL_RED, 60);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
|
||||
//#endif
|
472
acesrc/cgf_sfx_fog.cpp
Normal file
472
acesrc/cgf_sfx_fog.cpp
Normal file
|
@ -0,0 +1,472 @@
|
|||
/****************************************************************************/
|
||||
/* */
|
||||
/* project : CGF (c) 1999 William van der Sterren */
|
||||
/* */
|
||||
/* file : cgf_sfx_fog.h "fog openGL stuff" */
|
||||
/* author(s): William van der Sterren */
|
||||
/* version : 0.5 */
|
||||
/* */
|
||||
/* date (last revision): Aug 31, 99 */
|
||||
/* date (creation) : Aug 31, 99 */
|
||||
/* */
|
||||
/* */
|
||||
/* revision history */
|
||||
/* -- date ---- | -- revision ---------------------- | -- revisor -- */
|
||||
/* | | */
|
||||
/* */
|
||||
/****************************************************************************/
|
||||
|
||||
//#ifdef FOG
|
||||
|
||||
|
||||
#include <cmath> // prevent problems between C and STL
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "cgf_sfx_fog.h"
|
||||
#include "../g_local.h"
|
||||
//#include "sabin_debug.h"
|
||||
#include <windows.h>
|
||||
#include <GL/gl.h>
|
||||
|
||||
/*
|
||||
#ifdef CGF
|
||||
// AI update related to fog
|
||||
#include "cgf_tactics_visibility.h"
|
||||
#endif
|
||||
*/
|
||||
}
|
||||
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <strstream>
|
||||
using namespace std;
|
||||
|
||||
// defaults
|
||||
static vec_t m_FogDensity = 0.1000;
|
||||
static int m_FogEnabled = false;
|
||||
static int m_FogType = 2; // linear
|
||||
static int m_FogStart = 200;
|
||||
static int m_FogEnd = 1600;
|
||||
static vec_t m_FogColor[4] = {0.5, 0.5, 0.5, 1};
|
||||
static char m_Settings[64];
|
||||
|
||||
|
||||
const int kMaxViewingDistance = 8192;
|
||||
const int kViewingDistanceStep = 64;
|
||||
static vec_t m_FogViewReduction[kMaxViewingDistance / kViewingDistanceStep];
|
||||
//static vec_t m_FogViewReduction[128];
|
||||
|
||||
// cvar for fog and video mode
|
||||
static cvar_t* fog = 0;
|
||||
static cvar_t* vid_ref = 0;
|
||||
|
||||
|
||||
// forward decls
|
||||
void CGF_SFX_ActivateFog(vec_t aDensity,
|
||||
int aType,
|
||||
vec3_t aColor,
|
||||
vec_t aBegin,
|
||||
vec_t anEnd
|
||||
);
|
||||
void CGF_SFX_DeactivateFog();
|
||||
qboolean CGF_SFX_FindFogPreferences(const char* aMapName,
|
||||
vec_t* aDensity,
|
||||
int* aType,
|
||||
vec_t* aRed,
|
||||
vec_t* aGreen,
|
||||
vec_t* aBlue,
|
||||
vec_t* aBegin,
|
||||
vec_t* aEnd
|
||||
);
|
||||
void CGF_SFX_InitFogForDistance(int aFogMode, vec_t aFogDensity, vec_t aFogBegin, vec_t aFogEnd);
|
||||
|
||||
|
||||
void CGF_SFX_InstallFogSupport()
|
||||
{
|
||||
// reset settings
|
||||
m_Settings[0] = 0;//char(0);
|
||||
|
||||
// if fog > 0, then enabled
|
||||
fog = gi.cvar("fog", "0", 0);
|
||||
vid_ref = gi.cvar("vid_ref", "", 0);
|
||||
|
||||
// prevent issueing gl calls if no opengl present
|
||||
m_FogEnabled = ( (fog->value > 0)
|
||||
&& (0 != stricmp(vid_ref->string, "soft"))
|
||||
);
|
||||
|
||||
// if not fog enabled, act upon it
|
||||
if (!m_FogEnabled)
|
||||
{
|
||||
if (fog->value == 0)
|
||||
{
|
||||
strcpy(m_Settings, "cvar fog is 0");
|
||||
// CGF_Debug_LogMessage("fog disabled: cvar fog is 0");
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(m_Settings, "using software rendering");
|
||||
// CGF_Debug_LogMessage("fog disabled: using software rendering");
|
||||
}
|
||||
gi.cvar_set("fog", "0");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CGF_SFX_AdjustFogForMap(const char* aMapName)
|
||||
{
|
||||
// verify fog if m_FogEnabled, and reset if required
|
||||
fog = gi.cvar("fog", "0", 0);
|
||||
vid_ref = gi.cvar("vid_ref", "", 0);
|
||||
|
||||
if (m_FogEnabled)
|
||||
{
|
||||
if (!(m_FogEnabled = (fog->value != 0)))
|
||||
{
|
||||
#ifdef CGF
|
||||
CGF_Debug_LogMessage("fog disabled: fog set to 0 or video mode changed");
|
||||
// change visibility info for AI
|
||||
CGF_Tactics_IncorporateChangedFogSettings();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_FogEnabled = ( (fog->value > 0)
|
||||
&& (0 != stricmp(vid_ref->string, "soft"))
|
||||
);
|
||||
if (m_FogEnabled)
|
||||
{
|
||||
#ifdef CGF
|
||||
CGF_Debug_LogMessage("fog enabled : fog set to 1 or video mode changed");
|
||||
// change visibility info for AI
|
||||
CGF_Tactics_IncorporateChangedFogSettings();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// process fog setting
|
||||
if (m_FogEnabled)
|
||||
{
|
||||
vec_t d;
|
||||
int t;
|
||||
vec_t s;
|
||||
vec_t e;
|
||||
vec3_t rgb;
|
||||
if ( (!aMapName)
|
||||
|| (!CGF_SFX_FindFogPreferences(aMapName, &d, &t, &rgb[0], &rgb[1], &rgb[2], &s, &e))
|
||||
)
|
||||
{
|
||||
if (!CGF_SFX_FindFogPreferences("default", &d, &t, &rgb[0], &rgb[1], &rgb[2], &s, &e))
|
||||
{
|
||||
d = m_FogDensity;
|
||||
t = m_FogType;
|
||||
VectorCopy(m_FogColor, rgb);
|
||||
s = m_FogStart;
|
||||
e = m_FogEnd;
|
||||
}
|
||||
}
|
||||
// convert d to value usable for gl lib
|
||||
d /= 100;
|
||||
// convert fog type
|
||||
// 0 == GL_EXP, 1 == GL_EXP2, 2 == GL_LINEAR
|
||||
if ((t < 0) || (t > 2))
|
||||
t = m_FogType;
|
||||
if (t < 2)
|
||||
t = t + 0x800;
|
||||
else
|
||||
t = GL_LINEAR;
|
||||
|
||||
if (d > 0)
|
||||
CGF_SFX_ActivateFog(d, t, rgb, s, e);
|
||||
else
|
||||
CGF_SFX_DeactivateFog();
|
||||
}
|
||||
else
|
||||
{
|
||||
CGF_SFX_DeactivateFog();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
qboolean CGF_SFX_FindFogPreferences(const char* aMapName,
|
||||
vec_t* aDensity,
|
||||
int* aType,
|
||||
vec_t* aRed,
|
||||
vec_t* aGreen,
|
||||
vec_t* aBlue,
|
||||
vec_t* aBegin,
|
||||
vec_t* aEnd
|
||||
)
|
||||
{
|
||||
*aDensity = 0;
|
||||
|
||||
// try to read preferences from file action\cgf\fog\<mapname>.fog
|
||||
// format for file is:
|
||||
// // format: mapname density fogtype red green blue
|
||||
|
||||
char buf_filename[256];
|
||||
|
||||
ostrstream filename(buf_filename, 255);
|
||||
|
||||
cvar_t* gamedir;
|
||||
cvar_t* basedir;
|
||||
|
||||
gamedir = gi.cvar ("game", "", 0);
|
||||
basedir = gi.cvar("basedir", ".", 0);
|
||||
|
||||
// obtain file location
|
||||
filename << basedir->string << '\\' << gamedir->string
|
||||
<< '\\' << "cgf\\fog\\" << aMapName << ".fog" << char(0);
|
||||
|
||||
// open file, append mode
|
||||
ifstream preferences(buf_filename, ios_base::in);
|
||||
if (preferences.good())
|
||||
{
|
||||
char name[128];
|
||||
memset(name,0, sizeof(name));
|
||||
preferences >> name;
|
||||
|
||||
// skip comment lines if any
|
||||
while ( (strlen(name) >= 2)
|
||||
&& (name[0] == '/')
|
||||
&& ( (name[1] == '/')
|
||||
|| (name[1] == '*')
|
||||
)
|
||||
)
|
||||
{
|
||||
preferences.getline(name, 128);
|
||||
memset(name,0, sizeof(name));
|
||||
preferences >> name;
|
||||
}
|
||||
|
||||
/* test for name similarity removed
|
||||
if ( (strlen(name))
|
||||
&& ( 0 == stricmp(name, aMapName))
|
||||
)
|
||||
*/
|
||||
{
|
||||
preferences >> *aDensity;
|
||||
if (*aDensity > 0)
|
||||
{
|
||||
preferences >> *aType;
|
||||
preferences >> *aRed;
|
||||
preferences >> *aGreen;
|
||||
preferences >> *aBlue;
|
||||
preferences >> *aBegin;
|
||||
preferences >> *aEnd;
|
||||
}
|
||||
// adjust settings info
|
||||
ostrstream settings(buf_filename, 63);
|
||||
settings << "cgf\\fog\\" << aMapName << ".fog" << char(0);
|
||||
strcpy(m_Settings, buf_filename);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void CGF_SFX_ActivateFog(vec_t aDensity,
|
||||
int aType,
|
||||
vec3_t aColor,
|
||||
vec_t aBegin,
|
||||
vec_t anEnd
|
||||
)
|
||||
{
|
||||
#ifdef CGF
|
||||
if (CGF_Debug_IsLogging())
|
||||
{
|
||||
char message[256];
|
||||
if (aDensity)
|
||||
sprintf(message,
|
||||
"fog settings: mode %d, density %.2f, rgb=[%.2f, %.2f, %.2f], begin %d, end %d",
|
||||
(aType == GL_LINEAR ? 2 : aType - GL_FOG_START),
|
||||
aDensity, aColor[0], aColor[1], aColor[2], (int) aBegin, (int) anEnd
|
||||
);
|
||||
else
|
||||
sprintf(message,
|
||||
"fog settings: mode %d, density %.2f == disabled",
|
||||
(aType == GL_LINEAR ? 2 : aType - GL_FOG_START),
|
||||
aDensity
|
||||
);
|
||||
CGF_Debug_LogMessage(message);
|
||||
}
|
||||
#endif
|
||||
CGF_SFX_InitFogForDistance(aType, aDensity, aBegin, anEnd);
|
||||
|
||||
glEnable (GL_FOG); // turn on fog, otherwise you won't see any
|
||||
|
||||
glFogi (GL_FOG_MODE, aType);
|
||||
if (aType == GL_LINEAR)
|
||||
{
|
||||
glFogf (GL_FOG_START, aBegin);
|
||||
glFogf (GL_FOG_END, anEnd);
|
||||
}
|
||||
|
||||
glFogfv (GL_FOG_COLOR, aColor);
|
||||
glFogf (GL_FOG_DENSITY, aDensity);
|
||||
glHint (GL_FOG_HINT, GL_NICEST);
|
||||
}
|
||||
|
||||
|
||||
void CGF_SFX_DeactivateFog()
|
||||
{
|
||||
CGF_SFX_InitFogForDistance(0, 0, 0, 0);
|
||||
|
||||
// prevent opengl calls when no opengl
|
||||
if ( (vid_ref)
|
||||
&& (0 == stricmp(vid_ref->string, "soft"))
|
||||
)
|
||||
return;
|
||||
|
||||
glDisable (GL_FOG); // turn off fog
|
||||
}
|
||||
|
||||
|
||||
int CGF_SFX_IsFogEnabled()
|
||||
{
|
||||
return m_FogEnabled;
|
||||
}
|
||||
|
||||
|
||||
void CGF_SFX_InitFogForDistance(int aFogMode, vec_t aFogDensity, vec_t aFogBegin, vec_t aFogEnd)
|
||||
{
|
||||
// determine how many cells
|
||||
int cells;
|
||||
cells = kMaxViewingDistance / kViewingDistanceStep;
|
||||
|
||||
vec_t dist;
|
||||
vec_t maxdist;
|
||||
vec_t mindist;
|
||||
|
||||
if (aFogDensity == 0.0)
|
||||
aFogMode = 0;
|
||||
|
||||
// adjust fog density value to our values
|
||||
aFogDensity *= 100;
|
||||
|
||||
switch (aFogMode)
|
||||
{
|
||||
case GL_LINEAR:
|
||||
maxdist = aFogEnd;
|
||||
mindist = aFogBegin;
|
||||
break;
|
||||
case GL_EXP:
|
||||
maxdist = 400.0 / sqrt(aFogDensity);
|
||||
break;
|
||||
case GL_EXP2:
|
||||
maxdist = 130.0 / aFogDensity;
|
||||
break;
|
||||
default :
|
||||
maxdist = kMaxViewingDistance;
|
||||
}
|
||||
|
||||
dist = 0;
|
||||
for (int cell = 0; cell < cells; cell++)
|
||||
{
|
||||
vec_t reduction;
|
||||
switch (aFogMode)
|
||||
{
|
||||
case GL_LINEAR:
|
||||
if (dist < mindist)
|
||||
reduction = 1;
|
||||
else
|
||||
if (dist > maxdist)
|
||||
reduction = 0;
|
||||
else
|
||||
reduction = 1 - (dist - mindist) / (maxdist - mindist);
|
||||
break;
|
||||
case GL_EXP:
|
||||
reduction = 1 - dist / maxdist;
|
||||
break;
|
||||
case GL_EXP2:
|
||||
reduction = 1 - (dist / maxdist) * (dist / maxdist);
|
||||
break;
|
||||
default :
|
||||
reduction = 1.0;
|
||||
}
|
||||
if (reduction < 0)
|
||||
reduction = 0;
|
||||
m_FogViewReduction[cell] = reduction;
|
||||
|
||||
dist += kViewingDistanceStep;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char* CGF_SFX_FogSettingsInfo()
|
||||
{
|
||||
return (const char*) m_Settings;
|
||||
}
|
||||
|
||||
|
||||
vec_t CGF_SFX_GetFogForDistance(vec_t dist)
|
||||
{
|
||||
int index;
|
||||
index = ((int) dist) / (int) kViewingDistanceStep;
|
||||
assert( (index < (kMaxViewingDistance / kViewingDistanceStep))
|
||||
);
|
||||
return m_FogViewReduction[index];
|
||||
}
|
||||
|
||||
|
||||
vec_t CGF_SFX_GetViewingDistanceUpToReduction(vec_t aReduction)
|
||||
{
|
||||
int index;
|
||||
index = (kMaxViewingDistance / kViewingDistanceStep) - 1;
|
||||
while ( (index >= 0)
|
||||
&& (m_FogViewReduction[index] < aReduction)
|
||||
)
|
||||
index--;
|
||||
|
||||
return (vec_t) (index * kViewingDistanceStep);
|
||||
}
|
||||
|
||||
/*
|
||||
#ifdef _DEBUG
|
||||
extern "C" void CGF_SFX_Fog_DisplayRods(edict_t* aClient);
|
||||
|
||||
void CGF_SFX_Fog_DisplayRods(edict_t* aClient)
|
||||
{
|
||||
vec3_t forward;
|
||||
vec3_t v1;
|
||||
vec3_t v2;
|
||||
bool half;
|
||||
bool ten;
|
||||
|
||||
half = false;
|
||||
ten = false;
|
||||
|
||||
AngleVectors(aClient->s.angles, forward, 0, 0);
|
||||
forward[2] = 0;
|
||||
VectorNormalize(forward);
|
||||
|
||||
for (int rod = 0; rod < 20; rod ++)
|
||||
{
|
||||
VectorMA(aClient->s.origin, 100 * ((float) rod), forward, v1);
|
||||
VectorCopy(v1, v2);
|
||||
v2[2] -= 1000;
|
||||
SABIN_Debug_DisplayLine(v1, v2, 60);
|
||||
|
||||
if ( (!half)
|
||||
&& (half = (CGF_SFX_GetFogForDistance(100 * ((float) rod)) < 0.5))
|
||||
)
|
||||
{
|
||||
SABIN_Debug_DisplayPoint(v1, RF_SHELL_RED, 60);
|
||||
}
|
||||
|
||||
if ( (!ten)
|
||||
&& (ten = (CGF_SFX_GetFogForDistance(100 * ((float) rod)) < 0.1))
|
||||
)
|
||||
{
|
||||
SABIN_Debug_DisplayPoint(v1, RF_SHELL_RED, 60);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
|
||||
//#endif
|
51
acesrc/cgf_sfx_fog.h
Normal file
51
acesrc/cgf_sfx_fog.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/****************************************************************************/
|
||||
/* */
|
||||
/* project : CGF (c) 1999 William van der Sterren */
|
||||
/* */
|
||||
/* file : cgf_sfx_fog.h "fog openGL stuff" */
|
||||
/* author(s): William van der Sterren */
|
||||
/* version : 0.5 */
|
||||
/* */
|
||||
/* date (last revision): Aug 31, 99 */
|
||||
/* date (creation) : Aug 31, 99 */
|
||||
/* */
|
||||
/* */
|
||||
/* revision history */
|
||||
/* -- date ---- | -- revision ---------------------- | -- revisor -- */
|
||||
/* | | */
|
||||
/* */
|
||||
/****************************************************************************/
|
||||
|
||||
|
||||
#ifndef __CGF_SFX_FOG_
|
||||
#define __CGF_SFX_FOG_
|
||||
|
||||
|
||||
typedef float vec_t;
|
||||
|
||||
#define kFogMaxVisibilityReduction 0.02
|
||||
|
||||
void CGF_SFX_InstallFogSupport();
|
||||
|
||||
void CGF_SFX_AdjustFogForMap(const char* aMapName);
|
||||
|
||||
int CGF_SFX_IsFogEnabled();
|
||||
|
||||
vec_t CGF_SFX_GetFogForDistance(vec_t aDist);
|
||||
|
||||
vec_t CGF_SFX_GetViewingDistanceUpToReduction(vec_t aReduction);
|
||||
|
||||
const char* CGF_SFX_FogSettingsInfo();
|
||||
|
||||
/*
|
||||
// debugging facilities
|
||||
#ifdef _DEBUG
|
||||
|
||||
typedef struct edict_s edict_t;
|
||||
|
||||
void CGF_SFX_Fog_DisplayRods(edict_t* aClient);
|
||||
|
||||
#endif
|
||||
*/
|
||||
|
||||
#endif // __CGF_SFX_FOG_
|
66
acesrc/ltklist.c
Normal file
66
acesrc/ltklist.c
Normal file
|
@ -0,0 +1,66 @@
|
|||
///////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// LTK - ACTION Quake II Bot
|
||||
//
|
||||
// Original file is Copyright(c), Connor Caple 1999, All Rights Reserved
|
||||
//
|
||||
//
|
||||
// All other files are Copyright(c) Id Software, Inc.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* $Header: /LicenseToKill/src/acesrc/ltklist.cpp 1 24/09/99 10:15 Riever $
|
||||
*
|
||||
* $Log: /LicenseToKill/src/acesrc/ltklist.cpp $
|
||||
*
|
||||
* 1 24/09/99 10:15 Riever
|
||||
*
|
||||
*/
|
||||
|
||||
//#define _cplusplus
|
||||
//#include "ltklist.h"
|
||||
#include "..\g_local.h"
|
||||
|
||||
|
||||
//==========================
|
||||
// Add an item to the front of the queue
|
||||
//==========================
|
||||
void LTKLpush_front( searchNode_t *nodedata )
|
||||
{
|
||||
}
|
||||
|
||||
//==========================
|
||||
// Add an item to the back of the queue
|
||||
//==========================
|
||||
void LTKLpush_back( searchNode_t *nodedata)
|
||||
{
|
||||
}
|
||||
|
||||
//==========================
|
||||
// Remove an item from the front of the queue
|
||||
//==========================
|
||||
void LTKLpop_front( ltklist_t *listdata )
|
||||
{
|
||||
}
|
||||
|
||||
//==========================
|
||||
// Get an item from the back of the queue
|
||||
//==========================
|
||||
void LTKLpop_back( ltklist_t *listdata )
|
||||
{
|
||||
}
|
||||
|
||||
//==========================
|
||||
// Get a pointer to the front of the queue
|
||||
//==========================
|
||||
searchNode_t *LTKLfront( ltklist_t *listdata )
|
||||
{
|
||||
return listdata->head;
|
||||
}
|
||||
|
||||
//==========================
|
||||
// Check the status of the list
|
||||
//==========================
|
||||
qboolean LTKLempty( ltklist_t *listdata )
|
||||
{
|
||||
return ( listdata->head == NULL);
|
||||
}
|
BIN
acesrc/vssver.scc
Normal file
BIN
acesrc/vssver.scc
Normal file
Binary file not shown.
704
cgf_sfx_glass.c
Normal file
704
cgf_sfx_glass.c
Normal file
|
@ -0,0 +1,704 @@
|
|||
/****************************************************************************/
|
||||
/* */
|
||||
/* project : CGF (c) 1999 William van der Sterren */
|
||||
/* parts (c) 1998 id software */
|
||||
/* */
|
||||
/* file : cgf_sfx_glass.cpp "special effects for glass entities" */
|
||||
/* author(s): William van der Sterren */
|
||||
/* version : 0.5 */
|
||||
/* */
|
||||
/* date (last revision): Jun 12, 99 */
|
||||
/* date (creation) : Jun 04, 99 */
|
||||
/* */
|
||||
/* */
|
||||
/* revision history */
|
||||
/* -- date ---- | -- revision ---------------------- | -- revisor -- */
|
||||
/* Jun 12, 1999 | fixed knife slash breaks glass | William */
|
||||
/* Jun 08, 1999 | improved fragment limit | William */
|
||||
/* */
|
||||
/******* http://www.botepidemic.com/aid/cgf for CGF for Action Quake2 *******/
|
||||
|
||||
#ifdef __cplusplus
|
||||
// VC++, for CGF
|
||||
#include <cmath> // prevent problems between C and STL
|
||||
extern "C"
|
||||
{
|
||||
#include "g_local.h"
|
||||
#include "cgf_sfx_glass.h"
|
||||
}
|
||||
#else
|
||||
// C, for other AQ2 variants
|
||||
#include "g_local.h"
|
||||
#include "cgf_sfx_glass.h"
|
||||
#endif
|
||||
|
||||
|
||||
// cvar for breaking glass
|
||||
static cvar_t *breakableglass = 0;
|
||||
|
||||
// cvar for max glass fragment count
|
||||
static cvar_t *glassfragmentlimit = 0;
|
||||
|
||||
static int glassfragmentcount = 0;
|
||||
|
||||
|
||||
// additional functions - Q2 expects C calling convention
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
void CGF_SFX_TouchGlass(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
|
||||
// called whenever an entity hits the trigger spawned for the glass
|
||||
|
||||
void CGF_SFX_EmitGlass (edict_t* aGlassPane, edict_t* anInflictor, vec3_t aPoint);
|
||||
// emits glass fragments from aPoint, to show effects of firing thru window
|
||||
|
||||
void CGF_SFX_BreakGlass(edict_t* aGlassPane, edict_t* anOther, edict_t* anAttacker,
|
||||
int aDamage, vec3_t aPoint, vec_t aPaneDestructDelay
|
||||
);
|
||||
// breaks glass
|
||||
|
||||
void CGF_SFX_InstallBreakableGlass(edict_t* aGlassPane);
|
||||
// when working on a glass pane for the first time, just install trigger
|
||||
// when working on a glass pane again (after a game ended), move
|
||||
// glass back to original location
|
||||
|
||||
void CGF_SFX_HideBreakableGlass(edict_t* aGlassPane);
|
||||
// after being broken, the pane cannot be removed as it is needed in
|
||||
// subsequent missions/games, so hide it at about z = -1000
|
||||
|
||||
void CGF_SFX_ApplyGlassFragmentLimit(const char* aClassName);
|
||||
// updates glassfragmentcount and removes oldest glass fragement if
|
||||
// necessary to meet limit
|
||||
|
||||
void CGF_SFX_MiscGlassUse(edict_t *self, edict_t *other, edict_t *activator);
|
||||
// catches use from unforeseen objects (weapons, debris,
|
||||
// etc. touching the window)
|
||||
|
||||
void CGF_SFX_MiscGlassDie(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
|
||||
// catches die calls caused by unforeseen objects (weapons, debris,
|
||||
// etc. damaging the window)
|
||||
|
||||
void CGF_SFX_GlassThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin);
|
||||
// variant of id software's ThrowDebris, now numbering the entity (for later removal)
|
||||
|
||||
extern // from a_game.c
|
||||
edict_t *FindEdictByClassnum (char *classname, int classnum);
|
||||
|
||||
// declaration from g_misc.c
|
||||
extern // from g_misc.c
|
||||
void debris_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
void CGF_SFX_InstallGlassSupport()
|
||||
{
|
||||
breakableglass = gi.cvar("breakableglass", "0", 0);
|
||||
glassfragmentlimit = gi.cvar("glassfragmentlimit", "30", 0);
|
||||
}
|
||||
|
||||
|
||||
int CGF_SFX_IsBreakableGlassEnabled()
|
||||
{
|
||||
// returns whether breakable glass is enabled (cvar) and allowed (dm mode)
|
||||
return breakableglass->value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CGF_SFX_TestBreakableGlassAndRemoveIfNot_Think(edict_t* aPossibleGlassEntity)
|
||||
{
|
||||
// at level.time == 0.1 the entity has been introduced in the game,
|
||||
// and we can use gi.pointcontents and gi.trace to check the entity
|
||||
vec3_t origin;
|
||||
int breakingglass;
|
||||
trace_t trace;
|
||||
|
||||
// test for cvar
|
||||
if (!CGF_SFX_IsBreakableGlassEnabled())
|
||||
{
|
||||
G_FreeEdict(aPossibleGlassEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
VectorAdd(aPossibleGlassEntity->absmax, aPossibleGlassEntity->absmin, origin);
|
||||
VectorScale(origin, 0.5, origin);
|
||||
|
||||
// detect glass (does not work for complex shapes,
|
||||
// for example, the glass window near the satellite
|
||||
// dish at Q2 base3
|
||||
breakingglass = (gi.pointcontents(origin) & CONTENTS_TRANSLUCENT);
|
||||
|
||||
if (!breakingglass)
|
||||
{
|
||||
// test for complex brushes that happen to be
|
||||
// hollow in their origin (for instance, the
|
||||
// window at Q2 base3, near the satellite dish
|
||||
trace = gi.trace(origin, vec3_origin, vec3_origin, aPossibleGlassEntity->absmax, 0,
|
||||
MASK_PLAYERSOLID
|
||||
);
|
||||
breakingglass = ((trace.ent == aPossibleGlassEntity) &&
|
||||
(trace.contents & CONTENTS_TRANSLUCENT)
|
||||
);
|
||||
trace = gi.trace(origin, vec3_origin, vec3_origin, aPossibleGlassEntity->absmin, 0,
|
||||
MASK_PLAYERSOLID
|
||||
);
|
||||
breakingglass = ((breakingglass) ||
|
||||
((trace.ent == aPossibleGlassEntity) &&
|
||||
(trace.contents & CONTENTS_TRANSLUCENT)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!breakingglass)
|
||||
{
|
||||
// do remove other func_explosives
|
||||
G_FreeEdict (aPossibleGlassEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
// discovered some glass - now make store the origin
|
||||
// we need that after hiding the glass
|
||||
VectorCopy(aPossibleGlassEntity->s.origin, aPossibleGlassEntity->pos1); // IMPORTANT!
|
||||
|
||||
// make a backup of the health in light_level
|
||||
aPossibleGlassEntity->light_level = aPossibleGlassEntity->health;
|
||||
|
||||
// install the glass
|
||||
CGF_SFX_InstallBreakableGlass(aPossibleGlassEntity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CGF_SFX_InstallBreakableGlass(edict_t* aGlassPane)
|
||||
{
|
||||
// when working on a glass pane for the first time, just install trigger
|
||||
// when working on a glass pane again (after a game ended), move
|
||||
// glass back to original location
|
||||
edict_t* trigger;
|
||||
vec3_t maxs;
|
||||
vec3_t mins;
|
||||
|
||||
// reset origin based on aGlassPane->pos1
|
||||
VectorCopy(aGlassPane->pos1, aGlassPane->s.origin);
|
||||
|
||||
// reset health based on aGlassPane->light_level
|
||||
aGlassPane->health = aGlassPane->light_level;
|
||||
|
||||
// replace die and use functions by glass specific ones
|
||||
aGlassPane->die = CGF_SFX_MiscGlassDie;
|
||||
aGlassPane->use = CGF_SFX_MiscGlassUse;
|
||||
|
||||
// reset some pane attributes
|
||||
aGlassPane->takedamage = DAMAGE_YES;
|
||||
aGlassPane->solid = SOLID_BSP;
|
||||
aGlassPane->movetype = MOVETYPE_FLYMISSILE;
|
||||
// for other movetypes, cannot move pane to hidden location and back
|
||||
|
||||
// try to establish size
|
||||
VectorCopy(aGlassPane->maxs, maxs);
|
||||
VectorCopy(aGlassPane->mins, mins);
|
||||
|
||||
// set up trigger, similar to triggers for doors
|
||||
// but with a smaller box
|
||||
mins[0] -= 24;
|
||||
mins[1] -= 24;
|
||||
mins[2] -= 24;
|
||||
maxs[0] += 24;
|
||||
maxs[1] += 24;
|
||||
maxs[2] += 24;
|
||||
|
||||
// adjust some settings
|
||||
trigger = G_Spawn ();
|
||||
trigger->classname = "breakableglass_trigger";
|
||||
VectorCopy (mins, trigger->mins);
|
||||
VectorCopy (maxs, trigger->maxs);
|
||||
trigger->owner = aGlassPane;
|
||||
trigger->solid = SOLID_TRIGGER;
|
||||
trigger->movetype = MOVETYPE_NONE;
|
||||
trigger->touch = CGF_SFX_TouchGlass;
|
||||
gi.linkentity (trigger);
|
||||
}
|
||||
|
||||
|
||||
void CGF_SFX_ShootBreakableGlass(edict_t* aGlassPane,
|
||||
edict_t* anAttacker,
|
||||
/*trace_t**/ void* tr,
|
||||
int mod
|
||||
)
|
||||
{
|
||||
// process gunshots thru glass
|
||||
edict_t* trigger;
|
||||
int destruct;
|
||||
|
||||
// depending on mod, destroy window or emit fragments
|
||||
switch (mod)
|
||||
{
|
||||
// break for ap, shotgun, handcannon, and kick, destory window
|
||||
case MOD_M3 :
|
||||
case MOD_HC :
|
||||
case MOD_SNIPER :
|
||||
case MOD_KICK :
|
||||
case MOD_GRENADE :
|
||||
case MOD_G_SPLASH:
|
||||
case MOD_HANDGRENADE:
|
||||
case MOD_HG_SPLASH:
|
||||
case MOD_KNIFE : // slash damage
|
||||
destruct = true;
|
||||
break;
|
||||
default :
|
||||
destruct = (rand() % 3 == 0);
|
||||
break;
|
||||
};
|
||||
|
||||
if (destruct)
|
||||
{
|
||||
// break glass (and hurt if doing kick)
|
||||
CGF_SFX_BreakGlass(aGlassPane, anAttacker, 0, aGlassPane->health, vec3_origin, FRAMETIME);
|
||||
if (mod == MOD_KICK)
|
||||
{
|
||||
vec3_t bloodorigin;
|
||||
vec3_t dir;
|
||||
vec3_t normal;
|
||||
VectorAdd(aGlassPane->absmax, aGlassPane->absmin, bloodorigin);
|
||||
VectorScale(bloodorigin, 0.5, bloodorigin);
|
||||
VectorSubtract(bloodorigin, anAttacker->s.origin, dir);
|
||||
VectorNormalize(dir);
|
||||
VectorMA(anAttacker->s.origin, 32.0, dir, bloodorigin);
|
||||
VectorSet(normal, 0, 0, -1);
|
||||
T_Damage(anAttacker, aGlassPane, anAttacker, dir, bloodorigin, normal, 15.0, 0, 0, MOD_BREAKINGGLASS);
|
||||
}
|
||||
|
||||
// remove corresponding trigger
|
||||
trigger = 0;
|
||||
while (trigger = G_Find(trigger, FOFS(classname), "breakableglass_trigger"))
|
||||
{
|
||||
if (trigger->owner == aGlassPane)
|
||||
{
|
||||
// remove it
|
||||
G_FreeEdict(trigger);
|
||||
// only one to be found
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// add decal (if not grenade)
|
||||
if ( (mod != MOD_HANDGRENADE)
|
||||
&& (mod != MOD_HG_SPLASH)
|
||||
&& (mod != MOD_GRENADE)
|
||||
&& (mod != MOD_G_SPLASH)
|
||||
)
|
||||
{
|
||||
AddDecal(anAttacker, (trace_t*) tr);
|
||||
}
|
||||
// and emit glass
|
||||
CGF_SFX_EmitGlass(aGlassPane, anAttacker, ((trace_t*) tr)->endpos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CGF_SFX_TouchGlass(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||
{
|
||||
// called whenever an entity hits the trigger spawned for the glass
|
||||
|
||||
vec3_t origin;
|
||||
vec3_t normal;
|
||||
vec3_t spot;
|
||||
trace_t trace;
|
||||
edict_t* glass;
|
||||
vec3_t velocity;
|
||||
vec_t speed;
|
||||
vec_t projected_speed;
|
||||
int is_hgrenade;
|
||||
int is_knife;
|
||||
|
||||
is_hgrenade = is_knife = false;
|
||||
|
||||
// ignore non-clients-non-grenade-non-knife
|
||||
if (!other->client)
|
||||
{
|
||||
is_knife = (0 == Q_stricmp("weapon_knife", other->classname));
|
||||
if (!is_knife)
|
||||
{
|
||||
is_hgrenade = (0 == Q_stricmp("hgrenade", other->classname));
|
||||
}
|
||||
|
||||
if ((!is_knife) && (!is_hgrenade))
|
||||
return;
|
||||
if (is_knife)
|
||||
goto knife_and_grenade_handling;
|
||||
}
|
||||
|
||||
// test whether other really hits the glass - deal with
|
||||
// the special case that other hits some boundary close to the border of the glass pane
|
||||
//
|
||||
//
|
||||
// ....trigger.......
|
||||
// +++++++++ +++++++++++
|
||||
// .+---glass------+.
|
||||
// wall .+--------------+.wall
|
||||
// +++++++++ +++++++++++
|
||||
// ----->..................
|
||||
// wrong ^ ^
|
||||
// | |
|
||||
// wrong ok
|
||||
//
|
||||
glass = self->owner;
|
||||
// hack - set glass' movetype to MOVETYPE_PUSH as it is not
|
||||
// moving as long as the trigger is active
|
||||
glass->movetype = MOVETYPE_PUSH;
|
||||
|
||||
VectorAdd(glass->absmax, glass->absmin, origin);
|
||||
VectorScale(origin, 0.5, origin);
|
||||
|
||||
// other needs to be able to trace to glass origin
|
||||
trace = gi.trace(other->s.origin, vec3_origin, vec3_origin, origin, other,
|
||||
MASK_PLAYERSOLID
|
||||
);
|
||||
if (trace.ent != glass)
|
||||
return;
|
||||
|
||||
// we can reach the glass origin, so we have the normal of
|
||||
// the glass plane
|
||||
VectorCopy(trace.plane.normal, normal);
|
||||
|
||||
// we need to check if client is not running into wall next
|
||||
// to the glass (the trigger stretches into the wall)
|
||||
VectorScale(normal, -1000.0, spot);
|
||||
VectorAdd(spot, other->s.origin, spot);
|
||||
// line between other->s.origin and spot (perpendicular to glass
|
||||
// surface should not hit wall but glass instead
|
||||
trace = gi.trace(other->s.origin, vec3_origin, vec3_origin, spot, other,
|
||||
MASK_PLAYERSOLID
|
||||
);
|
||||
if (trace.ent != glass)
|
||||
return;
|
||||
|
||||
// now, we check if the client's speed perpendicular to
|
||||
// the glass plane, exceeds the required 175
|
||||
// (speed should be < -200, as the plane's normal
|
||||
// points towards the client
|
||||
VectorCopy(other->velocity, velocity);
|
||||
speed = VectorNormalize(velocity);
|
||||
projected_speed = speed * DotProduct(velocity, normal);
|
||||
|
||||
// bump projected speed for grenades - they should break
|
||||
// the window more easily
|
||||
if (is_hgrenade)
|
||||
projected_speed *= 1.5;
|
||||
|
||||
// if hitting the glass with sufficient speed (project < -175),
|
||||
// being jumpkicked (speed > 700, project < -5) break the window
|
||||
if (!((projected_speed < -175.0) ||
|
||||
((projected_speed < -5) && (speed > 700))
|
||||
)
|
||||
)
|
||||
goto knife_and_grenade_handling;
|
||||
|
||||
// break glass
|
||||
CGF_SFX_BreakGlass(glass, other, other, glass->health, vec3_origin, 3.0 * FRAMETIME);
|
||||
// glass can take care of itself, but the trigger isn't needed anymore
|
||||
G_FreeEdict (self);
|
||||
|
||||
/* not needed
|
||||
// reduce momentum of the client (he just broke the window
|
||||
// so he should lose speed. in addition, it doesn't feel
|
||||
// right if he overtakes the glass fragments
|
||||
// VectorScale(normal, 200.0, velocity);
|
||||
// VectorAdd(other->velocity, velocity, other->velocity);
|
||||
*/
|
||||
|
||||
// make sure client takes damage
|
||||
T_Damage(other, glass, other, normal, other->s.origin, normal, 15.0, 0, 0, MOD_BREAKINGGLASS);
|
||||
return;
|
||||
|
||||
// goto label
|
||||
knife_and_grenade_handling:
|
||||
// if knife or grenade, bounce them
|
||||
if ((is_knife) || (is_hgrenade))
|
||||
{
|
||||
// change clipmask to bounce of glass
|
||||
other->clipmask = MASK_SOLID;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CGF_SFX_BreakGlass(edict_t* aGlassPane, edict_t* anInflictor, edict_t* anAttacker,
|
||||
int aDamage, vec3_t aPoint, vec_t aPaneDestructDelay
|
||||
)
|
||||
{
|
||||
// based on func_explode, but with lotsa subtle differences
|
||||
vec3_t origin;
|
||||
vec3_t old_origin;
|
||||
vec3_t chunkorigin;
|
||||
vec3_t size;
|
||||
int count;
|
||||
int mass;
|
||||
|
||||
// bmodel origins are (0 0 0), we need to adjust that here
|
||||
VectorCopy(aGlassPane->s.origin, old_origin);
|
||||
VectorScale (aGlassPane->size, 0.5, size);
|
||||
VectorAdd (aGlassPane->absmin, size, origin);
|
||||
VectorCopy (origin, aGlassPane->s.origin);
|
||||
|
||||
aGlassPane->takedamage = DAMAGE_NO;
|
||||
|
||||
VectorSubtract (aGlassPane->s.origin, anInflictor->s.origin, aGlassPane->velocity);
|
||||
VectorNormalize (aGlassPane->velocity);
|
||||
// use speed 250 instead of 150 for funkier glass spray
|
||||
VectorScale (aGlassPane->velocity, 250.0, aGlassPane->velocity);
|
||||
|
||||
// start chunks towards the center
|
||||
VectorScale (size, 0.75, size);
|
||||
|
||||
mass = aGlassPane->mass;
|
||||
if (!mass)
|
||||
mass = 75;
|
||||
|
||||
// big chunks
|
||||
if (mass >= 100)
|
||||
{
|
||||
count = mass / 100;
|
||||
if (count > 8)
|
||||
count = 8;
|
||||
while(count--)
|
||||
{
|
||||
CGF_SFX_ApplyGlassFragmentLimit("debris");
|
||||
chunkorigin[0] = origin[0] + crandom() * size[0];
|
||||
chunkorigin[1] = origin[1] + crandom() * size[1];
|
||||
chunkorigin[2] = origin[2] + crandom() * size[2];
|
||||
CGF_SFX_GlassThrowDebris (aGlassPane, "models/objects/debris1/tris.md2", 1, chunkorigin);
|
||||
}
|
||||
}
|
||||
|
||||
// small chunks
|
||||
count = mass / 25;
|
||||
if (count > 16)
|
||||
count = 16;
|
||||
while(count--)
|
||||
{
|
||||
CGF_SFX_ApplyGlassFragmentLimit("debris");
|
||||
chunkorigin[0] = origin[0] + crandom() * size[0];
|
||||
chunkorigin[1] = origin[1] + crandom() * size[1];
|
||||
chunkorigin[2] = origin[2] + crandom() * size[2];
|
||||
CGF_SFX_GlassThrowDebris (aGlassPane, "models/objects/debris2/tris.md2", 2, chunkorigin);
|
||||
}
|
||||
|
||||
// clear velocity, reset origin (that has been abused in ThrowDebris)
|
||||
VectorClear(aGlassPane->velocity);
|
||||
VectorCopy (old_origin, aGlassPane->s.origin);
|
||||
|
||||
if (anAttacker)
|
||||
{
|
||||
// jumping thru
|
||||
G_UseTargets (aGlassPane, anAttacker);
|
||||
}
|
||||
else
|
||||
{
|
||||
// firing thru - the pane has no direct attacker to hurt,
|
||||
// but G_UseTargets expects one. So make it a DIY
|
||||
G_UseTargets (aGlassPane, aGlassPane);
|
||||
}
|
||||
|
||||
// have glass plane be visible for two more frames,
|
||||
// and have it self-destruct then
|
||||
// meanwhile, make sure the player can move thru
|
||||
aGlassPane->solid = SOLID_NOT;
|
||||
aGlassPane->think = CGF_SFX_HideBreakableGlass;
|
||||
aGlassPane->nextthink = level.time + aPaneDestructDelay;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CGF_SFX_EmitGlass (edict_t* aGlassPane, edict_t* anInflictor, vec3_t aPoint)
|
||||
{
|
||||
// based on func_explode, but with lotsa subtle differences
|
||||
vec3_t old_origin;
|
||||
vec3_t chunkorigin;
|
||||
vec3_t size;
|
||||
int count;
|
||||
|
||||
// bmodel origins are (0 0 0), we need to adjust that here
|
||||
VectorCopy(aGlassPane->s.origin, old_origin);
|
||||
VectorCopy (aPoint, aGlassPane->s.origin);
|
||||
|
||||
VectorSubtract (aGlassPane->s.origin, anInflictor->s.origin, aGlassPane->velocity);
|
||||
VectorNormalize (aGlassPane->velocity);
|
||||
// use speed 250 instead of 150 for funkier glass spray
|
||||
VectorScale (aGlassPane->velocity, 250.0, aGlassPane->velocity);
|
||||
|
||||
// start chunks towards the center
|
||||
VectorScale (aGlassPane->size, 0.25, size);
|
||||
|
||||
count = 4;
|
||||
while(count--)
|
||||
{
|
||||
CGF_SFX_ApplyGlassFragmentLimit("debris");
|
||||
chunkorigin[0] = aPoint[0] + crandom() * size[0];
|
||||
chunkorigin[1] = aPoint[1] + crandom() * size[1];
|
||||
chunkorigin[2] = aPoint[2] + crandom() * size[2];
|
||||
CGF_SFX_GlassThrowDebris (aGlassPane, "models/objects/debris2/tris.md2", 2, chunkorigin);
|
||||
}
|
||||
|
||||
// clear velocity, reset origin (that has been abused in ThrowDebris)
|
||||
VectorClear(aGlassPane->velocity);
|
||||
VectorCopy (old_origin, aGlassPane->s.origin);
|
||||
|
||||
// firing thru - the pane has no direct attacker to hurt,
|
||||
// but G_UseTargets expects one. So make it a DIY
|
||||
G_UseTargets (aGlassPane, aGlassPane);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CGF_SFX_HideBreakableGlass(edict_t* aGlassPane)
|
||||
{
|
||||
// remove all attached decals
|
||||
edict_t* decal;
|
||||
decal = 0;
|
||||
while (decal = G_Find(decal, FOFS(classname), "decal"))
|
||||
{
|
||||
if (decal->owner == aGlassPane)
|
||||
{
|
||||
// make it goaway in the next frame
|
||||
decal->nextthink = level.time + FRAMETIME;
|
||||
}
|
||||
}
|
||||
|
||||
while (decal = G_Find(decal, FOFS(classname), "splat"))
|
||||
{
|
||||
if (decal->owner == aGlassPane)
|
||||
{
|
||||
// make it goaway in the next frame
|
||||
decal->nextthink = level.time + FRAMETIME;
|
||||
}
|
||||
}
|
||||
|
||||
// after being broken, the pane cannot be freed as it is needed in
|
||||
// subsequent missions/games, so hide it at about z = -1000 lower
|
||||
aGlassPane->movetype = MOVETYPE_FLYMISSILE;
|
||||
VectorCopy(aGlassPane->s.origin, aGlassPane->pos1);
|
||||
aGlassPane->s.origin[2] -=1000.0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CGF_SFX_AttachDecalToGlass(edict_t* aGlassPane, edict_t* aDecal)
|
||||
{
|
||||
// just set aDecal's owner to be the glass pane
|
||||
aDecal->owner = aGlassPane;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CGF_SFX_RebuildAllBrokenGlass()
|
||||
{
|
||||
// iterate over all func_explosives
|
||||
edict_t* glass;
|
||||
glass = 0;
|
||||
while (glass = G_Find(glass, FOFS(classname), "func_explosive"))
|
||||
{
|
||||
// glass is broken if solid != SOLID_BSP
|
||||
if (glass->solid != SOLID_BSP)
|
||||
{
|
||||
CGF_SFX_InstallBreakableGlass(glass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CGF_SFX_ApplyGlassFragmentLimit(const char* aClassName)
|
||||
{
|
||||
edict_t* oldfragment;
|
||||
|
||||
glassfragmentcount++;
|
||||
if (glassfragmentcount > glassfragmentlimit->value)
|
||||
glassfragmentcount = 1;
|
||||
|
||||
// remove fragment with corresponding number if any
|
||||
oldfragment = FindEdictByClassnum((char*) aClassName, glassfragmentcount);
|
||||
|
||||
if (oldfragment)
|
||||
{
|
||||
// oldfragment->nextthink = level.time + FRAMETIME;
|
||||
G_FreeEdict(oldfragment);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CGF_SFX_MiscGlassUse(edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
const char* classname;
|
||||
classname = other->classname;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CGF_SFX_MiscGlassDie(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
const char* classname;
|
||||
classname = inflictor->classname;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
static vec_t previous_throw_time = 0;
|
||||
static int this_throw_count = 0;
|
||||
|
||||
void CGF_SFX_GlassThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin)
|
||||
{
|
||||
// based on ThrowDebris from id software - now returns debris created
|
||||
edict_t *chunk;
|
||||
vec3_t v;
|
||||
|
||||
if (level.time != previous_throw_time)
|
||||
{
|
||||
previous_throw_time = level.time;
|
||||
this_throw_count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this_throw_count++;
|
||||
if (this_throw_count > glassfragmentlimit->value)
|
||||
return;
|
||||
}
|
||||
|
||||
chunk = G_Spawn();
|
||||
VectorCopy (origin, chunk->s.origin);
|
||||
gi.setmodel (chunk, modelname);
|
||||
v[0] = 100 * crandom();
|
||||
v[1] = 100 * crandom();
|
||||
v[2] = 100 + 100 * crandom();
|
||||
VectorMA (self->velocity, speed, v, chunk->velocity);
|
||||
chunk->movetype = MOVETYPE_BOUNCE;
|
||||
chunk->solid = SOLID_NOT;
|
||||
chunk->avelocity[0] = random()*600;
|
||||
chunk->avelocity[1] = random()*600;
|
||||
chunk->avelocity[2] = random()*600;
|
||||
chunk->think = G_FreeEdict;
|
||||
chunk->nextthink = level.time + 5 + random()*5;
|
||||
chunk->s.frame = 0;
|
||||
chunk->flags = 0;
|
||||
chunk->classname = "debris";
|
||||
chunk->takedamage = DAMAGE_YES;
|
||||
chunk->die = debris_die;
|
||||
gi.linkentity (chunk);
|
||||
|
||||
// number chunk
|
||||
chunk->classnum = glassfragmentcount;
|
||||
}
|
||||
|
||||
|
281
cgf_sfx_glass.h
Normal file
281
cgf_sfx_glass.h
Normal file
|
@ -0,0 +1,281 @@
|
|||
/****************************************************************************/
|
||||
/* */
|
||||
/* project : CGF (c) 1999 William van der Sterren */
|
||||
/* parts (c) 1998 id software */
|
||||
/* */
|
||||
/* file : cgf_sfx_glass.h "special effects for glass entities" */
|
||||
/* author(s): William van der Sterren */
|
||||
/* version : 0.5 */
|
||||
/* */
|
||||
/* date (last revision): Jun 12, 99 */
|
||||
/* date (creation) : Jun 04, 99 */
|
||||
/* */
|
||||
/* */
|
||||
/* revision history */
|
||||
/* -- date ---- | -- revision ---------------------- | -- revisor -- */
|
||||
/* Jun 12, 1999 | fixed knife slash breaks glass | William */
|
||||
/* */
|
||||
/******* http://www.botepidemic.com/aid/cgf for CGF for Action Quake2 *******/
|
||||
|
||||
|
||||
#ifndef __CGF_SFX_GLASS_H_
|
||||
#define __CGF_SFX_GLASS_H_
|
||||
|
||||
|
||||
// defines (should be consistent with other weapon defs in g_local.h)
|
||||
#define MOD_BREAKINGGLASS 46
|
||||
|
||||
/*
|
||||
// forward definitions
|
||||
typedef struct edict_s edict_t;
|
||||
*/
|
||||
|
||||
|
||||
// export a number of functions to g_func.c:
|
||||
//
|
||||
//
|
||||
|
||||
void CGF_SFX_InstallGlassSupport();
|
||||
// registers cvar breakableglass (default 0)
|
||||
// registers cvar glassfragmentlimit (default 30)
|
||||
|
||||
|
||||
void CGF_SFX_RebuildAllBrokenGlass();
|
||||
// upon starting a new team play game, reconstruct any
|
||||
// broken glass because we like to break it again
|
||||
|
||||
|
||||
int CGF_SFX_IsBreakableGlassEnabled();
|
||||
// returns whether breakable glass is enabled (cvar breakableglass)
|
||||
|
||||
|
||||
void CGF_SFX_TestBreakableGlassAndRemoveIfNot_Think(edict_t* aPossibleGlassEntity);
|
||||
// initial think function for all func_explosives
|
||||
// because we cannot verify the contents of the entity unless the entity
|
||||
// has been spawned, we need to first introduce the entity, and remove
|
||||
// it later (level.time == 0.1) if required
|
||||
|
||||
|
||||
void CGF_SFX_ShootBreakableGlass(edict_t* aGlassPane,
|
||||
edict_t* anAttacker,
|
||||
/*trace_t*/ void* tr,
|
||||
int mod
|
||||
);
|
||||
// shoot thru glass - depending on bullet types and
|
||||
// random effects, glass just emits fragments, or breaks
|
||||
|
||||
|
||||
void CGF_SFX_AttachDecalToGlass(edict_t* aGlassPane, edict_t* aDecal);
|
||||
// a glass that breaks will remove all decals (blood splashes, bullet
|
||||
// holes) if they are attached
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
|
||||
further documentation:
|
||||
|
||||
cgf_sfx_glass.cpp can be compiled with ordinary c compilers as
|
||||
well - just change the extension from .cpp to .c
|
||||
|
||||
to install breakable glass in the ActionQuake2 1.51 code base,
|
||||
do the following:
|
||||
|
||||
@ file a_game.h
|
||||
+ modify
|
||||
#define ACTION_VERSION "1.51"
|
||||
to something that tells users breakable glass is supported
|
||||
|
||||
@ file a_team.c
|
||||
+ add #include "cgf_sfx_glass.h"
|
||||
+ in void CleanLevel()
|
||||
add
|
||||
CGF_SFX_RebuildAllBrokenGlass();
|
||||
as the last statement (thus after CleanBodies();)
|
||||
|
||||
@ file g_misc.c
|
||||
+ add #include "cgf_sfx_glass.h"
|
||||
+ in void SP_func_explosive (edict_t *self)
|
||||
disable the statements (put them within comments):
|
||||
if (deathmatch->value)
|
||||
{ // auto-remove for deathmatch
|
||||
G_FreeEdict (self);
|
||||
return;
|
||||
}
|
||||
+ in void SP_func_explosive (edict_t *self)
|
||||
add
|
||||
self->think = CGF_SFX_TestBreakableGlassAndRemoveIfNot_Think;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
as the last statement (thus after gi.linkentity (self);)
|
||||
|
||||
@ file g_save.c
|
||||
+ add #include "cgf_sfx_glass.h"
|
||||
+ in void InitGame (void)
|
||||
add
|
||||
CGF_SFX_InstallGlassSupport();
|
||||
under (splatlimit = gi.cvar ("splatlimit", "0", 0);)
|
||||
|
||||
@ file g_local.h
|
||||
+ replace
|
||||
void AddDecal (edict_t *self, vec3_t point, vec3_t direct);
|
||||
void AddSplat (edict_t *self, vec3_t point, vec3_t direct);
|
||||
by
|
||||
void AddDecal (edict_t *self, trace_t* tr);
|
||||
void AddSplat (edict_t *self, vec3_t point, trace_t* tr);
|
||||
|
||||
@ file a_game.c
|
||||
+ add #include "cgf_sfx_glass.h"
|
||||
+ replace
|
||||
void AddDecal (edict_t *self, vec3_t point, vec3_t direct)
|
||||
by
|
||||
void AddDecal (edict_t *self, trace_t* tr)
|
||||
+ replace
|
||||
void AddSplat (edict_t *self, vec3_t point, vec3_t direct);
|
||||
by
|
||||
void AddSplat (edict_t *self, vec3_t point, trace_t* tr);
|
||||
+ in void AddDecal (edict_t *self, trace_t* tr)
|
||||
replace each occurrence of 'point' by tr->endpos
|
||||
+ in void AddDecal (edict_t *self, trace_t* tr)
|
||||
replace each occurrence of 'direct' by tr->plane.normal
|
||||
+ in void AddDecal (edict_t *self, trace_t* tr)
|
||||
add (as the last line, thus under gi.linkentity (decal);)
|
||||
if ((tr->ent) && (0 == Q_stricmp("func_explosive", tr->ent->classname)))
|
||||
{
|
||||
CGF_SFX_AttachDecalToGlass(tr->ent, decal);
|
||||
}
|
||||
+ in void AddSplat (edict_t *self, vec3_t point, trace_t* tr)
|
||||
replace each occurrence of 'direct' by tr->plane.normal
|
||||
+ in void AddSplat (edict_t *self, vec3_t point, trace_t* tr)
|
||||
add (as the last line, thus under gi.linkentity (decal);)
|
||||
if ((tr->ent) && (0 == Q_stricmp("func_explosive", tr->ent->classname)))
|
||||
{
|
||||
CGF_SFX_AttachDecalToGlass(tr->ent, splat);
|
||||
}
|
||||
@ file g_weapon.c
|
||||
+ add #include "cgf_sfx_glass.h"
|
||||
+ in static void fire_lead (edict_t *self, ...)
|
||||
replace
|
||||
AddDecal (self, tr.endpos, tr.plane.normal);
|
||||
by
|
||||
AddDecal (self, &tr);
|
||||
+ in void fire_lead_ap (edict_t *self, ...)
|
||||
replace
|
||||
AddDecal (self, tr.endpos, tr.plane.normal);
|
||||
by
|
||||
AddDecal (self, &tr);
|
||||
+ in static void fire_lead (edict_t *self, ...)
|
||||
add
|
||||
between
|
||||
tr = do_trace (start, NULL, NULL, end, self, content_mask);
|
||||
and
|
||||
// see if we hit water
|
||||
the following
|
||||
// catch case of firing thru one or breakable glasses
|
||||
while ( (tr.fraction < 1.0)
|
||||
&& (tr.surface->flags & (SURF_TRANS33 | SURF_TRANS66))
|
||||
&& (tr.ent)
|
||||
&& (0 == Q_stricmp(tr.ent->classname, "func_explosive"))
|
||||
)
|
||||
{
|
||||
// break glass
|
||||
CGF_SFX_ShootBreakableGlass(tr.ent, self, &tr, mod);
|
||||
// continue trace from current endpos to start
|
||||
tr = do_trace (tr.endpos, NULL, NULL, end, tr.ent, content_mask);
|
||||
}
|
||||
+ in static void fire_lead_ap (edict_t *self, ...)
|
||||
add
|
||||
between
|
||||
tr = do_trace (start, NULL, NULL, end, self, content_mask);
|
||||
and
|
||||
// see if we hit water
|
||||
the following
|
||||
// catch case of firing thru one or breakable glasses
|
||||
while ( (tr.fraction < 1.0)
|
||||
&& (tr.surface->flags & (SURF_TRANS33 | SURF_TRANS66))
|
||||
&& (tr.ent)
|
||||
&& (0 == Q_stricmp(tr.ent->classname, "func_explosive"))
|
||||
)
|
||||
{
|
||||
// break glass
|
||||
CGF_SFX_ShootBreakableGlass(tr.ent, self, &tr, mod);
|
||||
// continue trace from current endpos to start
|
||||
tr = do_trace (tr.endpos, NULL, NULL, end, tr.ent, content_mask);
|
||||
}
|
||||
+ in void knife_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||
replace
|
||||
if (other->takedamage)
|
||||
by
|
||||
if (0 == Q_stricmp(other->classname, "func_explosive"))
|
||||
{
|
||||
// ignore it, so it can bounce
|
||||
return;
|
||||
}
|
||||
else
|
||||
if (other->takedamage)
|
||||
+ in void kick_attack (edict_t * ent )
|
||||
between
|
||||
// zucc stop powerful upwards kicking
|
||||
forward[2] = 0;
|
||||
and
|
||||
T_Damage (tr.ent, ent, ent, forward, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_KICK );
|
||||
add
|
||||
if (0 == Q_stricmp(tr.ent->classname, "func_explosive"))
|
||||
{
|
||||
CGF_SFX_ShootBreakableGlass(tr.ent, ent, &tr, MOD_KICK);
|
||||
}
|
||||
+ in int knife_attack ( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick)
|
||||
replace
|
||||
if (tr.ent->takedamage)
|
||||
by
|
||||
if (0 == Q_stricmp(tr.ent->classname, "func_explosive"))
|
||||
{
|
||||
CGF_SFX_ShootBreakableGlass(tr.ent, self, &tr, MOD_KNIFE);
|
||||
}
|
||||
else
|
||||
if (tr.ent->takedamage)
|
||||
|
||||
@ file g_phys.c
|
||||
+ add #include "cgf_sfx_glass.h"
|
||||
+ in void SV_Physics_Toss (edict_t *ent)
|
||||
replace
|
||||
AddSplat (self, tr.endpos, tr.plane.normal);
|
||||
by
|
||||
AddSplat (self, &tr);
|
||||
|
||||
@ file g_combat.c
|
||||
+ add #include "cgf_sfx_glass.h"
|
||||
+ in void T_Damage (edict_t *targ, ...)
|
||||
replace
|
||||
if ( (mod == MOD_M3) || (mod == MOD_HC) || (mod == MOD_HELD_GRENADE) ||
|
||||
(mod == MOD_HG_SPLASH) || (mod == MOD_G_SPLASH) )
|
||||
by
|
||||
if ( (mod == MOD_M3)
|
||||
|| (mod == MOD_HC)
|
||||
|| (mod == MOD_HELD_GRENADE)
|
||||
|| (mod == MOD_HG_SPLASH)
|
||||
|| (mod == MOD_G_SPLASH)
|
||||
|| (mod == MOD_BREAKINGGLASS)
|
||||
)
|
||||
+ in void T_RadiusDamage (...)
|
||||
add before if (CanDamage (ent, inflictor))
|
||||
if (0 == Q_stricmp(ent->classname, "func_explosive"))
|
||||
{
|
||||
CGF_SFX_ShootBreakableGlass(ent, inflictor, 0, mod);
|
||||
}
|
||||
else
|
||||
|
||||
@ file p_client.c
|
||||
+ add #include "cgf_sfx_glass.h"
|
||||
+ in void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker)
|
||||
between
|
||||
switch (mod)
|
||||
{
|
||||
and
|
||||
case MOD_SUICIDE:
|
||||
add
|
||||
case MOD_BREAKINGGLASS:
|
||||
message = "ate too much glass";
|
||||
break;
|
||||
*/
|
490
docs/CHANGES
Normal file
490
docs/CHANGES
Normal file
|
@ -0,0 +1,490 @@
|
|||
CHANGES FROM ACTION 1.51 to ACTION 1.52
|
||||
|
||||
o Support for friendly-fire in teamplay added. IF YOU ARE NOT ALREADY
|
||||
USING THE "NO FRIENDLY FIRE" FLAG IN YOUR DMFLAGS, YOU NEED TO ADD IT NOW
|
||||
IF YOU DON'T WANT TO USE FRIENDLY FIRE. The value for "no friendly fire"
|
||||
is 256. For a normal AQ teamplay game, you should set your dmflags to 256
|
||||
in your server.cfg ("set dmflags 256"). I believe the only other dmflag
|
||||
that would have a worthwhile effect on AQ teamplay is the "spawn farthest"
|
||||
flag (512), no others should be set unless you want the spawn farthest
|
||||
behavior. So...if you want FF in teamplay, set your dmflags to 0. If you
|
||||
don't, set it to 256. No friendly fire is the "normal" style of play, so
|
||||
the default/standard dmflags for AQ teamplay should be considered to be
|
||||
256. When FF is in effect, notification of it will be on the standard
|
||||
MOTD. Temporary banning for people who kill too many team members is
|
||||
available. The "maxteamkills" server cvar can be used to control the
|
||||
maximum number of teamkills that a player can make on a given map before
|
||||
he is kicked and temporarily banned, the default is 0 which means never
|
||||
temporarily ban. The "tkbanrounds" and "twbanrounds" variables affect ban
|
||||
time, see the README file for complete info. Players lose one frag for
|
||||
killing a teammate. Thanks go to Eek and Azerov for providing the patch
|
||||
which we used as a basis for this feature.
|
||||
|
||||
o Shot placement on crouching players is much improved and should
|
||||
basically be consistent with standing players now. Also, the view height
|
||||
of crouching players has been modified somewhat so it's more consistent
|
||||
with the actual amount of height loss that occurs. (thanks go to Mikael
|
||||
Lindh from AQ:Gangsters for lots of help in this area.)
|
||||
|
||||
o The shotgun and handcannon now inform you who you're hitting, when you
|
||||
hit someone, much like all the other weapons already did.
|
||||
|
||||
o New server variable "limchasecam" now available for clan match usage.
|
||||
See info in the README. When the chase cam limits are in effect, a line
|
||||
will be added to the MOTD stating it. (thanks to Black Cross for
|
||||
submitting the patch for this feature.)
|
||||
|
||||
o Breakable glass now added. Two new server variables for this:
|
||||
"breakableglass" and "glassfragmentlimit". This is not recommended for
|
||||
Internet games (much like the ejecting shells, blood splats, and bullet
|
||||
holes). This was contributed by William van der Sterren of the CGF
|
||||
project. It requires maps that are designed with breakable glass. For
|
||||
more info on how to make breakable glass in maps, see
|
||||
http://www.botepidemic.com/aid/cgf/exp_glass.shtml. For more info on the
|
||||
server variables, see the README.
|
||||
|
||||
o Door kicking added. You can now jump-kick a door open when it opens in
|
||||
the opposite direction. If there is a player sitting on the other side of
|
||||
the door, that player will be kicked along with the door. This was
|
||||
contributed by AQDT (written by Hal, sent to us by Homer).
|
||||
|
||||
o Vertical component of kicking has returned. You can kick people upwards
|
||||
or downwards by looking in that direction before kicking them.
|
||||
|
||||
o New commands "use throwing combat knife" and "use slashing combat knife"
|
||||
allow you to pull out the knife in whichever mode you want, instead of
|
||||
pulling out your knife then switching modes.
|
||||
|
||||
o A new item has been added to the secondary teamplay scoreboard: damage
|
||||
dealt. This number is the amount of damage the given player has inflicted
|
||||
upon other players on the current map. Note this is total damage, not
|
||||
just damage required to kill someone. For example, a point blank
|
||||
handcannon blast can count for a lot more than 100 health, even though it
|
||||
only takes 100 to kill someone. This isn't in the scoreboard if you have
|
||||
turned on the "noscore" option.
|
||||
|
||||
o New "sv nextmap" command allows a server operator to skip to the next
|
||||
map in the rotation (thanks go to Black Cross for submitting the patch for
|
||||
this command).
|
||||
|
||||
o The "spawn farthest" dmflag now works on teamplay games. The second
|
||||
team will spawn as far away from the first team as possible (unlike the
|
||||
normal behavior, where the farthest 1-4 spawn candidates are considered,
|
||||
depending on the total number of candidates).
|
||||
|
||||
o The "needpass" variable is now provided. This allows things like
|
||||
GameSpy to prompt you for a password when you try to connect to a
|
||||
passworded server.
|
||||
|
||||
o Players can no longer use say_team when not on a team, or say_partner
|
||||
when they have no partner.
|
||||
|
||||
o Delayed-death attribution bug fixed (you wouldn't get credit for a kill
|
||||
when someone bled for a while).
|
||||
|
||||
o Various reloading changes: switching from a reloading weapon to a new
|
||||
weapon will not cause the new weapon to be reloaded any longer. The
|
||||
reloading "queue" will immediately empty now whenever a player tries to
|
||||
shoot (assuming he has any ammo left in the gun to shoot) or bandage. Your
|
||||
pistol will no longer begin to reload after you get a sniper rifle or
|
||||
shotgun kicked from your hands while reloading it. The "reload" command
|
||||
no longer does anything during the "lights, camera, action" countdown in a
|
||||
teamplay game. You can no longer reload the pistols, M4's, or MP5's when
|
||||
you've already got a full magazine (to be consistent with the way the
|
||||
other weapons work).
|
||||
|
||||
o Switching between slashing and throwing for knives now gives a message.
|
||||
|
||||
o Increased knife slashing damage.
|
||||
|
||||
o Repeating "Out of ammo" message bug fixed.
|
||||
|
||||
o Lasersight now falls in the correct place if you're using one of the
|
||||
"classic" hand modes.
|
||||
|
||||
o Problem with sniper rifle's "weapon" command/bolt animation fixed (you
|
||||
could fire faster than normal using the right aliases).
|
||||
|
||||
o Dropped grenades now do the same amount of damage as thrown grenades.
|
||||
|
||||
o Green flash when players enter the game in a teamplay game removed (for
|
||||
real this time), as well as the red flash when a player exits the game in
|
||||
a teamplay game.
|
||||
|
||||
o The through-eyes chasecam now properly tracks weapon kick (ie with the
|
||||
M4), and also now gives you IR vision if the person you're viewing through
|
||||
the eyes of is using IR.
|
||||
|
||||
o A few bugs fixed in the way bodies fly after death. Floating bodies and
|
||||
bouncing bodies should be gone.
|
||||
|
||||
o Spectators can no longer drop a knife upon entering a teamplay game.
|
||||
|
||||
o Dead bodies no longer appear red when using IR vision.
|
||||
|
||||
o %-vars (ie %W, %H, and the others) are no longer parsed within players'
|
||||
names (ie a player named %W won't appear as his weapon when he says
|
||||
something).
|
||||
|
||||
o Minor bug in bleeding from grenades fixed.
|
||||
|
||||
o Bug where sniper rifle icon would be stuck on your screen if you were
|
||||
zoomed in when the other team all quit and you got "Not enough players to
|
||||
play!" is fixed.
|
||||
|
||||
o Adjusted spacing in MOTD so certain combinations don't cause lines to go
|
||||
off the edge of the screen.
|
||||
|
||||
o Shotgun shell ejection animation (when using "shelloff 0") improved.
|
||||
|
||||
o Fixed crash that occurred when a server ran a huge amount of maps in the
|
||||
rotation (around 100 or more).
|
||||
|
||||
o ReadConfigFile() now closes its file and SelectFarTeamplaySpawnPoint()
|
||||
frees its memory.
|
||||
|
||||
o Updated Action web page URL in MOTD.
|
||||
|
||||
|
||||
CHANGES FROM ACTION 1.5 TO ACTION 1.51
|
||||
|
||||
o Now remembers how many times reload key was pressed.
|
||||
|
||||
o Shotgun reduced to Action Quake 1.0c levels beyond distance of 450.
|
||||
|
||||
o Bandolier/weapon-dropping bug fixed.
|
||||
|
||||
o Empty pistols animation setup improved.
|
||||
|
||||
o Firing sequence for dual pistols fixed (firing was simultaneously
|
||||
before).
|
||||
|
||||
o Various grenade-related fixes.
|
||||
|
||||
o Knife slashing damage changed (should be more damaging now).
|
||||
|
||||
o You can now switch weapons while bandaging again.
|
||||
|
||||
o Leg damage (from falling) no longer taken while in god mode.
|
||||
|
||||
o Spelling of "trepanned" fixed.
|
||||
|
||||
o Item spawning problem that affected some old/odd maps fixed.
|
||||
|
||||
o Reloading sound/speed issues fixed.
|
||||
|
||||
o Point-blank knife-throwing bug fixed.
|
||||
|
||||
o Blood trail fixes.
|
||||
|
||||
o Death sound fixes.
|
||||
|
||||
o Negative value for "tgren" server variable is treated as 0 now.
|
||||
|
||||
o New server variable "skipmotd" allows you to leave out the standard
|
||||
Action MOTD contents (except the two lines at the top), for people who
|
||||
want really big local motd.txt files.
|
||||
|
||||
o Many server variables that didn't need to be SERVERINFO (ie: appearing
|
||||
in GameSpy, etc) are no longer SERVERINFO.
|
||||
|
||||
o New server command "sv reloadmotd" allows you to reload the MOTD from
|
||||
disk. Also, the MOTD is always reloaded at the end of each level.
|
||||
|
||||
o The "playerlist" and "players" commands no longer contain frags when
|
||||
"noscore" is turned on.
|
||||
|
||||
o New "ininame" variable lets you change the name of your Action INI file
|
||||
from "action.ini" to something else. Useful for people who run multiple
|
||||
servers from the same directory tree.
|
||||
|
||||
o Deathmatch mode is now forced on always, as in previous versions of
|
||||
Action. This was the cause of the "failed to find special item spawn
|
||||
point" error when starting a game locally on your computer.
|
||||
|
||||
o Delayed kill attribution for falling damage fixes.
|
||||
|
||||
o Lasersight properly follows M4 during ride-up.
|
||||
|
||||
o Scoreboard cosmetic fixes.
|
||||
|
||||
o Players entering the game in a teamplay game no longer generate the "enter
|
||||
game" teleport effect (green flash, etc) at a spawn point.
|
||||
|
||||
o Bug fixed that could cause crash when certain weird combinations of dmflags
|
||||
were used (ie teams-by-skin with teamplay turned on).
|
||||
|
||||
o Players can now select "classic" style firing where shots land slightly
|
||||
right or left (depending on your handedness) and down from the crosshair
|
||||
(see the "hand" command in the README for info).
|
||||
|
||||
o The [DEAD] flag on a dead chatter's name previously didn't get tacked on
|
||||
starting at the right time, so sometimes you'd see someone who appeared to
|
||||
be alive (due to no [DEAD] on their name) say something but %-variables
|
||||
(ie %W, %H, etc) didn't get expanded. This also caused dead people's
|
||||
messages to be able to reach teammates sometimes in the moments after
|
||||
dying.
|
||||
|
||||
o After a team round has ended, all players (including players who
|
||||
survived) can now see the scoreboard as if they were dead, to see who else
|
||||
survived the round.
|
||||
|
||||
o The join-a-team menu now, if both teams have the same number of players,
|
||||
defaults to the team that's losing (instead of defaulting to the first
|
||||
team).
|
||||
|
||||
o Optimization in SetIDView and DetermineViewedTeammate suggested by
|
||||
hal[9k].
|
||||
|
||||
|
||||
CHANGES FROM ACTION 1.1b2 TO ACTION 1.5
|
||||
|
||||
|
||||
General Weapon Changes
|
||||
|
||||
o Ducking alone (without lasersight) now improves aim. Previously, it only
|
||||
helped if you had a lasersight too (we believe this was a bug). See "Spread
|
||||
Modifiers" heading for more specific information.
|
||||
|
||||
o Guns now center their fire around your crosshair, rather than an invisible
|
||||
spot down/right of your crosshair.
|
||||
|
||||
o Players have smaller head regions. Any shot that would have hit the head in
|
||||
the old system that doesn't now will instead hit the chest.
|
||||
|
||||
o Added support for Akimbo and Sniper Rifle bullet shell ejection, and support
|
||||
for left handed usage with shell ejection turned on.
|
||||
|
||||
|
||||
Specific Weapon Changes
|
||||
|
||||
o M4 Assault Rifle is now less accurate, the original accuracy was due to a bug
|
||||
in the selection of which random function to call that determined spread. The
|
||||
same bug caused it to fire way to the right, and is also gone.
|
||||
|
||||
o M3 Super 90 Assault Shotgun now does slightly more damage. Reloads slightly
|
||||
faster.
|
||||
|
||||
o Sniper Rifle doesn't do the re-chambering sequence when leaving 6X
|
||||
magnification mode. Reloads slightly faster. Lens-changing and firing
|
||||
behavior improved slightly in some ways.
|
||||
|
||||
o Combat Knife remembers what mode it was last in (throwing/stabbing). Also has
|
||||
improved animation sequence for putting away a knife prepared for throwing.
|
||||
Doesn't have the old bug where knife becomes unusable for several seconds while
|
||||
playing random animation frames. Your last knife now isn't stopped by vests
|
||||
incorrectly. Knife slashing damage has been changed.
|
||||
|
||||
o M26 Fragmentation Grenade now drops to the ground at your feet if the pin is
|
||||
pulled and user switches to another weapon or begins bandaging. Slight
|
||||
improvement in animation sequence for it dropping from view.
|
||||
|
||||
o In teamplay, you now start with a full pistol plus one extra clip
|
||||
(previously you got no extra clips).
|
||||
|
||||
|
||||
Spread Modifiers:
|
||||
|
||||
This section gives the specific details on all the spread modifiers in the
|
||||
game. These only apply to the weapons that fire bullets.
|
||||
|
||||
(without lasersight)
|
||||
Standing: base
|
||||
Walking: 2x as much spread
|
||||
Running: 6x as much spread
|
||||
Crouching: 35% reduction in spread over standing.
|
||||
|
||||
(laser, compared to standing from above)
|
||||
Standing: 30% reduction in spread over standing w/o laser.
|
||||
Walking: base (standing from above)
|
||||
Running: base
|
||||
Crouching: same as crouching w/o the laser
|
||||
|
||||
Burst mode (or semi-auto mode with the pistol) gives an additional 30% spread
|
||||
reduction to any of the above.
|
||||
|
||||
The Sniper Rifle when zoomed (any magnification) is always 100% accurate,
|
||||
regardless of any of the above.
|
||||
|
||||
|
||||
Item Changes
|
||||
|
||||
o Lasersight improves accuracy while standing still and not ducking (makes you
|
||||
almost as accurate as crouching). Lasersight provides no benefit to a user who
|
||||
is also ducking. (See "Spread Modifiers".) Lasersight's red dot is
|
||||
translucent and follows your crosshair more closely.
|
||||
|
||||
o Silencer no longer suppresses flashes from Akimbo pistols.
|
||||
|
||||
o Bandolier correctly cleans up after itself when dropped. If server
|
||||
operator sets "ir" to 1 then the new command "irvision" is available which
|
||||
toggles on/off infra-red vision while wearing the bandolier. The new
|
||||
"tgren" variable can be set by the server operator to a number, and that
|
||||
many grenades will be given to each player who selects the bandolier in
|
||||
teamplay (default is 0). The bandolier now allows you to carry one extra
|
||||
weapon in teamplay (the normal max carry is now 1 in teamplay, the
|
||||
previous behavior was a bug).
|
||||
|
||||
|
||||
Existing Command Changes
|
||||
|
||||
o "use special" now loops through all available special weapons, ie: if you
|
||||
have two it will toggle between them.
|
||||
|
||||
o "drop special" will discard only one special weapon at a time. It defaults
|
||||
to the one you are currently using and then goes through your inventory in this
|
||||
order: Sniper Rifle, Handcannon, M3 Super 90 Assault Shotgun, MP5/10
|
||||
Submachinegun, and finally M4 Assault Rifle. Switches to single pistol if
|
||||
currently using Akimbo pistols, this is also the way it worked previously.
|
||||
|
||||
o "weapon": Knives now remember their last mode correctly. Live players no
|
||||
longer hear a "click" when a nearby observer changes their weapon mode.
|
||||
|
||||
o "team": Players can now use the "team none" to leave the teams and become a
|
||||
spectator again in teamplay.
|
||||
|
||||
|
||||
Other Changes/Improvements/New Features
|
||||
|
||||
o After dying in teamplay, you remain at the spot you died as an observer,
|
||||
instead of being moved to a spawn point.
|
||||
|
||||
o When a player enters a server, the default team to join is the one that
|
||||
currently has less players.
|
||||
|
||||
o "sv_roundtimelimit" server variable is now "roundtimelimit", for consistency
|
||||
with other server variable names. The "roundtimelimit" feature now uses this
|
||||
method for determining who won: which team has the most players, if the same,
|
||||
then which team has the most total health.
|
||||
|
||||
o All items (such as ammo) can be dropped, see the README file for exact item
|
||||
names.
|
||||
|
||||
o The new code appears to use a somewhat less CPU when running a server than
|
||||
1.1b2 did.
|
||||
|
||||
o Jumpkick code improved, jumpkicks are more predictable and reliable,
|
||||
less randomness.
|
||||
|
||||
o Location of grenade and item in right-side HUD reversed for a more logical
|
||||
arrangement.
|
||||
|
||||
o New "id" command allows a player to turn off player identification if he
|
||||
doesn't want it.
|
||||
|
||||
o Players can become observers in DM using the standard Q2 "spectator" variable
|
||||
(spectator 0 or spectator 1).
|
||||
|
||||
o Messages from dead players are now visible inbetween end-of-round and the
|
||||
start of the next round, for end of round chatting.
|
||||
|
||||
o MOTD: Server operator can now control how long the MOTD stays up by using the
|
||||
"motd_time" variable (see README for more info). Much more info about the
|
||||
server is now in the MOTD. If a "motd.txt" file is present in your action
|
||||
directory, it will be appended to the standard MOTD. The new "motd" command
|
||||
allows a player to bring up the MOTD at any time.
|
||||
|
||||
o Teams-by-skin and teams-by-model now work in DM (other teamplay features also
|
||||
work in conjunction with DM teams: radios, partners, etc...)
|
||||
|
||||
o New "unpartner" command allows you to break a partnership.
|
||||
|
||||
o New unstick system. The new system is unobtrusive, there is no "unstick"
|
||||
command and you do not get moved around in any way. Players remain in the
|
||||
"transparent" state where they are able to walk through fellow "transparent"
|
||||
players. After the "lights, camera" sequence finishes, players are removed
|
||||
from the "transparent" state as soon as they are free of other team members.
|
||||
Players can be shot and affected normally by all weapons/items regardless of
|
||||
"transparent" state.
|
||||
|
||||
o When in center-handed mode, you now get an icon at the bottom middle of the
|
||||
screen of which weapon you are currently using.
|
||||
|
||||
o Server operator can use the new "radiolog" variable to turn on logging
|
||||
of all radio messages to the console.
|
||||
|
||||
o Dead bodies are now removed at the beginning of each teamplay round, this
|
||||
should allow for more strategy (you can tell who's down) and also remove some
|
||||
lag (less entities lying around).
|
||||
|
||||
o New server variables: "weapons" and "items". These control the maximum
|
||||
number of special weapons and items that each player can carry. "items" always
|
||||
defaults to 1. "weapons" defaults to 2 in teamplay, 1 in DM. Special-weapon
|
||||
carrying is handled more cleanly in teamplay now, a player will always be able
|
||||
to carry up to 2 special weapons in teamplay, not just his main weapon plus the
|
||||
first other one he picks up.
|
||||
|
||||
o New server variable "knifelimit" allows the server operator to control how
|
||||
many thrown knives can be lying around on the map at any given time. Default is
|
||||
40. This should be lowered if you are having overflow or lag problems when
|
||||
players are using lots of knives.
|
||||
|
||||
o The "allweapon" and "allitem" server variables can be set to 1 in DM or
|
||||
teamplay to give all weapons and all items to each player at spawn. If used in
|
||||
teamplay, normal weapon/item menus etc will not be presented. Defaults on both
|
||||
are of course 0.
|
||||
|
||||
o The new "choose" command allows you to select your special weapon or item in
|
||||
teamplay, without going through the menus.
|
||||
|
||||
o IP banning and flood protection are now supported (see README).
|
||||
|
||||
o The "give" (cheats) command has been improved to work better in the Action
|
||||
environment.
|
||||
|
||||
o New "noscore" server variable allows a server operator to disable
|
||||
individual scores in teamplay.
|
||||
|
||||
o New "nohud" server variable allows a server operator to disable the
|
||||
standard HUD on his server to record better-looking special demos.
|
||||
|
||||
o New variable substitutions allow you to tell your teammates your current
|
||||
weapon, item, ammo, and health, as well as which teammates are near you
|
||||
(see README for a list).
|
||||
|
||||
|
||||
Other Bugs Gone
|
||||
|
||||
o Various death message typos and grammar errors corrected.
|
||||
|
||||
o Long model/skin names work in action.ini.
|
||||
|
||||
o "###" lines are not required at the end-of-sections in action.ini.
|
||||
|
||||
o Scoreboards: The main scoreboard now looks decent in 320x240. Scoreboard no
|
||||
longer gets stuck on your screen when chase-camming. Players' names are no
|
||||
longer truncated unnecessarily. More items on the scoreboard are updated in
|
||||
realtime than before.
|
||||
|
||||
o The list of maps in the MOTD is no longer overwritten by the "Hit tab for
|
||||
team selection menu" message.
|
||||
|
||||
o M4/sniper rifle can no longer crash the server when shooting two players that
|
||||
overlap.
|
||||
|
||||
o Random crashes on certain maps (ie cliff, teamjungle, ...) gone.
|
||||
|
||||
o Your frags no longer appear as the frags of the person you're observing to
|
||||
GameSpy, CLQ, etc.
|
||||
|
||||
o The item selection screen now always goes away properly after choosing your
|
||||
item.
|
||||
|
||||
o Players hanging in midair at the spawn points, unkillable, will no longer
|
||||
occur.
|
||||
|
||||
o Backdoor commands (testshow, forceend) gone.
|
||||
|
||||
o Picking up a gun in teamplay when you start with dual pistols no longer
|
||||
lowers your total ammo.
|
||||
|
||||
o You can establish a new radio partnership when your old partner dies now.
|
||||
|
||||
o Dropping and picking back up a handcannon or shotgun no longer results in
|
||||
ammo loss.
|
||||
|
||||
|
||||
Features No Longer Present
|
||||
|
||||
o "noguns" is not currently supported, we have plans for more flexible "weapons
|
||||
banning" support in the future.
|
91
docs/LICENSE.TXT
Normal file
91
docs/LICENSE.TXT
Normal file
|
@ -0,0 +1,91 @@
|
|||
LIMITED PROGRAM SOURCE CODE LICENSE
|
||||
|
||||
This Limited Program Source Code License (the "Agreement") is between Id Software, Inc., a Texas corporation, (hereinafter "Id Software") and Licensee (as defined below) and is made effective beginning on the date you, the Licensee, download the Code, as defined below, (the "Effective Date"). BY DOWNLOADING THE CODE, AS DEFINED BELOW, YOU, THE LICENSEE, AGREE TO ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT. YOU SHOULD READ THIS AGREEMENT CAREFULLY BEFORE DOWNLOADING THE CODE. EVERY PERSON IN POSSESSION OF AN AUTHORIZED COPY, AS DEFINED BELOW, OF THE CODE SHALL BE SUBJECT TO THE TERMS AND CONDITIONS OF THIS AGREEMENT.
|
||||
|
||||
R E C I T A L S
|
||||
|
||||
WHEREAS, Id Software is the owner and developer of the computer software program source code accompanied by this Agreement (the "Code");
|
||||
|
||||
WHEREAS, Id Software desires to license certain limited non-exclusive rights regarding the Code to Licensee; and
|
||||
|
||||
WHEREAS, Licensee desires to receive a limited license for such rights.
|
||||
|
||||
T E R M S A N D C O N D I T I O N S
|
||||
|
||||
NOW, THEREFORE, for and in consideration of the mutual premises contained herein and for other good and valuable consideration, the receipt and sufficiency of which is hereby acknowledged, the undersigned parties do hereby agree as follows:
|
||||
|
||||
1. Definitions. The parties hereto agree the following definitions shall apply to this Agreement:
|
||||
|
||||
a. "Authorized Copy" shall mean a copy of the Code obtained by Authorized Means, as defined below. A copy of the Code is not an "Authorized Copy" unless it is accompanied by a copy of this Agreement and obtained by Authorized Means. A Modified Copy, as defined below, is not an Authorized Copy;
|
||||
|
||||
b. "Authorized Means" shall mean obtaining an Authorized Copy only by downloading the Authorized Copy from Id Software's Internet web site or from another web site authorized or approved by Id Software for such purposes or by obtaining an Authorized Copy by electronic means via the Internet;
|
||||
|
||||
c. "Code" shall mean the computer software program source code which accompanies this Agreement and includes Code included within any Modified Copy and which is the code that constitutes the Authorized Copy;
|
||||
|
||||
d. "Game" shall mean QUAKE II;
|
||||
|
||||
e. "Licensee" shall mean you, the person who is in possession of an Authorized Copy by Authorized Means; and
|
||||
|
||||
f. "Modified Copy" shall mean a copy of the Code first obtained by Authorized Means which is subsequently modified by Licensee, as provided in paragraph 2. below.
|
||||
|
||||
2. Grant of Rights. Subject to the terms and provisions of this Agreement, Id Software hereby grants to Licensee and Licensee hereby accepts, a limited, world-wide (except as otherwise provided herein), non-exclusive, non-transferable, and non-assignable license to: (i) use the Authorized Copy and the Modified Copy, as defined above, for the development by Licensee of extra levels operable with the Game (the "Extra Levels"); (ii) incorporate all or a portion of the Authorized Copy and the Modified Copy within the Extra Levels; (iii) distribute by way of a sublicense limited by the terms of this Agreement, free of charge and at no cost, the Authorized Copy and the Modified Copy to the extent such Modified Copy and such Authorized Copy, or a portion thereof, is included within the Extra Levels; (iv) distribute by way of a sublicense limited by the terms of this Agreement, free of charge and at no cost, by electronic transmission via the Internet only the Authorized Copy without any alteration or modification along with a copy of this Agreement which must always accompany the Authorized Copy; (v) modify the Authorized Copy in order to create a Modified Copy, as defined above; and (vi) distribute the Modified Copy by way of a sublicense limited by the terms of this Agreement, free of charge and at no cost, by electronic transmission via the Internet only. Each person or entity who/which receives a copy of the Code shall be subject to the terms of this Agreement but, no rights are granted to any person or entity who/which obtains, receives, or is in possession of any copy of the Code by other than Authorized Means.
|
||||
|
||||
3. Reservation of Rights and Prohibitions. Id Software expressly reserves all rights not granted herein. Licensee shall not make any use of the trademarks relating to the Game or Id Software (the "Trademarks"). Any use by Licensee of the Authorized Copy or the Modified Copy not expressly permitted in paragraph 2. above is expressly prohibited and any such unauthorized use shall constitute a material breach of this Agreement by Licensee. Any use of the Code, whether included within a Modified Copy or otherwise, and/or the Authorized Copy not permitted in this Agreement shall constitute an infringement or violation of Id Software's copyright in the Code. Licensee shall not copy, reproduce, manufacture or distribute (free of charge or otherwise) the Authorized Copy or the Modified Copy in any tangible media, including, without limitation, a CD ROM. Licensee shall not commercially exploit by sale, lease, rental or otherwise the Authorized Copy or the Modified Copy whether included within Extra Levels or otherwise. Licensee shall not commercially exploit by sale, lease, rental or otherwise any Extra Levels developed by the use of the Code, whether in whole or in part. Licensee is not receiving any rights hereunder regarding the Game, the Trademarks or any audio-visual elements, artwork, sound, music, images, characters, or other element of the Game. Licensee may modify the Authorized Copy in order to create a Modified Copy, as noted above, but all sublicensees who receive the Modified Copy shall not receive any rights to commercially exploit or to make any other use of the Code included therein except the right to use such Code for such sublicensee's personal entertainment. By way of example and not exclusion, a sublicensee for the Modified Copy shall not further modify the Code within the Modified Copy. Only the Licensee who obtains the Code by Authorized Means shall be permitted to modify such Code on the terms as described in this Agreement.
|
||||
|
||||
4. Additional Obligations. In addition to the obligations of Licensee otherwise set forth in this Agreement, during the Term, and thereafter where specified, Licensee agrees that:
|
||||
|
||||
a. Licensee will not attack or challenge the ownership by Id Software of the Code, the Authorized Copy, the Game, the Trademarks, or any copyright, patent or trademark or other intellectual property right related thereto and Licensee will not attack or challenge the validity of the license granted hereunder during the Term or thereafter; and
|
||||
|
||||
b. Licensee will promptly inform Id Software of any unauthorized use of the Code, the Authorized Copy, the Trademarks, or the Game, or any portions thereof, and will reasonably assist Id Software in the enforcement of all rights Id Software may have against such unauthorized users.
|
||||
|
||||
5. Ownership. Title to and all ownership rights in and to the Code, whether included within the Modified Copy, the Authorized Copy or otherwise, the Game, the Authorized Copy, and the Trademarks and the copyrights, trade secrets, trademarks, patents and all other intellectual property rights related thereto shall remain with Id Software which shall have the exclusive right to protect the same by copyright or otherwise. Licensee shall have no ownership rights in or to the Game, the Code, the Authorized Copy or the Trademarks. Licensee acknowledges that Licensee, by this Agreement, is only receiving a limited license to use the Authorized Copy, as specified in paragraph 2. of this Agreement.
|
||||
|
||||
6. Compliance with Applicable Laws. In exercising Licensee's limited rights hereunder, Licensee shall comply with all applicable laws, [including, without limitation, 22 U.S.C., section 2778 and 22 U.S.C. C.F.R. Parts 120-130 (1995)] regulations, ordinances and statutes, including, but not limited to, the import/export laws and regulations of the United States and its governmental and regulatory agencies (including, without limitation, the Bureau of Export Administration and the U.S. Department of Commerce) and all applicable international treaties and laws.
|
||||
|
||||
7. Term and Termination.
|
||||
|
||||
a. The term of this Agreement and the license granted herein begins on the Effective Date and shall expire, without notice, on a date one (1) calendar year from the Effective Date (the "Term").
|
||||
|
||||
b. Either party may terminate this Agreement, for any reason or no reason, on thirty (30) days written notice to the other party. Termination will be effective on the thirtieth (30th) day following delivery of the notice of termination. Notwithstanding anything to the contrary herein, this Agreement shall immediately terminate, without the requirement of any notice from Id Software to Licensee, upon the occurrence of any of the following "Terminating Events": (i) if Licensee files a petition in bankruptcy; (ii) if Licensee makes an assignment for the benefit of creditors; (iii) if any bankruptcy proceeding or assignment for benefit of creditors is commenced against Licensee and not dismissed within sixty (60) days after the date of its commencement; (iv) the insolvency of Licensee; or (v) a breach, whether material or otherwise, of this Agreement by Licensee. Upon the occurrence of a Terminating Event, this Agreement and any and all rights hereunder shall terminate without prejudice to any rights or claims Id Software may have, and all rights granted hereunder shall revert, without notice, to and be vested in Id Software.
|
||||
|
||||
c. Termination or expiration of this Agreement shall not create any liability against Id Software and shall not relieve Licensee from any liability which arises prior to termination or expiration. Upon expiration or earlier termination of this Agreement, Licensee shall have no further right to exercise the rights licensed hereunder or otherwise acquired in relation to this Agreement.
|
||||
|
||||
8. Licensee's Warranties. Licensee warrants and represents that: (i) Licensee has full legal rights and authority to enter into and become bound by the terms of this Agreement; (ii) Licensee has full legal rights and authority to perform Licensee?s obligations hereunder; (iii) Licensee will comply, at all times during the Term, with all applicable laws, as set forth hereinabove; (iv) all modifications which Licensee performs on the Code in order to create the Modified Copy and all non-Id Software property included within Extra Levels shall not infringe against or misappropriate any third party rights, including, without limitation, copyrights and trade secrets; and (v) the use or non-use of all modifications which Licensee performs on the Code in order to create the Modified Copy and all non-Id Software property included within Extra Levels shall not infringe against or misappropriate any third party rights, including, without limitation, copyrights and trade secrets.
|
||||
|
||||
9. Indemnification. Licensee hereby agrees to indemnify, hold harmless and defend Id Software and Id Software's predecessors, successors, assigns, officers, directors, shareholders, employees, agents, representatives, licensees (but not including Licensee), sublicensees, distributors, attorneys and accountants (collectively, the "Id Related Parties") from and against any and all "Claims", which shall mean all damages, claims, losses, causes of action, liabilities, lawsuits, judgments and expenses (including, without limitation, reasonable attorneys' fees and expenses) arising from, relating to or in connection with (i) a breach of this Agreement by Licensee and/or (ii) Licensee's use or non-use of the Code, whether the Authorized Copy or whether a portion of the Code as may be included within the Modified Copy or within Extra Levels. Id Software agrees to notify Licensee of any such Claims within a reasonable time after Id Software learns of same. Licensee, at its own expense, shall defend Id Software and the Id Related Parties from and against any and all Claims. Id Software and the Id Related Parties reserve the right to participate in any defense of the Claims with counsel of their choice, and at their own expense. In the event Licensee fails to provide a defense, then Licensee shall be responsible for paying the attorneys' fees and expenses incurred by Id Software and the Id Related Parties regarding the defense of the Claims. Id Software and the Id Related Parties, as applicable, agree to reasonably assist in the defense of the Claims. No settlement by Licensee of any Claims shall be valid unless Licensee receives the prior written consent of Id Software and the Id Related Parties, as applicable, to any such settlement, with consent may be withheld in Id Software's and the Id Related Parties' sole discretion.
|
||||
|
||||
10. Limitation of Liability. UNDER NO CIRCUMSTANCES SHALL ID SOFTWARE BE LIABLE TO LICENSEE FOR ACTUAL, SPECIAL, INCIDENTAL, CONSEQUENTIAL OR PUNITIVE DAMAGES OR ANY OTHER DAMAGES, WHETHER OR NOT ID SOFTWARE RECEIVES NOTICE OF ANY SUCH DAMAGES.
|
||||
|
||||
11. Disclaimer of Warranties. ID SOFTWARE EXPRESSLY DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, WITH REGARD TO THE CODE, THE AUTHORIZED COPY AND OTHERWISE.
|
||||
|
||||
12. Goodwill. Licensee recognizes the great value of the goodwill associated with the Game and the Trademarks, and acknowledges that such goodwill, now existing and hereafter created, exclusively belongs to Id Software and that the Trademarks have acquired a secondary meaning in the mind of the public.
|
||||
|
||||
13. Remedies. In the event of a breach of this Agreement by Id Software, Licensee's sole remedy shall be to terminate this Agreement by delivering written notice of termination to Id Software. In the event of a breach by Licensee of this Agreement, Id Software may pursue the remedies to which Id Software is entitled under applicable law and this Agreement. Licensee agrees that Licensee's unauthorized use of the Authorized Copy would immediately and irreparably damage Id Software, and in the event of such threatened or actual unauthorized use, Id Software shall be entitled to an injunctive order appropriately restraining and/or prohibiting such unauthorized use without the necessity of Id Software posting bond or other security. Pursuit of any remedy by Id Software shall not constitute a waiver of any other right or remedy of Id Software under this Agreement or under applicable law.
|
||||
|
||||
14. Choice of Law, Venue and Service of Process. This Agreement shall be construed in accordance with the laws of the State of Texas and applicable United States federal law and all claims and/or lawsuits in connection with this Agreement must be brought in Dallas County, Texas where exclusive venue shall lie. Licensee hereby agrees that service of process by certified mail to the address set forth below, with return receipt requested, shall constitute valid service of process upon Licensee. If for any reason Licensee has moved or cannot be validly served, then Licensee appoints the Secretary of State of the state of Texas to accept service of process on Licensee's behalf.
|
||||
|
||||
15. Delivery of Notices. Unless otherwise directed in writing by the parties, all notices given hereunder shall be sent to the last known address of addressee. All notices, requests, consents and other communications under this Agreement shall be in writing and shall be deemed to have been delivered on the date personally delivered or on the date deposited in the United States Postal Service, postage prepaid, by certified mail, return receipt requested, or telegraphed and confirmed, or delivered by electronic facsimile and confirmed. Any notice to Id Software shall also be sent to its counsel: D. Wade Cloud, Jr., Hiersche, Martens, Hayward, Drakeley & Urbach, P.C., 15303 Dallas Parkway, Suite 700, LB 17, Dallas, Texas 75248.
|
||||
|
||||
16. No Partnership, Etc. This Agreement does not constitute and shall not be construed as constituting a partnership or joint venture between Id Software and Licensee. Neither party shall have any right to obligate or bind the other party in any manner whatsoever, and nothing herein contained shall give, or is intended to give, any rights of any kind to any third persons.
|
||||
|
||||
17. Entire agreement. This Agreement constitutes the entire understanding between Licensee and Id Software regarding the subject matter hereof. Each and every clause of this Agreement is severable from the whole and shall survive unless the entire Agreement is declared unenforceable. No prior or present agreements or representations between the parties hereto regarding the subject matter hereof shall be binding upon the parties hereto unless incorporated in this Agreement. No modification or change in this Agreement shall be valid or binding upon the parties hereto unless in writing and executed by the parties to be bound thereby.
|
||||
|
||||
18. Assignment. This Agreement shall bind and inure to the benefit of Id Software, its successors and assigns, and Id Software may assign its rights hereunder, in Id Software's sole discretion. This Agreement is personal to Licensee, and Licensee shall not assign, transfer, convey nor franchise its rights granted hereunder. As provided above, Licensee may sublicense Licensee's limited rights herein by transferring the Authorized Copy by Authorized Means. As noted, each sublicensee in possession of a copy of the Authorized Copy shall be subject to the terms and conditions of this Agreement.
|
||||
|
||||
19. Survival. The following provisions shall survive the expiration or earlier termination of this Agreement: paragraphs 5., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 19., 20.a. and 20.b.
|
||||
|
||||
20. Miscellaneous.
|
||||
|
||||
a. All captions in this Agreement are intended solely for the convenience of the parties, and none shall effect the meaning or construction of any provision.
|
||||
|
||||
b. The terms and conditions of this Agreement have been negotiated fully and freely among the parties. Accordingly, the preparation of this Agreement by counsel for a given party will not be material to the construction hereof, and the terms of this Agreement shall not be strictly construed against such party.
|
||||
|
||||
BY DOWNLOADING THE CODE, AS DEFINED ABOVE, YOU, THE LICENSEE, AGREE TO ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT.
|
||||
|
||||
|
||||
February 12, 1998
|
||||
|
||||
|
||||
LIMITED PROGRAM SOURCE CODE LICENSE PAGE 1
|
||||
|
||||
|
281
docs/README
Normal file
281
docs/README
Normal file
|
@ -0,0 +1,281 @@
|
|||
SERVER/PLAYER DOCUMENTATION FOR ACTION 1.52
|
||||
|
||||
|
||||
ACTION-SPECIFIC SERVER VARIABLES
|
||||
|
||||
actionmaps: set to 1 if you wish to use the map rotation as defined in your
|
||||
"action.ini" file (default = 1).
|
||||
|
||||
teamplay: whether the game is teamplay (1) or regular DM (0) (default = 0).
|
||||
|
||||
roundlimit: the number of wins by one team before the map is ended
|
||||
(default = 0 [no limit]).
|
||||
|
||||
roundtimelimit: the number of minutes a round can go on before a winner is
|
||||
declared (default = 0 [no limit]).
|
||||
|
||||
motd_time: the number of seconds that the initial MOTD should remain on the
|
||||
player's screen. This number is rounded up to the nearest 2-second interval
|
||||
(default = 2).
|
||||
|
||||
skipmotd: allows you to skip all but the top two lines of the normal
|
||||
Action MOTD, for server operators who want large motd.txt files
|
||||
(default = 0 [don't skip]).
|
||||
|
||||
weapons: the maximum number of "unique weapons" a player can carry (the
|
||||
bandolier adds 1 to a player's max carry) (default = 1).
|
||||
|
||||
items: the number of "special items" a player can carry (default = 1).
|
||||
|
||||
ir: whether or not IR vision goggles are enabled for use with the
|
||||
bandolier (default = 1 [on]).
|
||||
|
||||
radiolog: whether or not all radio messages sent are printed to the
|
||||
console (default = 0 [no]).
|
||||
|
||||
bholelimit: the number of bulletholes in walls/etc that should be allowed to be
|
||||
in the game at one time (default = 0 [don't use bulletholes, for a faster
|
||||
Internet game]).
|
||||
|
||||
splatlimit: the number of bloodsplats on the ground/etc that should be allowed
|
||||
to be in the game at one time (default = 0 [don't use bloodsplats, for a faster
|
||||
Internet game]).
|
||||
|
||||
shelloff: turns off the dropping of expended shells from your gun
|
||||
(default = 1 [turn off shells, for a faster Internet game]).
|
||||
|
||||
knifelimit: the number of throwing knives that can be lying around on the map
|
||||
at any given time (default = 40).
|
||||
|
||||
allweapon: gives all the weapons to each player in teamplay/DM
|
||||
(default = 0).
|
||||
|
||||
allitem: gives all the items to each player in teamplay/DM
|
||||
(default = 0).
|
||||
|
||||
tgren: sets the number of grenades that come with the bandolier in
|
||||
teamplay (default = 0).
|
||||
|
||||
noscore: if set to 1, individual scores (and damage stats) are not in
|
||||
effect for teamplay, the only scores visible will be team wins and total
|
||||
frags (default = 0).
|
||||
|
||||
nohud: if set to 1, the standard HUD (health, ammo, etc) is disabled for
|
||||
all players. This can allow you to record better-looking demos of staged
|
||||
scenes (default = 0).
|
||||
|
||||
ininame: if set, changes the name of the Action INI file from "action.ini"
|
||||
to whatever you specify. The file must always be located in your Action
|
||||
game directory. This should be used on the Quake2 commandline, ie:
|
||||
quake2 +set game action +set ininame alternate.ini +set dedicated 1 ...
|
||||
(default = "action.ini").
|
||||
|
||||
limchasecam: if set to 1, will prevent all players on teams from free
|
||||
floating, or chase-camming enemy players. If set to 2, will prevent all
|
||||
players on teams from using the normal chase cam as well (only the
|
||||
through-eyes cam on teammates will be allowed). This variable should be
|
||||
set to 2 for clan matches, etc (default = 0).
|
||||
|
||||
shelloff: turns off the dropping of expended shells from your gun
|
||||
(default = 1 [turn off shells, for a faster Internet game]).
|
||||
|
||||
breakableglass: turns on breakable glass. Not recommended for Internet
|
||||
games (default = 0).
|
||||
|
||||
glassfragmentlimit: controls the maximum number of glass fragments present
|
||||
on the map at any time (default = 30).
|
||||
|
||||
maxteamkills: the maximum number of teammates a player can kill in one map
|
||||
before he is temporarily banned from the server. Only applies during
|
||||
friendly-fire enabled teamplay games. Players will also be banned for
|
||||
wounding teammates, if they wound 4*maxteamkills teammates. Setting this
|
||||
to zero disables the feature (default = 0).
|
||||
|
||||
tkbanrounds: the number of maps a player will be banned for when he is
|
||||
banned for killing teammates (default = 2).
|
||||
|
||||
twbanrounds: the number of maps a player will be banned for when he is
|
||||
banned for wounding teammates (default = 2).
|
||||
|
||||
|
||||
ACTION-SPECIFIC PLAYER COMMANDS
|
||||
|
||||
reload: reloads current weapon, if applicable to the weapon you're currently
|
||||
using. Can be repeated quickly for "fast reload" on shotgun and sniper rifle.
|
||||
|
||||
weapon: toggles mode on current weapon. For single pistol, MP5, and M4, this
|
||||
toggles between semi-auto/burst and auto mode. For the sniper rifle, this
|
||||
changes the lens. For knives, this changes between slashing and throwing. For
|
||||
the grenade, this changes how far you are going to throw. Not applicable to
|
||||
any other weapon.
|
||||
|
||||
opendoor: opens a door in front of you.
|
||||
|
||||
bandage: applies bandages to stop bleeding and cure any limb damage.
|
||||
|
||||
team: displays the team you're on, or changes teams (if used with the team
|
||||
number to switch to as an argument, or "none" to leave your current team and
|
||||
become a spectator)
|
||||
|
||||
id: toggles identification of players on or off (default is on). only works on
|
||||
teammates, or when you're an observer, but this can be used to turn it off if
|
||||
you don't want it.
|
||||
|
||||
irvision: toggles IR vision goggles w/ the bandolier (assuming IR vision
|
||||
is enabled on the server).
|
||||
|
||||
motd: brings up the MOTD (message of the day) again.
|
||||
|
||||
spectator: can be set to 0 or 1 ("spectator 0", "spectator 1") to toggle being
|
||||
a spectator in DM games.
|
||||
|
||||
hand: in addition to the normal Q2 modes (0 = right-handed, 1 =
|
||||
left-handed, 2 = "center"-handed), you can select "classic" style shooting
|
||||
(where shots end up left/right and below the crosshair), or "classic high"
|
||||
style shooting (where shots end up left/right of the crosshair). The
|
||||
proper commands to select those modes are: hand "0 classic" (right-handed
|
||||
classic), hand "0 classic high" (right-handed classic high), etc. Note
|
||||
that the double-quotes are required.
|
||||
|
||||
choose: chooses a particular item or weapon as your item or weapon, without
|
||||
going through the menus, in teamplay (ie: "choose mp5/10 submachinegun" or
|
||||
"choose lasersight").
|
||||
|
||||
(The remaining commands are all radio/partner-related...)
|
||||
|
||||
radio: sends a message on the radio, in whatever your default mode is (set with
|
||||
the "channel" command, defaults to team). Useable in teamplay, teams-by-skin
|
||||
DM, or teams-by-model DM. Valid messages are: 1 ("one"), 2 ("two"), 3
|
||||
("three"), 4 ("four"), 5 ("five"), 6 ("six"), 7 ("seven"), 8 ("eight"), 9
|
||||
("nine"), 10 ("ten"), back ("back"), cover ("cover"), down ("down"), enemyd
|
||||
("enemy down"), enemys ("enemy spotted"), forward ("forward"), go ("go"),
|
||||
im_hit ("I'm hit"), left ("left"), reportin ("reporting in"), right ("right"),
|
||||
taking_f ("taking fire, requesting assistance"), teamdown ("teammate down"),
|
||||
treport ("team, report in"), up ("up").
|
||||
|
||||
radiogender: used without an argument, displays your current radio gender. If
|
||||
used with "male" or "female", sets your radio gender to that gender. This
|
||||
changes the voice on your radio messages. Default is male.
|
||||
|
||||
radio_power: toggles your radio power on or off (default is on). If your radio
|
||||
power is off, you won't receive any radio messages, but also can't send any.
|
||||
|
||||
radiopartner: sends a radio message to your partner, see the "radio" command
|
||||
for the valid messages.
|
||||
|
||||
radioteam: sends a radio message to your team, see the "radio" command for the
|
||||
valid messages.
|
||||
|
||||
channel: toggles your default radio destination (see the "radio" command)
|
||||
between team and partner. Default is team.
|
||||
|
||||
say_partner: sends a message to your partner, in much the same way as
|
||||
"say_team" sends a message to your team.
|
||||
|
||||
partner: attempts to establish a partnership. You must have the person you
|
||||
want to partner with's name displayed on your HUD, then use the "partner"
|
||||
command to send a partnership request to him. The other player will have the
|
||||
chance to accept (by doing the same thing to you), or denying by ignoring you
|
||||
or using the "deny" command (see below).
|
||||
|
||||
deny: deny a partnership request (see "partner" command above).
|
||||
|
||||
unpartner: breaks your current partnership.
|
||||
|
||||
|
||||
WEAPON/ITEM NAMES
|
||||
|
||||
These are the exact names of all the weapons/items in the game, for use with
|
||||
commands like "use", "drop", "choose", "give", etc:
|
||||
|
||||
MK23 Pistol
|
||||
M3 Super 90 Assault Shotgun
|
||||
MP5/10 Submachinegun
|
||||
Handcannon
|
||||
Sniper Rifle
|
||||
M4 Assault Rifle
|
||||
Dual MK23 Pistols
|
||||
Combat Knife (also aliases "throwing combat knife" and
|
||||
"slashing combat knife" for the "use" command)
|
||||
|
||||
Pistol Clip
|
||||
12 Gauge Shells
|
||||
Machinegun Magazine
|
||||
M4 Clip
|
||||
AP Sniper Ammo
|
||||
|
||||
M26 Fragmentation Grenade
|
||||
|
||||
Kevlar Vest
|
||||
Lasersight
|
||||
Stealth Slippers
|
||||
Silencer
|
||||
Bandolier
|
||||
|
||||
|
||||
SUBSTITUTIONS IN SAY COMMANDS
|
||||
|
||||
The following substitution variables can be used with say, say_team, or
|
||||
say_partner messages, and they will be replaced with the specified text:
|
||||
|
||||
%W = your current weapon
|
||||
%I = your current item (a random one, if you have more than one)
|
||||
%H = your current health
|
||||
%A = your current ammo for your current weapon (in-gun and in-inventory)
|
||||
%T = names of teammates that are near you and in your line of sight
|
||||
|
||||
|
||||
OTHER CONFIGURATION
|
||||
|
||||
IP banning is supported in the standard Q2 3.20 form. See below for full
|
||||
details from id's documentation.
|
||||
|
||||
Flood protection is supported using the standard Q2 3.20 variables,
|
||||
"flood_msgs" (default 4), "flood_persecond" (default 4), and "flood_waitdelay"
|
||||
(default 10).
|
||||
|
||||
DM teams-by-model, teams-by-skin, friendly fire, etc are supported using
|
||||
the standard Q2 "dmflags" values. A server should use dmflags 256 for
|
||||
"normal" play (including no friendly fire), 0 for friendly fire. Some of
|
||||
the other regular dmflags are also available, such as "spawn farthest"
|
||||
(512).
|
||||
|
||||
action/action.ini is the configuration file for map rotation and teamplay team
|
||||
name/model/skin setup. See the example for information on the format.
|
||||
|
||||
action/motd.txt, if present, will be appended to the server MOTD. The
|
||||
server command "sv reloadmotd" can be used to reload it from disk at any
|
||||
time, and it is also reloaded at the end of each level automatically.
|
||||
|
||||
|
||||
IP BANNING
|
||||
|
||||
Commands: sv addip, sv removeip, sv listip, sv writeip
|
||||
Server variables: filterban
|
||||
|
||||
You can add or remove addresses from the IP filter list with the commands
|
||||
"sv addip <ip>" and "sv removeip <ip>". The IP address is specified in
|
||||
numeric dot format, and any unspecified digits will match any value, so
|
||||
you can specify an entire class C network with "addip 240.200.100", for
|
||||
example. "sv removeip" will only remove an address specified in the exact
|
||||
same way. The "sv listip" command will print the current list of filters.
|
||||
The "sv writeip" command will dump the current filters to a config file,
|
||||
"listip.cfg" in your Action directory. You should add a line in your
|
||||
normal server.cfg that does an "exec listip.cfg" to load this file by
|
||||
default. IP addresses in the filter list will only be prohibited from
|
||||
connecting if the "filterban" variable is set to 1 (this is the default).
|
||||
|
||||
|
||||
OTHER SERVER COMMANDS
|
||||
|
||||
sv reloadmotd: reloads the MOTD file from disk.
|
||||
|
||||
sv nextmap: immediately skips to the next map in the rotation.
|
||||
|
||||
|
||||
REPORTING BUGS OR MAKING COMMENTS
|
||||
|
||||
The maintainers of the Action server code, Zucchini and Fireblade, can be
|
||||
contacted at spikard@u.washington.edu and ucs_brf@shsu.edu, respectively.
|
||||
The Action Quake 2 website is at http://aq2.action-web.net/ and has a
|
||||
message board where discussions about Action Quake 2 take place.
|
98
docs/acebot.txt
Normal file
98
docs/acebot.txt
Normal file
|
@ -0,0 +1,98 @@
|
|||
///////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// ACE - Quake II Bot Base Code
|
||||
//
|
||||
// Version 1.0 - \/ \/ \/ \/ \/ READ BELOW \/ \/ \/ \/ \/
|
||||
//
|
||||
// This file is Copyright(c), Steve Yeager 1998, All Rights Reserved
|
||||
//
|
||||
//
|
||||
// All other files are Copyright(c) Id Software, Inc.
|
||||
//
|
||||
// Please see license.txt in the source directory for the copyright
|
||||
// information regarding those files belonging to Id Software, Inc.
|
||||
//
|
||||
// Should you decide to release a modified version of ACE, you MUST
|
||||
// include the following text (minus the BEGIN and END lines) in the
|
||||
// documentation for your modification.
|
||||
//
|
||||
// --- BEGIN ---
|
||||
//
|
||||
// The ACE Bot is a product of Steve Yeager, and is available from
|
||||
// the ACE Bot homepage, at http://www.axionfx.com/ace.
|
||||
//
|
||||
// This program is a modification of the ACE Bot, and is therefore
|
||||
// in NO WAY supported by Steve Yeager.
|
||||
//
|
||||
// This program MUST NOT be sold in ANY form. If you have paid for
|
||||
// this product, you should contact Steve Yeager immediately, via
|
||||
// the ACE Bot homepage.
|
||||
//
|
||||
// --- END ---
|
||||
//
|
||||
// I, Steve Yeager, hold no responsibility for any harm caused by the
|
||||
// use of this source code, especially to small children and animals.
|
||||
// It is provided as-is with no implied warranty or support.
|
||||
//
|
||||
// I also wish to thank and acknowledge the great work of others
|
||||
// that has helped me to develop this code.
|
||||
//
|
||||
// John Cricket - For ideas and swapping code.
|
||||
// Ryan Feltrin - For ideas and swapping code.
|
||||
// SABIN - For showing how to do true client based movement.
|
||||
// BotEpidemic - For keeping us up to date.
|
||||
// Telefragged.com - For giving ACE a home.
|
||||
// Microsoft - For giving us such a wonderful crash free OS.
|
||||
// id - Need I say more.
|
||||
//
|
||||
// And to all the other testers, pathers, and players and people
|
||||
// who I can't remember who the heck they were, but helped out.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
ACEBOT II BASE
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
I decided to release the base code for ACEBOT II for further study.
|
||||
Hopefully it will provide a simple base on which to build future bots.
|
||||
I think I have solved most of the basic problems associated with bots and
|
||||
by using this source as a starting point, much more advanced bots can be
|
||||
created. My time now is quite limited and I am not sure what I will be
|
||||
doing with this source in the future. I do have plans for some interesting
|
||||
"additions" to this bot, but time may not make it possible for these
|
||||
to be realized.
|
||||
|
||||
A number of the basic routines have been "dumbed" down from the
|
||||
original ACE sourcecode and some are only put there just to make the bot
|
||||
function. Even so, it still plays a pretty good game.
|
||||
|
||||
I hope my comments in the source code are easy enough to read and I will
|
||||
answer questions about the source as long as they are well thought out. I
|
||||
will not respond to stupid or obvious questions. So if you are new to
|
||||
C, I ain't gonna spend my time teaching you the basics. I've also
|
||||
simplified my C usage and some routines are written for readability vs.
|
||||
performance.
|
||||
|
||||
If you wish to add to the base code and think others would benifit from
|
||||
your addition, please email me and we can discuss it.
|
||||
|
||||
All code was written and compiled using MS Visual C++ 6.0+SP1. To install
|
||||
the source, create a directory named "ace" under <drive>:\quake2 (c:\quake2\ace).
|
||||
Unzip the source to \quake2\ace and it will create a src directory for the
|
||||
source code and a \quake2\nav directory for the node files.
|
||||
|
||||
I would also like to see the work that others create using this base.
|
||||
So if you create the next "killer" mod, please let me know about it.
|
||||
|
||||
Good luck and happy coding!
|
||||
|
||||
Steve Yeager
|
||||
www.axionfx.com
|
||||
syeager@axionfx.com
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
264
g_chase.c
Normal file
264
g_chase.c
Normal file
|
@ -0,0 +1,264 @@
|
|||
// This source file comes from the 3.20 source originally.
|
||||
//
|
||||
// Added through-the-eyes cam mode, as well as the ability to spin around the player
|
||||
// when in regular chase cam mode, among other Axshun-related mods.
|
||||
// -Fireblade
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
int ChaseTargetGone(edict_t *ent)
|
||||
{
|
||||
// is our chase target gone?
|
||||
if (!ent->client->chase_target->inuse
|
||||
|| (ent->client->chase_target->solid == SOLID_NOT &&
|
||||
ent->client->chase_target->deadflag != DEAD_DEAD))
|
||||
{
|
||||
edict_t *old = ent->client->chase_target;
|
||||
ChaseNext(ent);
|
||||
if (ent->client->chase_target == old)
|
||||
{
|
||||
ent->client->chase_target = NULL;
|
||||
ent->client->desired_fov = 90;
|
||||
ent->client->ps.fov = 90;
|
||||
ent->client->chase_mode = 0;
|
||||
ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UpdateChaseCam(edict_t *ent)
|
||||
{
|
||||
vec3_t o, ownerv, goal;
|
||||
edict_t *targ;
|
||||
vec3_t forward, right;
|
||||
trace_t trace;
|
||||
int i;
|
||||
vec3_t angles;
|
||||
|
||||
if (ChaseTargetGone(ent))
|
||||
return;
|
||||
|
||||
targ = ent->client->resp.last_chase_target = ent->client->chase_target;
|
||||
|
||||
if (ent->client->chase_mode == 1)
|
||||
{
|
||||
ent->client->desired_fov = 90;
|
||||
ent->client->ps.fov = 90;
|
||||
|
||||
if (ent->client->resp.cmd_angles[PITCH] > 89)
|
||||
ent->client->resp.cmd_angles[PITCH] = 89;
|
||||
if (ent->client->resp.cmd_angles[PITCH] < -89)
|
||||
ent->client->resp.cmd_angles[PITCH] = -89;
|
||||
|
||||
VectorCopy(targ->s.origin, ownerv);
|
||||
|
||||
ownerv[2] += targ->viewheight;
|
||||
|
||||
VectorCopy(ent->client->ps.viewangles, angles);
|
||||
AngleVectors (angles, forward, right, NULL);
|
||||
VectorNormalize(forward);
|
||||
VectorMA(ownerv, -150, forward, o);
|
||||
|
||||
// not sure if this should be left in... -FB
|
||||
// if (o[2] < targ->s.origin[2] + 20)
|
||||
// o[2] = targ->s.origin[2] + 20;
|
||||
|
||||
// jump animation lifts
|
||||
if (!targ->groundentity)
|
||||
o[2] += 16;
|
||||
|
||||
PRETRACE();
|
||||
trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
|
||||
POSTTRACE();
|
||||
|
||||
VectorCopy(trace.endpos, goal);
|
||||
|
||||
VectorMA(goal, 2, forward, goal);
|
||||
|
||||
// pad for floors and ceilings
|
||||
VectorCopy(goal, o);
|
||||
o[2] += 6;
|
||||
PRETRACE();
|
||||
trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
|
||||
POSTTRACE();
|
||||
if (trace.fraction < 1) {
|
||||
VectorCopy(trace.endpos, goal);
|
||||
goal[2] -= 6;
|
||||
}
|
||||
|
||||
VectorCopy(goal, o);
|
||||
o[2] -= 6;
|
||||
PRETRACE();
|
||||
trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
|
||||
POSTTRACE();
|
||||
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(ent->client->v_angle[i] - ent->client->resp.cmd_angles[i]);
|
||||
|
||||
VectorCopy(ent->client->resp.cmd_angles, ent->client->ps.viewangles);
|
||||
}
|
||||
else // chase_mode == 2
|
||||
{
|
||||
VectorCopy(targ->s.origin, ownerv);
|
||||
VectorCopy(targ->client->v_angle, angles);
|
||||
|
||||
AngleVectors (angles, forward, right, NULL);
|
||||
VectorNormalize(forward);
|
||||
VectorMA(ownerv, 16, forward, o);
|
||||
|
||||
o[2] += targ->viewheight;
|
||||
|
||||
VectorCopy(o, ent->s.origin);
|
||||
|
||||
ent->client->ps.fov = targ->client->ps.fov;
|
||||
ent->client->desired_fov = targ->client->ps.fov;
|
||||
|
||||
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
|
||||
{
|
||||
VectorAdd(targ->client->v_angle,
|
||||
targ->client->ps.kick_angles,
|
||||
angles);
|
||||
VectorCopy(angles, ent->client->ps.viewangles);
|
||||
VectorCopy(angles, 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;
|
||||
//Black Cross - Begin
|
||||
if (teamplay->value && limchasecam->value &&
|
||||
ent->client->resp.team != NOTEAM &&
|
||||
ent->client->resp.team != e->client->resp.team)
|
||||
continue;
|
||||
//Black Cross - End
|
||||
if (e->solid != SOLID_NOT || e->deadflag == DEAD_DEAD)
|
||||
break;
|
||||
} while (e != ent->client->chase_target);
|
||||
|
||||
ent->client->chase_target = e;
|
||||
}
|
||||
|
||||
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;
|
||||
//Black Cross - Begin
|
||||
if (teamplay->value && limchasecam->value &&
|
||||
ent->client->resp.team != NOTEAM &&
|
||||
ent->client->resp.team != e->client->resp.team)
|
||||
continue;
|
||||
//Black Cross - End
|
||||
if (e->solid != SOLID_NOT || e->deadflag == DEAD_DEAD)
|
||||
break;
|
||||
} while (e != ent->client->chase_target);
|
||||
|
||||
ent->client->chase_target = e;
|
||||
}
|
||||
|
||||
void GetChaseTarget(edict_t *ent)
|
||||
{
|
||||
int i, found, searched;
|
||||
edict_t *e, *start_target;
|
||||
|
||||
start_target = ent->client->resp.last_chase_target;
|
||||
|
||||
if (start_target == NULL)
|
||||
{
|
||||
start_target = g_edicts + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (start_target < (g_edicts + 1) ||
|
||||
start_target > (g_edicts + game.maxclients))
|
||||
{
|
||||
gi.dprintf("Warning: start_target ended up out of range\n");
|
||||
}
|
||||
}
|
||||
|
||||
i = (start_target - g_edicts) + 1;
|
||||
found = searched = 0;
|
||||
do
|
||||
{
|
||||
searched++;
|
||||
i--;
|
||||
if (i < 1)
|
||||
i = game.maxclients;
|
||||
e = g_edicts + i;
|
||||
if (!e->inuse)
|
||||
continue;
|
||||
//Black Cross - Begin
|
||||
if (teamplay->value && limchasecam->value &&
|
||||
ent->client->resp.team != NOTEAM &&
|
||||
ent->client->resp.team != e->client->resp.team)
|
||||
continue;
|
||||
//Black Cross - End
|
||||
if (e->solid != SOLID_NOT || e->deadflag == DEAD_DEAD)
|
||||
{
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
} while ((e != (start_target + 1)) && searched < 100);
|
||||
|
||||
if (searched >= 100)
|
||||
{
|
||||
gi.dprintf("Warning: prevented loop in GetChaseTarget\n");
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
ent->client->chase_target = e;
|
||||
}
|
||||
}
|
1116
g_combat.c
Normal file
1116
g_combat.c
Normal file
File diff suppressed because it is too large
Load diff
507
g_main.c
Normal file
507
g_main.c
Normal file
|
@ -0,0 +1,507 @@
|
|||
/*
|
||||
* $Header: /LicenseToKill/src/g_main.c 6 21/10/99 8:15 Riever $
|
||||
*
|
||||
* $Log: /LicenseToKill/src/g_main.c $
|
||||
*
|
||||
* 6 21/10/99 8:15 Riever
|
||||
* Added ltk_showpath CVAR to toggle display of bot path selection
|
||||
* information.
|
||||
*
|
||||
* 5 16/10/99 8:47 Riever
|
||||
* Added ltk_chat CVAR to turn on/ off chatting for bots.
|
||||
*
|
||||
* 4 17/09/99 6:04 Riever
|
||||
* Updated to 1.52 source code
|
||||
*
|
||||
* 3 14/09/99 21:53 Riever
|
||||
* Used safe_bprintf method for all source files
|
||||
*
|
||||
* 2 14/09/99 8:04 Riever
|
||||
* Added ltk_skill cvar (0 to 10)
|
||||
*
|
||||
* 2 13/09/99 19:52 Riever
|
||||
* Added headers
|
||||
*
|
||||
*/
|
||||
|
||||
#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;
|
||||
// zucc for location
|
||||
int locOfDeath;
|
||||
int stopAP;
|
||||
|
||||
edict_t *g_edicts;
|
||||
|
||||
//FIREBLADE
|
||||
cvar_t *hostname;
|
||||
cvar_t *teamplay;
|
||||
cvar_t *radiolog;
|
||||
cvar_t *motd_time;
|
||||
cvar_t *actionmaps;
|
||||
cvar_t *roundtimelimit;
|
||||
cvar_t *maxteamkills;
|
||||
cvar_t *twbanrounds;
|
||||
cvar_t *tkbanrounds;
|
||||
cvar_t *limchasecam;
|
||||
cvar_t *roundlimit;
|
||||
cvar_t *skipmotd;
|
||||
cvar_t *nohud;
|
||||
cvar_t *noscore;
|
||||
cvar_t *actionversion;
|
||||
cvar_t *needpass;
|
||||
//FIREBLADE
|
||||
cvar_t *deathmatch;
|
||||
cvar_t *coop;
|
||||
cvar_t *dmflags;
|
||||
cvar_t *skill;
|
||||
cvar_t *fraglimit;
|
||||
cvar_t *timelimit;
|
||||
cvar_t *password;
|
||||
cvar_t *maxclients;
|
||||
cvar_t *maxentities;
|
||||
cvar_t *g_select_empty;
|
||||
cvar_t *dedicated;
|
||||
|
||||
cvar_t *filterban;
|
||||
|
||||
cvar_t *sv_maxvelocity;
|
||||
cvar_t *sv_gravity;
|
||||
|
||||
cvar_t *sv_rollspeed;
|
||||
cvar_t *sv_rollangle;
|
||||
cvar_t *gun_x;
|
||||
cvar_t *gun_y;
|
||||
cvar_t *gun_z;
|
||||
|
||||
cvar_t *run_pitch;
|
||||
cvar_t *run_roll;
|
||||
cvar_t *bob_up;
|
||||
cvar_t *bob_pitch;
|
||||
cvar_t *bob_roll;
|
||||
|
||||
cvar_t *sv_cheats;
|
||||
|
||||
cvar_t *flood_msgs;
|
||||
cvar_t *flood_persecond;
|
||||
cvar_t *flood_waitdelay;
|
||||
|
||||
//zucc server variables
|
||||
cvar_t *unique_weapons;
|
||||
cvar_t *unique_items;
|
||||
cvar_t *ir;
|
||||
cvar_t *knifelimit;
|
||||
cvar_t *tgren;
|
||||
cvar_t *allweapon;
|
||||
cvar_t *allitem;
|
||||
|
||||
//zucc from action
|
||||
cvar_t *sv_shelloff;
|
||||
cvar_t *bholelimit;
|
||||
cvar_t *splatlimit;
|
||||
|
||||
//ACEBOT ADD - RiEvEr
|
||||
cvar_t *ltk_skill;
|
||||
cvar_t *ltk_showpath;
|
||||
cvar_t *ltk_chat;
|
||||
//ACEBOT END
|
||||
|
||||
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 CheckNeedPass (void);
|
||||
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");
|
||||
// ACEBOT ADD
|
||||
ACECM_Store();
|
||||
// ACEBOT END
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
EndDMLevel
|
||||
|
||||
The timelimit or fraglimit has been exceeded
|
||||
=================
|
||||
*/
|
||||
|
||||
//AZEROV
|
||||
extern void UnBan_TeamKillers (void);
|
||||
//AZEROV
|
||||
|
||||
void EndDMLevel (void)
|
||||
{
|
||||
edict_t *ent;
|
||||
char *nextmapname = NULL;
|
||||
// ACEBOT ADD
|
||||
ACECM_Store();
|
||||
// ACEBOT END
|
||||
// stay on same level flag
|
||||
if ((int)dmflags->value & DF_SAME_LEVEL)
|
||||
{
|
||||
ent = G_Spawn ();
|
||||
ent->classname = "target_changelevel";
|
||||
nextmapname = ent->map = level.mapname;
|
||||
}
|
||||
//FIREBLADE
|
||||
else if (!actionmaps->value || num_maps < 1)
|
||||
//FIREBLADE
|
||||
{
|
||||
if (level.nextmap[0])
|
||||
{ // go to a specific map
|
||||
ent = G_Spawn ();
|
||||
ent->classname = "target_changelevel";
|
||||
nextmapname = ent->map = 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
|
||||
ent = G_Spawn ();
|
||||
ent->classname = "target_changelevel";
|
||||
nextmapname = ent->map = level.mapname;
|
||||
}
|
||||
}
|
||||
}
|
||||
//FIREBLADE
|
||||
else
|
||||
{
|
||||
cur_map++;
|
||||
if (cur_map >= num_maps)
|
||||
cur_map = 0;
|
||||
ent = G_Spawn();
|
||||
ent->classname = "target_changelevel";
|
||||
Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map_rotation[cur_map]);
|
||||
nextmapname = ent->map = level.nextmap;
|
||||
}
|
||||
|
||||
if (level.nextmap != NULL)
|
||||
{
|
||||
safe_bprintf(PRINT_HIGH, "Next map in rotation is %s.\n",
|
||||
level.nextmap);
|
||||
}
|
||||
//FIREBLADE
|
||||
|
||||
ReadMOTDFile();
|
||||
BeginIntermission (ent);
|
||||
|
||||
//AZEROV
|
||||
UnBan_TeamKillers ();
|
||||
//AZEROV
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CheckDMRules
|
||||
=================
|
||||
*/
|
||||
void CheckDMRules (void)
|
||||
{
|
||||
int i;
|
||||
gclient_t *cl;
|
||||
|
||||
if (level.intermissiontime)
|
||||
return;
|
||||
|
||||
if (!deathmatch->value)
|
||||
return;
|
||||
|
||||
//FIREBLADE
|
||||
if (teamplay->value)
|
||||
{
|
||||
CheckTeamRules();
|
||||
}
|
||||
else /* not teamplay */
|
||||
{
|
||||
if (timelimit->value)
|
||||
{
|
||||
if (level.time >= timelimit->value*60)
|
||||
{
|
||||
safe_bprintf (PRINT_HIGH, "Timelimit hit.\n");
|
||||
EndDMLevel ();
|
||||
return;
|
||||
}
|
||||
}
|
||||
//FIREBLADE
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
safe_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;
|
||||
}
|
||||
|
||||
//FIREBLADE
|
||||
if (teamplay->value)
|
||||
{
|
||||
team1_score = 0;
|
||||
team2_score = 0;
|
||||
}
|
||||
//FIREBLADE
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
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);
|
||||
// ACEBOT_ADD
|
||||
//continue;
|
||||
// ACEBOT_END
|
||||
}
|
||||
|
||||
G_RunEntity (ent);
|
||||
}
|
||||
|
||||
// see if it is time to end a deathmatch
|
||||
CheckDMRules ();
|
||||
|
||||
//FIREBLADE
|
||||
CheckNeedPass();
|
||||
//FIREBLADE
|
||||
|
||||
// build the playerstate_t structures for all players
|
||||
ClientEndServerFrames ();
|
||||
}
|
||||
|
||||
//ADDED FROM 3.20 SOURCE -FB
|
||||
//Commented out spectator_password stuff since we don't have that now.
|
||||
/*
|
||||
=================
|
||||
CheckNeedPass
|
||||
=================
|
||||
*/
|
||||
void CheckNeedPass (void)
|
||||
{
|
||||
int need;
|
||||
|
||||
// if password or spectator_password has changed, update needpass
|
||||
// as needed
|
||||
if (password->modified /*|| spectator_password->modified*/)
|
||||
{
|
||||
password->modified = /*spectator_password->modified = */ false;
|
||||
|
||||
need = 0;
|
||||
|
||||
if (*password->string && Q_stricmp(password->string, "none"))
|
||||
need |= 1;
|
||||
/*
|
||||
if (*spectator_password->string && Q_stricmp(spectator_password->string, "none"))
|
||||
need |= 2;
|
||||
*/
|
||||
|
||||
gi.cvar_set("needpass", va("%d", need));
|
||||
}
|
||||
}
|
||||
//FROM 3.20 END
|
721
g_monster.c
Normal file
721
g_monster.c
Normal file
|
@ -0,0 +1,721 @@
|
|||
#include "g_local.h"
|
||||
|
||||
|
||||
//
|
||||
// monster weapons
|
||||
//
|
||||
|
||||
//FIXME mosnters should call these with a totally accurate direction
|
||||
// and we can mess it up based on skill. Spread should be for normal
|
||||
// and we can tighten or loosen based on skill. We could muck with
|
||||
// the damages too, but I'm not sure that's such a good idea.
|
||||
void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype)
|
||||
{
|
||||
fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype)
|
||||
{
|
||||
fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
|
||||
{
|
||||
fire_blaster (self, start, dir, damage, speed, effect, false);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype)
|
||||
{
|
||||
fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype)
|
||||
{
|
||||
fire_rocket (self, start, dir, damage, speed, damage+20, damage);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype)
|
||||
{
|
||||
fire_rail (self, start, aimdir, damage, kick);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype)
|
||||
{
|
||||
fire_bfg (self, start, aimdir, damage, speed, damage_radius);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Monster utility functions
|
||||
//
|
||||
|
||||
static void M_FliesOff (edict_t *self)
|
||||
{
|
||||
self->s.effects &= ~EF_FLIES;
|
||||
self->s.sound = 0;
|
||||
}
|
||||
|
||||
static void M_FliesOn (edict_t *self)
|
||||
{
|
||||
if (self->waterlevel)
|
||||
return;
|
||||
self->s.effects |= EF_FLIES;
|
||||
self->s.sound = gi.soundindex ("infantry/inflies1.wav");
|
||||
self->think = M_FliesOff;
|
||||
self->nextthink = level.time + 60;
|
||||
}
|
||||
|
||||
void M_FlyCheck (edict_t *self)
|
||||
{
|
||||
if (self->waterlevel)
|
||||
return;
|
||||
|
||||
if (random() > 0.5)
|
||||
return;
|
||||
|
||||
self->think = M_FliesOn;
|
||||
self->nextthink = level.time + 5 + 10 * random();
|
||||
}
|
||||
|
||||
void AttackFinished (edict_t *self, float time)
|
||||
{
|
||||
self->monsterinfo.attack_finished = level.time + time;
|
||||
}
|
||||
|
||||
|
||||
void M_CheckGround (edict_t *ent)
|
||||
{
|
||||
vec3_t point;
|
||||
trace_t trace;
|
||||
|
||||
if (ent->flags & (FL_SWIM|FL_FLY))
|
||||
return;
|
||||
|
||||
if (ent->velocity[2] > 100)
|
||||
{
|
||||
ent->groundentity = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// if the hull point one-quarter unit down is solid the entity is on ground
|
||||
point[0] = ent->s.origin[0];
|
||||
point[1] = ent->s.origin[1];
|
||||
point[2] = ent->s.origin[2] - 0.25;
|
||||
|
||||
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID);
|
||||
|
||||
// check steepness
|
||||
if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
|
||||
{
|
||||
ent->groundentity = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// ent->groundentity = trace.ent;
|
||||
// ent->groundentity_linkcount = trace.ent->linkcount;
|
||||
// if (!trace.startsolid && !trace.allsolid)
|
||||
// VectorCopy (trace.endpos, ent->s.origin);
|
||||
if (!trace.startsolid && !trace.allsolid)
|
||||
{
|
||||
VectorCopy (trace.endpos, ent->s.origin);
|
||||
ent->groundentity = trace.ent;
|
||||
ent->groundentity_linkcount = trace.ent->linkcount;
|
||||
ent->velocity[2] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void M_CatagorizePosition (edict_t *ent)
|
||||
{
|
||||
vec3_t point;
|
||||
int cont;
|
||||
|
||||
//
|
||||
// get waterlevel
|
||||
//
|
||||
point[0] = ent->s.origin[0];
|
||||
point[1] = ent->s.origin[1];
|
||||
point[2] = ent->s.origin[2] + ent->mins[2] + 1;
|
||||
cont = gi.pointcontents (point);
|
||||
|
||||
if (!(cont & MASK_WATER))
|
||||
{
|
||||
ent->waterlevel = 0;
|
||||
ent->watertype = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
ent->watertype = cont;
|
||||
ent->waterlevel = 1;
|
||||
point[2] += 26;
|
||||
cont = gi.pointcontents (point);
|
||||
if (!(cont & MASK_WATER))
|
||||
return;
|
||||
|
||||
ent->waterlevel = 2;
|
||||
point[2] += 22;
|
||||
cont = gi.pointcontents (point);
|
||||
if (cont & MASK_WATER)
|
||||
ent->waterlevel = 3;
|
||||
}
|
||||
|
||||
|
||||
void M_WorldEffects (edict_t *ent)
|
||||
{
|
||||
int dmg;
|
||||
|
||||
if (ent->health > 0)
|
||||
{
|
||||
if (!(ent->flags & FL_SWIM))
|
||||
{
|
||||
if (ent->waterlevel < 3)
|
||||
{
|
||||
ent->air_finished = level.time + 12;
|
||||
}
|
||||
else if (ent->air_finished < level.time)
|
||||
{ // drown!
|
||||
if (ent->pain_debounce_time < level.time)
|
||||
{
|
||||
dmg = 2 + 2 * floor(level.time - ent->air_finished);
|
||||
if (dmg > 15)
|
||||
dmg = 15;
|
||||
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
|
||||
ent->pain_debounce_time = level.time + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ent->waterlevel > 0)
|
||||
{
|
||||
ent->air_finished = level.time + 9;
|
||||
}
|
||||
else if (ent->air_finished < level.time)
|
||||
{ // suffocate!
|
||||
if (ent->pain_debounce_time < level.time)
|
||||
{
|
||||
dmg = 2 + 2 * floor(level.time - ent->air_finished);
|
||||
if (dmg > 15)
|
||||
dmg = 15;
|
||||
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
|
||||
ent->pain_debounce_time = level.time + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ent->waterlevel == 0)
|
||||
{
|
||||
if (ent->flags & FL_INWATER)
|
||||
{
|
||||
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
|
||||
ent->flags &= ~FL_INWATER;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
|
||||
{
|
||||
if (ent->damage_debounce_time < level.time)
|
||||
{
|
||||
ent->damage_debounce_time = level.time + 0.2;
|
||||
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA);
|
||||
}
|
||||
}
|
||||
if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
|
||||
{
|
||||
if (ent->damage_debounce_time < level.time)
|
||||
{
|
||||
ent->damage_debounce_time = level.time + 1;
|
||||
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME);
|
||||
}
|
||||
}
|
||||
|
||||
if ( !(ent->flags & FL_INWATER) )
|
||||
{
|
||||
if (!(ent->svflags & SVF_DEADMONSTER))
|
||||
{
|
||||
if (ent->watertype & CONTENTS_LAVA)
|
||||
if (random() <= 0.5)
|
||||
gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
|
||||
else
|
||||
gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
|
||||
else if (ent->watertype & CONTENTS_SLIME)
|
||||
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
|
||||
else if (ent->watertype & CONTENTS_WATER)
|
||||
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
|
||||
}
|
||||
|
||||
ent->flags |= FL_INWATER;
|
||||
ent->damage_debounce_time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void M_droptofloor (edict_t *ent)
|
||||
{
|
||||
vec3_t end;
|
||||
trace_t trace;
|
||||
|
||||
ent->s.origin[2] += 1;
|
||||
VectorCopy (ent->s.origin, end);
|
||||
end[2] -= 256;
|
||||
|
||||
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
|
||||
|
||||
if (trace.fraction == 1 || trace.allsolid)
|
||||
return;
|
||||
|
||||
VectorCopy (trace.endpos, ent->s.origin);
|
||||
|
||||
gi.linkentity (ent);
|
||||
M_CheckGround (ent);
|
||||
M_CatagorizePosition (ent);
|
||||
}
|
||||
|
||||
|
||||
void M_SetEffects (edict_t *ent)
|
||||
{
|
||||
ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN);
|
||||
ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
|
||||
|
||||
if (ent->monsterinfo.aiflags & AI_RESURRECTING)
|
||||
{
|
||||
ent->s.effects |= EF_COLOR_SHELL;
|
||||
ent->s.renderfx |= RF_SHELL_RED;
|
||||
}
|
||||
|
||||
if (ent->health <= 0)
|
||||
return;
|
||||
|
||||
if (ent->powerarmor_time > level.time)
|
||||
{
|
||||
if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN)
|
||||
{
|
||||
ent->s.effects |= EF_POWERSCREEN;
|
||||
}
|
||||
else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD)
|
||||
{
|
||||
ent->s.effects |= EF_COLOR_SHELL;
|
||||
ent->s.renderfx |= RF_SHELL_GREEN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void M_MoveFrame (edict_t *self)
|
||||
{
|
||||
mmove_t *move;
|
||||
int index;
|
||||
|
||||
move = self->monsterinfo.currentmove;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
|
||||
if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe))
|
||||
{
|
||||
self->s.frame = self->monsterinfo.nextframe;
|
||||
self->monsterinfo.nextframe = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (self->s.frame == move->lastframe)
|
||||
{
|
||||
if (move->endfunc)
|
||||
{
|
||||
move->endfunc (self);
|
||||
|
||||
// regrab move, endfunc is very likely to change it
|
||||
move = self->monsterinfo.currentmove;
|
||||
|
||||
// check for death
|
||||
if (self->svflags & SVF_DEADMONSTER)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (self->s.frame < move->firstframe || self->s.frame > move->lastframe)
|
||||
{
|
||||
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
||||
self->s.frame = move->firstframe;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
|
||||
{
|
||||
self->s.frame++;
|
||||
if (self->s.frame > move->lastframe)
|
||||
self->s.frame = move->firstframe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index = self->s.frame - move->firstframe;
|
||||
if (move->frame[index].aifunc)
|
||||
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
|
||||
move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale);
|
||||
else
|
||||
move->frame[index].aifunc (self, 0);
|
||||
|
||||
if (move->frame[index].thinkfunc)
|
||||
move->frame[index].thinkfunc (self);
|
||||
}
|
||||
|
||||
|
||||
void monster_think (edict_t *self)
|
||||
{
|
||||
M_MoveFrame (self);
|
||||
if (self->linkcount != self->monsterinfo.linkcount)
|
||||
{
|
||||
self->monsterinfo.linkcount = self->linkcount;
|
||||
M_CheckGround (self);
|
||||
}
|
||||
M_CatagorizePosition (self);
|
||||
M_WorldEffects (self);
|
||||
M_SetEffects (self);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
monster_use
|
||||
|
||||
Using a monster makes it angry at the current activator
|
||||
================
|
||||
*/
|
||||
void monster_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
if (self->enemy)
|
||||
return;
|
||||
if (self->health <= 0)
|
||||
return;
|
||||
if (activator->flags & FL_NOTARGET)
|
||||
return;
|
||||
if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
|
||||
return;
|
||||
|
||||
// delay reaction so if the monster is teleported, its sound is still heard
|
||||
self->enemy = activator;
|
||||
FoundTarget (self);
|
||||
}
|
||||
|
||||
|
||||
void monster_start_go (edict_t *self);
|
||||
|
||||
|
||||
void monster_triggered_spawn (edict_t *self)
|
||||
{
|
||||
self->s.origin[2] += 1;
|
||||
KillBox (self);
|
||||
|
||||
self->solid = SOLID_BBOX;
|
||||
self->movetype = MOVETYPE_STEP;
|
||||
self->svflags &= ~SVF_NOCLIENT;
|
||||
self->air_finished = level.time + 12;
|
||||
gi.linkentity (self);
|
||||
|
||||
monster_start_go (self);
|
||||
|
||||
if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
|
||||
{
|
||||
FoundTarget (self);
|
||||
}
|
||||
else
|
||||
{
|
||||
self->enemy = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
// we have a one frame delay here so we don't telefrag the guy who activated us
|
||||
self->think = monster_triggered_spawn;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
if (activator->client)
|
||||
self->enemy = activator;
|
||||
self->use = monster_use;
|
||||
}
|
||||
|
||||
void monster_triggered_start (edict_t *self)
|
||||
{
|
||||
self->solid = SOLID_NOT;
|
||||
self->movetype = MOVETYPE_NONE;
|
||||
self->svflags |= SVF_NOCLIENT;
|
||||
self->nextthink = 0;
|
||||
self->use = monster_triggered_spawn_use;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
monster_death_use
|
||||
|
||||
When a monster dies, it fires all of its targets with the current
|
||||
enemy as activator.
|
||||
================
|
||||
*/
|
||||
void monster_death_use (edict_t *self)
|
||||
{
|
||||
self->flags &= ~(FL_FLY|FL_SWIM);
|
||||
self->monsterinfo.aiflags &= AI_GOOD_GUY;
|
||||
|
||||
if (self->item)
|
||||
{
|
||||
Drop_Item (self, self->item);
|
||||
self->item = NULL;
|
||||
}
|
||||
|
||||
if (self->deathtarget)
|
||||
self->target = self->deathtarget;
|
||||
|
||||
if (!self->target)
|
||||
return;
|
||||
|
||||
G_UseTargets (self, self->enemy);
|
||||
}
|
||||
|
||||
|
||||
//============================================================================
|
||||
|
||||
qboolean monster_start (edict_t *self)
|
||||
{
|
||||
if (deathmatch->value)
|
||||
{
|
||||
G_FreeEdict (self);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
|
||||
{
|
||||
self->spawnflags &= ~4;
|
||||
self->spawnflags |= 1;
|
||||
// gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin));
|
||||
}
|
||||
|
||||
if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
|
||||
level.total_monsters++;
|
||||
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
self->svflags |= SVF_MONSTER;
|
||||
self->s.renderfx |= RF_FRAMELERP;
|
||||
self->takedamage = DAMAGE_AIM;
|
||||
self->air_finished = level.time + 12;
|
||||
self->use = monster_use;
|
||||
self->max_health = self->health;
|
||||
self->clipmask = MASK_MONSTERSOLID;
|
||||
|
||||
self->s.skinnum = 0;
|
||||
self->deadflag = DEAD_NO;
|
||||
self->svflags &= ~SVF_DEADMONSTER;
|
||||
|
||||
if (!self->monsterinfo.checkattack)
|
||||
self->monsterinfo.checkattack = M_CheckAttack;
|
||||
VectorCopy (self->s.origin, self->s.old_origin);
|
||||
|
||||
if (st.item)
|
||||
{
|
||||
self->item = FindItemByClassname (st.item);
|
||||
if (!self->item)
|
||||
gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
|
||||
}
|
||||
|
||||
// randomize what frame they start on
|
||||
if (self->monsterinfo.currentmove)
|
||||
self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void monster_start_go (edict_t *self)
|
||||
{
|
||||
vec3_t v;
|
||||
|
||||
if (self->health <= 0)
|
||||
return;
|
||||
|
||||
// check for target to combat_point and change to combattarget
|
||||
if (self->target)
|
||||
{
|
||||
qboolean notcombat;
|
||||
qboolean fixup;
|
||||
edict_t *target;
|
||||
|
||||
target = NULL;
|
||||
notcombat = false;
|
||||
fixup = false;
|
||||
while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL)
|
||||
{
|
||||
if (strcmp(target->classname, "point_combat") == 0)
|
||||
{
|
||||
self->combattarget = self->target;
|
||||
fixup = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
notcombat = true;
|
||||
}
|
||||
}
|
||||
if (notcombat && self->combattarget)
|
||||
gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin));
|
||||
if (fixup)
|
||||
self->target = NULL;
|
||||
}
|
||||
|
||||
// validate combattarget
|
||||
if (self->combattarget)
|
||||
{
|
||||
edict_t *target;
|
||||
|
||||
target = NULL;
|
||||
while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL)
|
||||
{
|
||||
if (strcmp(target->classname, "point_combat") != 0)
|
||||
{
|
||||
gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
|
||||
self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2],
|
||||
self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1],
|
||||
(int)target->s.origin[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self->target)
|
||||
{
|
||||
self->goalentity = self->movetarget = G_PickTarget(self->target);
|
||||
if (!self->movetarget)
|
||||
{
|
||||
gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
|
||||
self->target = NULL;
|
||||
self->monsterinfo.pausetime = 100000000;
|
||||
self->monsterinfo.stand (self);
|
||||
}
|
||||
else if (strcmp (self->movetarget->classname, "path_corner") == 0)
|
||||
{
|
||||
VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
|
||||
self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
|
||||
self->monsterinfo.walk (self);
|
||||
self->target = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->goalentity = self->movetarget = NULL;
|
||||
self->monsterinfo.pausetime = 100000000;
|
||||
self->monsterinfo.stand (self);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self->monsterinfo.pausetime = 100000000;
|
||||
self->monsterinfo.stand (self);
|
||||
}
|
||||
|
||||
self->think = monster_think;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
}
|
||||
|
||||
|
||||
void walkmonster_start_go (edict_t *self)
|
||||
{
|
||||
if (!(self->spawnflags & 2) && level.time < 1)
|
||||
{
|
||||
M_droptofloor (self);
|
||||
|
||||
if (self->groundentity)
|
||||
if (!M_walkmove (self, 0, 0))
|
||||
gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
|
||||
}
|
||||
|
||||
if (!self->yaw_speed)
|
||||
self->yaw_speed = 20;
|
||||
self->viewheight = 25;
|
||||
|
||||
monster_start_go (self);
|
||||
|
||||
if (self->spawnflags & 2)
|
||||
monster_triggered_start (self);
|
||||
}
|
||||
|
||||
void walkmonster_start (edict_t *self)
|
||||
{
|
||||
self->think = walkmonster_start_go;
|
||||
monster_start (self);
|
||||
}
|
||||
|
||||
|
||||
void flymonster_start_go (edict_t *self)
|
||||
{
|
||||
if (!M_walkmove (self, 0, 0))
|
||||
gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
|
||||
|
||||
if (!self->yaw_speed)
|
||||
self->yaw_speed = 10;
|
||||
self->viewheight = 25;
|
||||
|
||||
monster_start_go (self);
|
||||
|
||||
if (self->spawnflags & 2)
|
||||
monster_triggered_start (self);
|
||||
}
|
||||
|
||||
|
||||
void flymonster_start (edict_t *self)
|
||||
{
|
||||
self->flags |= FL_FLY;
|
||||
self->think = flymonster_start_go;
|
||||
monster_start (self);
|
||||
}
|
||||
|
||||
|
||||
void swimmonster_start_go (edict_t *self)
|
||||
{
|
||||
if (!self->yaw_speed)
|
||||
self->yaw_speed = 10;
|
||||
self->viewheight = 10;
|
||||
|
||||
monster_start_go (self);
|
||||
|
||||
if (self->spawnflags & 2)
|
||||
monster_triggered_start (self);
|
||||
}
|
||||
|
||||
void swimmonster_start (edict_t *self)
|
||||
{
|
||||
self->flags |= FL_SWIM;
|
||||
self->think = swimmonster_start_go;
|
||||
monster_start (self);
|
||||
}
|
800
g_save.c
Normal file
800
g_save.c
Normal file
|
@ -0,0 +1,800 @@
|
|||
/*
|
||||
* $Header: /LicenseToKill/src/g_save.c 7 2/11/99 12:57 Riever $
|
||||
*
|
||||
* $Log: /LicenseToKill/src/g_save.c $
|
||||
*
|
||||
* 7 2/11/99 12:57 Riever
|
||||
* Fog header file and call included.
|
||||
*
|
||||
* 6 21/10/99 8:15 Riever
|
||||
* Added ltk_showpath CVAR to toggle display of bot path selection
|
||||
* information.
|
||||
*
|
||||
* 5 16/10/99 8:47 Riever
|
||||
* Added ltk_chat CVAR to turn on/ off chatting for bots.
|
||||
*
|
||||
* 4 17/09/99 6:04 Riever
|
||||
* Updated to 1.52 source code
|
||||
*
|
||||
* 3 14/09/99 8:03 Riever
|
||||
* Added ltk_skill cvar
|
||||
*
|
||||
* 2 13/09/99 19:52 Riever
|
||||
* Added headers
|
||||
*
|
||||
*/
|
||||
#include "g_local.h"
|
||||
#include "cgf_sfx_glass.h"
|
||||
// CGF_FOG ADD
|
||||
#include "acesrc/cgf_sfx_fog.h"
|
||||
// CGF_FOG END
|
||||
|
||||
field_t fields[] = {
|
||||
{"classname", FOFS(classname), F_LSTRING},
|
||||
{"origin", FOFS(s.origin), F_VECTOR},
|
||||
{"model", FOFS(model), F_LSTRING},
|
||||
{"spawnflags", FOFS(spawnflags), F_INT},
|
||||
{"speed", FOFS(speed), F_FLOAT},
|
||||
{"accel", FOFS(accel), F_FLOAT},
|
||||
{"decel", FOFS(decel), F_FLOAT},
|
||||
{"target", FOFS(target), F_LSTRING},
|
||||
{"targetname", FOFS(targetname), F_LSTRING},
|
||||
{"pathtarget", FOFS(pathtarget), F_LSTRING},
|
||||
{"deathtarget", FOFS(deathtarget), F_LSTRING},
|
||||
{"killtarget", FOFS(killtarget), F_LSTRING},
|
||||
{"combattarget", FOFS(combattarget), F_LSTRING},
|
||||
{"message", FOFS(message), F_LSTRING},
|
||||
{"team", FOFS(team), F_LSTRING},
|
||||
{"wait", FOFS(wait), F_FLOAT},
|
||||
{"delay", FOFS(delay), F_FLOAT},
|
||||
{"random", FOFS(random), F_FLOAT},
|
||||
{"move_origin", FOFS(move_origin), F_VECTOR},
|
||||
{"move_angles", FOFS(move_angles), F_VECTOR},
|
||||
{"style", FOFS(style), F_INT},
|
||||
{"count", FOFS(count), F_INT},
|
||||
{"health", FOFS(health), F_INT},
|
||||
{"sounds", FOFS(sounds), F_INT},
|
||||
{"light", 0, F_IGNORE},
|
||||
{"dmg", FOFS(dmg), F_INT},
|
||||
{"angles", FOFS(s.angles), F_VECTOR},
|
||||
{"angle", FOFS(s.angles), F_ANGLEHACK},
|
||||
{"mass", FOFS(mass), F_INT},
|
||||
{"volume", FOFS(volume), F_FLOAT},
|
||||
{"attenuation", FOFS(attenuation), F_FLOAT},
|
||||
{"map", FOFS(map), F_LSTRING},
|
||||
|
||||
// temp spawn vars -- only valid when the spawn function is called
|
||||
{"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP},
|
||||
{"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP},
|
||||
{"height", STOFS(height), F_INT, FFL_SPAWNTEMP},
|
||||
{"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP},
|
||||
{"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP},
|
||||
{"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP},
|
||||
{"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP},
|
||||
{"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP},
|
||||
{"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP},
|
||||
{"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP},
|
||||
{"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP},
|
||||
{"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP},
|
||||
{"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP},
|
||||
{"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP},
|
||||
{"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP}
|
||||
};
|
||||
|
||||
// -------- just for savegames ----------
|
||||
// all pointer fields should be listed here, or savegames
|
||||
// won't work properly (they will crash and burn).
|
||||
// this wasn't just tacked on to the fields array, because
|
||||
// these don't need names, we wouldn't want map fields using
|
||||
// some of these, and if one were accidentally present twice
|
||||
// it would double swizzle (fuck) the pointer.
|
||||
|
||||
field_t savefields[] =
|
||||
{
|
||||
{"", FOFS(classname), F_LSTRING},
|
||||
{"", FOFS(target), F_LSTRING},
|
||||
{"", FOFS(targetname), F_LSTRING},
|
||||
{"", FOFS(killtarget), F_LSTRING},
|
||||
{"", FOFS(team), F_LSTRING},
|
||||
{"", FOFS(pathtarget), F_LSTRING},
|
||||
{"", FOFS(deathtarget), F_LSTRING},
|
||||
{"", FOFS(combattarget), F_LSTRING},
|
||||
{"", FOFS(model), F_LSTRING},
|
||||
{"", FOFS(map), F_LSTRING},
|
||||
{"", FOFS(message), F_LSTRING},
|
||||
|
||||
{"", FOFS(client), F_CLIENT},
|
||||
{"", FOFS(item), F_ITEM},
|
||||
|
||||
{"", FOFS(goalentity), F_EDICT},
|
||||
{"", FOFS(movetarget), F_EDICT},
|
||||
{"", FOFS(enemy), F_EDICT},
|
||||
{"", FOFS(oldenemy), F_EDICT},
|
||||
{"", FOFS(activator), F_EDICT},
|
||||
{"", FOFS(groundentity), F_EDICT},
|
||||
{"", FOFS(teamchain), F_EDICT},
|
||||
{"", FOFS(teammaster), F_EDICT},
|
||||
{"", FOFS(owner), F_EDICT},
|
||||
{"", FOFS(mynoise), F_EDICT},
|
||||
{"", FOFS(mynoise2), F_EDICT},
|
||||
{"", FOFS(target_ent), F_EDICT},
|
||||
{"", FOFS(chain), F_EDICT},
|
||||
|
||||
{NULL, 0, F_INT}
|
||||
};
|
||||
|
||||
field_t levelfields[] =
|
||||
{
|
||||
{"", LLOFS(changemap), F_LSTRING},
|
||||
|
||||
{"", LLOFS(sight_client), F_EDICT},
|
||||
{"", LLOFS(sight_entity), F_EDICT},
|
||||
{"", LLOFS(sound_entity), F_EDICT},
|
||||
{"", LLOFS(sound2_entity), F_EDICT},
|
||||
|
||||
{NULL, 0, F_INT}
|
||||
};
|
||||
|
||||
field_t clientfields[] =
|
||||
{
|
||||
{"", CLOFS(pers.weapon), F_ITEM},
|
||||
{"", CLOFS(pers.lastweapon), F_ITEM},
|
||||
{"", CLOFS(newweapon), F_ITEM},
|
||||
|
||||
{NULL, 0, F_INT}
|
||||
};
|
||||
|
||||
/*
|
||||
============
|
||||
InitGame
|
||||
|
||||
This will be called when the dll is first loaded, which
|
||||
only happens when a new game is started or a save game
|
||||
is loaded.
|
||||
============
|
||||
*/
|
||||
void InitGame (void)
|
||||
{
|
||||
gi.dprintf ("==== InitGame ====\n");
|
||||
|
||||
ReadConfigFile();
|
||||
ReadMOTDFile();
|
||||
|
||||
gun_x = gi.cvar ("gun_x", "0", 0);
|
||||
gun_y = gi.cvar ("gun_y", "0", 0);
|
||||
gun_z = gi.cvar ("gun_z", "0", 0);
|
||||
|
||||
//FIXME: sv_ prefix is wrong for these
|
||||
sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0);
|
||||
sv_rollangle = gi.cvar ("sv_rollangle", "2", 0);
|
||||
sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0);
|
||||
sv_gravity = gi.cvar ("sv_gravity", "800", 0);
|
||||
|
||||
// noset vars
|
||||
dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET);
|
||||
|
||||
// latched vars
|
||||
sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
|
||||
gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH);
|
||||
gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH);
|
||||
|
||||
maxclients = gi.cvar ("maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH);
|
||||
deathmatch = gi.cvar ("deathmatch", "1", CVAR_LATCH);
|
||||
coop = gi.cvar ("coop", "0", CVAR_LATCH);
|
||||
skill = gi.cvar ("skill", "1", CVAR_LATCH);
|
||||
maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH);
|
||||
|
||||
//FIREBLADE
|
||||
if (!deathmatch->value)
|
||||
{
|
||||
gi.dprintf("Turning deathmatch on.\n");
|
||||
gi.cvar_set("deathmatch", "1");
|
||||
}
|
||||
if (coop->value)
|
||||
{
|
||||
gi.dprintf("Turning coop off.\n");
|
||||
gi.cvar_set("coop", "0");
|
||||
}
|
||||
//FIREBLADE
|
||||
|
||||
// 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);
|
||||
filterban = gi.cvar("filterban", "1", 0);
|
||||
//FIREBLADE
|
||||
needpass = gi.cvar("needpass", "0", CVAR_SERVERINFO);
|
||||
radiolog = gi.cvar("radiolog", "0", 0);
|
||||
teamplay = gi.cvar ("teamplay", "0", CVAR_SERVERINFO|CVAR_LATCH);
|
||||
motd_time = gi.cvar("motd_time", "2", 0);
|
||||
hostname = gi.cvar("hostname", "unnamed", CVAR_SERVERINFO);
|
||||
actionmaps = gi.cvar ("actionmaps", "1", 0);
|
||||
if (actionmaps->value && num_maps < 1)
|
||||
{
|
||||
gi.dprintf("No maps were read from the config file, \"actionmaps\" won't be used.\n");
|
||||
gi.cvar_set("actionmaps", "0");
|
||||
}
|
||||
nohud = gi.cvar("nohud", "0", CVAR_LATCH);
|
||||
roundlimit = gi.cvar("roundlimit", "0", CVAR_SERVERINFO);
|
||||
limchasecam = gi.cvar("limchasecam", "0", CVAR_SERVERINFO|CVAR_LATCH);
|
||||
skipmotd = gi.cvar("skipmotd", "0", 0);
|
||||
roundtimelimit = gi.cvar("roundtimelimit", "0", CVAR_SERVERINFO);
|
||||
maxteamkills = gi.cvar("maxteamkills", "0", 0);
|
||||
twbanrounds = gi.cvar("twbanrounds", "2", 0);
|
||||
tkbanrounds = gi.cvar("tkbanrounds", "2", 0);
|
||||
noscore = gi.cvar("noscore", "0", CVAR_SERVERINFO|CVAR_LATCH);
|
||||
actionversion = gi.cvar("actionversion", "none set", CVAR_SERVERINFO|CVAR_LATCH);
|
||||
gi.cvar_set("actionversion", ACTION_VERSION);
|
||||
//FIREBLADE
|
||||
|
||||
//zucc get server variables
|
||||
unique_weapons = gi.cvar("weapons",
|
||||
//FIREBLADE
|
||||
// zucc changed teamplay to 1
|
||||
teamplay->value ? "1" : "1", CVAR_SERVERINFO|CVAR_LATCH);
|
||||
//FIREBLADE
|
||||
unique_items = gi.cvar("items", "1", CVAR_SERVERINFO|CVAR_LATCH);
|
||||
// zucc changed ir to 1, enabled
|
||||
|
||||
ir = gi.cvar("ir", "1", CVAR_SERVERINFO);
|
||||
knifelimit = gi.cvar ("knifelimit", "40", 0);
|
||||
allweapon = gi.cvar ("allweapon", "0", CVAR_SERVERINFO);
|
||||
allitem = gi.cvar ("allitem", "0", CVAR_SERVERINFO);
|
||||
tgren = gi.cvar ("tgren", "0", CVAR_SERVERINFO);
|
||||
|
||||
// zucc from action
|
||||
sv_shelloff = gi.cvar ("shelloff", "1", 0);
|
||||
bholelimit = gi.cvar ("bholelimit", "0", 0);
|
||||
splatlimit = gi.cvar ("splatlimit", "0", 0);
|
||||
|
||||
//ACEBOT ADD - RiEvEr
|
||||
ltk_skill = gi.cvar( "ltk_skill", "10", 0);
|
||||
ltk_showpath = gi.cvar( "ltk_showpath", "0", 0);
|
||||
ltk_chat = gi.cvar( "ltk_chat", "1", 0);
|
||||
//ACEBOT END
|
||||
// william for CGF (glass fx)
|
||||
CGF_SFX_InstallGlassSupport();
|
||||
|
||||
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);
|
||||
|
||||
// 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;
|
||||
|
||||
// CGF_FOG ADD
|
||||
CGF_SFX_InstallFogSupport();
|
||||
// CGF_FOG END
|
||||
// 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;
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
void WriteField1 (FILE *f, field_t *field, byte *base)
|
||||
{
|
||||
void *p;
|
||||
int len;
|
||||
int index;
|
||||
|
||||
p = (void *)(base + field->ofs);
|
||||
switch (field->type)
|
||||
{
|
||||
case F_INT:
|
||||
case F_FLOAT:
|
||||
case F_ANGLEHACK:
|
||||
case F_VECTOR:
|
||||
case F_IGNORE:
|
||||
break;
|
||||
|
||||
case F_LSTRING:
|
||||
case F_GSTRING:
|
||||
if ( *(char **)p )
|
||||
len = strlen(*(char **)p) + 1;
|
||||
else
|
||||
len = 0;
|
||||
*(int *)p = len;
|
||||
break;
|
||||
case F_EDICT:
|
||||
if ( *(edict_t **)p == NULL)
|
||||
index = -1;
|
||||
else
|
||||
index = *(edict_t **)p - g_edicts;
|
||||
*(int *)p = index;
|
||||
break;
|
||||
case F_CLIENT:
|
||||
if ( *(gclient_t **)p == NULL)
|
||||
index = -1;
|
||||
else
|
||||
index = *(gclient_t **)p - game.clients;
|
||||
*(int *)p = index;
|
||||
break;
|
||||
case F_ITEM:
|
||||
if ( *(edict_t **)p == NULL)
|
||||
index = -1;
|
||||
else
|
||||
index = *(gitem_t **)p - itemlist;
|
||||
*(int *)p = index;
|
||||
break;
|
||||
|
||||
default:
|
||||
gi.error ("WriteEdict: unknown field type");
|
||||
}
|
||||
}
|
||||
|
||||
void WriteField2 (FILE *f, field_t *field, byte *base)
|
||||
{
|
||||
int len;
|
||||
void *p;
|
||||
|
||||
p = (void *)(base + field->ofs);
|
||||
switch (field->type)
|
||||
{
|
||||
case F_LSTRING:
|
||||
case F_GSTRING:
|
||||
if ( *(char **)p )
|
||||
{
|
||||
len = strlen(*(char **)p) + 1;
|
||||
fwrite (*(char **)p, len, 1, f);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ReadField (FILE *f, field_t *field, byte *base)
|
||||
{
|
||||
void *p;
|
||||
int len;
|
||||
int index;
|
||||
|
||||
p = (void *)(base + field->ofs);
|
||||
switch (field->type)
|
||||
{
|
||||
case F_INT:
|
||||
case F_FLOAT:
|
||||
case F_ANGLEHACK:
|
||||
case F_VECTOR:
|
||||
case F_IGNORE:
|
||||
break;
|
||||
|
||||
case F_LSTRING:
|
||||
len = *(int *)p;
|
||||
if (!len)
|
||||
*(char **)p = NULL;
|
||||
else
|
||||
{
|
||||
*(char **)p = gi.TagMalloc (len, TAG_LEVEL);
|
||||
fread (*(char **)p, len, 1, f);
|
||||
}
|
||||
break;
|
||||
case F_GSTRING:
|
||||
len = *(int *)p;
|
||||
if (!len)
|
||||
*(char **)p = NULL;
|
||||
else
|
||||
{
|
||||
*(char **)p = gi.TagMalloc (len, TAG_GAME);
|
||||
fread (*(char **)p, len, 1, f);
|
||||
}
|
||||
break;
|
||||
case F_EDICT:
|
||||
index = *(int *)p;
|
||||
if ( index == -1 )
|
||||
*(edict_t **)p = NULL;
|
||||
else
|
||||
*(edict_t **)p = &g_edicts[index];
|
||||
break;
|
||||
case F_CLIENT:
|
||||
index = *(int *)p;
|
||||
if ( index == -1 )
|
||||
*(gclient_t **)p = NULL;
|
||||
else
|
||||
*(gclient_t **)p = &game.clients[index];
|
||||
break;
|
||||
case F_ITEM:
|
||||
index = *(int *)p;
|
||||
if ( index == -1 )
|
||||
*(gitem_t **)p = NULL;
|
||||
else
|
||||
*(gitem_t **)p = &itemlist[index];
|
||||
break;
|
||||
|
||||
default:
|
||||
gi.error ("ReadEdict: unknown field type");
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
/*
|
||||
==============
|
||||
WriteClient
|
||||
|
||||
All pointer variables (except function pointers) must be handled specially.
|
||||
==============
|
||||
*/
|
||||
void WriteClient (FILE *f, gclient_t *client)
|
||||
{
|
||||
field_t *field;
|
||||
gclient_t temp;
|
||||
|
||||
// all of the ints, floats, and vectors stay as they are
|
||||
temp = *client;
|
||||
|
||||
// change the pointers to lengths or indexes
|
||||
for (field=clientfields ; field->name ; field++)
|
||||
{
|
||||
WriteField1 (f, field, (byte *)&temp);
|
||||
}
|
||||
|
||||
// write the block
|
||||
fwrite (&temp, sizeof(temp), 1, f);
|
||||
|
||||
// now write any allocated data following the edict
|
||||
for (field=clientfields ; field->name ; field++)
|
||||
{
|
||||
WriteField2 (f, field, (byte *)client);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
ReadClient
|
||||
|
||||
All pointer variables (except function pointers) must be handled specially.
|
||||
==============
|
||||
*/
|
||||
void ReadClient (FILE *f, gclient_t *client)
|
||||
{
|
||||
field_t *field;
|
||||
|
||||
fread (client, sizeof(*client), 1, f);
|
||||
|
||||
for (field=clientfields ; field->name ; field++)
|
||||
{
|
||||
ReadField (f, field, (byte *)client);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
WriteGame
|
||||
|
||||
This will be called whenever the game goes to a new level,
|
||||
and when the user explicitly saves the game.
|
||||
|
||||
Game information include cross level data, like multi level
|
||||
triggers, help computer info, and all client states.
|
||||
|
||||
A single player death will automatically restore from the
|
||||
last save position.
|
||||
============
|
||||
*/
|
||||
void WriteGame (char *filename, qboolean autosave)
|
||||
{
|
||||
FILE *f;
|
||||
int i;
|
||||
char str[16];
|
||||
|
||||
if (!autosave)
|
||||
SaveClientData ();
|
||||
|
||||
f = fopen (filename, "wb");
|
||||
if (!f)
|
||||
gi.error ("Couldn't open %s", filename);
|
||||
|
||||
memset (str, 0, sizeof(str));
|
||||
strcpy (str, __DATE__);
|
||||
fwrite (str, sizeof(str), 1, f);
|
||||
|
||||
game.autosaved = autosave;
|
||||
fwrite (&game, sizeof(game), 1, f);
|
||||
game.autosaved = false;
|
||||
|
||||
for (i=0 ; i<game.maxclients ; i++)
|
||||
WriteClient (f, &game.clients[i]);
|
||||
|
||||
fclose (f);
|
||||
}
|
||||
|
||||
void ReadGame (char *filename)
|
||||
{
|
||||
FILE *f;
|
||||
int i;
|
||||
char str[16];
|
||||
|
||||
gi.FreeTags (TAG_GAME);
|
||||
|
||||
f = fopen (filename, "rb");
|
||||
if (!f)
|
||||
gi.error ("Couldn't open %s", filename);
|
||||
|
||||
fread (str, sizeof(str), 1, f);
|
||||
if (strcmp (str, __DATE__))
|
||||
{
|
||||
fclose (f);
|
||||
gi.error ("Savegame from an older version.\n");
|
||||
}
|
||||
|
||||
g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
|
||||
globals.edicts = g_edicts;
|
||||
|
||||
fread (&game, sizeof(game), 1, f);
|
||||
game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
|
||||
for (i=0 ; i<game.maxclients ; i++)
|
||||
ReadClient (f, &game.clients[i]);
|
||||
|
||||
fclose (f);
|
||||
}
|
||||
|
||||
//==========================================================
|
||||
|
||||
|
||||
/*
|
||||
==============
|
||||
WriteEdict
|
||||
|
||||
All pointer variables (except function pointers) must be handled specially.
|
||||
==============
|
||||
*/
|
||||
void WriteEdict (FILE *f, edict_t *ent)
|
||||
{
|
||||
field_t *field;
|
||||
edict_t temp;
|
||||
|
||||
// all of the ints, floats, and vectors stay as they are
|
||||
temp = *ent;
|
||||
|
||||
// change the pointers to lengths or indexes
|
||||
for (field=savefields ; field->name ; field++)
|
||||
{
|
||||
WriteField1 (f, field, (byte *)&temp);
|
||||
}
|
||||
|
||||
// write the block
|
||||
fwrite (&temp, sizeof(temp), 1, f);
|
||||
|
||||
// now write any allocated data following the edict
|
||||
for (field=savefields ; field->name ; field++)
|
||||
{
|
||||
WriteField2 (f, field, (byte *)ent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
WriteLevelLocals
|
||||
|
||||
All pointer variables (except function pointers) must be handled specially.
|
||||
==============
|
||||
*/
|
||||
void WriteLevelLocals (FILE *f)
|
||||
{
|
||||
field_t *field;
|
||||
level_locals_t temp;
|
||||
|
||||
// all of the ints, floats, and vectors stay as they are
|
||||
temp = level;
|
||||
|
||||
// change the pointers to lengths or indexes
|
||||
for (field=levelfields ; field->name ; field++)
|
||||
{
|
||||
WriteField1 (f, field, (byte *)&temp);
|
||||
}
|
||||
|
||||
// write the block
|
||||
fwrite (&temp, sizeof(temp), 1, f);
|
||||
|
||||
// now write any allocated data following the edict
|
||||
for (field=levelfields ; field->name ; field++)
|
||||
{
|
||||
WriteField2 (f, field, (byte *)&level);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============
|
||||
ReadEdict
|
||||
|
||||
All pointer variables (except function pointers) must be handled specially.
|
||||
==============
|
||||
*/
|
||||
void ReadEdict (FILE *f, edict_t *ent)
|
||||
{
|
||||
field_t *field;
|
||||
|
||||
fread (ent, sizeof(*ent), 1, f);
|
||||
|
||||
for (field=savefields ; field->name ; field++)
|
||||
{
|
||||
ReadField (f, field, (byte *)ent);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
ReadLevelLocals
|
||||
|
||||
All pointer variables (except function pointers) must be handled specially.
|
||||
==============
|
||||
*/
|
||||
void ReadLevelLocals (FILE *f)
|
||||
{
|
||||
field_t *field;
|
||||
|
||||
fread (&level, sizeof(level), 1, f);
|
||||
|
||||
for (field=levelfields ; field->name ; field++)
|
||||
{
|
||||
ReadField (f, field, (byte *)&level);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
WriteLevel
|
||||
|
||||
=================
|
||||
*/
|
||||
void WriteLevel (char *filename)
|
||||
{
|
||||
int i;
|
||||
edict_t *ent;
|
||||
FILE *f;
|
||||
void *base;
|
||||
|
||||
f = fopen (filename, "wb");
|
||||
if (!f)
|
||||
gi.error ("Couldn't open %s", filename);
|
||||
|
||||
// write out edict size for checking
|
||||
i = sizeof(edict_t);
|
||||
fwrite (&i, sizeof(i), 1, f);
|
||||
|
||||
// write out a function pointer for checking
|
||||
base = (void *)InitGame;
|
||||
fwrite (&base, sizeof(base), 1, f);
|
||||
|
||||
// write out level_locals_t
|
||||
WriteLevelLocals (f);
|
||||
|
||||
// write out all the entities
|
||||
for (i=0 ; i<globals.num_edicts ; i++)
|
||||
{
|
||||
ent = &g_edicts[i];
|
||||
if (!ent->inuse)
|
||||
continue;
|
||||
fwrite (&i, sizeof(i), 1, f);
|
||||
WriteEdict (f, ent);
|
||||
}
|
||||
i = -1;
|
||||
fwrite (&i, sizeof(i), 1, f);
|
||||
|
||||
fclose (f);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
ReadLevel
|
||||
|
||||
SpawnEntities will 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);
|
||||
if (base != (void *)InitGame)
|
||||
{
|
||||
fclose (f);
|
||||
gi.error ("ReadLevel: function pointers have moved");
|
||||
}
|
||||
|
||||
// load the level locals
|
||||
ReadLevelLocals (f);
|
||||
|
||||
// load all the entities
|
||||
while (1)
|
||||
{
|
||||
if (fread (&entnum, sizeof(entnum), 1, f) != 1)
|
||||
{
|
||||
fclose (f);
|
||||
gi.error ("ReadLevel: failed to read entnum");
|
||||
}
|
||||
if (entnum == -1)
|
||||
break;
|
||||
if (entnum >= globals.num_edicts)
|
||||
globals.num_edicts = entnum+1;
|
||||
|
||||
ent = &g_edicts[entnum];
|
||||
ReadEdict (f, ent);
|
||||
|
||||
// let the server rebuild world links for this ent
|
||||
memset (&ent->area, 0, sizeof(ent->area));
|
||||
gi.linkentity (ent);
|
||||
}
|
||||
|
||||
fclose (f);
|
||||
|
||||
// mark all clients as unconnected
|
||||
for (i=0 ; i<maxclients->value ; i++)
|
||||
{
|
||||
ent = &g_edicts[i+1];
|
||||
ent->client = game.clients + i;
|
||||
ent->client->pers.connected = false;
|
||||
}
|
||||
|
||||
// do any load time things at this point
|
||||
for (i=0 ; i<globals.num_edicts ; i++)
|
||||
{
|
||||
ent = &g_edicts[i];
|
||||
|
||||
if (!ent->inuse)
|
||||
continue;
|
||||
|
||||
// fire any cross-level triggers
|
||||
if (ent->classname)
|
||||
if (strcmp(ent->classname, "target_crosslevel_target") == 0)
|
||||
ent->nextthink = level.time + ent->delay;
|
||||
}
|
||||
}
|
451
g_svcmds.c
Normal file
451
g_svcmds.c
Normal file
|
@ -0,0 +1,451 @@
|
|||
#include "g_local.h"
|
||||
|
||||
void SVCmd_ReloadMOTD_f()
|
||||
{
|
||||
ReadMOTDFile();
|
||||
safe_cprintf(NULL, PRINT_HIGH, "MOTD reloaded.\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;
|
||||
|
||||
//AZEROV
|
||||
int temp_ban_games;
|
||||
//AZEROV
|
||||
} ipfilter_t;
|
||||
|
||||
#define MAX_IPFILTERS 1024
|
||||
|
||||
ipfilter_t ipfilters[MAX_IPFILTERS];
|
||||
int numipfilters;
|
||||
|
||||
/*
|
||||
=================
|
||||
StringToFilter
|
||||
=================
|
||||
*/
|
||||
static qboolean StringToFilter (char *s, ipfilter_t *f, int temp_ban_games)
|
||||
{
|
||||
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')
|
||||
{
|
||||
safe_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;
|
||||
|
||||
f->temp_ban_games = temp_ban_games;
|
||||
|
||||
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) {
|
||||
safe_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)
|
||||
{
|
||||
safe_cprintf (NULL, PRINT_HIGH, "IP filter list is full\n");
|
||||
return;
|
||||
}
|
||||
numipfilters++;
|
||||
}
|
||||
|
||||
if (!StringToFilter (gi.argv(2), &ipfilters[i], 0))
|
||||
ipfilters[i].compare = 0xffffffff;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_RemoveIP_f
|
||||
=================
|
||||
*/
|
||||
void SVCmd_RemoveIP_f (void)
|
||||
{
|
||||
ipfilter_t f;
|
||||
int i, j;
|
||||
|
||||
if (gi.argc() < 3) {
|
||||
safe_cprintf(NULL, PRINT_HIGH, "Usage: sv removeip <ip-mask>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!StringToFilter (gi.argv(2), &f, 0))
|
||||
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--;
|
||||
safe_cprintf (NULL, PRINT_HIGH, "Removed.\n");
|
||||
return;
|
||||
}
|
||||
safe_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];
|
||||
|
||||
safe_cprintf (NULL, PRINT_HIGH, "Filter list:\n");
|
||||
for (i=0 ; i<numipfilters ; i++)
|
||||
{
|
||||
*(unsigned *)b = ipfilters[i].compare;
|
||||
if (!ipfilters[i].temp_ban_games)
|
||||
{
|
||||
safe_cprintf (NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n", b[0], b[1], b[2], b[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_cprintf (NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i (%d more game(s))\n", b[0], b[1], b[2], b[3], ipfilters[i].temp_ban_games);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
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);
|
||||
|
||||
safe_cprintf (NULL, PRINT_HIGH, "Writing %s.\n", name);
|
||||
|
||||
f = fopen (name, "wb");
|
||||
if (!f)
|
||||
{
|
||||
safe_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++)
|
||||
{
|
||||
if (!ipfilters[i].temp_ban_games)
|
||||
{
|
||||
*(unsigned *)b = ipfilters[i].compare;
|
||||
fprintf (f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]);
|
||||
}
|
||||
}
|
||||
|
||||
fclose (f);
|
||||
}
|
||||
|
||||
|
||||
// zucc so it works under vc++
|
||||
void ExitLevel (void);
|
||||
|
||||
//Black Cross - Begin
|
||||
/*
|
||||
=================
|
||||
SV_Nextmap_f
|
||||
=================
|
||||
*/
|
||||
void SVCmd_Nextmap_f (char *arg)
|
||||
{
|
||||
// end level and go to next map in map rotation
|
||||
safe_bprintf(PRINT_HIGH, "Changing to next map in rotation.\n");
|
||||
EndDMLevel ();
|
||||
if (arg != NULL && Q_stricmp(arg, "force") == 0)
|
||||
ExitLevel ();
|
||||
return;
|
||||
}
|
||||
//Black Cross - End
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
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, "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 if (Q_stricmp (cmd, "nextmap") == 0)
|
||||
|
||||
SVCmd_Nextmap_f (gi.argv(2)); // Added by Black Cross
|
||||
else if (Q_stricmp (cmd, "reloadmotd") == 0)
|
||||
SVCmd_ReloadMOTD_f();
|
||||
// ACEBOT_ADD
|
||||
else if(Q_stricmp (cmd, "acedebug") == 0)
|
||||
if (strcmp(gi.argv(2),"on")==0)
|
||||
{
|
||||
safe_bprintf (PRINT_MEDIUM, "ACE: Debug Mode On\n");
|
||||
debug_mode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_bprintf (PRINT_MEDIUM, "ACE: Debug Mode Off\n");
|
||||
debug_mode = false;
|
||||
}
|
||||
|
||||
else if (Q_stricmp (cmd, "addbot") == 0)
|
||||
{
|
||||
if(teamplay->value) // name, skin, team
|
||||
ACESP_SpawnBot (gi.argv(2), gi.argv(3), gi.argv(4), NULL);
|
||||
else // name, skin
|
||||
ACESP_SpawnBot (NULL, gi.argv(2), gi.argv(3), NULL);
|
||||
}
|
||||
// removebot
|
||||
else if(Q_stricmp (cmd, "removebot") == 0)
|
||||
ACESP_RemoveBot(gi.argv(2));
|
||||
|
||||
// Node saving
|
||||
else if(Q_stricmp (cmd, "savenodes") == 0)
|
||||
ACEND_SaveNodes();
|
||||
// ACEBOT_END
|
||||
else
|
||||
safe_cprintf (NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==========================
|
||||
Kick a client entity
|
||||
==========================
|
||||
*/
|
||||
void Kick_Client (edict_t *ent)
|
||||
{
|
||||
int i = 0;
|
||||
char ban_string[256];
|
||||
edict_t *entL;
|
||||
|
||||
if (!ent->client)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We used to kick on names, but people got crafty and figured
|
||||
// out that putting in a space after their name let them get
|
||||
// around the stupid 'kick' function. So now we kick by number.
|
||||
for (i=0 ; i<game.maxclients ; i++)
|
||||
{
|
||||
entL = &g_edicts[1+i];
|
||||
if (!entL || !entL->inuse)
|
||||
continue;
|
||||
if (entL->client && ent == entL)
|
||||
{
|
||||
sprintf (ban_string, "kick %d\n", i);
|
||||
gi.AddCommandString (ban_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==========================
|
||||
Ban a client for N rounds
|
||||
==========================
|
||||
*/
|
||||
qboolean Ban_TeamKiller ( edict_t *ent, int rounds )
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (!ent || !ent->client || !ent->client->ipaddr)
|
||||
{
|
||||
safe_cprintf (NULL, PRINT_HIGH, "Unable to determine client->ipaddr for edict\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i=0 ; i<numipfilters ; i++)
|
||||
{
|
||||
if (ipfilters[i].compare == 0xffffffff)
|
||||
break; // free spot
|
||||
}
|
||||
|
||||
if (i == numipfilters)
|
||||
{
|
||||
if (numipfilters == MAX_IPFILTERS)
|
||||
{
|
||||
safe_cprintf (NULL, PRINT_HIGH, "IP filter list is full\n");
|
||||
return false;
|
||||
}
|
||||
numipfilters++;
|
||||
}
|
||||
if (!StringToFilter (ent->client->ipaddr, &ipfilters[i], rounds))
|
||||
{
|
||||
ipfilters[i].compare = 0xffffffff;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UnBan_TeamKillers (void)
|
||||
{
|
||||
// We don't directly unban them all - we subtract 1 from temp_ban_games,
|
||||
// and unban them if it's 0.
|
||||
|
||||
int i, j;
|
||||
|
||||
for (i=0 ; i<numipfilters ; i++)
|
||||
{
|
||||
if (ipfilters[i].temp_ban_games > 0)
|
||||
{
|
||||
if (!--ipfilters[i].temp_ban_games)
|
||||
{
|
||||
// re-pack the filters
|
||||
for (j=i+1 ; j<numipfilters ; j++)
|
||||
ipfilters[j-1] = ipfilters[j];
|
||||
numipfilters--;
|
||||
safe_cprintf (NULL, PRINT_HIGH, "Unbanned teamkiller.\n");
|
||||
|
||||
// since we removed the current we have to re-process the new current
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//AZEROV
|
||||
|
792
g_target.c
Normal file
792
g_target.c
Normal file
|
@ -0,0 +1,792 @@
|
|||
#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 (!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)
|
||||
safe_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((stricmp(level.mapname, "fact1") == 0) && (stricmp(ent->map, "fact3") == 0))
|
||||
ent->map = "fact3$secret1";
|
||||
|
||||
ent->use = use_target_changelevel;
|
||||
ent->svflags = SVF_NOCLIENT;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================
|
||||
|
||||
/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
|
||||
Creates a particle splash effect when used.
|
||||
|
||||
Set "sounds" to one of the following:
|
||||
1) sparks
|
||||
2) blue water
|
||||
3) brown water
|
||||
4) slime
|
||||
5) lava
|
||||
6) blood
|
||||
|
||||
"count" how many pixels in the splash
|
||||
"dmg" if set, does a radius damage at this location when it splashes
|
||||
useful for lava/sparks
|
||||
*/
|
||||
|
||||
void use_target_splash (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_SPLASH);
|
||||
gi.WriteByte (self->count);
|
||||
gi.WritePosition (self->s.origin);
|
||||
gi.WriteDir (self->movedir);
|
||||
gi.WriteByte (self->sounds);
|
||||
gi.multicast (self->s.origin, MULTICAST_PVS);
|
||||
|
||||
if (self->dmg)
|
||||
T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH);
|
||||
}
|
||||
|
||||
void SP_target_splash (edict_t *self)
|
||||
{
|
||||
self->use = use_target_splash;
|
||||
G_SetMovedir (self->s.angles, self->movedir);
|
||||
|
||||
if (!self->count)
|
||||
self->count = 32;
|
||||
|
||||
self->svflags = SVF_NOCLIENT;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================
|
||||
|
||||
/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
|
||||
Set target to the type of entity you want spawned.
|
||||
Useful for spawning monsters and gibs in the factory levels.
|
||||
|
||||
For monsters:
|
||||
Set direction to the facing you want it to have.
|
||||
|
||||
For gibs:
|
||||
Set direction if you want it moving and
|
||||
speed how fast it should be moving otherwise it
|
||||
will just be dropped
|
||||
*/
|
||||
void ED_CallSpawn (edict_t *ent);
|
||||
|
||||
void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
edict_t *ent;
|
||||
|
||||
ent = G_Spawn();
|
||||
ent->classname = self->target;
|
||||
VectorCopy (self->s.origin, ent->s.origin);
|
||||
VectorCopy (self->s.angles, ent->s.angles);
|
||||
ED_CallSpawn (ent);
|
||||
gi.unlinkentity (ent);
|
||||
KillBox (ent);
|
||||
gi.linkentity (ent);
|
||||
if (self->speed)
|
||||
VectorCopy (self->movedir, ent->velocity);
|
||||
}
|
||||
|
||||
void SP_target_spawner (edict_t *self)
|
||||
{
|
||||
self->use = use_target_spawner;
|
||||
self->svflags = SVF_NOCLIENT;
|
||||
if (self->speed)
|
||||
{
|
||||
G_SetMovedir (self->s.angles, self->movedir);
|
||||
VectorScale (self->movedir, self->speed, self->movedir);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================
|
||||
|
||||
/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
|
||||
Fires a blaster bolt in the set direction when triggered.
|
||||
|
||||
dmg default is 15
|
||||
speed default is 1000
|
||||
*/
|
||||
|
||||
void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
int effect;
|
||||
|
||||
if (self->spawnflags & 2)
|
||||
effect = 0;
|
||||
else if (self->spawnflags & 1)
|
||||
effect = EF_HYPERBLASTER;
|
||||
else
|
||||
effect = EF_BLASTER;
|
||||
|
||||
fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER);
|
||||
gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
|
||||
}
|
||||
|
||||
void SP_target_blaster (edict_t *self)
|
||||
{
|
||||
self->use = use_target_blaster;
|
||||
G_SetMovedir (self->s.angles, self->movedir);
|
||||
self->noise_index = gi.soundindex ("weapons/laser2.wav");
|
||||
|
||||
if (!self->dmg)
|
||||
self->dmg = 15;
|
||||
if (!self->speed)
|
||||
self->speed = 1000;
|
||||
|
||||
self->svflags = SVF_NOCLIENT;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================
|
||||
|
||||
/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
|
||||
Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work.
|
||||
*/
|
||||
void trigger_crosslevel_trigger_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
game.serverflags |= self->spawnflags;
|
||||
G_FreeEdict (self);
|
||||
}
|
||||
|
||||
void SP_target_crosslevel_trigger (edict_t *self)
|
||||
{
|
||||
self->svflags = SVF_NOCLIENT;
|
||||
self->use = trigger_crosslevel_trigger_use;
|
||||
}
|
||||
|
||||
/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
|
||||
Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and
|
||||
killtarget also work.
|
||||
|
||||
"delay" delay before using targets if the trigger has been activated (default 1)
|
||||
*/
|
||||
void target_crosslevel_target_think (edict_t *self)
|
||||
{
|
||||
if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
|
||||
{
|
||||
G_UseTargets (self, self);
|
||||
G_FreeEdict (self);
|
||||
}
|
||||
}
|
||||
|
||||
void SP_target_crosslevel_target (edict_t *self)
|
||||
{
|
||||
if (! self->delay)
|
||||
self->delay = 1;
|
||||
self->svflags = SVF_NOCLIENT;
|
||||
|
||||
self->think = target_crosslevel_target_think;
|
||||
self->nextthink = level.time + self->delay;
|
||||
}
|
||||
|
||||
//==========================================================
|
||||
|
||||
/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
|
||||
When triggered, fires a laser. You can either set a target
|
||||
or a direction.
|
||||
*/
|
||||
|
||||
void target_laser_think (edict_t *self)
|
||||
{
|
||||
edict_t *ignore;
|
||||
vec3_t start;
|
||||
vec3_t end;
|
||||
trace_t tr;
|
||||
vec3_t point;
|
||||
vec3_t last_movedir;
|
||||
int count;
|
||||
|
||||
if (self->spawnflags & 0x80000000)
|
||||
count = 8;
|
||||
else
|
||||
count = 4;
|
||||
|
||||
if (self->enemy)
|
||||
{
|
||||
VectorCopy (self->movedir, last_movedir);
|
||||
VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
|
||||
VectorSubtract (point, self->s.origin, self->movedir);
|
||||
VectorNormalize (self->movedir);
|
||||
if (!VectorCompare(self->movedir, last_movedir))
|
||||
self->spawnflags |= 0x80000000;
|
||||
}
|
||||
|
||||
ignore = self;
|
||||
VectorCopy (self->s.origin, start);
|
||||
VectorMA (start, 2048, self->movedir, end);
|
||||
while(1)
|
||||
{
|
||||
PRETRACE();
|
||||
tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
|
||||
POSTTRACE();
|
||||
|
||||
if (!tr.ent)
|
||||
break;
|
||||
|
||||
// hurt it if we can
|
||||
if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
|
||||
T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
|
||||
|
||||
// if we hit something that's not a monster or player or is immune to lasers, we're done
|
||||
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
|
||||
{
|
||||
if (self->spawnflags & 0x80000000)
|
||||
{
|
||||
self->spawnflags &= ~0x80000000;
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_LASER_SPARKS);
|
||||
gi.WriteByte (count);
|
||||
gi.WritePosition (tr.endpos);
|
||||
gi.WriteDir (tr.plane.normal);
|
||||
gi.WriteByte (self->s.skinnum);
|
||||
gi.multicast (tr.endpos, MULTICAST_PVS);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ignore = tr.ent;
|
||||
VectorCopy (tr.endpos, start);
|
||||
}
|
||||
|
||||
VectorCopy (tr.endpos, self->s.old_origin);
|
||||
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
}
|
||||
|
||||
void target_laser_on (edict_t *self)
|
||||
{
|
||||
if (!self->activator)
|
||||
self->activator = self;
|
||||
self->spawnflags |= 0x80000001;
|
||||
self->svflags &= ~SVF_NOCLIENT;
|
||||
target_laser_think (self);
|
||||
}
|
||||
|
||||
void target_laser_off (edict_t *self)
|
||||
{
|
||||
self->spawnflags &= ~1;
|
||||
self->svflags |= SVF_NOCLIENT;
|
||||
self->nextthink = 0;
|
||||
}
|
||||
|
||||
void target_laser_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
self->activator = activator;
|
||||
if (self->spawnflags & 1)
|
||||
target_laser_off (self);
|
||||
else
|
||||
target_laser_on (self);
|
||||
}
|
||||
|
||||
void target_laser_start (edict_t *self)
|
||||
{
|
||||
edict_t *ent;
|
||||
|
||||
self->movetype = MOVETYPE_NONE;
|
||||
self->solid = SOLID_NOT;
|
||||
self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT;
|
||||
self->s.modelindex = 1; // must be non-zero
|
||||
|
||||
// set the beam diameter
|
||||
if (self->spawnflags & 64)
|
||||
self->s.frame = 16;
|
||||
else
|
||||
self->s.frame = 4;
|
||||
|
||||
// set the color
|
||||
if (self->spawnflags & 2)
|
||||
self->s.skinnum = 0xf2f2f0f0;
|
||||
else if (self->spawnflags & 4)
|
||||
self->s.skinnum = 0xd0d1d2d3;
|
||||
else if (self->spawnflags & 8)
|
||||
self->s.skinnum = 0xf3f3f1f1;
|
||||
else if (self->spawnflags & 16)
|
||||
self->s.skinnum = 0xdcdddedf;
|
||||
else if (self->spawnflags & 32)
|
||||
self->s.skinnum = 0xe0e1e2e3;
|
||||
|
||||
if (!self->enemy)
|
||||
{
|
||||
if (self->target)
|
||||
{
|
||||
ent = G_Find (NULL, FOFS(targetname), self->target);
|
||||
if (!ent)
|
||||
gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
|
||||
self->enemy = ent;
|
||||
}
|
||||
else
|
||||
{
|
||||
G_SetMovedir (self->s.angles, self->movedir);
|
||||
}
|
||||
}
|
||||
self->use = target_laser_use;
|
||||
self->think = target_laser_think;
|
||||
|
||||
if (!self->dmg)
|
||||
self->dmg = 1;
|
||||
|
||||
VectorSet (self->mins, -8, -8, -8);
|
||||
VectorSet (self->maxs, 8, 8, 8);
|
||||
gi.linkentity (self);
|
||||
|
||||
if (self->spawnflags & 1)
|
||||
target_laser_on (self);
|
||||
else
|
||||
target_laser_off (self);
|
||||
}
|
||||
|
||||
void SP_target_laser (edict_t *self)
|
||||
{
|
||||
// let everything else get spawned before we start firing
|
||||
self->think = target_laser_start;
|
||||
self->nextthink = level.time + 1;
|
||||
}
|
||||
|
||||
//==========================================================
|
||||
|
||||
/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
|
||||
speed How many seconds the ramping will take
|
||||
message two letters; starting lightlevel and ending lightlevel
|
||||
*/
|
||||
|
||||
void target_lightramp_think (edict_t *self)
|
||||
{
|
||||
char style[2];
|
||||
|
||||
style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
|
||||
style[1] = 0;
|
||||
gi.configstring (CS_LIGHTS+self->enemy->style, style);
|
||||
|
||||
if ((level.time - self->timestamp) < self->speed)
|
||||
{
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
}
|
||||
else if (self->spawnflags & 1)
|
||||
{
|
||||
char temp;
|
||||
|
||||
temp = self->movedir[0];
|
||||
self->movedir[0] = self->movedir[1];
|
||||
self->movedir[1] = temp;
|
||||
self->movedir[2] *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
if (!self->enemy)
|
||||
{
|
||||
edict_t *e;
|
||||
|
||||
// check all the targets
|
||||
e = NULL;
|
||||
while (1)
|
||||
{
|
||||
e = G_Find (e, FOFS(targetname), self->target);
|
||||
if (!e)
|
||||
break;
|
||||
if (strcmp(e->classname, "light") != 0)
|
||||
{
|
||||
gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
|
||||
gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin));
|
||||
}
|
||||
else
|
||||
{
|
||||
self->enemy = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!self->enemy)
|
||||
{
|
||||
gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin));
|
||||
G_FreeEdict (self);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self->timestamp = level.time;
|
||||
target_lightramp_think (self);
|
||||
}
|
||||
|
||||
void SP_target_lightramp (edict_t *self)
|
||||
{
|
||||
if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1])
|
||||
{
|
||||
gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin));
|
||||
G_FreeEdict (self);
|
||||
return;
|
||||
}
|
||||
|
||||
if (deathmatch->value)
|
||||
{
|
||||
G_FreeEdict (self);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self->target)
|
||||
{
|
||||
gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
|
||||
G_FreeEdict (self);
|
||||
return;
|
||||
}
|
||||
|
||||
self->svflags |= SVF_NOCLIENT;
|
||||
self->use = target_lightramp_use;
|
||||
self->think = target_lightramp_think;
|
||||
|
||||
self->movedir[0] = self->message[0] - 'a';
|
||||
self->movedir[1] = self->message[1] - 'a';
|
||||
self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
|
||||
}
|
||||
|
||||
//==========================================================
|
||||
|
||||
/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
|
||||
When triggered, this initiates a level-wide earthquake.
|
||||
All players and monsters are affected.
|
||||
"speed" severity of the quake (default:200)
|
||||
"count" duration of the quake (default:5)
|
||||
*/
|
||||
|
||||
void target_earthquake_think (edict_t *self)
|
||||
{
|
||||
int i;
|
||||
edict_t *e;
|
||||
|
||||
if (self->last_move_time < level.time)
|
||||
{
|
||||
gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0);
|
||||
self->last_move_time = level.time + 0.5;
|
||||
}
|
||||
|
||||
for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
|
||||
{
|
||||
if (!e->inuse)
|
||||
continue;
|
||||
if (!e->client)
|
||||
continue;
|
||||
if (!e->groundentity)
|
||||
continue;
|
||||
|
||||
e->groundentity = NULL;
|
||||
e->velocity[0] += crandom()* 150;
|
||||
e->velocity[1] += crandom()* 150;
|
||||
e->velocity[2] = self->speed * (100.0 / e->mass);
|
||||
}
|
||||
|
||||
if (level.time < self->timestamp)
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
}
|
||||
|
||||
void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
self->timestamp = level.time + self->count;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
self->activator = activator;
|
||||
self->last_move_time = 0;
|
||||
}
|
||||
|
||||
void SP_target_earthquake (edict_t *self)
|
||||
{
|
||||
if (!self->targetname)
|
||||
gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
|
||||
|
||||
if (!self->count)
|
||||
self->count = 5;
|
||||
|
||||
if (!self->speed)
|
||||
self->speed = 200;
|
||||
|
||||
self->svflags |= SVF_NOCLIENT;
|
||||
self->think = target_earthquake_think;
|
||||
self->use = target_earthquake_use;
|
||||
|
||||
self->noise_index = gi.soundindex ("world/quake.wav");
|
||||
}
|
579
g_trigger.c
Normal file
579
g_trigger.c
Normal file
|
@ -0,0 +1,579 @@
|
|||
#include "g_local.h"
|
||||
|
||||
|
||||
void InitTrigger (edict_t *self)
|
||||
{
|
||||
if (!VectorCompare (self->s.angles, vec3_origin))
|
||||
G_SetMovedir (self->s.angles, self->movedir);
|
||||
|
||||
self->solid = SOLID_TRIGGER;
|
||||
self->movetype = MOVETYPE_NONE;
|
||||
gi.setmodel (self, self->model);
|
||||
self->svflags = SVF_NOCLIENT;
|
||||
}
|
||||
|
||||
|
||||
// the wait time has passed, so set back up for another activation
|
||||
void multi_wait (edict_t *ent)
|
||||
{
|
||||
ent->nextthink = 0;
|
||||
}
|
||||
|
||||
|
||||
// the trigger was just activated
|
||||
// ent->activator should be set to the activator so it can be held through a delay
|
||||
// so wait for the delay time before firing
|
||||
void multi_trigger (edict_t *ent)
|
||||
{
|
||||
if (ent->nextthink)
|
||||
return; // already been triggered
|
||||
|
||||
G_UseTargets (ent, ent->activator);
|
||||
|
||||
if (ent->wait > 0)
|
||||
{
|
||||
ent->think = multi_wait;
|
||||
ent->nextthink = level.time + ent->wait;
|
||||
}
|
||||
else
|
||||
{ // we can't just remove (self) here, because this is a touch function
|
||||
// called while looping through area links...
|
||||
ent->touch = NULL;
|
||||
ent->nextthink = level.time + FRAMETIME;
|
||||
ent->think = G_FreeEdict;
|
||||
}
|
||||
}
|
||||
|
||||
void Use_Multi (edict_t *ent, edict_t *other, edict_t *activator)
|
||||
{
|
||||
ent->activator = activator;
|
||||
multi_trigger (ent);
|
||||
}
|
||||
|
||||
void Touch_Multi (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||
{
|
||||
if(other->client)
|
||||
{
|
||||
if (self->spawnflags & 2)
|
||||
return;
|
||||
}
|
||||
else if (other->svflags & SVF_MONSTER)
|
||||
{
|
||||
if (!(self->spawnflags & 1))
|
||||
return;
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
if (!VectorCompare(self->movedir, vec3_origin))
|
||||
{
|
||||
vec3_t forward;
|
||||
|
||||
AngleVectors(other->s.angles, forward, NULL, NULL);
|
||||
if (_DotProduct(forward, self->movedir) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
self->activator = other;
|
||||
multi_trigger (self);
|
||||
}
|
||||
|
||||
/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED
|
||||
Variable sized repeatable trigger. Must be targeted at one or more entities.
|
||||
If "delay" is set, the trigger waits some time after activating before firing.
|
||||
"wait" : Seconds between triggerings. (.2 default)
|
||||
sounds
|
||||
1) secret
|
||||
2) beep beep
|
||||
3) large switch
|
||||
4)
|
||||
set "message" to text string
|
||||
*/
|
||||
void trigger_enable (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
self->solid = SOLID_TRIGGER;
|
||||
self->use = Use_Multi;
|
||||
gi.linkentity (self);
|
||||
}
|
||||
|
||||
void SP_trigger_multiple (edict_t *ent)
|
||||
{
|
||||
if (ent->sounds == 1)
|
||||
ent->noise_index = gi.soundindex ("misc/secret.wav");
|
||||
else if (ent->sounds == 2)
|
||||
ent->noise_index = gi.soundindex ("misc/talk.wav");
|
||||
else if (ent->sounds == 3)
|
||||
ent->noise_index = gi.soundindex ("misc/trigger1.wav");
|
||||
|
||||
if (!ent->wait)
|
||||
ent->wait = 0.2;
|
||||
ent->touch = Touch_Multi;
|
||||
ent->movetype = MOVETYPE_NONE;
|
||||
ent->svflags |= SVF_NOCLIENT;
|
||||
|
||||
|
||||
if (ent->spawnflags & 4)
|
||||
{
|
||||
ent->solid = SOLID_NOT;
|
||||
ent->use = trigger_enable;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->solid = SOLID_TRIGGER;
|
||||
ent->use = Use_Multi;
|
||||
}
|
||||
|
||||
if (!VectorCompare(ent->s.angles, vec3_origin))
|
||||
G_SetMovedir (ent->s.angles, ent->movedir);
|
||||
|
||||
gi.setmodel (ent, ent->model);
|
||||
gi.linkentity (ent);
|
||||
}
|
||||
|
||||
|
||||
/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED
|
||||
Triggers once, then removes itself.
|
||||
You must set the key "target" to the name of another object in the level that has a matching "targetname".
|
||||
|
||||
If TRIGGERED, this trigger must be triggered before it is live.
|
||||
|
||||
sounds
|
||||
1) secret
|
||||
2) beep beep
|
||||
3) large switch
|
||||
4)
|
||||
|
||||
"message" string to be displayed when triggered
|
||||
*/
|
||||
|
||||
void SP_trigger_once(edict_t *ent)
|
||||
{
|
||||
// make old maps work because I messed up on flag assignments here
|
||||
// triggered was on bit 1 when it should have been on bit 4
|
||||
if (ent->spawnflags & 1)
|
||||
{
|
||||
vec3_t v;
|
||||
|
||||
VectorMA (ent->mins, 0.5, ent->size, v);
|
||||
ent->spawnflags &= ~1;
|
||||
ent->spawnflags |= 4;
|
||||
gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v));
|
||||
}
|
||||
|
||||
ent->wait = -1;
|
||||
SP_trigger_multiple (ent);
|
||||
}
|
||||
|
||||
/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
|
||||
This fixed size trigger cannot be touched, it can only be fired by other events.
|
||||
*/
|
||||
void trigger_relay_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
G_UseTargets (self, activator);
|
||||
}
|
||||
|
||||
void SP_trigger_relay (edict_t *self)
|
||||
{
|
||||
self->use = trigger_relay_use;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
trigger_key
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8)
|
||||
A relay trigger that only fires it's targets if player has the proper key.
|
||||
Use "item" to specify the required key, for example "key_data_cd"
|
||||
*/
|
||||
void trigger_key_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (!self->item)
|
||||
return;
|
||||
if (!activator->client)
|
||||
return;
|
||||
|
||||
index = ITEM_INDEX(self->item);
|
||||
if (!activator->client->pers.inventory[index])
|
||||
{
|
||||
if (level.time < self->touch_debounce_time)
|
||||
return;
|
||||
self->touch_debounce_time = level.time + 5.0;
|
||||
safe_centerprintf (activator, "You need the %s", self->item->pickup_name);
|
||||
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0);
|
||||
if (coop->value)
|
||||
{
|
||||
int player;
|
||||
edict_t *ent;
|
||||
|
||||
if (strcmp(self->item->classname, "key_power_cube") == 0)
|
||||
{
|
||||
int cube;
|
||||
|
||||
for (cube = 0; cube < 8; cube++)
|
||||
if (activator->client->pers.power_cubes & (1 << cube))
|
||||
break;
|
||||
for (player = 1; player <= game.maxclients; player++)
|
||||
{
|
||||
ent = &g_edicts[player];
|
||||
if (!ent->inuse)
|
||||
continue;
|
||||
if (!ent->client)
|
||||
continue;
|
||||
if (ent->client->pers.power_cubes & (1 << cube))
|
||||
{
|
||||
ent->client->pers.inventory[index]--;
|
||||
ent->client->pers.power_cubes &= ~(1 << cube);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (player = 1; player <= game.maxclients; player++)
|
||||
{
|
||||
ent = &g_edicts[player];
|
||||
if (!ent->inuse)
|
||||
continue;
|
||||
if (!ent->client)
|
||||
continue;
|
||||
ent->client->pers.inventory[index] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
activator->client->pers.inventory[index]--;
|
||||
}
|
||||
|
||||
G_UseTargets (self, activator);
|
||||
|
||||
self->use = NULL;
|
||||
}
|
||||
|
||||
void SP_trigger_key (edict_t *self)
|
||||
{
|
||||
if (!st.item)
|
||||
{
|
||||
gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin));
|
||||
return;
|
||||
}
|
||||
self->item = FindItemByClassname (st.item);
|
||||
|
||||
if (!self->item)
|
||||
{
|
||||
gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self->target)
|
||||
{
|
||||
gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin));
|
||||
return;
|
||||
}
|
||||
|
||||
gi.soundindex ("misc/keytry.wav");
|
||||
gi.soundindex ("misc/keyuse.wav");
|
||||
|
||||
self->use = trigger_key_use;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
trigger_counter
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*QUAKED trigger_counter (.5 .5 .5) ? nomessage
|
||||
Acts as an intermediary for an action that takes multiple inputs.
|
||||
|
||||
If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
|
||||
|
||||
After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
|
||||
*/
|
||||
|
||||
void trigger_counter_use(edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
if (self->count == 0)
|
||||
return;
|
||||
|
||||
self->count--;
|
||||
|
||||
if (self->count)
|
||||
{
|
||||
if (! (self->spawnflags & 1))
|
||||
{
|
||||
safe_centerprintf(activator, "%i more to go...", self->count);
|
||||
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (! (self->spawnflags & 1))
|
||||
{
|
||||
safe_centerprintf(activator, "Sequence completed!");
|
||||
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
||||
}
|
||||
self->activator = activator;
|
||||
multi_trigger (self);
|
||||
}
|
||||
|
||||
void SP_trigger_counter (edict_t *self)
|
||||
{
|
||||
self->wait = -1;
|
||||
if (!self->count)
|
||||
self->count = 2;
|
||||
|
||||
self->use = trigger_counter_use;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
trigger_always
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
|
||||
This trigger will always fire. It is activated by the world.
|
||||
*/
|
||||
void SP_trigger_always (edict_t *ent)
|
||||
{
|
||||
// we must have some delay to make sure our use targets are present
|
||||
if (ent->delay < 0.2)
|
||||
ent->delay = 0.2;
|
||||
G_UseTargets(ent, ent);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
trigger_push
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#define PUSH_ONCE 1
|
||||
|
||||
static int windsound;
|
||||
|
||||
void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||
{
|
||||
if (strcmp(other->classname, "grenade") == 0)
|
||||
{
|
||||
VectorScale (self->movedir, self->speed * 10, other->velocity);
|
||||
}
|
||||
else if (other->health > 0)
|
||||
{
|
||||
VectorScale (self->movedir, self->speed * 10, other->velocity);
|
||||
|
||||
if (other->client)
|
||||
{
|
||||
// don't take falling damage immediately from this
|
||||
VectorCopy (other->velocity, other->client->oldvelocity);
|
||||
if (other->fly_sound_debounce_time < level.time)
|
||||
{
|
||||
other->fly_sound_debounce_time = level.time + 1.5;
|
||||
gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self->spawnflags & PUSH_ONCE)
|
||||
G_FreeEdict (self);
|
||||
}
|
||||
|
||||
|
||||
/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE
|
||||
Pushes the player
|
||||
"speed" defaults to 1000
|
||||
*/
|
||||
void SP_trigger_push (edict_t *self)
|
||||
{
|
||||
InitTrigger (self);
|
||||
windsound = gi.soundindex ("misc/windfly.wav");
|
||||
self->touch = trigger_push_touch;
|
||||
if (!self->speed)
|
||||
self->speed = 1000;
|
||||
gi.linkentity (self);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
trigger_hurt
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW
|
||||
Any entity that touches this will be hurt.
|
||||
|
||||
It does dmg points of damage each server frame
|
||||
|
||||
SILENT supresses playing the sound
|
||||
SLOW changes the damage rate to once per second
|
||||
NO_PROTECTION *nothing* stops the damage
|
||||
|
||||
"dmg" default 5 (whole numbers only)
|
||||
|
||||
*/
|
||||
void hurt_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
if (self->solid == SOLID_NOT)
|
||||
self->solid = SOLID_TRIGGER;
|
||||
else
|
||||
self->solid = SOLID_NOT;
|
||||
gi.linkentity (self);
|
||||
|
||||
if (!(self->spawnflags & 2))
|
||||
self->use = NULL;
|
||||
}
|
||||
|
||||
|
||||
void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||
{
|
||||
int dflags;
|
||||
|
||||
if (!other->takedamage)
|
||||
return;
|
||||
|
||||
if (self->timestamp > level.time)
|
||||
return;
|
||||
|
||||
if (self->spawnflags & 16)
|
||||
self->timestamp = level.time + 1;
|
||||
else
|
||||
self->timestamp = level.time + FRAMETIME;
|
||||
|
||||
if (!(self->spawnflags & 4))
|
||||
{
|
||||
if ((level.framenum % 10) == 0)
|
||||
gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
|
||||
}
|
||||
|
||||
if (self->spawnflags & 8)
|
||||
dflags = DAMAGE_NO_PROTECTION;
|
||||
else
|
||||
dflags = 0;
|
||||
T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT);
|
||||
}
|
||||
|
||||
void SP_trigger_hurt (edict_t *self)
|
||||
{
|
||||
InitTrigger (self);
|
||||
|
||||
self->noise_index = gi.soundindex ("world/electro.wav");
|
||||
self->touch = hurt_touch;
|
||||
|
||||
if (!self->dmg)
|
||||
self->dmg = 5;
|
||||
|
||||
if (self->spawnflags & 1)
|
||||
self->solid = SOLID_NOT;
|
||||
else
|
||||
self->solid = SOLID_TRIGGER;
|
||||
|
||||
if (self->spawnflags & 2)
|
||||
self->use = hurt_use;
|
||||
|
||||
gi.linkentity (self);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
trigger_gravity
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*QUAKED trigger_gravity (.5 .5 .5) ?
|
||||
Changes the touching entites gravity to
|
||||
the value of "gravity". 1.0 is standard
|
||||
gravity for the level.
|
||||
*/
|
||||
|
||||
void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||
{
|
||||
other->gravity = self->gravity;
|
||||
}
|
||||
|
||||
void SP_trigger_gravity (edict_t *self)
|
||||
{
|
||||
if (st.gravity == 0)
|
||||
{
|
||||
gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin));
|
||||
G_FreeEdict (self);
|
||||
return;
|
||||
}
|
||||
|
||||
InitTrigger (self);
|
||||
self->gravity = atoi(st.gravity);
|
||||
self->touch = trigger_gravity_touch;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
trigger_monsterjump
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*QUAKED trigger_monsterjump (.5 .5 .5) ?
|
||||
Walking monsters that touch this will jump in the direction of the trigger's angle
|
||||
"speed" default to 200, the speed thrown forward
|
||||
"height" default to 200, the speed thrown upwards
|
||||
*/
|
||||
|
||||
void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||
{
|
||||
if (other->flags & (FL_FLY | FL_SWIM) )
|
||||
return;
|
||||
if (other->svflags & SVF_DEADMONSTER)
|
||||
return;
|
||||
if ( !(other->svflags & SVF_MONSTER))
|
||||
return;
|
||||
|
||||
// set XY even if not on ground, so the jump will clear lips
|
||||
other->velocity[0] = self->movedir[0] * self->speed;
|
||||
other->velocity[1] = self->movedir[1] * self->speed;
|
||||
|
||||
if (!other->groundentity)
|
||||
return;
|
||||
|
||||
other->groundentity = NULL;
|
||||
other->velocity[2] = self->movedir[2];
|
||||
}
|
||||
|
||||
void SP_trigger_monsterjump (edict_t *self)
|
||||
{
|
||||
if (!self->speed)
|
||||
self->speed = 200;
|
||||
if (!st.height)
|
||||
st.height = 200;
|
||||
if (self->s.angles[YAW] == 0)
|
||||
self->s.angles[YAW] = 360;
|
||||
InitTrigger (self);
|
||||
self->touch = trigger_monsterjump_touch;
|
||||
self->movedir[2] = st.height;
|
||||
}
|
||||
|
413
g_turret.c
Normal file
413
g_turret.c
Normal file
|
@ -0,0 +1,413 @@
|
|||
// g_turret.c
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
|
||||
void AnglesNormalize(vec3_t vec)
|
||||
{
|
||||
while(vec[0] > 360)
|
||||
vec[0] -= 360;
|
||||
while(vec[0] < 0)
|
||||
vec[0] += 360;
|
||||
while(vec[1] > 360)
|
||||
vec[1] -= 360;
|
||||
while(vec[1] < 0)
|
||||
vec[1] += 360;
|
||||
}
|
||||
|
||||
float SnapToEights(float x)
|
||||
{
|
||||
x *= 8.0;
|
||||
if (x > 0.0)
|
||||
x += 0.5;
|
||||
else
|
||||
x -= 0.5;
|
||||
return 0.125 * (int)x;
|
||||
}
|
||||
|
||||
|
||||
void turret_blocked(edict_t *self, edict_t *other)
|
||||
{
|
||||
edict_t *attacker;
|
||||
|
||||
if (other->takedamage)
|
||||
{
|
||||
if (self->teammaster->owner)
|
||||
attacker = self->teammaster->owner;
|
||||
else
|
||||
attacker = self->teammaster;
|
||||
T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
|
||||
}
|
||||
}
|
||||
|
||||
/*QUAKED turret_breach (0 0 0) ?
|
||||
This portion of the turret can change both pitch and yaw.
|
||||
The model should be made with a flat pitch.
|
||||
It (and the associated base) need to be oriented towards 0.
|
||||
Use "angle" to set the starting angle.
|
||||
|
||||
"speed" default 50
|
||||
"dmg" default 10
|
||||
"angle" point this forward
|
||||
"target" point this at an info_notnull at the muzzle tip
|
||||
"minpitch" min acceptable pitch angle : default -30
|
||||
"maxpitch" max acceptable pitch angle : default 30
|
||||
"minyaw" min acceptable yaw angle : default 0
|
||||
"maxyaw" max acceptable yaw angle : default 360
|
||||
*/
|
||||
|
||||
void turret_breach_fire (edict_t *self)
|
||||
{
|
||||
vec3_t f, r, u;
|
||||
vec3_t start;
|
||||
int damage;
|
||||
int speed;
|
||||
|
||||
AngleVectors (self->s.angles, f, r, u);
|
||||
VectorMA (self->s.origin, self->move_origin[0], f, start);
|
||||
VectorMA (start, self->move_origin[1], r, start);
|
||||
VectorMA (start, self->move_origin[2], u, start);
|
||||
|
||||
damage = 100 + random() * 50;
|
||||
speed = 550 + 50 * skill->value;
|
||||
fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage);
|
||||
gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
|
||||
}
|
||||
|
||||
void turret_breach_think (edict_t *self)
|
||||
{
|
||||
edict_t *ent;
|
||||
vec3_t current_angles;
|
||||
vec3_t delta;
|
||||
|
||||
VectorCopy (self->s.angles, current_angles);
|
||||
AnglesNormalize(current_angles);
|
||||
|
||||
AnglesNormalize(self->move_angles);
|
||||
if (self->move_angles[PITCH] > 180)
|
||||
self->move_angles[PITCH] -= 360;
|
||||
|
||||
// clamp angles to mins & maxs
|
||||
if (self->move_angles[PITCH] > self->pos1[PITCH])
|
||||
self->move_angles[PITCH] = self->pos1[PITCH];
|
||||
else if (self->move_angles[PITCH] < self->pos2[PITCH])
|
||||
self->move_angles[PITCH] = self->pos2[PITCH];
|
||||
|
||||
if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW]))
|
||||
{
|
||||
float dmin, dmax;
|
||||
|
||||
dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
|
||||
if (dmin < -180)
|
||||
dmin += 360;
|
||||
else if (dmin > 180)
|
||||
dmin -= 360;
|
||||
dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
|
||||
if (dmax < -180)
|
||||
dmax += 360;
|
||||
else if (dmax > 180)
|
||||
dmax -= 360;
|
||||
if (fabs(dmin) < fabs(dmax))
|
||||
self->move_angles[YAW] = self->pos1[YAW];
|
||||
else
|
||||
self->move_angles[YAW] = self->pos2[YAW];
|
||||
}
|
||||
|
||||
VectorSubtract (self->move_angles, current_angles, delta);
|
||||
if (delta[0] < -180)
|
||||
delta[0] += 360;
|
||||
else if (delta[0] > 180)
|
||||
delta[0] -= 360;
|
||||
if (delta[1] < -180)
|
||||
delta[1] += 360;
|
||||
else if (delta[1] > 180)
|
||||
delta[1] -= 360;
|
||||
delta[2] = 0;
|
||||
|
||||
if (delta[0] > self->speed * FRAMETIME)
|
||||
delta[0] = self->speed * FRAMETIME;
|
||||
if (delta[0] < -1 * self->speed * FRAMETIME)
|
||||
delta[0] = -1 * self->speed * FRAMETIME;
|
||||
if (delta[1] > self->speed * FRAMETIME)
|
||||
delta[1] = self->speed * FRAMETIME;
|
||||
if (delta[1] < -1 * self->speed * FRAMETIME)
|
||||
delta[1] = -1 * self->speed * FRAMETIME;
|
||||
|
||||
VectorScale (delta, 1.0/FRAMETIME, self->avelocity);
|
||||
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
|
||||
for (ent = self->teammaster; ent; ent = ent->teamchain)
|
||||
ent->avelocity[1] = self->avelocity[1];
|
||||
|
||||
// if we have adriver, adjust his velocities
|
||||
if (self->owner)
|
||||
{
|
||||
float angle;
|
||||
float target_z;
|
||||
float diff;
|
||||
vec3_t target;
|
||||
vec3_t dir;
|
||||
|
||||
// angular is easy, just copy ours
|
||||
self->owner->avelocity[0] = self->avelocity[0];
|
||||
self->owner->avelocity[1] = self->avelocity[1];
|
||||
|
||||
// x & y
|
||||
angle = self->s.angles[1] + self->owner->move_origin[1];
|
||||
angle *= (M_PI*2 / 360);
|
||||
target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
|
||||
target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
|
||||
target[2] = self->owner->s.origin[2];
|
||||
|
||||
VectorSubtract (target, self->owner->s.origin, dir);
|
||||
self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
|
||||
self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
|
||||
|
||||
// z
|
||||
angle = self->s.angles[PITCH] * (M_PI*2 / 360);
|
||||
target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
|
||||
|
||||
diff = target_z - self->owner->s.origin[2];
|
||||
self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
|
||||
|
||||
if (self->spawnflags & 65536)
|
||||
{
|
||||
turret_breach_fire (self);
|
||||
self->spawnflags &= ~65536;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void turret_breach_finish_init (edict_t *self)
|
||||
{
|
||||
// get and save info for muzzle location
|
||||
if (!self->target)
|
||||
{
|
||||
gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
|
||||
}
|
||||
else
|
||||
{
|
||||
self->target_ent = G_PickTarget (self->target);
|
||||
VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin);
|
||||
G_FreeEdict(self->target_ent);
|
||||
}
|
||||
|
||||
self->teammaster->dmg = self->dmg;
|
||||
self->think = turret_breach_think;
|
||||
self->think (self);
|
||||
}
|
||||
|
||||
void SP_turret_breach (edict_t *self)
|
||||
{
|
||||
self->solid = SOLID_BSP;
|
||||
self->movetype = MOVETYPE_PUSH;
|
||||
gi.setmodel (self, self->model);
|
||||
|
||||
if (!self->speed)
|
||||
self->speed = 50;
|
||||
if (!self->dmg)
|
||||
self->dmg = 10;
|
||||
|
||||
if (!st.minpitch)
|
||||
st.minpitch = -30;
|
||||
if (!st.maxpitch)
|
||||
st.maxpitch = 30;
|
||||
if (!st.maxyaw)
|
||||
st.maxyaw = 360;
|
||||
|
||||
self->pos1[PITCH] = -1 * st.minpitch;
|
||||
self->pos1[YAW] = st.minyaw;
|
||||
self->pos2[PITCH] = -1 * st.maxpitch;
|
||||
self->pos2[YAW] = st.maxyaw;
|
||||
|
||||
self->ideal_yaw = self->s.angles[YAW];
|
||||
self->move_angles[YAW] = self->ideal_yaw;
|
||||
|
||||
self->blocked = turret_blocked;
|
||||
|
||||
self->think = turret_breach_finish_init;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
gi.linkentity (self);
|
||||
}
|
||||
|
||||
|
||||
/*QUAKED turret_base (0 0 0) ?
|
||||
This portion of the turret changes yaw only.
|
||||
MUST be teamed with a turret_breach.
|
||||
*/
|
||||
|
||||
void SP_turret_base (edict_t *self)
|
||||
{
|
||||
self->solid = SOLID_BSP;
|
||||
self->movetype = MOVETYPE_PUSH;
|
||||
gi.setmodel (self, self->model);
|
||||
self->blocked = turret_blocked;
|
||||
gi.linkentity (self);
|
||||
}
|
||||
|
||||
|
||||
/*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
|
||||
Must NOT be on the team with the rest of the turret parts.
|
||||
Instead it must target the turret_breach.
|
||||
*/
|
||||
|
||||
void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage);
|
||||
void infantry_stand (edict_t *self);
|
||||
void monster_use (edict_t *self, edict_t *other, edict_t *activator);
|
||||
|
||||
void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
||||
{
|
||||
edict_t *ent;
|
||||
|
||||
// level the gun
|
||||
self->target_ent->move_angles[0] = 0;
|
||||
|
||||
// remove the driver from the end of them team chain
|
||||
for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain)
|
||||
;
|
||||
ent->teamchain = NULL;
|
||||
self->teammaster = NULL;
|
||||
self->flags &= ~FL_TEAMSLAVE;
|
||||
|
||||
self->target_ent->owner = NULL;
|
||||
self->target_ent->teammaster->owner = NULL;
|
||||
|
||||
//FB infantry_die (self, inflictor, attacker, damage);
|
||||
}
|
||||
|
||||
qboolean FindTarget (edict_t *self);
|
||||
|
||||
void turret_driver_think (edict_t *self)
|
||||
{
|
||||
vec3_t target;
|
||||
vec3_t dir;
|
||||
float reaction_time;
|
||||
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
|
||||
if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0))
|
||||
self->enemy = NULL;
|
||||
|
||||
if (!self->enemy)
|
||||
{
|
||||
if (!FindTarget (self))
|
||||
return;
|
||||
self->monsterinfo.trail_time = level.time;
|
||||
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (visible (self, self->enemy))
|
||||
{
|
||||
if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
|
||||
{
|
||||
self->monsterinfo.trail_time = level.time;
|
||||
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self->monsterinfo.aiflags |= AI_LOST_SIGHT;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// let the turret know where we want it to aim
|
||||
VectorCopy (self->enemy->s.origin, target);
|
||||
target[2] += self->enemy->viewheight;
|
||||
VectorSubtract (target, self->target_ent->s.origin, dir);
|
||||
vectoangles (dir, self->target_ent->move_angles);
|
||||
|
||||
// decide if we should shoot
|
||||
if (level.time < self->monsterinfo.attack_finished)
|
||||
return;
|
||||
|
||||
reaction_time = (3 - skill->value) * 1.0;
|
||||
if ((level.time - self->monsterinfo.trail_time) < reaction_time)
|
||||
return;
|
||||
|
||||
self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
|
||||
//FIXME how do we really want to pass this along?
|
||||
self->target_ent->spawnflags |= 65536;
|
||||
}
|
||||
|
||||
void turret_driver_link (edict_t *self)
|
||||
{
|
||||
vec3_t vec;
|
||||
edict_t *ent;
|
||||
|
||||
self->think = turret_driver_think;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
|
||||
self->target_ent = G_PickTarget (self->target);
|
||||
self->target_ent->owner = self;
|
||||
self->target_ent->teammaster->owner = self;
|
||||
VectorCopy (self->target_ent->s.angles, self->s.angles);
|
||||
|
||||
vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
|
||||
vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
|
||||
vec[2] = 0;
|
||||
self->move_origin[0] = VectorLength(vec);
|
||||
|
||||
VectorSubtract (self->s.origin, self->target_ent->s.origin, vec);
|
||||
vectoangles (vec, vec);
|
||||
AnglesNormalize(vec);
|
||||
self->move_origin[1] = vec[1];
|
||||
|
||||
self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
|
||||
|
||||
// add the driver to the end of them team chain
|
||||
for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
|
||||
;
|
||||
ent->teamchain = self;
|
||||
self->teammaster = self->target_ent->teammaster;
|
||||
self->flags |= FL_TEAMSLAVE;
|
||||
}
|
||||
|
||||
void SP_turret_driver (edict_t *self)
|
||||
{
|
||||
if (deathmatch->value)
|
||||
{
|
||||
G_FreeEdict (self);
|
||||
return;
|
||||
}
|
||||
|
||||
self->movetype = MOVETYPE_PUSH;
|
||||
self->solid = SOLID_BBOX;
|
||||
self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
|
||||
VectorSet (self->mins, -16, -16, -24);
|
||||
VectorSet (self->maxs, 16, 16, 32);
|
||||
|
||||
self->health = 100;
|
||||
self->gib_health = 0;
|
||||
self->mass = 200;
|
||||
self->viewheight = 24;
|
||||
|
||||
self->die = turret_driver_die;
|
||||
//FB self->monsterinfo.stand = infantry_stand;
|
||||
|
||||
self->flags |= FL_NO_KNOCKBACK;
|
||||
|
||||
level.total_monsters++;
|
||||
|
||||
self->svflags |= SVF_MONSTER;
|
||||
self->s.renderfx |= RF_FRAMELERP;
|
||||
self->takedamage = DAMAGE_AIM;
|
||||
self->use = monster_use;
|
||||
self->clipmask = MASK_MONSTERSOLID;
|
||||
VectorCopy (self->s.origin, self->s.old_origin);
|
||||
self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED;
|
||||
|
||||
if (st.item)
|
||||
{
|
||||
self->item = FindItemByClassname (st.item);
|
||||
if (!self->item)
|
||||
gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
|
||||
}
|
||||
|
||||
self->think = turret_driver_link;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
|
||||
gi.linkentity (self);
|
||||
}
|
554
g_utils.c
Normal file
554
g_utils.c
Normal file
|
@ -0,0 +1,554 @@
|
|||
// g_utils.c -- misc utility functions for game module
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
|
||||
void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
|
||||
{
|
||||
result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
|
||||
result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
|
||||
result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
G_Find
|
||||
|
||||
Searches all active entities for the next one that holds
|
||||
the matching string at fieldofs (use the FOFS() macro) in the structure.
|
||||
|
||||
Searches beginning at the edict after from, or the beginning if NULL
|
||||
NULL will be returned if the end of the list is reached.
|
||||
|
||||
=============
|
||||
*/
|
||||
edict_t *G_Find (edict_t *from, int fieldofs, char *match)
|
||||
{
|
||||
char *s;
|
||||
|
||||
if (!from)
|
||||
from = g_edicts;
|
||||
else
|
||||
from++;
|
||||
|
||||
for ( ; from < &g_edicts[globals.num_edicts] ; from++)
|
||||
{
|
||||
if (!from->inuse)
|
||||
continue;
|
||||
s = *(char **) ((byte *)from + fieldofs);
|
||||
if (!s)
|
||||
continue;
|
||||
if (!Q_stricmp (s, match))
|
||||
return from;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
findradius
|
||||
|
||||
Returns entities that have origins within a spherical area
|
||||
|
||||
findradius (origin, radius)
|
||||
=================
|
||||
*/
|
||||
edict_t *findradius (edict_t *from, vec3_t org, float rad)
|
||||
{
|
||||
vec3_t eorg;
|
||||
int j;
|
||||
|
||||
if (!from)
|
||||
from = g_edicts;
|
||||
else
|
||||
from++;
|
||||
for ( ; from < &g_edicts[globals.num_edicts]; from++)
|
||||
{
|
||||
if (!from->inuse)
|
||||
continue;
|
||||
if (from->solid == SOLID_NOT)
|
||||
continue;
|
||||
for (j=0 ; j<3 ; j++)
|
||||
eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
|
||||
if (VectorLength(eorg) > rad)
|
||||
continue;
|
||||
return from;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
G_PickTarget
|
||||
|
||||
Searches all active entities for the next one that holds
|
||||
the matching string at fieldofs (use the FOFS() macro) in the structure.
|
||||
|
||||
Searches beginning at the edict after from, or the beginning if NULL
|
||||
NULL will be returned if the end of the list is reached.
|
||||
|
||||
=============
|
||||
*/
|
||||
#define MAXCHOICES 8
|
||||
|
||||
edict_t *G_PickTarget (char *targetname)
|
||||
{
|
||||
edict_t *ent = NULL;
|
||||
int num_choices = 0;
|
||||
edict_t *choice[MAXCHOICES];
|
||||
|
||||
if (!targetname)
|
||||
{
|
||||
gi.dprintf("G_PickTarget called with NULL targetname\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while(1)
|
||||
{
|
||||
ent = G_Find (ent, FOFS(targetname), targetname);
|
||||
if (!ent)
|
||||
break;
|
||||
choice[num_choices++] = ent;
|
||||
if (num_choices == MAXCHOICES)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!num_choices)
|
||||
{
|
||||
gi.dprintf("G_PickTarget: target %s not found\n", targetname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return choice[rand() % num_choices];
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Think_Delay (edict_t *ent)
|
||||
{
|
||||
G_UseTargets (ent, ent->activator);
|
||||
G_FreeEdict (ent);
|
||||
}
|
||||
|
||||
/*
|
||||
==============================
|
||||
G_UseTargets
|
||||
|
||||
the global "activator" should be set to the entity that initiated the firing.
|
||||
|
||||
If self.delay is set, a DelayedUse entity will be created that will actually
|
||||
do the SUB_UseTargets after that many seconds have passed.
|
||||
|
||||
Centerprints any self.message to the activator.
|
||||
|
||||
Search for (string)targetname in all entities that
|
||||
match (string)self.target and call their .use function
|
||||
|
||||
==============================
|
||||
*/
|
||||
void G_UseTargets (edict_t *ent, edict_t *activator)
|
||||
{
|
||||
edict_t *t;
|
||||
|
||||
//
|
||||
// check for a delay
|
||||
//
|
||||
if (ent->delay)
|
||||
{
|
||||
// create a temp object to fire at a later time
|
||||
t = G_Spawn();
|
||||
t->classname = "DelayedUse";
|
||||
t->nextthink = level.time + ent->delay;
|
||||
t->think = Think_Delay;
|
||||
t->activator = activator;
|
||||
if (!activator)
|
||||
gi.dprintf ("Think_Delay with no activator\n");
|
||||
t->message = ent->message;
|
||||
t->target = ent->target;
|
||||
t->killtarget = ent->killtarget;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// print the message
|
||||
//
|
||||
if ((ent->message) && !(activator->svflags & SVF_MONSTER))
|
||||
{
|
||||
safe_centerprintf (activator, "%s", ent->message);
|
||||
if (ent->noise_index)
|
||||
gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
|
||||
else
|
||||
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
||||
}
|
||||
|
||||
//
|
||||
// kill killtargets
|
||||
//
|
||||
if (ent->killtarget)
|
||||
{
|
||||
t = NULL;
|
||||
while ((t = G_Find (t, FOFS(targetname), ent->killtarget)))
|
||||
{
|
||||
G_FreeEdict (t);
|
||||
if (!ent->inuse)
|
||||
{
|
||||
gi.dprintf("entity was removed while using killtargets\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// fire targets
|
||||
//
|
||||
if (ent->target)
|
||||
{
|
||||
t = NULL;
|
||||
while ((t = G_Find (t, FOFS(targetname), ent->target)))
|
||||
{
|
||||
// doors fire area portals in a specific way
|
||||
if (!Q_stricmp(t->classname, "func_areaportal") &&
|
||||
(!Q_stricmp(ent->classname, "func_door") || !Q_stricmp(ent->classname, "func_door_rotating")))
|
||||
continue;
|
||||
|
||||
if (t == ent)
|
||||
{
|
||||
gi.dprintf ("WARNING: Entity used itself.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (t->use)
|
||||
t->use (t, ent, activator);
|
||||
}
|
||||
if (!ent->inuse)
|
||||
{
|
||||
gi.dprintf("entity was removed while using targets\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
TempVector
|
||||
|
||||
This is just a convenience function
|
||||
for making temporary vectors for function calls
|
||||
=============
|
||||
*/
|
||||
float *tv (float x, float y, float z)
|
||||
{
|
||||
static int index;
|
||||
static vec3_t vecs[8];
|
||||
float *v;
|
||||
|
||||
// use an array so that multiple tempvectors won't collide
|
||||
// for a while
|
||||
v = vecs[index];
|
||||
index = (index + 1)&7;
|
||||
|
||||
v[0] = x;
|
||||
v[1] = y;
|
||||
v[2] = z;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
VectorToString
|
||||
|
||||
This is just a convenience function
|
||||
for printing vectors
|
||||
=============
|
||||
*/
|
||||
char *vtos (vec3_t v)
|
||||
{
|
||||
static int index;
|
||||
static char str[8][32];
|
||||
char *s;
|
||||
|
||||
// use an array so that multiple vtos won't collide
|
||||
s = str[index];
|
||||
index = (index + 1)&7;
|
||||
|
||||
Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
vec3_t VEC_UP = {0, -1, 0};
|
||||
vec3_t MOVEDIR_UP = {0, 0, 1};
|
||||
vec3_t VEC_DOWN = {0, -2, 0};
|
||||
vec3_t MOVEDIR_DOWN = {0, 0, -1};
|
||||
|
||||
void G_SetMovedir (vec3_t angles, vec3_t movedir)
|
||||
{
|
||||
if (VectorCompare (angles, VEC_UP))
|
||||
{
|
||||
VectorCopy (MOVEDIR_UP, movedir);
|
||||
}
|
||||
else if (VectorCompare (angles, VEC_DOWN))
|
||||
{
|
||||
VectorCopy (MOVEDIR_DOWN, movedir);
|
||||
}
|
||||
else
|
||||
{
|
||||
AngleVectors (angles, movedir, NULL, NULL);
|
||||
}
|
||||
|
||||
VectorClear (angles);
|
||||
}
|
||||
|
||||
|
||||
float vectoyaw (vec3_t vec)
|
||||
{
|
||||
float yaw;
|
||||
|
||||
// FIXES HERE FROM 3.20 -FB
|
||||
if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0)
|
||||
{
|
||||
yaw = 0;
|
||||
if (vec[YAW] > 0)
|
||||
yaw = 90;
|
||||
else if (vec[YAW] < 0)
|
||||
yaw = -90;
|
||||
}
|
||||
// ^^^
|
||||
else
|
||||
{
|
||||
yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
|
||||
if (yaw < 0)
|
||||
yaw += 360;
|
||||
}
|
||||
|
||||
return yaw;
|
||||
}
|
||||
|
||||
|
||||
void vectoangles (vec3_t value1, vec3_t angles)
|
||||
{
|
||||
float forward;
|
||||
float yaw, pitch;
|
||||
|
||||
if (value1[1] == 0 && value1[0] == 0)
|
||||
{
|
||||
yaw = 0;
|
||||
if (value1[2] > 0)
|
||||
pitch = 90;
|
||||
else
|
||||
pitch = 270;
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXES HERE FROM 3.20 -FB
|
||||
// zucc changing casts to floats
|
||||
if (value1[0])
|
||||
yaw = (float) (atan2(value1[1], value1[0]) * 180 / M_PI);
|
||||
else if (value1[1] > 0)
|
||||
yaw = 90;
|
||||
else
|
||||
yaw = -90;
|
||||
// ^^^
|
||||
if (yaw < 0)
|
||||
yaw += 360;
|
||||
|
||||
forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]);
|
||||
pitch = (float) (atan2(value1[2], forward) * 180 / M_PI);
|
||||
if (pitch < 0)
|
||||
pitch += 360;
|
||||
}
|
||||
|
||||
angles[PITCH] = -pitch;
|
||||
angles[YAW] = yaw;
|
||||
angles[ROLL] = 0;
|
||||
}
|
||||
|
||||
char *G_CopyString (char *in)
|
||||
{
|
||||
char *out;
|
||||
|
||||
out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL);
|
||||
strcpy (out, in);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
void G_InitEdict (edict_t *e)
|
||||
{
|
||||
e->inuse = true;
|
||||
e->classname = "noclass";
|
||||
e->gravity = 1.0;
|
||||
e->s.number = e - g_edicts;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
G_Spawn
|
||||
|
||||
Either finds a free edict, or allocates a new one.
|
||||
Try to avoid reusing an entity that was recently freed, because it
|
||||
can cause the client to think the entity morphed into something else
|
||||
instead of being removed and recreated, which can cause interpolated
|
||||
angles and bad trails.
|
||||
=================
|
||||
*/
|
||||
edict_t *G_Spawn (void)
|
||||
{
|
||||
int i;
|
||||
edict_t *e;
|
||||
|
||||
e = &g_edicts[(int)maxclients->value+1];
|
||||
for ( i=maxclients->value+1 ; i<globals.num_edicts ; i++, e++)
|
||||
{
|
||||
// the first couple seconds of server time can involve a lot of
|
||||
// freeing and allocating, so relax the replacement policy
|
||||
if (!e->inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
|
||||
{
|
||||
G_InitEdict (e);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == game.maxentities)
|
||||
gi.error ("ED_Alloc: no free edicts");
|
||||
|
||||
globals.num_edicts++;
|
||||
G_InitEdict (e);
|
||||
return e;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
G_FreeEdict
|
||||
|
||||
Marks the edict as free
|
||||
=================
|
||||
*/
|
||||
void G_FreeEdict (edict_t *ed)
|
||||
{
|
||||
gi.unlinkentity (ed); // unlink from world
|
||||
|
||||
if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
|
||||
{
|
||||
// gi.dprintf("tried to free special edict\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memset (ed, 0, sizeof(*ed));
|
||||
ed->classname = "freed";
|
||||
ed->freetime = level.time;
|
||||
ed->inuse = false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
G_TouchTriggers
|
||||
|
||||
============
|
||||
*/
|
||||
void G_TouchTriggers (edict_t *ent)
|
||||
{
|
||||
int i, num;
|
||||
edict_t *touch[MAX_EDICTS], *hit;
|
||||
|
||||
// dead things don't activate triggers!
|
||||
if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
|
||||
return;
|
||||
|
||||
num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
|
||||
, MAX_EDICTS, AREA_TRIGGERS);
|
||||
|
||||
// be careful, it is possible to have an entity in this
|
||||
// list removed before we get to it (killtriggered)
|
||||
for (i=0 ; i<num ; i++)
|
||||
{
|
||||
hit = touch[i];
|
||||
if (!hit->inuse)
|
||||
continue;
|
||||
if (!hit->touch)
|
||||
continue;
|
||||
hit->touch (hit, ent, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
G_TouchSolids
|
||||
|
||||
Call after linking a new trigger in during gameplay
|
||||
to force all entities it covers to immediately touch it
|
||||
============
|
||||
*/
|
||||
void G_TouchSolids (edict_t *ent)
|
||||
{
|
||||
int i, num;
|
||||
edict_t *touch[MAX_EDICTS], *hit;
|
||||
|
||||
num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
|
||||
, MAX_EDICTS, AREA_SOLID);
|
||||
|
||||
// be careful, it is possible to have an entity in this
|
||||
// list removed before we get to it (killtriggered)
|
||||
for (i=0 ; i<num ; i++)
|
||||
{
|
||||
hit = touch[i];
|
||||
if (!hit->inuse)
|
||||
continue;
|
||||
if (ent->touch)
|
||||
ent->touch (hit, ent, NULL, NULL);
|
||||
if (!ent->inuse)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
Kill box
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
=================
|
||||
KillBox
|
||||
|
||||
Kills all entities that would touch the proposed new positioning
|
||||
of ent. Ent should be unlinked before calling this!
|
||||
=================
|
||||
*/
|
||||
qboolean KillBox (edict_t *ent)
|
||||
{
|
||||
trace_t tr;
|
||||
|
||||
while (1)
|
||||
{
|
||||
tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID);
|
||||
if (!tr.ent)
|
||||
break;
|
||||
|
||||
// nail it
|
||||
T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
|
||||
|
||||
// if we didn't kill it, fail
|
||||
if (tr.ent->solid)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // all clear
|
||||
}
|
1629
g_weapon.c
Normal file
1629
g_weapon.c
Normal file
File diff suppressed because it is too large
Load diff
216
game.h
Normal file
216
game.h
Normal file
|
@ -0,0 +1,216 @@
|
|||
|
||||
// game.h -- game dll information visible to server
|
||||
|
||||
#define GAME_API_VERSION 3
|
||||
|
||||
// edict->svflags
|
||||
|
||||
#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects
|
||||
#define SVF_DEADMONSTER 0x00000002 // treat as CONTENTS_DEADMONSTER for collision
|
||||
#define SVF_MONSTER 0x00000004 // treat as CONTENTS_MONSTER for collision
|
||||
|
||||
// edict->solid values
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SOLID_NOT, // no interaction with other objects
|
||||
SOLID_TRIGGER, // only touch when inside, after moving
|
||||
SOLID_BBOX, // touch on edge
|
||||
SOLID_BSP // bsp clip, touch on edge
|
||||
} solid_t;
|
||||
|
||||
//===============================================================
|
||||
|
||||
// link_t is only used for entity area links now
|
||||
typedef struct link_s
|
||||
{
|
||||
struct link_s *prev, *next;
|
||||
} link_t;
|
||||
|
||||
#define MAX_ENT_CLUSTERS 16
|
||||
|
||||
|
||||
typedef struct edict_s edict_t;
|
||||
typedef struct gclient_s gclient_t;
|
||||
|
||||
|
||||
#ifndef GAME_INCLUDE
|
||||
|
||||
struct gclient_s
|
||||
{
|
||||
player_state_t ps; // communicated by server to clients
|
||||
int ping;
|
||||
// the game dll can add anything it wants after
|
||||
// this point in the structure
|
||||
};
|
||||
|
||||
|
||||
struct edict_s
|
||||
{
|
||||
entity_state_t s;
|
||||
struct gclient_s *client;
|
||||
qboolean inuse;
|
||||
int linkcount;
|
||||
|
||||
// FIXME: move these fields to a server private sv_entity_t
|
||||
link_t area; // linked to a division node or leaf
|
||||
|
||||
int num_clusters; // if -1, use headnode instead
|
||||
int clusternums[MAX_ENT_CLUSTERS];
|
||||
int headnode; // unused if num_clusters != -1
|
||||
int areanum, areanum2;
|
||||
|
||||
//================================
|
||||
|
||||
int svflags; // SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc
|
||||
vec3_t mins, maxs;
|
||||
vec3_t absmin, absmax, size;
|
||||
solid_t solid;
|
||||
int clipmask;
|
||||
edict_t *owner;
|
||||
|
||||
// the game dll can add anything it wants after
|
||||
// this point in the structure
|
||||
};
|
||||
|
||||
#endif // GAME_INCLUDE
|
||||
|
||||
//===============================================================
|
||||
|
||||
//
|
||||
// functions provided by the main engine
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
// special messages
|
||||
void (*bprintf) (int printlevel, char *fmt, ...);
|
||||
void (*dprintf) (char *fmt, ...);
|
||||
void (*cprintf) (edict_t *ent, int printlevel, char *fmt, ...);
|
||||
void (*centerprintf) (edict_t *ent, char *fmt, ...);
|
||||
void (*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs);
|
||||
void (*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs);
|
||||
|
||||
// config strings hold all the index strings, the lightstyles,
|
||||
// and misc data like the sky definition and cdtrack.
|
||||
// All of the current configstrings are sent to clients when
|
||||
// they connect, and changes are sent to all connected clients.
|
||||
void (*configstring) (int num, char *string);
|
||||
|
||||
void (*error) (char *fmt, ...);
|
||||
|
||||
// the *index functions create configstrings and some internal server state
|
||||
int (*modelindex) (char *name);
|
||||
int (*soundindex) (char *name);
|
||||
int (*imageindex) (char *name);
|
||||
|
||||
void (*setmodel) (edict_t *ent, char *name);
|
||||
|
||||
// collision detection
|
||||
trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask);
|
||||
int (*pointcontents) (vec3_t point);
|
||||
qboolean (*inPVS) (vec3_t p1, vec3_t p2);
|
||||
qboolean (*inPHS) (vec3_t p1, vec3_t p2);
|
||||
void (*SetAreaPortalState) (int portalnum, qboolean open);
|
||||
qboolean (*AreasConnected) (int area1, int area2);
|
||||
|
||||
// an entity will never be sent to a client or used for collision
|
||||
// if it is not passed to linkentity. If the size, position, or
|
||||
// solidity changes, it must be relinked.
|
||||
void (*linkentity) (edict_t *ent);
|
||||
void (*unlinkentity) (edict_t *ent); // call before removing an interactive edict
|
||||
int (*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype);
|
||||
void (*Pmove) (pmove_t *pmove); // player movement code common with client prediction
|
||||
|
||||
// network messaging
|
||||
void (*multicast) (vec3_t origin, multicast_t to);
|
||||
void (*unicast) (edict_t *ent, qboolean reliable);
|
||||
void (*WriteChar) (int c);
|
||||
void (*WriteByte) (int c);
|
||||
void (*WriteShort) (int c);
|
||||
void (*WriteLong) (int c);
|
||||
void (*WriteFloat) (float f);
|
||||
void (*WriteString) (char *s);
|
||||
void (*WritePosition) (vec3_t pos); // some fractional bits
|
||||
void (*WriteDir) (vec3_t pos); // single byte encoded, very coarse
|
||||
void (*WriteAngle) (float f);
|
||||
|
||||
// managed memory allocation
|
||||
void *(*TagMalloc) (int size, int tag);
|
||||
void (*TagFree) (void *block);
|
||||
void (*FreeTags) (int tag);
|
||||
|
||||
// console variable interaction
|
||||
cvar_t *(*cvar) (char *var_name, char *value, int flags);
|
||||
cvar_t *(*cvar_set) (char *var_name, char *value);
|
||||
cvar_t *(*cvar_forceset) (char *var_name, char *value);
|
||||
|
||||
// ClientCommand and ServerCommand parameter access
|
||||
int (*argc) (void);
|
||||
char *(*argv) (int n);
|
||||
char *(*args) (void); // concatenation of all argv >= 1
|
||||
|
||||
// add commands to the server console as if they were typed in
|
||||
// for map changing, etc
|
||||
void (*AddCommandString) (char *text);
|
||||
|
||||
void (*DebugGraph) (float value, int color);
|
||||
} game_import_t;
|
||||
|
||||
//
|
||||
// functions exported by the game subsystem
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
int apiversion;
|
||||
|
||||
// the init function will only be called when a game starts,
|
||||
// not each time a level is loaded. Persistant data for clients
|
||||
// and the server can be allocated in init
|
||||
void (*Init) (void);
|
||||
void (*Shutdown) (void);
|
||||
|
||||
// each new level entered will cause a call to SpawnEntities
|
||||
void (*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint);
|
||||
|
||||
// Read/Write Game is for storing persistant cross level information
|
||||
// about the world state and the clients.
|
||||
// WriteGame is called every time a level is exited.
|
||||
// ReadGame is called on a loadgame.
|
||||
void (*WriteGame) (char *filename, qboolean autosave);
|
||||
void (*ReadGame) (char *filename);
|
||||
|
||||
// ReadLevel is called after the default map information has been
|
||||
// loaded with SpawnEntities
|
||||
void (*WriteLevel) (char *filename);
|
||||
void (*ReadLevel) (char *filename);
|
||||
|
||||
qboolean (*ClientConnect) (edict_t *ent, char *userinfo);
|
||||
void (*ClientBegin) (edict_t *ent);
|
||||
void (*ClientUserinfoChanged) (edict_t *ent, char *userinfo);
|
||||
void (*ClientDisconnect) (edict_t *ent);
|
||||
void (*ClientCommand) (edict_t *ent);
|
||||
void (*ClientThink) (edict_t *ent, usercmd_t *cmd);
|
||||
|
||||
void (*RunFrame) (void);
|
||||
|
||||
// ServerCommand will be called when an "sv <command>" command is issued on the
|
||||
// server console.
|
||||
// The game can issue gi.argc() / gi.argv() commands to get the rest
|
||||
// of the parameters
|
||||
void (*ServerCommand) (void);
|
||||
|
||||
//
|
||||
// global variables shared between game and server
|
||||
//
|
||||
|
||||
// The edict array is allocated in the game dll so it
|
||||
// can vary in size from one game to another.
|
||||
//
|
||||
// The size will be fixed when ge->Init() is called
|
||||
struct edict_s *edicts;
|
||||
int edict_size;
|
||||
int num_edicts; // current number, <= max_edicts
|
||||
int max_edicts;
|
||||
} game_export_t;
|
||||
|
||||
game_export_t *GetGameApi (game_import_t *import);
|
537
m_move.c
Normal file
537
m_move.c
Normal file
|
@ -0,0 +1,537 @@
|
|||
// m_move.c -- monster movement
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
#define STEPSIZE 18
|
||||
|
||||
/*
|
||||
=============
|
||||
M_CheckBottom
|
||||
|
||||
Returns false if any part of the bottom of the entity is off an edge that
|
||||
is not a staircase.
|
||||
|
||||
=============
|
||||
*/
|
||||
int c_yes, c_no;
|
||||
|
||||
qboolean M_CheckBottom (edict_t *ent)
|
||||
{
|
||||
vec3_t mins, maxs, start, stop;
|
||||
trace_t trace;
|
||||
int x, y;
|
||||
float mid, bottom;
|
||||
|
||||
VectorAdd (ent->s.origin, ent->mins, mins);
|
||||
VectorAdd (ent->s.origin, ent->maxs, maxs);
|
||||
|
||||
// if all of the points under the corners are solid world, don't bother
|
||||
// with the tougher checks
|
||||
// the corners must be within 16 of the midpoint
|
||||
start[2] = mins[2] - 1;
|
||||
for (x=0 ; x<=1 ; x++)
|
||||
for (y=0 ; y<=1 ; y++)
|
||||
{
|
||||
start[0] = x ? maxs[0] : mins[0];
|
||||
start[1] = y ? maxs[1] : mins[1];
|
||||
if (gi.pointcontents (start) != CONTENTS_SOLID)
|
||||
goto realcheck;
|
||||
}
|
||||
|
||||
c_yes++;
|
||||
return true; // we got out easy
|
||||
|
||||
realcheck:
|
||||
c_no++;
|
||||
//
|
||||
// check it for real...
|
||||
//
|
||||
start[2] = mins[2];
|
||||
|
||||
// the midpoint must be within 16 of the bottom
|
||||
start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
|
||||
start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
|
||||
stop[2] = start[2] - 2*STEPSIZE;
|
||||
trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
|
||||
|
||||
if (trace.fraction == 1.0)
|
||||
return false;
|
||||
mid = bottom = trace.endpos[2];
|
||||
|
||||
// the corners must be within 16 of the midpoint
|
||||
for (x=0 ; x<=1 ; x++)
|
||||
for (y=0 ; y<=1 ; y++)
|
||||
{
|
||||
start[0] = stop[0] = x ? maxs[0] : mins[0];
|
||||
start[1] = stop[1] = y ? maxs[1] : mins[1];
|
||||
|
||||
trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
|
||||
|
||||
if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
|
||||
bottom = trace.endpos[2];
|
||||
if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE)
|
||||
return false;
|
||||
}
|
||||
|
||||
c_yes++;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
SV_movestep
|
||||
|
||||
Called by monster program code.
|
||||
The move will be adjusted for slopes and stairs, but if the move isn't
|
||||
possible, no move is done, false is returned, and
|
||||
pr_global_struct->trace_normal is set to the normal of the blocking wall
|
||||
=============
|
||||
*/
|
||||
//FIXME since we need to test end position contents here, can we avoid doing
|
||||
//it again later in catagorize position?
|
||||
qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink)
|
||||
{
|
||||
float dz;
|
||||
vec3_t oldorg, neworg, end;
|
||||
trace_t trace;
|
||||
int i;
|
||||
float stepsize;
|
||||
vec3_t test;
|
||||
int contents;
|
||||
|
||||
// try the move
|
||||
VectorCopy (ent->s.origin, oldorg);
|
||||
VectorAdd (ent->s.origin, move, neworg);
|
||||
|
||||
// flying monsters don't step up
|
||||
if ( ent->flags & (FL_SWIM | FL_FLY) )
|
||||
{
|
||||
// try one move with vertical motion, then one without
|
||||
for (i=0 ; i<2 ; i++)
|
||||
{
|
||||
VectorAdd (ent->s.origin, move, neworg);
|
||||
if (i == 0 && ent->enemy)
|
||||
{
|
||||
if (!ent->goalentity)
|
||||
ent->goalentity = ent->enemy;
|
||||
dz = ent->s.origin[2] - ent->goalentity->s.origin[2];
|
||||
if (ent->goalentity->client)
|
||||
{
|
||||
if (dz > 40)
|
||||
neworg[2] -= 8;
|
||||
if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2)))
|
||||
if (dz < 30)
|
||||
neworg[2] += 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dz > 8)
|
||||
neworg[2] -= 8;
|
||||
else if (dz > 0)
|
||||
neworg[2] -= dz;
|
||||
else if (dz < -8)
|
||||
neworg[2] += 8;
|
||||
else
|
||||
neworg[2] += dz;
|
||||
}
|
||||
}
|
||||
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID);
|
||||
|
||||
// fly monsters don't enter water voluntarily
|
||||
if (ent->flags & FL_FLY)
|
||||
{
|
||||
if (!ent->waterlevel)
|
||||
{
|
||||
test[0] = trace.endpos[0];
|
||||
test[1] = trace.endpos[1];
|
||||
test[2] = trace.endpos[2] + ent->mins[2] + 1;
|
||||
contents = gi.pointcontents(test);
|
||||
if (contents & MASK_WATER)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// swim monsters don't exit water voluntarily
|
||||
if (ent->flags & FL_SWIM)
|
||||
{
|
||||
if (ent->waterlevel < 2)
|
||||
{
|
||||
test[0] = trace.endpos[0];
|
||||
test[1] = trace.endpos[1];
|
||||
test[2] = trace.endpos[2] + ent->mins[2] + 1;
|
||||
contents = gi.pointcontents(test);
|
||||
if (!(contents & MASK_WATER))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (trace.fraction == 1)
|
||||
{
|
||||
VectorCopy (trace.endpos, ent->s.origin);
|
||||
if (relink)
|
||||
{
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ent->enemy)
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// push down from a step height above the wished position
|
||||
if (!(ent->monsterinfo.aiflags & AI_NOSTEP))
|
||||
stepsize = STEPSIZE;
|
||||
else
|
||||
stepsize = 1;
|
||||
|
||||
neworg[2] += stepsize;
|
||||
VectorCopy (neworg, end);
|
||||
end[2] -= stepsize*2;
|
||||
|
||||
trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
|
||||
|
||||
if (trace.allsolid)
|
||||
return false;
|
||||
|
||||
if (trace.startsolid)
|
||||
{
|
||||
neworg[2] -= stepsize;
|
||||
trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
|
||||
if (trace.allsolid || trace.startsolid)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// don't go in to water
|
||||
if (ent->waterlevel == 0)
|
||||
{
|
||||
test[0] = trace.endpos[0];
|
||||
test[1] = trace.endpos[1];
|
||||
test[2] = trace.endpos[2] + ent->mins[2] + 1;
|
||||
contents = gi.pointcontents(test);
|
||||
|
||||
if (contents & MASK_WATER)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trace.fraction == 1)
|
||||
{
|
||||
// if monster had the ground pulled out, go ahead and fall
|
||||
if ( ent->flags & FL_PARTIALGROUND )
|
||||
{
|
||||
VectorAdd (ent->s.origin, move, ent->s.origin);
|
||||
if (relink)
|
||||
{
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
}
|
||||
ent->groundentity = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // walked off an edge
|
||||
}
|
||||
|
||||
// check point traces down for dangling corners
|
||||
VectorCopy (trace.endpos, ent->s.origin);
|
||||
|
||||
if (!M_CheckBottom (ent))
|
||||
{
|
||||
if ( ent->flags & FL_PARTIALGROUND )
|
||||
{ // entity had floor mostly pulled out from underneath it
|
||||
// and is trying to correct
|
||||
if (relink)
|
||||
{
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
VectorCopy (oldorg, ent->s.origin);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ent->flags & FL_PARTIALGROUND )
|
||||
{
|
||||
ent->flags &= ~FL_PARTIALGROUND;
|
||||
}
|
||||
ent->groundentity = trace.ent;
|
||||
ent->groundentity_linkcount = trace.ent->linkcount;
|
||||
|
||||
// the move is ok
|
||||
if (relink)
|
||||
{
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//============================================================================
|
||||
|
||||
/*
|
||||
===============
|
||||
M_ChangeYaw
|
||||
|
||||
===============
|
||||
*/
|
||||
void M_ChangeYaw (edict_t *ent)
|
||||
{
|
||||
float ideal;
|
||||
float current;
|
||||
float move;
|
||||
float speed;
|
||||
|
||||
current = anglemod(ent->s.angles[YAW]);
|
||||
ideal = ent->ideal_yaw;
|
||||
|
||||
if (current == ideal)
|
||||
return;
|
||||
|
||||
move = ideal - current;
|
||||
speed = ent->yaw_speed;
|
||||
if (ideal > current)
|
||||
{
|
||||
if (move >= 180)
|
||||
move = move - 360;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (move <= -180)
|
||||
move = move + 360;
|
||||
}
|
||||
if (move > 0)
|
||||
{
|
||||
if (move > speed)
|
||||
move = speed;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (move < -speed)
|
||||
move = -speed;
|
||||
}
|
||||
|
||||
ent->s.angles[YAW] = anglemod (current + move);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
======================
|
||||
SV_StepDirection
|
||||
|
||||
Turns to the movement direction, and walks the current distance if
|
||||
facing it.
|
||||
|
||||
======================
|
||||
*/
|
||||
qboolean SV_StepDirection (edict_t *ent, float yaw, float dist)
|
||||
{
|
||||
vec3_t move, oldorigin;
|
||||
float delta;
|
||||
|
||||
ent->ideal_yaw = yaw;
|
||||
M_ChangeYaw (ent);
|
||||
|
||||
yaw = yaw*M_PI*2 / 360;
|
||||
move[0] = cos(yaw)*dist;
|
||||
move[1] = sin(yaw)*dist;
|
||||
move[2] = 0;
|
||||
|
||||
VectorCopy (ent->s.origin, oldorigin);
|
||||
if (SV_movestep (ent, move, false))
|
||||
{
|
||||
delta = ent->s.angles[YAW] - ent->ideal_yaw;
|
||||
if (delta > 45 && delta < 315)
|
||||
{ // not turned far enough, so don't take the step
|
||||
VectorCopy (oldorigin, ent->s.origin);
|
||||
}
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
return true;
|
||||
}
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
======================
|
||||
SV_FixCheckBottom
|
||||
|
||||
======================
|
||||
*/
|
||||
void SV_FixCheckBottom (edict_t *ent)
|
||||
{
|
||||
ent->flags |= FL_PARTIALGROUND;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
SV_NewChaseDir
|
||||
|
||||
================
|
||||
*/
|
||||
#define DI_NODIR -1
|
||||
void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist)
|
||||
{
|
||||
float deltax,deltay;
|
||||
float d[3];
|
||||
float tdir, olddir, turnaround;
|
||||
|
||||
//FIXME: how did we get here with no enemy
|
||||
if (!enemy)
|
||||
return;
|
||||
|
||||
olddir = anglemod( (int)(actor->ideal_yaw/45)*45 );
|
||||
turnaround = anglemod(olddir - 180);
|
||||
|
||||
deltax = enemy->s.origin[0] - actor->s.origin[0];
|
||||
deltay = enemy->s.origin[1] - actor->s.origin[1];
|
||||
if (deltax>10)
|
||||
d[1]= 0;
|
||||
else if (deltax<-10)
|
||||
d[1]= 180;
|
||||
else
|
||||
d[1]= DI_NODIR;
|
||||
if (deltay<-10)
|
||||
d[2]= 270;
|
||||
else if (deltay>10)
|
||||
d[2]= 90;
|
||||
else
|
||||
d[2]= DI_NODIR;
|
||||
|
||||
// try direct route
|
||||
if (d[1] != DI_NODIR && d[2] != DI_NODIR)
|
||||
{
|
||||
if (d[1] == 0)
|
||||
tdir = d[2] == 90 ? 45 : 315;
|
||||
else
|
||||
tdir = d[2] == 90 ? 135 : 215;
|
||||
|
||||
if (tdir != turnaround && SV_StepDirection(actor, tdir, dist))
|
||||
return;
|
||||
}
|
||||
|
||||
// try other directions
|
||||
if ( ((rand()&3) & 1) || abs(deltay)>abs(deltax))
|
||||
{
|
||||
tdir=d[1];
|
||||
d[1]=d[2];
|
||||
d[2]=tdir;
|
||||
}
|
||||
|
||||
if (d[1]!=DI_NODIR && d[1]!=turnaround
|
||||
&& SV_StepDirection(actor, d[1], dist))
|
||||
return;
|
||||
|
||||
if (d[2]!=DI_NODIR && d[2]!=turnaround
|
||||
&& SV_StepDirection(actor, d[2], dist))
|
||||
return;
|
||||
|
||||
/* there is no direct path to the player, so pick another direction */
|
||||
|
||||
if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist))
|
||||
return;
|
||||
|
||||
if (rand()&1) /*randomly determine direction of search*/
|
||||
{
|
||||
for (tdir=0 ; tdir<=315 ; tdir += 45)
|
||||
if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (tdir=315 ; tdir >=0 ; tdir -= 45)
|
||||
if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
|
||||
return;
|
||||
}
|
||||
|
||||
if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) )
|
||||
return;
|
||||
|
||||
actor->ideal_yaw = olddir; // can't move
|
||||
|
||||
// if a bridge was pulled out from underneath a monster, it may not have
|
||||
// a valid standing position at all
|
||||
|
||||
if (!M_CheckBottom (actor))
|
||||
SV_FixCheckBottom (actor);
|
||||
}
|
||||
|
||||
/*
|
||||
======================
|
||||
SV_CloseEnough
|
||||
|
||||
======================
|
||||
*/
|
||||
qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0 ; i<3 ; i++)
|
||||
{
|
||||
if (goal->absmin[i] > ent->absmax[i] + dist)
|
||||
return false;
|
||||
if (goal->absmax[i] < ent->absmin[i] - dist)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
======================
|
||||
M_MoveToGoal
|
||||
======================
|
||||
*/
|
||||
void M_MoveToGoal (edict_t *ent, float dist)
|
||||
{
|
||||
edict_t *goal;
|
||||
|
||||
goal = ent->goalentity;
|
||||
|
||||
if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
|
||||
return;
|
||||
|
||||
// if the next step hits the enemy, return immediately
|
||||
if (ent->enemy && SV_CloseEnough (ent, ent->enemy, dist) )
|
||||
return;
|
||||
|
||||
// bump around...
|
||||
if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist))
|
||||
{
|
||||
if (ent->inuse)
|
||||
SV_NewChaseDir (ent, goal, dist);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
M_walkmove
|
||||
===============
|
||||
*/
|
||||
qboolean M_walkmove (edict_t *ent, float yaw, float dist)
|
||||
{
|
||||
vec3_t move;
|
||||
|
||||
if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
|
||||
return false;
|
||||
|
||||
yaw = yaw*M_PI*2 / 360;
|
||||
|
||||
move[0] = cos(yaw)*dist;
|
||||
move[1] = sin(yaw)*dist;
|
||||
move[2] = 0;
|
||||
|
||||
return SV_movestep(ent, move, true);
|
||||
}
|
205
m_player.h
Normal file
205
m_player.h
Normal file
|
@ -0,0 +1,205 @@
|
|||
// G:\quake2\baseq2\models/player_x/frames
|
||||
|
||||
// This file generated by qdata - Do NOT Modify
|
||||
|
||||
#define FRAME_stand01 0
|
||||
#define FRAME_stand02 1
|
||||
#define FRAME_stand03 2
|
||||
#define FRAME_stand04 3
|
||||
#define FRAME_stand05 4
|
||||
#define FRAME_stand06 5
|
||||
#define FRAME_stand07 6
|
||||
#define FRAME_stand08 7
|
||||
#define FRAME_stand09 8
|
||||
#define FRAME_stand10 9
|
||||
#define FRAME_stand11 10
|
||||
#define FRAME_stand12 11
|
||||
#define FRAME_stand13 12
|
||||
#define FRAME_stand14 13
|
||||
#define FRAME_stand15 14
|
||||
#define FRAME_stand16 15
|
||||
#define FRAME_stand17 16
|
||||
#define FRAME_stand18 17
|
||||
#define FRAME_stand19 18
|
||||
#define FRAME_stand20 19
|
||||
#define FRAME_stand21 20
|
||||
#define FRAME_stand22 21
|
||||
#define FRAME_stand23 22
|
||||
#define FRAME_stand24 23
|
||||
#define FRAME_stand25 24
|
||||
#define FRAME_stand26 25
|
||||
#define FRAME_stand27 26
|
||||
#define FRAME_stand28 27
|
||||
#define FRAME_stand29 28
|
||||
#define FRAME_stand30 29
|
||||
#define FRAME_stand31 30
|
||||
#define FRAME_stand32 31
|
||||
#define FRAME_stand33 32
|
||||
#define FRAME_stand34 33
|
||||
#define FRAME_stand35 34
|
||||
#define FRAME_stand36 35
|
||||
#define FRAME_stand37 36
|
||||
#define FRAME_stand38 37
|
||||
#define FRAME_stand39 38
|
||||
#define FRAME_stand40 39
|
||||
#define FRAME_run1 40
|
||||
#define FRAME_run2 41
|
||||
#define FRAME_run3 42
|
||||
#define FRAME_run4 43
|
||||
#define FRAME_run5 44
|
||||
#define FRAME_run6 45
|
||||
#define FRAME_attack1 46
|
||||
#define FRAME_attack2 47
|
||||
#define FRAME_attack3 48
|
||||
#define FRAME_attack4 49
|
||||
#define FRAME_attack5 50
|
||||
#define FRAME_attack6 51
|
||||
#define FRAME_attack7 52
|
||||
#define FRAME_attack8 53
|
||||
#define FRAME_pain101 54
|
||||
#define FRAME_pain102 55
|
||||
#define FRAME_pain103 56
|
||||
#define FRAME_pain104 57
|
||||
#define FRAME_pain201 58
|
||||
#define FRAME_pain202 59
|
||||
#define FRAME_pain203 60
|
||||
#define FRAME_pain204 61
|
||||
#define FRAME_pain301 62
|
||||
#define FRAME_pain302 63
|
||||
#define FRAME_pain303 64
|
||||
#define FRAME_pain304 65
|
||||
#define FRAME_jump1 66
|
||||
#define FRAME_jump2 67
|
||||
#define FRAME_jump3 68
|
||||
#define FRAME_jump4 69
|
||||
#define FRAME_jump5 70
|
||||
#define FRAME_jump6 71
|
||||
#define FRAME_flip01 72
|
||||
#define FRAME_flip02 73
|
||||
#define FRAME_flip03 74
|
||||
#define FRAME_flip04 75
|
||||
#define FRAME_flip05 76
|
||||
#define FRAME_flip06 77
|
||||
#define FRAME_flip07 78
|
||||
#define FRAME_flip08 79
|
||||
#define FRAME_flip09 80
|
||||
#define FRAME_flip10 81
|
||||
#define FRAME_flip11 82
|
||||
#define FRAME_flip12 83
|
||||
#define FRAME_salute01 84
|
||||
#define FRAME_salute02 85
|
||||
#define FRAME_salute03 86
|
||||
#define FRAME_salute04 87
|
||||
#define FRAME_salute05 88
|
||||
#define FRAME_salute06 89
|
||||
#define FRAME_salute07 90
|
||||
#define FRAME_salute08 91
|
||||
#define FRAME_salute09 92
|
||||
#define FRAME_salute10 93
|
||||
#define FRAME_salute11 94
|
||||
#define FRAME_taunt01 95
|
||||
#define FRAME_taunt02 96
|
||||
#define FRAME_taunt03 97
|
||||
#define FRAME_taunt04 98
|
||||
#define FRAME_taunt05 99
|
||||
#define FRAME_taunt06 100
|
||||
#define FRAME_taunt07 101
|
||||
#define FRAME_taunt08 102
|
||||
#define FRAME_taunt09 103
|
||||
#define FRAME_taunt10 104
|
||||
#define FRAME_taunt11 105
|
||||
#define FRAME_taunt12 106
|
||||
#define FRAME_taunt13 107
|
||||
#define FRAME_taunt14 108
|
||||
#define FRAME_taunt15 109
|
||||
#define FRAME_taunt16 110
|
||||
#define FRAME_taunt17 111
|
||||
#define FRAME_wave01 112
|
||||
#define FRAME_wave02 113
|
||||
#define FRAME_wave03 114
|
||||
#define FRAME_wave04 115
|
||||
#define FRAME_wave05 116
|
||||
#define FRAME_wave06 117
|
||||
#define FRAME_wave07 118
|
||||
#define FRAME_wave08 119
|
||||
#define FRAME_wave09 120
|
||||
#define FRAME_wave10 121
|
||||
#define FRAME_wave11 122
|
||||
#define FRAME_point01 123
|
||||
#define FRAME_point02 124
|
||||
#define FRAME_point03 125
|
||||
#define FRAME_point04 126
|
||||
#define FRAME_point05 127
|
||||
#define FRAME_point06 128
|
||||
#define FRAME_point07 129
|
||||
#define FRAME_point08 130
|
||||
#define FRAME_point09 131
|
||||
#define FRAME_point10 132
|
||||
#define FRAME_point11 133
|
||||
#define FRAME_point12 134
|
||||
#define FRAME_crstnd01 135
|
||||
#define FRAME_crstnd02 136
|
||||
#define FRAME_crstnd03 137
|
||||
#define FRAME_crstnd04 138
|
||||
#define FRAME_crstnd05 139
|
||||
#define FRAME_crstnd06 140
|
||||
#define FRAME_crstnd07 141
|
||||
#define FRAME_crstnd08 142
|
||||
#define FRAME_crstnd09 143
|
||||
#define FRAME_crstnd10 144
|
||||
#define FRAME_crstnd11 145
|
||||
#define FRAME_crstnd12 146
|
||||
#define FRAME_crstnd13 147
|
||||
#define FRAME_crstnd14 148
|
||||
#define FRAME_crstnd15 149
|
||||
#define FRAME_crstnd16 150
|
||||
#define FRAME_crstnd17 151
|
||||
#define FRAME_crstnd18 152
|
||||
#define FRAME_crstnd19 153
|
||||
#define FRAME_crwalk1 154
|
||||
#define FRAME_crwalk2 155
|
||||
#define FRAME_crwalk3 156
|
||||
#define FRAME_crwalk4 157
|
||||
#define FRAME_crwalk5 158
|
||||
#define FRAME_crwalk6 159
|
||||
#define FRAME_crattak1 160
|
||||
#define FRAME_crattak2 161
|
||||
#define FRAME_crattak3 162
|
||||
#define FRAME_crattak4 163
|
||||
#define FRAME_crattak5 164
|
||||
#define FRAME_crattak6 165
|
||||
#define FRAME_crattak7 166
|
||||
#define FRAME_crattak8 167
|
||||
#define FRAME_crattak9 168
|
||||
#define FRAME_crpain1 169
|
||||
#define FRAME_crpain2 170
|
||||
#define FRAME_crpain3 171
|
||||
#define FRAME_crpain4 172
|
||||
#define FRAME_crdeath1 173
|
||||
#define FRAME_crdeath2 174
|
||||
#define FRAME_crdeath3 175
|
||||
#define FRAME_crdeath4 176
|
||||
#define FRAME_crdeath5 177
|
||||
#define FRAME_death101 178
|
||||
#define FRAME_death102 179
|
||||
#define FRAME_death103 180
|
||||
#define FRAME_death104 181
|
||||
#define FRAME_death105 182
|
||||
#define FRAME_death106 183
|
||||
#define FRAME_death201 184
|
||||
#define FRAME_death202 185
|
||||
#define FRAME_death203 186
|
||||
#define FRAME_death204 187
|
||||
#define FRAME_death205 188
|
||||
#define FRAME_death206 189
|
||||
#define FRAME_death301 190
|
||||
#define FRAME_death302 191
|
||||
#define FRAME_death303 192
|
||||
#define FRAME_death304 193
|
||||
#define FRAME_death305 194
|
||||
#define FRAME_death306 195
|
||||
#define FRAME_death307 196
|
||||
#define FRAME_death308 197
|
||||
|
||||
#define MODEL_SCALE 1.000000
|
||||
|
3303
p_client.c
Normal file
3303
p_client.c
Normal file
File diff suppressed because it is too large
Load diff
713
p_hud.c
Normal file
713
p_hud.c
Normal file
|
@ -0,0 +1,713 @@
|
|||
#include "g_local.h"
|
||||
|
||||
|
||||
|
||||
/*
|
||||
======================================================================
|
||||
|
||||
INTERMISSION
|
||||
|
||||
======================================================================
|
||||
*/
|
||||
|
||||
void MoveClientToIntermission (edict_t *ent)
|
||||
{
|
||||
if (deathmatch->value || coop->value)
|
||||
{
|
||||
ent->client->showscores = true;
|
||||
ent->client->scoreboardnum = 1;
|
||||
}
|
||||
VectorCopy (level.intermission_origin, ent->s.origin);
|
||||
ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8;
|
||||
ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8;
|
||||
ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8;
|
||||
VectorCopy (level.intermission_angle, ent->client->ps.viewangles);
|
||||
ent->client->ps.pmove.pm_type = PM_FREEZE;
|
||||
ent->client->ps.gunindex = 0;
|
||||
ent->client->ps.blend[3] = 0;
|
||||
ent->client->ps.rdflags &= ~RDF_UNDERWATER;
|
||||
|
||||
// clean up powerup info
|
||||
ent->client->quad_framenum = 0;
|
||||
ent->client->invincible_framenum = 0;
|
||||
ent->client->breather_framenum = 0;
|
||||
ent->client->enviro_framenum = 0;
|
||||
ent->client->grenade_blew_up = false;
|
||||
ent->client->grenade_time = 0;
|
||||
|
||||
ent->viewheight = 0;
|
||||
ent->s.modelindex = 0;
|
||||
ent->s.modelindex2 = 0;
|
||||
ent->s.modelindex3 = 0;
|
||||
ent->s.modelindex = 0;
|
||||
ent->s.effects = 0;
|
||||
ent->s.sound = 0;
|
||||
ent->solid = SOLID_NOT;
|
||||
|
||||
//FIREBLADE
|
||||
ent->client->resp.sniper_mode = SNIPER_1X;
|
||||
ent->client->desired_fov = 90;
|
||||
ent->client->ps.fov = 90;
|
||||
ent->client->ps.stats[STAT_SNIPER_ICON] = 0;
|
||||
//FIREBLADE
|
||||
|
||||
// add the layout
|
||||
|
||||
if (deathmatch->value || coop->value)
|
||||
{
|
||||
//RiEvEr
|
||||
if( !(Q_stricmp(ent->classname, "bot") ) )
|
||||
return;
|
||||
//R
|
||||
DeathmatchScoreboardMessage (ent, NULL);
|
||||
gi.unicast (ent, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void BeginIntermission (edict_t *targ)
|
||||
{
|
||||
int i, n;
|
||||
edict_t *ent, *client;
|
||||
|
||||
if (level.intermissiontime)
|
||||
return; // already activated
|
||||
|
||||
//FIREBLADE
|
||||
if (teamplay->value)
|
||||
TallyEndOfLevelTeamScores();
|
||||
//FIREBLADE
|
||||
|
||||
game.autosaved = false;
|
||||
|
||||
// respawn any dead clients
|
||||
for (i=0 ; i<maxclients->value ; i++)
|
||||
{
|
||||
client = g_edicts + 1 + i;
|
||||
if (!client->inuse)
|
||||
continue;
|
||||
if (client->health <= 0)
|
||||
respawn(client);
|
||||
}
|
||||
|
||||
level.intermissiontime = level.time;
|
||||
level.changemap = targ->map;
|
||||
|
||||
if (strstr(level.changemap, "*"))
|
||||
{
|
||||
if (coop->value)
|
||||
{
|
||||
for (i=0 ; i<maxclients->value ; i++)
|
||||
{
|
||||
client = g_edicts + 1 + i;
|
||||
if (!client->inuse)
|
||||
continue;
|
||||
// strip players of all keys between units
|
||||
for (n = 0; n < MAX_ITEMS; n++)
|
||||
{
|
||||
if (itemlist[n].flags & IT_KEY)
|
||||
client->client->pers.inventory[n] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!deathmatch->value)
|
||||
{
|
||||
level.exitintermission = 1; // go immediately to the next level
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
level.exitintermission = 0;
|
||||
|
||||
// find an intermission spot
|
||||
ent = G_Find (NULL, FOFS(classname), "info_player_intermission");
|
||||
if (!ent)
|
||||
{ // the map creator forgot to put in an intermission point...
|
||||
ent = G_Find (NULL, FOFS(classname), "info_player_start");
|
||||
if (!ent)
|
||||
ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch");
|
||||
}
|
||||
else
|
||||
{ // chose one of four spots
|
||||
i = rand() & 3;
|
||||
while (i--)
|
||||
{
|
||||
ent = G_Find (ent, FOFS(classname), "info_player_intermission");
|
||||
if (!ent) // wrap around the list
|
||||
ent = G_Find (ent, FOFS(classname), "info_player_intermission");
|
||||
}
|
||||
}
|
||||
|
||||
VectorCopy (ent->s.origin, level.intermission_origin);
|
||||
VectorCopy (ent->s.angles, level.intermission_angle);
|
||||
|
||||
// move all clients to the intermission point
|
||||
for (i=0 ; i<maxclients->value ; i++)
|
||||
{
|
||||
client = g_edicts + 1 + i;
|
||||
if (!client->inuse)
|
||||
continue;
|
||||
MoveClientToIntermission (client);
|
||||
}
|
||||
|
||||
// AZEROV: Clear the team kills for everyone
|
||||
//safe_cprintf(NULL,PRINT_MEDIUM,"Resetting all team kills\n");
|
||||
for (i=1; i<=maxclients->value; i++)
|
||||
{
|
||||
edict_t *temp_ent;
|
||||
temp_ent = g_edicts + i;
|
||||
|
||||
if (!temp_ent->inuse || !temp_ent->client)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
temp_ent->client->team_wounds = 0;
|
||||
temp_ent->client->team_kills = 0;
|
||||
}
|
||||
// AZEROV
|
||||
}
|
||||
|
||||
void A_ScoreboardMessage (edict_t *ent, edict_t *killer);
|
||||
|
||||
/*
|
||||
==================
|
||||
DeathmatchScoreboardMessage
|
||||
|
||||
==================
|
||||
*/
|
||||
void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer)
|
||||
{
|
||||
char entry[1024];
|
||||
char string[1400];
|
||||
int stringlength;
|
||||
int i, j, k;
|
||||
int sorted[MAX_CLIENTS];
|
||||
int sortedscores[MAX_CLIENTS];
|
||||
int score, total;
|
||||
int picnum;
|
||||
int x, y;
|
||||
gclient_t *cl;
|
||||
edict_t *cl_ent;
|
||||
char *tag;
|
||||
|
||||
// ACEBOT_ADD
|
||||
if (ent->is_bot)
|
||||
return;
|
||||
// ACEBOT_END
|
||||
|
||||
//FIREBLADE
|
||||
if (teamplay->value) {
|
||||
A_ScoreboardMessage (ent, killer);
|
||||
return;
|
||||
}
|
||||
//FIREBLADE
|
||||
|
||||
// sort the clients by score
|
||||
total = 0;
|
||||
for (i=0 ; i<game.maxclients ; i++)
|
||||
{
|
||||
cl_ent = g_edicts + 1 + i;
|
||||
if (!cl_ent->inuse)
|
||||
continue;
|
||||
score = game.clients[i].resp.score;
|
||||
for (j=0 ; j<total ; j++)
|
||||
{
|
||||
if (score > sortedscores[j])
|
||||
break;
|
||||
}
|
||||
for (k=total ; k>j ; k--)
|
||||
{
|
||||
sorted[k] = sorted[k-1];
|
||||
sortedscores[k] = sortedscores[k-1];
|
||||
}
|
||||
sorted[j] = i;
|
||||
sortedscores[j] = score;
|
||||
total++;
|
||||
}
|
||||
|
||||
// print level name and exit rules
|
||||
string[0] = 0;
|
||||
|
||||
stringlength = strlen(string);
|
||||
|
||||
// add the clients in sorted order
|
||||
if (total > 12)
|
||||
total = 12;
|
||||
|
||||
for (i=0 ; i<total ; i++)
|
||||
{
|
||||
cl = &game.clients[sorted[i]];
|
||||
cl_ent = g_edicts + 1 + sorted[i];
|
||||
|
||||
picnum = gi.imageindex ("i_fixme");
|
||||
x = (i>=6) ? 160 : 0;
|
||||
y = 32 + 32 * (i%6);
|
||||
|
||||
// add a dogtag
|
||||
if (cl_ent == ent)
|
||||
tag = "tag1";
|
||||
else if (cl_ent == killer)
|
||||
tag = "tag2";
|
||||
else
|
||||
tag = NULL;
|
||||
if (tag)
|
||||
{
|
||||
Com_sprintf (entry, sizeof(entry),
|
||||
"xv %i yv %i picn %s ",x+32, y, tag);
|
||||
j = strlen(entry);
|
||||
if (stringlength + j > 1024)
|
||||
break;
|
||||
strcpy (string + stringlength, entry);
|
||||
stringlength += j;
|
||||
}
|
||||
|
||||
// send the layout
|
||||
Com_sprintf (entry, sizeof(entry),
|
||||
"client %i %i %i %i %i %i ",
|
||||
x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600);
|
||||
j = strlen(entry);
|
||||
if (stringlength + j > 1024)
|
||||
break;
|
||||
strcpy (string + stringlength, entry);
|
||||
stringlength += j;
|
||||
}
|
||||
|
||||
gi.WriteByte (svc_layout);
|
||||
gi.WriteString (string);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
DeathmatchScoreboard
|
||||
|
||||
Draw instead of help message.
|
||||
Note that it isn't that hard to overflow the 1400 byte message limit!
|
||||
==================
|
||||
*/
|
||||
void DeathmatchScoreboard (edict_t *ent)
|
||||
{
|
||||
//RiEvEr
|
||||
if( !(Q_stricmp(ent->classname, "bot") ) )
|
||||
return;
|
||||
//R
|
||||
|
||||
DeathmatchScoreboardMessage (ent, ent->enemy);
|
||||
gi.unicast (ent, true);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
Cmd_Score_f
|
||||
|
||||
Display the scoreboard
|
||||
==================
|
||||
*/
|
||||
void Cmd_Score_f (edict_t *ent)
|
||||
{
|
||||
ent->client->showinventory = false;
|
||||
ent->client->showhelp = false;
|
||||
|
||||
//FIREBLADE
|
||||
if (ent->client->menu)
|
||||
PMenu_Close(ent);
|
||||
//FIREBLADE
|
||||
|
||||
if (!deathmatch->value && !coop->value)
|
||||
return;
|
||||
|
||||
if (ent->client->showscores)
|
||||
{
|
||||
//FIREBLADE
|
||||
if (teamplay->value && ent->client->scoreboardnum < 2) // toggle scoreboards...
|
||||
{
|
||||
ent->client->scoreboardnum++;
|
||||
DeathmatchScoreboard(ent);
|
||||
return;
|
||||
}
|
||||
//FIREBLADE
|
||||
|
||||
ent->client->showscores = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ent->client->showscores = true;
|
||||
//FIREBLADE
|
||||
ent->client->scoreboardnum = 1;
|
||||
//FIREBLADE
|
||||
DeathmatchScoreboard (ent);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
HelpComputer
|
||||
|
||||
Draw help computer.
|
||||
==================
|
||||
*/
|
||||
void HelpComputer (edict_t *ent)
|
||||
{
|
||||
char string[1024];
|
||||
char *sk;
|
||||
|
||||
if (skill->value == 0)
|
||||
sk = "easy";
|
||||
else if (skill->value == 1)
|
||||
sk = "medium";
|
||||
else if (skill->value == 2)
|
||||
sk = "hard";
|
||||
else
|
||||
sk = "hard+";
|
||||
|
||||
// send the layout
|
||||
Com_sprintf (string, sizeof(string),
|
||||
"xv 32 yv 8 picn help " // background
|
||||
"xv 202 yv 12 string2 \"%s\" " // skill
|
||||
"xv 0 yv 24 cstring2 \"%s\" " // level name
|
||||
"xv 0 yv 54 cstring2 \"%s\" " // help 1
|
||||
"xv 0 yv 110 cstring2 \"%s\" " // help 2
|
||||
"xv 50 yv 164 string2 \" kills goals secrets\" "
|
||||
"xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ",
|
||||
sk,
|
||||
level.level_name,
|
||||
game.helpmessage1,
|
||||
game.helpmessage2,
|
||||
level.killed_monsters, level.total_monsters,
|
||||
level.found_goals, level.total_goals,
|
||||
level.found_secrets, level.total_secrets);
|
||||
|
||||
gi.WriteByte (svc_layout);
|
||||
gi.WriteString (string);
|
||||
gi.unicast (ent, true);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
Cmd_Help_f
|
||||
|
||||
Display the current help message
|
||||
==================
|
||||
*/
|
||||
void Cmd_Help_f (edict_t *ent)
|
||||
{
|
||||
// this is for backwards compatability
|
||||
if (deathmatch->value)
|
||||
{
|
||||
Cmd_Score_f (ent);
|
||||
return;
|
||||
}
|
||||
|
||||
ent->client->showinventory = false;
|
||||
ent->client->showscores = false;
|
||||
|
||||
if (ent->client->showhelp && (ent->client->resp.game_helpchanged == game.helpchanged))
|
||||
{
|
||||
ent->client->showhelp = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ent->client->showhelp = true;
|
||||
ent->client->resp.helpchanged = 0;
|
||||
HelpComputer (ent);
|
||||
}
|
||||
|
||||
|
||||
//=======================================================================
|
||||
|
||||
/*
|
||||
===============
|
||||
G_SetStats
|
||||
|
||||
Rearranged for chase cam support -FB
|
||||
===============
|
||||
*/
|
||||
void G_SetStats (edict_t *ent)
|
||||
{
|
||||
gitem_t *item;
|
||||
int index, cells, index2;
|
||||
int power_armor_type;
|
||||
|
||||
if (!ent->client->chase_mode)
|
||||
{
|
||||
//
|
||||
// health
|
||||
//
|
||||
ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
|
||||
ent->client->ps.stats[STAT_HEALTH] = ent->health;
|
||||
|
||||
//
|
||||
// ammo (now clips really)
|
||||
//
|
||||
// zucc modified this to do clips instead
|
||||
if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */)
|
||||
{
|
||||
ent->client->ps.stats[STAT_CLIP_ICON] = 0;
|
||||
ent->client->ps.stats[STAT_CLIP] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
item = &itemlist[ent->client->ammo_index];
|
||||
ent->client->ps.stats[STAT_CLIP_ICON] = gi.imageindex (item->icon);
|
||||
ent->client->ps.stats[STAT_CLIP] = ent->client->pers.inventory[ent->client->ammo_index];
|
||||
}
|
||||
|
||||
// zucc display special item and special weapon
|
||||
if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SNIPER_NAME))] )
|
||||
ent->client->ps.stats[STAT_WEAPONS_ICON] = gi.imageindex(FindItem(SNIPER_NAME)->icon);
|
||||
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(M4_NAME))] )
|
||||
ent->client->ps.stats[STAT_WEAPONS_ICON] = gi.imageindex(FindItem(M4_NAME)->icon);
|
||||
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(MP5_NAME))] )
|
||||
ent->client->ps.stats[STAT_WEAPONS_ICON] = gi.imageindex(FindItem(MP5_NAME)->icon);
|
||||
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(M3_NAME))] )
|
||||
ent->client->ps.stats[STAT_WEAPONS_ICON] = gi.imageindex(FindItem(M3_NAME)->icon);
|
||||
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(HC_NAME))] )
|
||||
ent->client->ps.stats[STAT_WEAPONS_ICON] = gi.imageindex(FindItem(HC_NAME)->icon);
|
||||
else
|
||||
ent->client->ps.stats[STAT_WEAPONS_ICON] = 0;
|
||||
|
||||
if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(KEV_NAME))] )
|
||||
ent->client->ps.stats[STAT_ITEMS_ICON] = gi.imageindex(FindItem(KEV_NAME)->icon);
|
||||
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(LASER_NAME))] )
|
||||
ent->client->ps.stats[STAT_ITEMS_ICON] = gi.imageindex(FindItem(LASER_NAME)->icon);
|
||||
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SLIP_NAME))] )
|
||||
ent->client->ps.stats[STAT_ITEMS_ICON] = gi.imageindex(FindItem(SLIP_NAME)->icon);
|
||||
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SIL_NAME))] )
|
||||
ent->client->ps.stats[STAT_ITEMS_ICON] = gi.imageindex(FindItem(SIL_NAME)->icon);
|
||||
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(BAND_NAME))] )
|
||||
ent->client->ps.stats[STAT_ITEMS_ICON] = gi.imageindex(FindItem(BAND_NAME)->icon);
|
||||
else
|
||||
ent->client->ps.stats[STAT_ITEMS_ICON] = 0;
|
||||
|
||||
|
||||
// grenades remaining
|
||||
index2 = ITEM_INDEX(FindItem(GRENADE_NAME));
|
||||
if ( ent->client->pers.inventory[index2] )
|
||||
{
|
||||
ent->client->ps.stats[STAT_GRENADE_ICON] = gi.imageindex ("a_m61frag");
|
||||
ent->client->ps.stats[STAT_GRENADES] = ent->client->pers.inventory[index2];
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->client->ps.stats[STAT_GRENADE_ICON] = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// ammo by weapon
|
||||
//
|
||||
//
|
||||
if ( ent->client->pers.weapon )
|
||||
{
|
||||
switch ( ent->client->curr_weap )
|
||||
{
|
||||
case MK23_NUM:
|
||||
{
|
||||
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_bullets");
|
||||
ent->client->ps.stats[STAT_AMMO] = ent->client->mk23_rds;
|
||||
break;
|
||||
}
|
||||
case MP5_NUM:
|
||||
{
|
||||
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_bullets");
|
||||
ent->client->ps.stats[STAT_AMMO] = ent->client->mp5_rds;
|
||||
break;
|
||||
}
|
||||
case M4_NUM:
|
||||
{
|
||||
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_bullets");
|
||||
ent->client->ps.stats[STAT_AMMO] = ent->client->m4_rds;
|
||||
break;
|
||||
}
|
||||
case M3_NUM:
|
||||
{
|
||||
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_shells");
|
||||
ent->client->ps.stats[STAT_AMMO] = ent->client->shot_rds;
|
||||
break;
|
||||
}
|
||||
case HC_NUM:
|
||||
{
|
||||
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_shells");
|
||||
ent->client->ps.stats[STAT_AMMO] = ent->client->cannon_rds;
|
||||
break;
|
||||
}
|
||||
case SNIPER_NUM:
|
||||
{
|
||||
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_bullets");
|
||||
ent->client->ps.stats[STAT_AMMO] = ent->client->sniper_rds;
|
||||
break;
|
||||
}
|
||||
case DUAL_NUM:
|
||||
{
|
||||
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_bullets");
|
||||
ent->client->ps.stats[STAT_AMMO] = ent->client->dual_rds;
|
||||
break;
|
||||
}
|
||||
case KNIFE_NUM:
|
||||
{
|
||||
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("w_knife");
|
||||
ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ITEM_INDEX(FindItem (KNIFE_NAME))];
|
||||
break;
|
||||
}
|
||||
case GRENADE_NUM:
|
||||
{
|
||||
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_m61frag");
|
||||
ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ITEM_INDEX(FindItem (GRENADE_NAME))];
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
gi.dprintf("Failed to find weapon/icon for hud.\n");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// sniper mode icons
|
||||
//
|
||||
//if ( ent->client->sniper_mode )
|
||||
// safe_cprintf (ent, PRINT_HIGH, "Sniper Zoom set at %d.\n", ent->client->sniper_mode);
|
||||
|
||||
|
||||
if ( ent->client->resp.sniper_mode == SNIPER_1X
|
||||
|| ent->client->weaponstate == WEAPON_RELOADING
|
||||
|| ent->client->weaponstate == WEAPON_BUSY
|
||||
|| ent->client->no_sniper_display )
|
||||
ent->client->ps.stats[STAT_SNIPER_ICON] = 0;
|
||||
else if ( ent->client->resp.sniper_mode == SNIPER_2X )
|
||||
ent->client->ps.stats[STAT_SNIPER_ICON] = gi.imageindex ("scope2x");
|
||||
else if ( ent->client->resp.sniper_mode == SNIPER_4X )
|
||||
ent->client->ps.stats[STAT_SNIPER_ICON] = gi.imageindex ("scope4x");
|
||||
else if ( ent->client->resp.sniper_mode == SNIPER_6X )
|
||||
ent->client->ps.stats[STAT_SNIPER_ICON] = gi.imageindex ("scope6x");
|
||||
|
||||
//
|
||||
// armor
|
||||
//
|
||||
power_armor_type = PowerArmorType (ent);
|
||||
if (power_armor_type)
|
||||
{
|
||||
cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
|
||||
if (cells == 0)
|
||||
{ // ran out of cells for power armor
|
||||
ent->flags &= ~FL_POWER_ARMOR;
|
||||
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
|
||||
power_armor_type = 0;;
|
||||
}
|
||||
}
|
||||
|
||||
index = ArmorIndex (ent);
|
||||
if (power_armor_type && (!index || (level.framenum & 8) ) )
|
||||
{ // flash between power armor and other armor icon
|
||||
ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield");
|
||||
ent->client->ps.stats[STAT_ARMOR] = cells;
|
||||
}
|
||||
else if (index)
|
||||
{
|
||||
item = GetItemByIndex (index);
|
||||
ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon);
|
||||
ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
|
||||
ent->client->ps.stats[STAT_ARMOR] = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// pickup message
|
||||
//
|
||||
if (level.time > ent->client->pickup_msg_time)
|
||||
{
|
||||
ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
|
||||
ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// timers
|
||||
//
|
||||
if (ent->client->quad_framenum > level.framenum)
|
||||
{
|
||||
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad");
|
||||
ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10;
|
||||
}
|
||||
else if (ent->client->invincible_framenum > level.framenum)
|
||||
{
|
||||
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability");
|
||||
ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10;
|
||||
}
|
||||
else if (ent->client->enviro_framenum > level.framenum)
|
||||
{
|
||||
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit");
|
||||
ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10;
|
||||
}
|
||||
else if (ent->client->breather_framenum > level.framenum)
|
||||
{
|
||||
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather");
|
||||
ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->client->ps.stats[STAT_TIMER_ICON] = 0;
|
||||
ent->client->ps.stats[STAT_TIMER] = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// selected item
|
||||
//
|
||||
if (ent->client->pers.selected_item == -1)
|
||||
ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
|
||||
else
|
||||
ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon);
|
||||
|
||||
ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
|
||||
|
||||
//
|
||||
// frags
|
||||
//
|
||||
ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
|
||||
|
||||
//
|
||||
// help icon / current weapon if not shown
|
||||
//
|
||||
if (ent->client->resp.helpchanged && (level.framenum&8) )
|
||||
ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help");
|
||||
else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91)
|
||||
&& ent->client->pers.weapon)
|
||||
ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon);
|
||||
else
|
||||
ent->client->ps.stats[STAT_HELPICON] = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// layouts
|
||||
//
|
||||
ent->client->ps.stats[STAT_LAYOUTS] = 0;
|
||||
|
||||
if (deathmatch->value)
|
||||
{
|
||||
if (ent->client->pers.health <= 0 || level.intermissiontime
|
||||
|| ent->client->showscores)
|
||||
ent->client->ps.stats[STAT_LAYOUTS] |= 1;
|
||||
if (ent->client->showinventory && ent->client->pers.health > 0)
|
||||
ent->client->ps.stats[STAT_LAYOUTS] |= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ent->client->showscores || ent->client->showhelp)
|
||||
ent->client->ps.stats[STAT_LAYOUTS] |= 1;
|
||||
if (ent->client->showinventory && ent->client->pers.health > 0)
|
||||
ent->client->ps.stats[STAT_LAYOUTS] |= 2;
|
||||
}
|
||||
|
||||
SetIDView(ent);
|
||||
|
||||
//FIREBLADE
|
||||
if (teamplay->value)
|
||||
A_Scoreboard(ent);
|
||||
//FIREBLADE
|
||||
}
|
127
p_trail.c
Normal file
127
p_trail.c
Normal file
|
@ -0,0 +1,127 @@
|
|||
#include "g_local.h"
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
PLAYER TRAIL
|
||||
|
||||
==============================================================================
|
||||
|
||||
This is a circular list containing the a list of points of where
|
||||
the player has been recently. It is used by monsters for pursuit.
|
||||
|
||||
.origin the spot
|
||||
.owner forward link
|
||||
.aiment backward link
|
||||
*/
|
||||
|
||||
|
||||
#define TRAIL_LENGTH 8
|
||||
|
||||
edict_t *trail[TRAIL_LENGTH];
|
||||
int trail_head;
|
||||
qboolean trail_active = false;
|
||||
|
||||
#define NEXT(n) (((n) + 1) & (TRAIL_LENGTH - 1))
|
||||
#define PREV(n) (((n) - 1) & (TRAIL_LENGTH - 1))
|
||||
|
||||
|
||||
void PlayerTrail_Init (void)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (deathmatch->value /* FIXME || coop */)
|
||||
return;
|
||||
|
||||
for (n = 0; n < TRAIL_LENGTH; n++)
|
||||
{
|
||||
trail[n] = G_Spawn();
|
||||
trail[n]->classname = "player_trail";
|
||||
}
|
||||
|
||||
trail_head = 0;
|
||||
trail_active = true;
|
||||
}
|
||||
|
||||
|
||||
void PlayerTrail_Add (vec3_t spot)
|
||||
{
|
||||
vec3_t temp;
|
||||
|
||||
if (!trail_active)
|
||||
return;
|
||||
|
||||
VectorCopy (spot, trail[trail_head]->s.origin);
|
||||
|
||||
trail[trail_head]->timestamp = level.time;
|
||||
|
||||
VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp);
|
||||
trail[trail_head]->s.angles[1] = vectoyaw (temp);
|
||||
|
||||
trail_head = NEXT(trail_head);
|
||||
}
|
||||
|
||||
|
||||
void PlayerTrail_New (vec3_t spot)
|
||||
{
|
||||
if (!trail_active)
|
||||
return;
|
||||
|
||||
PlayerTrail_Init ();
|
||||
PlayerTrail_Add (spot);
|
||||
}
|
||||
|
||||
|
||||
edict_t *PlayerTrail_PickFirst (edict_t *self)
|
||||
{
|
||||
int marker;
|
||||
int n;
|
||||
|
||||
if (!trail_active)
|
||||
return NULL;
|
||||
|
||||
for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
|
||||
{
|
||||
if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
|
||||
marker = NEXT(marker);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (visible(self, trail[marker]))
|
||||
{
|
||||
return trail[marker];
|
||||
}
|
||||
|
||||
if (visible(self, trail[PREV(marker)]))
|
||||
{
|
||||
return trail[PREV(marker)];
|
||||
}
|
||||
|
||||
return trail[marker];
|
||||
}
|
||||
|
||||
edict_t *PlayerTrail_PickNext (edict_t *self)
|
||||
{
|
||||
int marker;
|
||||
int n;
|
||||
|
||||
if (!trail_active)
|
||||
return NULL;
|
||||
|
||||
for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
|
||||
{
|
||||
if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
|
||||
marker = NEXT(marker);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return trail[marker];
|
||||
}
|
||||
|
||||
edict_t *PlayerTrail_LastSpot (void)
|
||||
{
|
||||
return trail[PREV(trail_head)];
|
||||
}
|
4640
p_weapon.c
Normal file
4640
p_weapon.c
Normal file
File diff suppressed because it is too large
Load diff
1401
q_shared.c
Normal file
1401
q_shared.c
Normal file
File diff suppressed because it is too large
Load diff
1281
q_shared.h
Normal file
1281
q_shared.h
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue