commit 51e076c7ac8d36409cf2f76dbdf6e99459eff53f Author: Marco Hladik Date: Tue Nov 13 00:25:08 2018 -0800 Initial commit... diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..74a7ea6 --- /dev/null +++ b/Makefile @@ -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 diff --git a/a_cmds.c b/a_cmds.c new file mode 100644 index 0000000..6813a82 --- /dev/null +++ b/a_cmds.c @@ -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); + +} + diff --git a/a_doorkick.c b/a_doorkick.c new file mode 100644 index 0000000..4e1af3a --- /dev/null +++ b/a_doorkick.c @@ -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 ); +} diff --git a/a_game.c b/a_game.c new file mode 100644 index 0000000..9bbd4a2 --- /dev/null +++ b/a_game.c @@ -0,0 +1,1064 @@ +/* + * a_game.c + * General game code for Action (formerly Axshun). + * + * Zucchini (spikard@u.washington.edu) and Fireblade (ucs_brf@shsu.edu) + * (splat/bullethole/shell ejection code from original Action source) + */ + +#include "g_local.h" +#include "cgf_sfx_glass.h" + +#define MAX_MAP_ROTATION 1000 // just in case... +#define MAX_STR_LEN 1000 +#define MAX_TOTAL_MOTD_LINES 30 + +char team1_name[MAX_STR_LEN]; +char team2_name[MAX_STR_LEN]; +char team1_skin[MAX_STR_LEN]; +char team2_skin[MAX_STR_LEN]; +char team1_skin_index[MAX_STR_LEN + 30]; +char team2_skin_index[MAX_STR_LEN + 30]; +char *map_rotation[MAX_MAP_ROTATION]; +int num_maps, cur_map; +char motd_lines[MAX_TOTAL_MOTD_LINES][70]; +int motd_num_lines; + +/* + * ReadConfigFile() + * Config file format is backwards compatible with Action's, but doesn't need + * the "###" designator at end of sections. + * -Fireblade + */ +void ReadConfigFile() +{ + FILE *config_file; + char buf[MAX_STR_LEN]; + char reading_section[MAX_STR_LEN]; + char inipath[MAX_STR_LEN]; + int lines_into_section = -1; + cvar_t *ininame; + + ininame = gi.cvar("ininame", "action.ini", 0); + if (ininame->string && *(ininame->string)) + sprintf(inipath, "%s/%s", GAMEVERSION, ininame->string); + else + sprintf(inipath, "%s/%s", GAMEVERSION, "action.ini"); + + config_file = fopen(inipath, "r"); + if (config_file == NULL) + { + gi.dprintf("Unable to read %s\n", inipath); + return; + } + + while (fgets(buf, MAX_STR_LEN - 10, config_file) != NULL) + { + int bs; + bs = strlen(buf); + while (buf[bs-1] == '\r' || buf[bs-1] == '\n') + { + buf[bs-1] = 0; + bs--; + } + + if ((buf[0] == '/' && buf[1] == '/') || buf[0] == 0) + { + continue; + } + + if (buf[0] == '[') + { + char *p; + p = strchr(buf, ']'); + if (p == NULL) + continue; + *p = 0; + strcpy(reading_section, buf + 1); + lines_into_section = 0; + continue; + } + if (buf[0] == '#' && buf[1] == '#' && buf[2] == '#') + { + lines_into_section = -1; + continue; + } + if (lines_into_section > -1) + { + if (!strcmp(reading_section, "team1")) + { + if (lines_into_section == 0) + { + strcpy(team1_name, buf); + } + else if (lines_into_section == 1) + { + strcpy(team1_skin, buf); + } + } + else if (!strcmp(reading_section, "team2")) + { + if (lines_into_section == 0) + { + strcpy(team2_name, buf); + } + else if (lines_into_section == 1) + { + strcpy(team2_skin, buf); + } + } + else if (!strcmp(reading_section, "maplist")) + { + map_rotation[num_maps] = (char *)gi.TagMalloc(strlen(buf) + 1, TAG_GAME); + strcpy(map_rotation[num_maps], buf); + num_maps++; + } + lines_into_section++; + } + } + + sprintf(team1_skin_index, "../players/%s_i", team1_skin); + sprintf(team2_skin_index, "../players/%s_i", team2_skin); + cur_map = 0; + + fclose(config_file); +} + +void ReadMOTDFile() +{ + FILE *motd_file; + char buf[1000]; + int lbuf; + + motd_file = fopen(GAMEVERSION "/motd.txt", "r"); + if (motd_file == NULL) + return; + + motd_num_lines = 0; + while (fgets(buf, 900, motd_file) != NULL) + { + lbuf = strlen(buf); + while (buf[lbuf-1] == '\r' || buf[lbuf-1] == '\n') + { + buf[lbuf-1] = 0; + lbuf--; + } + + if (lbuf > 40) + buf[40] = 0; + + strcpy(motd_lines[motd_num_lines], buf); + + motd_num_lines++; + + if (motd_num_lines >= MAX_TOTAL_MOTD_LINES) + break; + } + + fclose(motd_file); +} + +void PrintMOTD(edict_t *ent) +{ + int mapnum, i, lines = 0; + int max_lines = MAX_TOTAL_MOTD_LINES; + char msg_buf[16384], *server_type; + + /* + * Welcome message + */ + + // 3 lines for version number & website (third blank) + strcpy(msg_buf, "Welcome to Action Quake v" ACTION_VERSION "\n" + "http://aq2.action-web.net/\n" + "\n"); + lines = 3; + + if (!skipmotd->value) + { + // Line for server hostname, if set + if (hostname->string && strlen(hostname->string) && strcmp(hostname->string, "unnamed")) + { + //strcat(msg_buf, "Running at: "); + strcat(msg_buf, hostname->string); + strcat(msg_buf, "\n"); + lines++; + } + + /* + * Server-specific settings information + */ + + // Line for game type + if (teamplay->value) + { + server_type = "teamplay"; + } + else + { + if ((int)dmflags->value & DF_MODELTEAMS) + server_type = "deathmatch (teams by model)"; + else if ((int)dmflags->value & DF_SKINTEAMS) + server_type = "deathmatch (teams by skin)"; + else + server_type = "deathmatch (no teams)"; + } + sprintf(msg_buf + strlen(msg_buf), + "Game type: %s\n", + server_type); + lines++; + + // Line for frag and time limit + if ((int)fraglimit->value) + sprintf(msg_buf + strlen(msg_buf), "Frag limit: %d", (int)fraglimit->value); + else + strcat(msg_buf, "Frag limit: none"); + if ((int)timelimit->value) + sprintf(msg_buf + strlen(msg_buf), " Time limit: %d\n", (int)timelimit->value); + else + strcat(msg_buf, " Time limit: none\n"); + lines++; + + // Teamplay: 3 lines for round limit/round time limit, and menu instructions + if (teamplay->value) + { + if ((int)roundlimit->value) + sprintf(msg_buf + strlen(msg_buf), "Round limit: %d", (int)roundlimit->value); + else + strcat(msg_buf, "Round limit: none"); + if ((int)roundtimelimit->value) + sprintf(msg_buf + strlen(msg_buf), " Round time limit: %d\n", (int)roundtimelimit->value); + else + strcat(msg_buf, " Round time limit: none\n"); + lines++; + } + + // Check for weird unique items/weapons settings, and inform with a line + if ((int)unique_weapons->value != 1 || + (int)unique_items->value != 1) + { + sprintf(msg_buf + strlen(msg_buf), "Max carry of special weaps: %d items: %d\n", + (int)unique_weapons->value, (int)unique_items->value); + lines++; + } + if (tgren->value > 0 || !(ir->value)) + { + char grenade_num[32]; + if (tgren->value > 0) + sprintf(grenade_num, "%d grenade%s", + (int)tgren->value, + (int)tgren->value == 1 ? "" : "s"); + sprintf(msg_buf + strlen(msg_buf), "Bandolier w/ %s%s%s\n", + !(ir->value) ? "no IR" : "", + (tgren->value > 0 && !(ir->value)) ? " & " : "", + tgren->value > 0 ? grenade_num : ""); + lines++; + } + if (allitem->value || allweapon->value) + { + sprintf(msg_buf + strlen(msg_buf), "Players receive %s%s%s\n", + allweapon->value ? "all weapons" : "", + (allweapon->value && allitem->value) ? " & " : "", + allitem->value ? "all items" : ""); + lines++; + } + if (limchasecam->value) + { + if ((int)limchasecam->value == 2) + sprintf(msg_buf + strlen(msg_buf), "Chase cam disallowed\n"); + else + sprintf(msg_buf + strlen(msg_buf), "Chase cam restricted\n"); + lines++; + } + if (teamplay->value && !((int)dmflags->value & DF_NO_FRIENDLY_FIRE)) + { + sprintf(msg_buf + strlen(msg_buf), "Friendly fire enabled\n"); + lines++; + } + + // Teamplay: 2 lines (one blank) for menu instructions + if (teamplay->value) + { + strcat(msg_buf, "\nHit tab to open the team selection menu\n"); + lines += 2; + } + + /* + * If actionmaps, put a blank line then the maps list + */ + + // hopefully no one will run enough maps to exceed the line limit, if so, oh well... -FB + if (actionmaps->value && num_maps > 0) + { + int chars_on_line, len_mr; + + strcat(msg_buf, "\nRunning the following maps in order:\n"); + lines += 2; + + chars_on_line = 0; + for (mapnum = 0; mapnum < num_maps; mapnum++) + { + len_mr = strlen(*(map_rotation + mapnum)); + if ((chars_on_line + len_mr + 2) > 39) + { + strcat(msg_buf, "\n"); + lines++; + if (lines >= max_lines) + break; + chars_on_line = 0; + } + strcat(msg_buf, *(map_rotation + mapnum)); + chars_on_line += len_mr; + if (mapnum < (num_maps - 1)) + { + strcat(msg_buf, ", "); + chars_on_line += 2; + } + } + + if (lines < max_lines) + { + strcat(msg_buf, "\n"); + lines++; + } + } + + if (motd_num_lines && lines < max_lines) + { + strcat(msg_buf, "\n"); + lines++; + } + } + + /* + * Insert action/motd.txt contents (whole MOTD gets truncated after 30 lines) + */ + + if (motd_num_lines) + { + if (lines < max_lines) + { + for (i = 0; i < motd_num_lines; i++) + { + strcat(msg_buf, motd_lines[i]); + strcat(msg_buf, "\n"); + lines++; + if (lines >= max_lines) + break; + } + } + } + + safe_centerprintf(ent, msg_buf); +} + +// stuffcmd: forces a player to execute a command. +void stuffcmd(edict_t *ent, char *c) +{ +// ACEBOT ADD + if( !stricmp( ent->classname,"bot")) + return; +// ACEBOT END + gi.WriteByte(svc_stufftext); + gi.WriteString(c); + gi.unicast(ent, true); +} + + + +/******************************************************************************** +* +* zucc: following are EjectBlooder, EjectShell, AddSplat, and AddDecal +* code. All from actionquake, some had to be modified to fit Axshun or fix +* bugs. +* +*/ + +int decals = 0; +int shells = 0; +int splats = 0; + +//blooder used for bleeding + +void BlooderDie(edict_t *self) +{ + G_FreeEdict(self); +} + +void BlooderTouch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + G_FreeEdict(self); +} + + +void EjectBlooder (edict_t *self, vec3_t start, vec3_t veloc ) +{ + edict_t *blooder; + vec3_t forward; + int spd = 0; + + blooder = G_Spawn(); + + VectorCopy (veloc, forward); + + VectorCopy (start , blooder->s.origin); + + + + spd = 0; + + + + VectorScale (forward, spd, blooder->velocity); + + blooder->solid = SOLID_NOT; + blooder->movetype = MOVETYPE_TOSS; + + blooder->s.modelindex = gi.modelindex("sprites/null.sp2"); + blooder->s.effects |= EF_GIB; + + blooder->owner = self; + blooder->touch = BlooderTouch; + blooder->nextthink = level.time + 3.2; + blooder->think = BlooderDie; + blooder->classname = "blooder"; + + + gi.linkentity (blooder); +} + + +// zucc - Adding EjectShell code from action quake, modified for Axshun. +/********* SHELL EJECTION **************/ + + + + +void ShellTouch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->owner->client->curr_weap == M3_NUM) + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/shellhit1.wav"), 1, ATTN_STATIC, 0); + else if (random() < 0.5) + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/tink1.wav"), 1, ATTN_STATIC, 0); + else + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/tink2.wav"), 1, ATTN_STATIC, 0); +} + +void ShellDie(edict_t *self) +{ + G_FreeEdict(self); + + --shells; +} + + +// zucc fixed this so it works with the sniper rifle and checks handedness +// had to add the toggle feature to handle the akimbos correctly, if 1 +// it sets up for ejecting the shell from the left akimbo weapon, if 2 +// it fires right handed akimbo + +void EjectShell (edict_t *self, vec3_t start, int toggle ) +{ + edict_t *shell; + vec3_t forward, right, up; + float r; + float fix = 1.0; + int left = 0; + + if (sv_shelloff->value) + return; + + shell = G_Spawn(); + ++shells; + + AngleVectors (self->client->v_angle, forward, right, up); + + if (self->client->pers.hand == LEFT_HANDED) + { + left = 1; + fix = -1.0; + } + else if ( self->client->pers.hand == CENTER_HANDED) + fix = 0; + + + // zucc spent a fair amount of time hacking these until they look ok, + // several of them could be improved however. + + if (self->client->curr_weap == MK23_NUM ) + { + VectorMA (start, left?-7:.4, right, start); + VectorMA (start, left?5:2, forward, start); + VectorMA (start, left?-10:-8 , up, start); + } + else if (self->client->curr_weap == M4_NUM) + { + VectorMA (start, left?-10:5, right, start); + VectorMA (start, left?6:12, forward, start); + VectorMA (start, left?-9:-11, up, start); + } + else if (self->client->curr_weap == MP5_NUM) + { + VectorMA (start, left?-10:6, right, start); + VectorMA (start, left?6:8, forward, start); + VectorMA (start, left?-9:-10, up, start); + } + else if (self->client->curr_weap == SNIPER_NUM) + { + VectorMA (start, fix*11, right, start); + VectorMA (start, 2, forward, start); + VectorMA (start, -11, up, start); + + } + else if (self->client->curr_weap == M3_NUM) + { + VectorMA(start, left?-9:3, right, start); + VectorMA(start, left?4:4, forward, start); + VectorMA(start, left?-1:-1, up, start); + } + + else if (self->client->curr_weap == DUAL_NUM) + { + if (self->client->pers.hand == LEFT_HANDED) + VectorMA (start, ((toggle==1)?8:-8), right, start); + else + VectorMA (start, ((toggle==1)?-4:4), right, start); + VectorMA (start, 6, forward, start); + VectorMA (start, -9, up, start); + + } + + + if ( (forward[2] >= -1) && (forward[2] < -0.99) ) { + VectorMA (start, 5, forward, start); + VectorMA (start, -0.5, up, start); } + + else if ( (forward[2] >= -0.99) && (forward[2] < -0.98) ) { + VectorMA (start, 5, forward, start); + VectorMA (start, -.1, up, start); } + else if ( (forward[2] >= -0.98) && (forward[2] < -0.97) ) { + VectorMA (start, 5.1, forward, start); + VectorMA (start, 0.3, up, start); } + else if ( (forward[2] >= -0.97) && (forward[2] < -0.96) ) { + VectorMA (start, 5.2, forward, start); + VectorMA (start, 0.7, up, start); } + else if ( (forward[2] >= -0.96) && (forward[2] < -0.95) ) { + VectorMA (start, 5.2, forward, start); + VectorMA (start, 1.1, up, start); } + else if ( (forward[2] >= -0.95) && (forward[2] < -0.94) ) { + VectorMA (start, 5.3, forward, start); + VectorMA (start, 1.5, up, start); } + else if ( (forward[2] >= -0.94) && (forward[2] < -0.93) ) { + VectorMA (start, 5.4, forward, start); + VectorMA (start, 1.9, up, start); } + else if ( (forward[2] >= -0.93) && (forward[2] < -0.92) ) { + VectorMA (start, 5.5, forward, start); + VectorMA (start, 2.3, up, start); } + else if ( (forward[2] >= -0.92) && (forward[2] < -0.91) ) { + VectorMA (start, 5.6, forward, start); + VectorMA (start, 2.7, up, start); } + else if ( (forward[2] >= -0.91) && (forward[2] < -0.9) ) { + VectorMA (start, 5.7, forward, start); + VectorMA (start, 3.1, up, start); } + + else if ( (forward[2] >= -0.9) && (forward[2] < -0.85) ) { + VectorMA (start, 5.8, forward, start); + VectorMA (start, 3.5, up, start); } + else if ( (forward[2] >= -0.85) && (forward[2] < -0.8) ) { + VectorMA (start, 6, forward, start); + VectorMA (start, 4, up, start); } + else if ( (forward[2] >= -0.8) && (forward[2] < -0.6) ) { + VectorMA (start, 6.5, forward, start); + VectorMA (start, 4.5, up , start); } + else if ( (forward[2] >= -0.6) && (forward[2] < -0.4) ) { + VectorMA (start, 8, forward, start); + VectorMA (start, 5.5, up , start); } + else if ( (forward[2] >= -0.4) && (forward[2] < -0.2) ) { + VectorMA (start, 9.5, forward, start); + VectorMA (start, 6, up , start); } + else if ( (forward[2] >= -0.2) && (forward[2] < 0) ) { + VectorMA (start, 11, forward, start); + VectorMA (start, 6.5, up , start); } + else if ( (forward[2] >= 0) && (forward[2] < 0.2) ) { + VectorMA (start, 12, forward, start); + VectorMA (start, 7, up, start); } + else if ( (forward[2] >= 0.2) && (forward[2] < 0.4) ) { + VectorMA (start, 14, forward, start); + VectorMA (start, 6.5, up, start); } + else if ( (forward[2] >= 0.4) && (forward[2] < 0.6) ) { + VectorMA (start, 16, forward, start); + VectorMA (start, 6, up, start); } + else if ( (forward[2] >= 0.6) && (forward[2] < 0.8) ) { + VectorMA (start, 18, forward, start); + VectorMA (start, 5, up, start); } + else if ( (forward[2] >= 0.8) && (forward[2] < 0.85) ) { + VectorMA (start, 18, forward, start); + VectorMA (start, 4, up, start); } + else if ( (forward[2] >= 0.85) && (forward[2] < 0.9) ) { + VectorMA (start, 18, forward, start); + VectorMA (start, 2.5, up, start); } + + else if ( (forward[2] >= 0.9) && (forward[2] < 0.91) ) { + VectorMA (start, 18.2, forward, start); + VectorMA (start, 2.2, up, start); } + else if ( (forward[2] >= 0.91) && (forward[2] < 0.92) ) { + VectorMA (start, 18.4, forward, start); + VectorMA (start, 1.9, up, start); } + else if ( (forward[2] >= 0.92) && (forward[2] < 0.93) ) { + VectorMA (start, 18.6, forward, start); + VectorMA (start, 1.6, up, start); } + else if ( (forward[2] >= 0.93) && (forward[2] < 0.94) ) { + VectorMA (start, 18.8, forward, start); + VectorMA (start, 1.3, up, start); } + else if ( (forward[2] >= 0.94) && (forward[2] < 0.95) ) { + VectorMA (start, 19, forward, start); + VectorMA (start, 1, up, start); } + else if ( (forward[2] >= 0.95) && (forward[2] < 0.96) ) { + VectorMA (start, 19.2, forward, start); + VectorMA (start, 0.7, up, start); } + else if ( (forward[2] >= 0.96) && (forward[2] < 0.97) ) { + VectorMA (start, 19.4, forward, start); + VectorMA (start, 0.4, up, start); } + else if ( (forward[2] >= 0.97) && (forward[2] < 0.98) ) { + VectorMA (start, 19.6, forward, start); + VectorMA (start, -0.2, up, start); } + else if ( (forward[2] >= 0.98) && (forward[2] < 0.99) ) { + VectorMA (start, 19.8, forward, start); + VectorMA (start, -0.6, up, start); } + + else if ( (forward[2] >= 0.99) && (forward[2] <= 1) ) { + VectorMA (start, 20, forward, start); + VectorMA (start, -1, up , start); } + + VectorCopy (start , shell->s.origin); + if (fix == 0) // we want some velocity on those center handed ones + fix = 1; + if (self->client->curr_weap == SNIPER_NUM) + VectorMA (shell->velocity, fix*(-35 + random() * -60), right, shell->velocity); + else if ( self->client->curr_weap == DUAL_NUM) + { + if (self->client->pers.hand == LEFT_HANDED) + VectorMA (shell->velocity, (toggle==1?1:-1)*(35 + random() * 60), right, shell->velocity); + else + VectorMA (shell->velocity, (toggle==1?-1:1)*(35 + random() * 60), right, shell->velocity); + } + else + VectorMA (shell->velocity, fix*(35 + random() * 60), right, shell->velocity); + VectorMA (shell->avelocity, 500, right, shell->avelocity); + if (self->client->curr_weap == SNIPER_NUM) + VectorMA (shell->velocity, 60 + 40, up, shell->velocity); + else + VectorMA (shell->velocity, 60 + random() * 90, up, shell->velocity); + + shell->movetype = MOVETYPE_BOUNCE; + shell->solid = SOLID_BBOX; + + if (self->client->curr_weap == M3_NUM) + shell->s.modelindex = gi.modelindex ("models/weapons/shell/tris2.md2"); + else if (self->client->curr_weap == SNIPER_NUM) + shell->s.modelindex = gi.modelindex ("models/weapons/shell/tris3.md2"); + else + shell->s.modelindex = gi.modelindex ("models/weapons/shell/tris.md2"); + + r = random(); + if (r < 0.1) + shell->s.frame = 0; + else if (r < 0.2) + shell->s.frame = 1; + else if (r < 0.3) + shell->s.frame = 2; + else if (r < 0.5) + shell->s.frame = 3; + else if (r < 0.6) + shell->s.frame = 4; + else if (r < 0.7) + shell->s.frame = 5; + else if (r < 0.8) + shell->s.frame = 6; + else if (r < 0.9) + shell->s.frame = 7; + else + shell->s.frame = 8; + + shell->owner = self; + shell->touch = ShellTouch; + shell->nextthink = level.time + 1.2 - (shells * .05); + shell->think = ShellDie; + shell->classname = "shell"; + + + + gi.linkentity (shell); +} + + + +/* +================== +FindEdictByClassnum +================== +*/ +edict_t *FindEdictByClassnum (char *classname, int classnum) +{ + int i; + edict_t *it; + + + for (i=0 ; iclassname) + continue; + if (!it->classnum) + continue; + if (Q_stricmp(it->classname, classname) == 0) + { + if (it->classnum == classnum) + return it; + } + + } + + return NULL; + + +} + + + + + +/********* Bulletholes/wall stuff ***********/ + +void DecalDie(edict_t *self) +{ + G_FreeEdict(self); +} + +void AddDecal (edict_t *self, trace_t* tr) +{ + edict_t *decal; + edict_t *dec; + + if (bholelimit->value < 1) + return; + + decal = G_Spawn(); + ++decals; + + if (decals > bholelimit->value) + decals = 1; + + dec = FindEdictByClassnum ("decal", decals); + + if (dec) + { +// gi.bprintf (PRINT_HIGH, "Decal->classnum == %i\n", dec->classnum); + dec->nextthink = level.time + .1; + } + + + decal->solid = SOLID_NOT; + decal->movetype = MOVETYPE_NONE; + + decal->s.modelindex = gi.modelindex("models/objects/holes/hole1/hole.md2"); + + VectorCopy (tr->endpos, decal->s.origin); + + vectoangles (tr->plane.normal, decal->s.angles); + + decal->owner = self; + decal->classnum = decals; + decal->touch = NULL; + decal->nextthink = level.time + 20; + decal->think = DecalDie; + decal->classname = "decal"; + + gi.linkentity (decal); +//glass + if ((tr->ent) && (0 == Q_stricmp("func_explosive", tr->ent->classname))) + { + CGF_SFX_AttachDecalToGlass(tr->ent, decal); + } +//--- +} + +void SplatDie(edict_t *self) +{ + G_FreeEdict(self); +} + +void AddSplat (edict_t *self, vec3_t point, trace_t* tr) +{ + edict_t *splat; + edict_t *spt; + float r; + + + if (splatlimit->value < 1) + return; + + splat = G_Spawn(); + ++splats; + + if (splats > splatlimit->value) + splats = 1; + + spt = FindEdictByClassnum ("splat", splats); + + if (spt) + { +// gi.bprintf (PRINT_HIGH, "Decal->classnum == %i\n", dec->classnum); + spt->nextthink = level.time + .1; + } + + splat->solid = SOLID_NOT; + splat->movetype = MOVETYPE_NONE; + + r = random(); + if ( r > .67 ) + splat->s.modelindex = gi.modelindex("models/objects/splats/splat1/splat.md2"); + else if ( r > .33 ) + splat->s.modelindex = gi.modelindex("models/objects/splats/splat2/splat.md2"); + else + splat->s.modelindex = gi.modelindex("models/objects/splats/splat3/splat.md2"); + + VectorCopy (point, splat->s.origin); + + vectoangles (tr->plane.normal, splat->s.angles); + + splat->owner = self; + splat->touch = NULL; + splat->nextthink = level.time + 25;// - (splats * .05); + splat->think = SplatDie; + splat->classname = "splat"; + splat->classnum = splats; + + gi.linkentity (splat); +//glass + if ((tr->ent) && (0 == Q_stricmp("func_explosive", tr->ent->classname))) + { + CGF_SFX_AttachDecalToGlass(tr->ent, splat); + } +//--- +} + +/* %-variables for chat msgs */ + +void GetWeaponName(edict_t *ent, char *buf) +{ + if (ent->client->pers.weapon) + { + strcpy(buf, ent->client->pers.weapon->pickup_name); + return; + } + + strcpy(buf, "No Weapon"); +} + +void GetItemName(edict_t *ent, char *buf) +{ + gitem_t *spec; + int i; + + i = 0; + while (tnames[i]) + { + if ((spec = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(spec)]) + { + strcpy(buf, spec->pickup_name); + return; + } + i++; + } + + strcpy(buf, "No Item"); +} + +void GetHealth(edict_t *ent, char *buf) +{ + sprintf(buf, "%d", ent->health); +} + +void GetAmmo(edict_t *ent, char *buf) +{ + int ammo; + + if (ent->client->pers.weapon) + { + switch (ent->client->curr_weap) + { + case MK23_NUM: + sprintf(buf, "%d rounds (%d extra clips)", + ent->client->mk23_rds, + ent->client->pers.inventory[ent->client->ammo_index]); + return; + case MP5_NUM: + sprintf(buf, "%d rounds (%d extra clips)", + ent->client->mp5_rds, + ent->client->pers.inventory[ent->client->ammo_index]); + return; + case M4_NUM: + sprintf(buf, "%d rounds (%d extra clips)", + ent->client->m4_rds, + ent->client->pers.inventory[ent->client->ammo_index]); + return; + case M3_NUM: + sprintf(buf, "%d shells (%d extra shells)", + ent->client->shot_rds, + ent->client->pers.inventory[ent->client->ammo_index]); + return; + case HC_NUM: + sprintf(buf, "%d shells (%d extra shells)", + ent->client->cannon_rds, + ent->client->pers.inventory[ent->client->ammo_index]); + return; + case SNIPER_NUM: + sprintf(buf, "%d rounds (%d extra rounds)", + ent->client->sniper_rds, + ent->client->pers.inventory[ent->client->ammo_index]); + return; + case DUAL_NUM: + sprintf(buf, "%d rounds (%d extra clips)", + ent->client->dual_rds, + ent->client->pers.inventory[ent->client->ammo_index]); + return; + case KNIFE_NUM: + ammo = ent->client->pers.inventory[ITEM_INDEX(FindItem(KNIFE_NAME))]; + sprintf(buf, "%d kni%s", ammo, (ammo == 1) ? "fe" : "ves"); + return; + case GRENADE_NUM: + ammo = ent->client->pers.inventory[ITEM_INDEX(FindItem(GRENADE_NAME))]; + sprintf(buf, "%d grenade%s", ammo, (ammo == 1) ? "" : "s"); + return; + } + } + + strcpy(buf, "no ammo"); +} + +void GetNearbyTeammates(edict_t *self, char *buf) +{ + unsigned char nearby_teammates[10][16]; + int nearby_teammates_num, l; + edict_t *ent = NULL; + + nearby_teammates_num = 0; + + while ((ent = findradius(ent, self->s.origin, 1500)) != NULL) + { + if (ent == self || !ent->client || !CanDamage(ent, self) || + !OnSameTeam(ent, self)) + continue; + + strncpy(nearby_teammates[nearby_teammates_num], ent->client->pers.netname, 15); + nearby_teammates[nearby_teammates_num][15] = 0; // in case their name is 15 chars... + nearby_teammates_num++; + if (nearby_teammates_num >= 10) + break; + } + + if (nearby_teammates_num == 0) + { + strcpy(buf, "nobody"); + return; + } + + for (l = 0; l < nearby_teammates_num; l++) + { + if (l == 0) + { + strcpy(buf, nearby_teammates[l]); + } + else + { + if (nearby_teammates_num == 2) + { + strcat(buf, " and "); + strcat(buf, nearby_teammates[l]); + } + else + { + if (l == (nearby_teammates_num - 1)) + { + strcat(buf, ", and "); + strcat(buf, nearby_teammates[l]); + } + else + { + strcat(buf, ", "); + strcat(buf, nearby_teammates[l]); + } + } + } + + } +} + +char *SeekBufEnd(char *buf) +{ + while (*buf != 0) + buf++; + return buf; +} + +void ParseSayText(edict_t *ent, char *text) +{ + static unsigned char buf[10240], infobuf[10240]; + char *p, *pbuf; + + p = text; + pbuf = buf; + *pbuf = 0; + + while (*p != 0) + { + if (((ptrdiff_t)pbuf - (ptrdiff_t)buf) > 300) + { + break; + } + if (*p == '%') + { + switch (*(p+1)) + { + case 'H': + GetHealth(ent, infobuf); + strcpy(pbuf, infobuf); + pbuf = SeekBufEnd(pbuf); + p += 2; + continue; + case 'A': + GetAmmo(ent, infobuf); + strcpy(pbuf, infobuf); + pbuf = SeekBufEnd(pbuf); + p += 2; + continue; + case 'W': + GetWeaponName(ent, infobuf); + strcpy(pbuf, infobuf); + pbuf = SeekBufEnd(pbuf); + p += 2; + continue; + case 'I': + GetItemName(ent, infobuf); + strcpy(pbuf, infobuf); + pbuf = SeekBufEnd(pbuf); + p += 2; + continue; + case 'T': + GetNearbyTeammates(ent, infobuf); + strcpy(pbuf, infobuf); + pbuf = SeekBufEnd(pbuf); + p += 2; + continue; + } + } + *pbuf++ = *p++; + } + + *pbuf = 0; + + strncpy(text, buf, 300); + text[300] = 0; // in case it's 300 +} diff --git a/a_game.h b/a_game.h new file mode 100644 index 0000000..a55be6d --- /dev/null +++ b/a_game.h @@ -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 + diff --git a/a_items.c b/a_items.c new file mode 100644 index 0000000..09f2dc0 --- /dev/null +++ b/a_items.c @@ -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; +} diff --git a/a_menu.c b/a_menu.c new file mode 100644 index 0000000..93ccd6c --- /dev/null +++ b/a_menu.c @@ -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); +} diff --git a/a_menu.h b/a_menu.h new file mode 100644 index 0000000..5eab87e --- /dev/null +++ b/a_menu.h @@ -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); diff --git a/a_radio.c b/a_radio.c new file mode 100644 index 0000000..05a65c8 --- /dev/null +++ b/a_radio.c @@ -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); +} diff --git a/a_radio.h b/a_radio.h new file mode 100644 index 0000000..86b9002 --- /dev/null +++ b/a_radio.h @@ -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(); diff --git a/a_team.c b/a_team.c new file mode 100644 index 0000000..836fc84 --- /dev/null +++ b/a_team.c @@ -0,0 +1,1574 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /LicenseToKill/src/a_team.c $ +// $Revision:: 4 $ +// $Author:: Riever $ +// $Date:: 26/09/99 9:25 $ +// +//----------------------------------------------------------------------------- + +/* + * $Log: /LicenseToKill/src/a_team.c $ + * + * 4 26/09/99 9:25 Riever + * Changed string length safety checks in scoreboard to try to prevent + * overflows. + */ +/* + * Teamplay-related code for Action (formerly Axshun). + * Some of this is borrowed from Zoid's CTF (thanks Zoid) + * -Fireblade + */ + +#include "g_local.h" +#include "cgf_sfx_glass.h" + +qboolean team_game_going = 0; // is a team game going right now? +qboolean team_round_going = 0; // is an actual round of a team game going right now? +int team_round_countdown = 0; // countdown variable for start of a round +int rulecheckfrequency = 0; // accumulator variable for checking rules every 1.5 secs +int lights_camera_action = 0; // countdown variable for "lights...camera...action!" +int holding_on_tie_check = 0; // when a team "wins", countdown for a bit and wait... +int current_round_length = 0; // frames that the current team round has lasted + +int team1_score = 0; +int team2_score = 0; +int team1_total = 0; +int team2_total = 0; + +#define MAX_SPAWNS 1000 // max DM spawn points supported + +edict_t *potential_spawns[MAX_SPAWNS]; +int num_potential_spawns; +edict_t *teamplay_spawns[MAX_TEAMS]; +trace_t trace_t_temp; // used by our trace replace macro in ax_team.h + +int num_teams = 2; // teams in current game, fixed at 2 for now... + +transparent_list_t *transparent_list = NULL; + + +void CreditsMenu(edict_t *ent, pmenu_t *p); + + +void InitTransparentList() +{ + if (transparent_list != NULL) + { + transparent_list_t *p, *q; + + p = transparent_list; + while (p != NULL) + { + q = p->next; + gi.TagFree(p); + p = q; + } + + transparent_list = NULL; + } +} + +void AddToTransparentList(edict_t *ent) +{ + transparent_list_t *p, *n; + + n = (transparent_list_t *)gi.TagMalloc(sizeof(transparent_list_t), TAG_GAME); + if (n == NULL) + { + gi.dprintf("Out of memory\n"); + exit(1); + } + n->ent = ent; + n->next = NULL; + + if (transparent_list == NULL) + { + transparent_list = n; + } + else + { + p = transparent_list; + while (p->next != NULL) + { + p = p->next; + } + p->next = n; + } +} + +void RemoveFromTransparentList(edict_t *ent) +{ + transparent_list_t *p, *q, *r; + + if (transparent_list != NULL) + { + if (transparent_list->ent == ent) + { + q = transparent_list->next; + gi.TagFree(transparent_list); + transparent_list = q; + return; + } + else + { + p = transparent_list; + q = p->next; + while (q != NULL) + { + if (q->ent == ent) + { + r = q->next; + gi.TagFree(q); + p->next = r; + return; + } + p = p->next; + q = p->next; + } + } + } + + gi.dprintf("Warning: attempt to RemoveFromTransparentList when not in it\n"); +} + +void TransparentListSet(solid_t solid_type) +{ + transparent_list_t *p = transparent_list; + + while (p != NULL) + { + p->ent->solid = solid_type; + gi.linkentity(p->ent); + p = p->next; + } +} + +void ReprintMOTD(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + PrintMOTD(ent); +} + +void JoinTeam1(edict_t *ent, pmenu_t *p) +{ + JoinTeam(ent, TEAM1, 0); +} + +void JoinTeam2(edict_t *ent, pmenu_t *p) +{ + JoinTeam(ent, TEAM2, 0); +} + +void SelectWeapon2(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.weapon = FindItem(MP5_NAME); + PMenu_Close(ent); + OpenItemMenu(ent); +} + +void SelectWeapon3(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.weapon = FindItem(M3_NAME); + PMenu_Close(ent); + OpenItemMenu(ent); +} + +void SelectWeapon4(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.weapon = FindItem(HC_NAME); + PMenu_Close(ent); + OpenItemMenu(ent); +} + +void SelectWeapon5(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.weapon = FindItem(SNIPER_NAME); + PMenu_Close(ent); + OpenItemMenu(ent); +} + +void SelectWeapon6(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.weapon = FindItem(M4_NAME); + PMenu_Close(ent); + OpenItemMenu(ent); +} + +void SelectWeapon0(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.weapon = FindItem(KNIFE_NAME); + PMenu_Close(ent); + OpenItemMenu(ent); +} + +void SelectWeapon9(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.weapon = FindItem(DUAL_NAME); + PMenu_Close(ent); + OpenItemMenu(ent); +} + +void SelectItem1(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.item = FindItem(KEV_NAME); + PMenu_Close(ent); +} + +void SelectItem2(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.item = FindItem(LASER_NAME); + PMenu_Close(ent); +} + +void SelectItem3(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.item = FindItem(SLIP_NAME); + PMenu_Close(ent); +} + +void SelectItem4(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.item = FindItem(SIL_NAME); + PMenu_Close(ent); +} + +void SelectItem5(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.item = FindItem(BAND_NAME); + PMenu_Close(ent); +} + +void CreditsReturnToMain(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + OpenJoinMenu(ent); +} + +pmenu_t creditsmenu[] = { + { "*Action Quake 2", PMENU_ALIGN_CENTER, NULL, NULL }, + { "--------------", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*Head Design, Original Programming", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Sam 'Cail' Thompson", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*Sounds, Second-In-Command", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Patrick 'Bartender' Mills", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*Original Programming", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Nathan 'Pietro' Kovner", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Michael 'Siris' Taylor", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*Skins, Etc", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Jon 'Vain' Delee", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*Levels", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Evan 'Ace12GA' Prentice", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*Models, Skins, Etc", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Minh 'Gooseman' Le", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Dallas 'Suislide' Frank", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*Action 1.5 ('Axshun') Programming", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Carl 'Zucchini' Schedvin", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Bob 'Fireblade' Farmer", PMENU_ALIGN_CENTER, NULL, NULL }, + + + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "Return to main menu", PMENU_ALIGN_LEFT, NULL, CreditsReturnToMain }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "TAB to exit menu", PMENU_ALIGN_LEFT, NULL, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "v" ACTION_VERSION, PMENU_ALIGN_RIGHT, NULL, NULL }, +}; + +pmenu_t weapmenu[] = { + { "*Action Quake 2", PMENU_ALIGN_CENTER, NULL, NULL }, + { "--------------", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Select your weapon", PMENU_ALIGN_CENTER, NULL, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "MP5/10 Submachinegun", PMENU_ALIGN_LEFT, NULL, SelectWeapon2 }, + { "M3 Super90 Assault Shotgun", PMENU_ALIGN_LEFT, NULL, SelectWeapon3 }, + { "Handcannon", PMENU_ALIGN_LEFT, NULL, SelectWeapon4 }, + { "SSG 3000 Sniper Rifle", PMENU_ALIGN_LEFT, NULL, SelectWeapon5 }, + { "M4 Assault Rifle", PMENU_ALIGN_LEFT, NULL, SelectWeapon6 }, + { "Combat Knives", PMENU_ALIGN_LEFT, NULL, SelectWeapon0 }, + { "Akimbo Pistols", PMENU_ALIGN_LEFT, NULL, SelectWeapon9 }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL, NULL }, + { "ENTER to select", PMENU_ALIGN_LEFT, NULL, NULL }, + { "TAB to exit menu", PMENU_ALIGN_LEFT, NULL, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "v" ACTION_VERSION, PMENU_ALIGN_RIGHT, NULL, NULL }, +}; + +pmenu_t itemmenu[] = { + { "*Action Quake 2", PMENU_ALIGN_CENTER, NULL, NULL }, + { "--------------", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Select your item", PMENU_ALIGN_CENTER, NULL, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "Kevlar Vest", PMENU_ALIGN_LEFT, NULL, SelectItem1 }, + { "Laser Sight", PMENU_ALIGN_LEFT, NULL, SelectItem2 }, + { "Stealth Slippers", PMENU_ALIGN_LEFT, NULL, SelectItem3 }, + { "Silencer", PMENU_ALIGN_LEFT, NULL, SelectItem4 }, + { "Bandolier", PMENU_ALIGN_LEFT, NULL, SelectItem5 }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL, NULL }, + { "ENTER to select", PMENU_ALIGN_LEFT, NULL, NULL }, + { "TAB to exit menu", PMENU_ALIGN_LEFT, NULL, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "v" ACTION_VERSION, PMENU_ALIGN_RIGHT, NULL, NULL }, +}; + +pmenu_t joinmenu[] = { + { "*Action Quake 2", PMENU_ALIGN_CENTER, NULL, NULL }, + { NULL /* lvl name */, PMENU_ALIGN_CENTER, NULL, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL, NULL }, + { NULL /* team 1 */, PMENU_ALIGN_LEFT, NULL, JoinTeam1 }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { NULL /* team 2 */, PMENU_ALIGN_LEFT, NULL, JoinTeam2 }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "MOTD", PMENU_ALIGN_LEFT, NULL, ReprintMOTD }, + { "Credits", PMENU_ALIGN_LEFT, NULL, CreditsMenu }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL, NULL }, + { "ENTER to select", PMENU_ALIGN_LEFT, NULL, NULL }, + { "TAB to exit menu", PMENU_ALIGN_LEFT, NULL, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "v" ACTION_VERSION, PMENU_ALIGN_RIGHT, NULL, NULL }, +}; + +void CreditsMenu(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + PMenu_Open(ent, creditsmenu, 4, sizeof(creditsmenu) / sizeof(pmenu_t)); +} + +char *TeamName(int team) +{ + if (team == TEAM1) + return team1_name; + else if (team == TEAM2) + return team2_name; + else + return "None"; +} + +void AssignSkin(edict_t *ent, char *s) +{ + int playernum = ent-g_edicts-1; + + switch (ent->client->resp.team) + { + case TEAM1: + gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", + ent->client->pers.netname, team1_skin) ); + break; + case TEAM2: + gi.configstring (CS_PLAYERSKINS+playernum, + va("%s\\%s", ent->client->pers.netname, team2_skin) ); + break; + default: + gi.configstring (CS_PLAYERSKINS+playernum, + va("%s\\%s", ent->client->pers.netname, s) ); + break; + } +} + +void Team_f(edict_t *ent) +{ + char *t; + int desired_team; + + t = gi.args(); + if (!*t) + { + safe_cprintf(ent, PRINT_HIGH, "You are on %s.\n", + TeamName(ent->client->resp.team)); + return; + } + + if (level.framenum < (ent->client->resp.joined_team + 50)) + { + safe_cprintf(ent, PRINT_HIGH, "You must wait 5 seconds before changing teams again.\n"); + return; + } + + if (Q_stricmp(t, "none") == 0) + { + if (ent->client->resp.team == NOTEAM) + { + safe_cprintf(ent, PRINT_HIGH, "You're not on a team.\n"); + } + else + { + LeaveTeam(ent); + } + return; + } + + if (Q_stricmp(t, "1") == 0) + desired_team = TEAM1; + else if (Q_stricmp(t, "2") == 0) + desired_team = TEAM2; + else if (Q_stricmp(t, team1_name) == 0) + desired_team = TEAM1; + else if (Q_stricmp(t, team2_name) == 0) + desired_team = TEAM2; + else + { + safe_cprintf(ent, PRINT_HIGH, "Unknown team %s.\n", t); + return; + } + + if (ent->client->resp.team == desired_team) + { + safe_cprintf(ent, PRINT_HIGH, "You are already on %s.\n", + TeamName(ent->client->resp.team)); + return; + } + + JoinTeam(ent, desired_team, 1); +} + +void UnevenTeamsMsg(int whichteam, int uneven_amount, char *opponent) +{ + int i; + edict_t *e; + + for (i = 1; i <= maxclients->value; i++) + { + e = g_edicts + i; + if (e->inuse) + { + if (e->client->resp.team == whichteam) + { + safe_cprintf(e, PRINT_HIGH, "Your team now has %d more player%s than %s.\n", + uneven_amount, uneven_amount == 1 ? "" : "s", opponent); + stuffcmd(e, "play misc/comp_up.wav"); + } + } + } +} + +void CheckForUnevenTeams() +{ + int i, onteam1 = 0, onteam2 = 0; + edict_t *e; + + // only use these messages during 2-team games... + if (num_teams > 2) + return; + + for (i = 1; i <= maxclients->value; i++) + { + e = g_edicts + i; + if (e->inuse) + { + if (e->client->resp.team == TEAM1) + onteam1++; + else if (e->client->resp.team == TEAM2) + onteam2++; + } + } + if (onteam1 > onteam2) + UnevenTeamsMsg(TEAM1, onteam1 - onteam2, team2_name); + else if (onteam2 > onteam1) + UnevenTeamsMsg(TEAM2, onteam2 - onteam1, team1_name); +} + +void JoinTeam(edict_t *ent, int desired_team, int skip_menuclose) +{ + char *s, *a; + + if (!skip_menuclose) + { + PMenu_Close(ent); + } + + if (ent->client->resp.team == desired_team) + return; + + a = (ent->client->resp.team == NOTEAM) ? "joined" : "changed to"; + + ent->client->resp.team = desired_team; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + AssignSkin(ent, s); + + if (ent->solid != SOLID_NOT) // alive, in game + { + ent->health = 0; + player_die (ent, ent, ent, 100000, vec3_origin); + ent->deadflag = DEAD_DEAD; + } + + safe_bprintf(PRINT_HIGH, "%s %s %s.\n", + ent->client->pers.netname, a, TeamName(desired_team)); + + ent->client->resp.joined_team = level.framenum; + + CheckForUnevenTeams(); + + if (!skip_menuclose) + OpenWeaponMenu(ent); +} + +void LeaveTeam(edict_t *ent) +{ + char *g; + + if (ent->client->resp.team == NOTEAM) + return; + + if (ent->solid != SOLID_NOT) // alive, in game + { + ent->health = 0; + player_die (ent, ent, ent, 100000, vec3_origin); + ent->deadflag = DEAD_DEAD; + } + + if (IsNeutral(ent)) + g = "its"; + else if (IsFemale(ent)) + g = "her"; + else + g = "his"; + safe_bprintf(PRINT_HIGH, "%s left %s team.\n", ent->client->pers.netname, g); + + ent->client->resp.joined_team = 0; + ent->client->resp.team = NOTEAM; + + CheckForUnevenTeams(); +} + +void ReturnToMain(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + OpenJoinMenu(ent); +} + +void OpenItemMenu(edict_t *ent) +{ + PMenu_Open(ent, itemmenu, 4, sizeof(itemmenu) / sizeof(pmenu_t)); +} + +void OpenWeaponMenu(edict_t *ent) +{ + PMenu_Open(ent, weapmenu, 4, sizeof(weapmenu) / sizeof(pmenu_t)); +} + +int UpdateJoinMenu(edict_t *ent) +{ + static char levelname[32]; + static char team1players[32]; + static char team2players[32]; + int num1, num2, i; + + joinmenu[3].text = team1_name; + joinmenu[3].SelectFunc = JoinTeam1; + joinmenu[5].text = team2_name; + joinmenu[5].SelectFunc = JoinTeam2; + + levelname[0] = '*'; + if (g_edicts[0].message) + strncpy(levelname+1, g_edicts[0].message, sizeof(levelname) - 2); + else + strncpy(levelname+1, level.mapname, sizeof(levelname) - 2); + levelname[sizeof(levelname) - 1] = 0; + + num1 = num2 = 0; + for (i = 0; i < maxclients->value; i++) + { + if (!g_edicts[i+1].inuse) + continue; + if (game.clients[i].resp.team == TEAM1) + num1++; + else if (game.clients[i].resp.team == TEAM2) + num2++; + } + + sprintf(team1players, " (%d players)", num1); + sprintf(team2players, " (%d players)", num2); + + joinmenu[1].text = levelname; + if (joinmenu[3].text) + joinmenu[4].text = team1players; + else + joinmenu[4].text = NULL; + if (joinmenu[5].text) + joinmenu[6].text = team2players; + else + joinmenu[6].text = NULL; + + if (num1 > num2) + return TEAM2; + else if (num2 > num1) + return TEAM1; + else if (team1_score > team2_score) + return TEAM2; + else if (team2_score > team1_score) + return TEAM1; + else + return TEAM1; +} + +void OpenJoinMenu(edict_t *ent) +{ + int team; + + team = UpdateJoinMenu(ent); + if (team == TEAM1) + team = 3; + else + team = 5; + PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t)); +} + +int member_array(char *str, char *arr[], int num_elems) +{ + int l; + for (l = 0; l < num_elems; l++) + { + if (!strcmp(str, arr[l])) + return l; + } + return -1; +} + +void CleanLevel() +{ + char *remove_classnames[] = + { + "weapon_Mk23", + "weapon_MP5", + "weapon_M4", + "weapon_M3", + "weapon_HC", + "weapon_Sniper", + "weapon_Dual", + "weapon_Knife", + "weapon_Grenade", + "ammo_sniper", + "ammo_clip", + "ammo_mag", + "ammo_m4", + "ammo_m3", + "item_quiet", + "item_slippers", + "item_band", + "item_lasersight", + "item_vest", + "thrown_knife", + "hgrenade", + + }; + int i; + int base; + edict_t *ent; + + base = 1 + maxclients->value + BODY_QUEUE_SIZE; + ent = g_edicts + base; + for (i = 1 + maxclients->value + BODY_QUEUE_SIZE; + i < globals.num_edicts; + i++, ent++) + { + if (!ent->classname) + continue; + if (member_array(ent->classname, remove_classnames, + sizeof(remove_classnames) / sizeof(char *)) > -1) + { + G_FreeEdict(ent); + } + } + + CleanBodies(); + + // fix glass + CGF_SFX_RebuildAllBrokenGlass(); +} + +qboolean StartClient(edict_t *ent) +{ + if (ent->client->resp.team != NOTEAM) + return false; + + // start as 'observer' + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->resp.team = NOTEAM; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + + return true; +} + +void CenterPrintAll(char *msg) +{ + int i; + edict_t *ent; + + safe_cprintf(NULL, PRINT_HIGH, msg); // so it goes to the server console... + + for (i = 0; i < game.maxclients; i++) + { + ent = &g_edicts[1+i]; + if (!ent->inuse) + continue; + safe_centerprintf(ent, "%s", msg); + } +} + +qboolean BothTeamsHavePlayers() +{ + int onteam1 = 0, onteam2 = 0, i; + edict_t *ent; + + for (i = 0; i < game.maxclients; i++) + { + ent = &g_edicts[1+i]; + if (!ent->inuse) + continue; + if (game.clients[i].resp.team == TEAM1) + onteam1++; + else if (game.clients[i].resp.team == TEAM2) + onteam2++; + } + + return (onteam1 > 0 && onteam2 > 0); +} + +// CheckForWinner: Checks for a winner (or not). +int CheckForWinner() +{ + int onteam1 = 0, onteam2 = 0, i; + edict_t *ent; + + for (i = 0; i < game.maxclients; i++) + { + ent = &g_edicts[1+i]; + if (!ent->inuse) + continue; + if (game.clients[i].resp.team == TEAM1 && + ent->solid != SOLID_NOT) + onteam1++; + else if (game.clients[i].resp.team == TEAM2 && + ent->solid != SOLID_NOT) + onteam2++; + } + + if (onteam1 > 0 && onteam2 > 0) + return WINNER_NONE; + else if (onteam1 == 0 && onteam2 == 0) + return WINNER_TIE; + else if (onteam1 > 0 && onteam2 == 0) + return WINNER_TEAM1; + else + return WINNER_TEAM2; +} + +// CheckForForcedWinner: A winner is being forced, find who it is. +int CheckForForcedWinner() +{ + int onteam1 = 0, onteam2 = 0, i; + int health1 = 0, health2 = 0; + edict_t *ent; + + for (i = 0; i < game.maxclients; i++) + { + ent = &g_edicts[1+i]; + if (!ent->inuse) + continue; + if (game.clients[i].resp.team == TEAM1 && ent->solid != SOLID_NOT) + { + onteam1++; + health1 += ent->health; + } + else if (game.clients[i].resp.team == TEAM2 && ent->solid != SOLID_NOT) + { + onteam2++; + health2 += ent->health; + } + } + + if (onteam1 > onteam2) + { + return WINNER_TEAM1; + } + else if (onteam2 > onteam1) + { + return WINNER_TEAM2; + } + else + { + if (health1 > health2) + return WINNER_TEAM1; + else if (health2 > health1) + return WINNER_TEAM2; + else + return WINNER_TIE; + } +} + +void SpawnPlayers() +{ + int i; + edict_t *ent; + + GetSpawnPoints(); + SetupTeamSpawnPoints(); + InitTransparentList(); + for (i = 0; i < game.maxclients; i++) + { + ent = &g_edicts[1+i]; + if (ent->inuse && ent->client->resp.team != NOTEAM) + { +// ACEBOT CHANGE + if( !stricmp(ent->classname, "bot") ) + ACESP_PutClientInServer( ent, true,ent->client->resp.team); + else + PutClientInServer(ent); +// ACEBOT END + AddToTransparentList(ent); + } + } +} + +void StartRound() +{ + team_round_going = 1; + current_round_length = 0; +} + +void StartLCA() +{ + CleanLevel(); + + CenterPrintAll("LIGHTS...\n"); + gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, + gi.soundindex("atl/lights.wav"), 1.0, ATTN_NONE, 0.0); + lights_camera_action = 41; + SpawnPlayers(); +} + +// FindOverlap: Find the first (or next) overlapping player for ent. +edict_t *FindOverlap(edict_t *ent, edict_t *last_overlap) +{ + int i; + edict_t *other; + vec3_t diff; + + for (i = last_overlap ? last_overlap - g_edicts : 0; i < game.maxclients; i++) + { + other = &g_edicts[i+1]; + + if (!other->inuse || other->client->resp.team == NOTEAM + || other == ent + || other->solid == SOLID_NOT + || other->deadflag == DEAD_DEAD) + continue; + + VectorSubtract(ent->s.origin, other->s.origin, diff); + + if (diff[0] >= -33 && diff[0] <= 33 && + diff[1] >= -33 && diff[1] <= 33 && + diff[2] >= -65 && diff[2] <= 65) + return other; + } + + return NULL; +} + +void ContinueLCA() +{ + if (lights_camera_action == 21) + { + CenterPrintAll("CAMERA...\n"); + gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, + gi.soundindex("atl/camera.wav"), 1.0, ATTN_NONE, 0.0); + } + else if (lights_camera_action == 1) + { + CenterPrintAll("ACTION!\n"); + gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, + gi.soundindex("atl/action.wav"), 1.0, ATTN_NONE, 0.0); + StartRound(); + } + lights_camera_action--; +} + +void MakeAllLivePlayersObservers() +{ + edict_t *ent; + int saveteam, i; + + for (i = 0; i < game.maxclients; i++) + { + ent = &g_edicts[1+i]; + if (!ent->inuse || ent->solid == SOLID_NOT) + continue; + saveteam = ent->client->resp.team; + ent->client->resp.team = NOTEAM; + PutClientInServer(ent); + ent->client->resp.team = saveteam; + } +} + +// WonGame: returns true if we're exiting the level. +int WonGame(int winner) +{ + safe_bprintf(PRINT_HIGH, "The round is over:\n"); + + if (winner == WINNER_TIE) + { + safe_bprintf(PRINT_HIGH, "It was a tie, no points awarded!\n"); + } + else + { + if (winner == WINNER_TEAM1) + { + safe_bprintf(PRINT_HIGH, "%s won!\n", TeamName(TEAM1)); + team1_score++; + } + else + { + safe_bprintf(PRINT_HIGH, "%s won!\n", TeamName(TEAM2)); + team2_score++; + } + } + + if (timelimit->value) + { + if (level.time >= timelimit->value*60) + { + safe_bprintf(PRINT_HIGH, "Timelimit hit.\n"); + EndDMLevel(); + team_round_going = team_round_countdown = team_game_going = 0; + return 1; + } + } + + if (roundlimit->value) + { + if (team1_score >= roundlimit->value || team2_score >= roundlimit->value) + { + safe_bprintf(PRINT_HIGH, "Roundlimit hit.\n"); + EndDMLevel(); + team_round_going = team_round_countdown = team_game_going = 0; + return 1; + } + } + + return 0; +} + + +void CheckTeamRules() +{ + int winner; + int checked_tie = 0; + + if (lights_camera_action) + { + ContinueLCA(); + return; + } + + if (team_round_going) + current_round_length++; + + if (holding_on_tie_check) + { + holding_on_tie_check--; + if (holding_on_tie_check > 0) + return; + holding_on_tie_check = 0; + checked_tie = 1; + } + + if (team_round_countdown == 1) + { + team_round_countdown = 0; + if (BothTeamsHavePlayers()) + { + team_game_going = 1; + StartLCA(); + } + else + { + CenterPrintAll("Not enough players to play!\n"); + MakeAllLivePlayersObservers(); + } + } + else + { + if (team_round_countdown) + team_round_countdown--; + } + + // check these rules every 1.5 seconds... + rulecheckfrequency++; + if (rulecheckfrequency % 15 && !checked_tie) + return; + + if (!team_round_going) + { + if (timelimit->value) + { + if (level.time >= timelimit->value*60) + { + safe_bprintf (PRINT_HIGH, "Timelimit hit.\n"); + EndDMLevel(); + team_round_going = team_round_countdown = team_game_going = 0; + return; + } + } + + if (!team_round_countdown) + { + if (BothTeamsHavePlayers()) + { + CenterPrintAll("The round will begin in 20 seconds!\n"); + team_round_countdown = 201; + } + } + } + else /* team_round_going */ + { + if ((winner = CheckForWinner()) != WINNER_NONE) + { + if (!checked_tie) + { + holding_on_tie_check = 50; + return; + } + if (WonGame(winner)) + return; + team_round_going = 0; + lights_camera_action = 0; + holding_on_tie_check = 0; + team_round_countdown = 71; + return; + } + + if (roundtimelimit->value && + (current_round_length > roundtimelimit->value * 600)) + { + safe_bprintf(PRINT_HIGH, "Round timelimit hit.\n"); + winner = CheckForForcedWinner(); + if (WonGame(winner)) + return; + team_round_going = 0; + lights_camera_action = 0; + holding_on_tie_check = 0; + team_round_countdown = 71; + return; + } + } +} + + +void A_Scoreboard(edict_t *ent) +{ + if (ent->client->showscores && ent->client->scoreboardnum == 1) + { + // blink header of the winning team during intermission + if (level.intermissiontime && (level.framenum & 8)) { // blink 1/8th second + if (team1_score > team2_score) + ent->client->ps.stats[STAT_TEAM1_PIC] = 0; + else if (team2_score > team1_score) + ent->client->ps.stats[STAT_TEAM2_PIC] = 0; + else if (team1_total > team2_total) // frag tie breaker + ent->client->ps.stats[STAT_TEAM1_PIC] = 0; + else if (team2_total > team1_total) + ent->client->ps.stats[STAT_TEAM2_PIC] = 0; + else { // tie game! + ent->client->ps.stats[STAT_TEAM1_PIC] = 0; + ent->client->ps.stats[STAT_TEAM2_PIC] = 0; + } + } + else + { + ent->client->ps.stats[STAT_TEAM1_PIC] = gi.imageindex(team1_skin_index); + ent->client->ps.stats[STAT_TEAM2_PIC] = gi.imageindex(team2_skin_index); + } + + ent->client->ps.stats[STAT_TEAM1_SCORE] = team1_score; + ent->client->ps.stats[STAT_TEAM2_SCORE] = team2_score; + } +} + +// Maximum number of lines of scores to put under each team's header. +#define MAX_SCORES_PER_TEAM 9 + +void A_ScoreboardMessage (edict_t *ent, edict_t *killer) +{ + char string[1400], damage[50]; + gclient_t *cl; + edict_t *cl_ent; + int maxsize = 1000, i, j, k; + + if (ent->client->scoreboardnum == 1) + { + int team, len, deadview; + int sorted[TEAM_TOP][MAX_CLIENTS]; + int sortedscores[TEAM_TOP][MAX_CLIENTS]; + int score, total[TEAM_TOP], totalscore[TEAM_TOP]; + int totalalive[TEAM_TOP], totalaliveprinted[TEAM_TOP]; + int stoppedat[TEAM_TOP]; + int name_pos[TEAM_TOP]; + + deadview = (ent->solid == SOLID_NOT || + ent->deadflag == DEAD_DEAD || + !team_round_going); + + ent->client->ps.stats[STAT_TEAM_HEADER] = gi.imageindex ("tag3"); + + total[TEAM1] = total[TEAM2] = totalalive[TEAM1] = totalalive[TEAM2] = + totalscore[TEAM1] = totalscore[TEAM2] = 0; + + for (i=0 ; iinuse) + continue; + + if (game.clients[i].resp.team == NOTEAM) + continue; + else + team = game.clients[i].resp.team; + + score = game.clients[i].resp.score; + if (noscore->value) + { + j = total[team]; + } + else + { + for (j = 0; j < total[team]; j++) + { + if (score > sortedscores[team][j]) + break; + } + for (k=total[team] ; k>j ; k--) + { + sorted[team][k] = sorted[team][k-1]; + sortedscores[team][k] = sortedscores[team][k-1]; + } + } + sorted[team][j] = i; + sortedscores[team][j] = score; + totalscore[team] += score; + total[team]++; + if (cl_ent->solid != SOLID_NOT && + cl_ent->deadflag != DEAD_DEAD) + totalalive[team]++; + } + + // I've shifted the scoreboard position 8 pixels to the left in Axshun so it works + // correctly in 320x240 (Action's does not)--any problems with this? -FB + // Also going to center the team names. + + name_pos[TEAM1] = ((20 - strlen(team1_name)) / 2) * 8; + if (name_pos[TEAM1] < 0) + name_pos[TEAM1] = 0; + name_pos[TEAM2] = ((20 - strlen(team2_name)) / 2) * 8; + if (name_pos[TEAM2] < 0) + name_pos[TEAM2] = 0; + + sprintf(string, + // TEAM1 + "if 24 xv 0 yv 8 pic 24 endif " + "if 22 xv 32 yv 8 pic 22 endif " + "xv 32 yv 28 string \"%4d/%-3d\" " + "xv 90 yv 12 num 2 26 " + "xv %d yv 0 string \"%s\" " + // TEAM2 + "if 25 xv 160 yv 8 pic 25 endif " + "if 22 xv 192 yv 8 pic 22 endif " + "xv 192 yv 28 string \"%4d/%-3d\" " + "xv 248 yv 12 num 2 27 " + "xv %d yv 0 string \"%s\" ", + totalscore[TEAM1], total[TEAM1], name_pos[TEAM1], team1_name, + totalscore[TEAM2], total[TEAM2], name_pos[TEAM2] + 160, team2_name); + + len = strlen(string); + + totalaliveprinted[TEAM1] = totalaliveprinted[TEAM2] = 0; + stoppedat[TEAM1] = stoppedat[TEAM2] = -1; + + for (i=0 ; i < (MAX_SCORES_PER_TEAM + 1) ; i++) + { + if (i >= total[TEAM1] && i >= total[TEAM2]) + break; + + // ok, if we're approaching the "maxsize", then let's stop printing members of each + // teams (if there's more than one member left to print in that team...) + if (len > (maxsize - 200)) //RiEvEr was 100 + { + if (i < (total[TEAM1] - 1)) + stoppedat[TEAM1] = i; + if (i < (total[TEAM2] - 1)) + stoppedat[TEAM2] = i; + } + if (i == MAX_SCORES_PER_TEAM-1) + { + if (total[TEAM1] > MAX_SCORES_PER_TEAM) + stoppedat[TEAM1] = i; + if (total[TEAM2] > MAX_SCORES_PER_TEAM) + stoppedat[TEAM2] = i; + } + + if (i < total[TEAM1] && stoppedat[TEAM1] == -1) // print next team 1 member... + { + cl = &game.clients[sorted[TEAM1][i]]; + cl_ent = g_edicts + 1 + sorted[TEAM1][i]; + if (cl_ent->solid != SOLID_NOT && + cl_ent->deadflag != DEAD_DEAD) + totalaliveprinted[TEAM1]++; + + // AQ truncates names at 12, not sure why, except maybe to conserve scoreboard + // string space? skipping that "feature". -FB + + sprintf(string+strlen(string), + "xv 0 yv %d string%s \"%s\" ", + 42 + i * 8, + deadview ? (cl_ent->solid == SOLID_NOT ? "" : "2") : "", + game.clients[sorted[TEAM1][i]].pers.netname); + } + + if (i < total[TEAM2] && stoppedat[TEAM2] == -1) // print next team 2 member... + { + cl = &game.clients[sorted[TEAM2][i]]; + cl_ent = g_edicts + 1 + sorted[TEAM2][i]; + if (cl_ent->solid != SOLID_NOT && + cl_ent->deadflag != DEAD_DEAD) + totalaliveprinted[TEAM2]++; + + // AQ truncates names at 12, not sure why, except maybe to conserve scoreboard + // string space? skipping that "feature". -FB + + sprintf(string+strlen(string), + "xv 160 yv %d string%s \"%s\" ", + 42 + i * 8, + deadview ? (cl_ent->solid == SOLID_NOT ? "" : "2") : "", + game.clients[sorted[TEAM2][i]].pers.netname); + } + + len = strlen(string); + } + + // Print remaining players if we ran out of room... + if (!deadview) // live player viewing scoreboard... + { + if (stoppedat[TEAM1] > -1) + { + sprintf(string + strlen(string), "xv 0 yv %d string \"..and %d more\" ", + 42 + (stoppedat[TEAM1] * 8), total[TEAM1] - stoppedat[TEAM1]); + } + if (stoppedat[TEAM2] > -1) + { + sprintf(string + strlen(string), "xv 160 yv %d string \"..and %d more\" ", + 42 + (stoppedat[TEAM2] * 8), total[TEAM2] - stoppedat[TEAM2]); + } + } + else // dead player viewing scoreboard... + { + if (stoppedat[TEAM1] > -1) + { + sprintf(string + strlen(string), "xv 0 yv %d string%s \"..and %d/%d more\" ", + 42 + (stoppedat[TEAM1] * 8), + (totalalive[TEAM1] - totalaliveprinted[TEAM1]) ? "2" : "", + totalalive[TEAM1] - totalaliveprinted[TEAM1], + total[TEAM1] - stoppedat[TEAM1]); + } + if (stoppedat[TEAM2] > -1) + { + sprintf(string + strlen(string), "xv 160 yv %d string%s \"..and %d/%d more\" ", + 42 + (stoppedat[TEAM2] * 8), + (totalalive[TEAM2] - totalaliveprinted[TEAM2]) ? "2" : "", + totalalive[TEAM2] - totalaliveprinted[TEAM2], + total[TEAM2] - stoppedat[TEAM2]); + } + } + } + + else if (ent->client->scoreboardnum == 2) + + { + int total, score, ping; + int sortedscores[MAX_CLIENTS], sorted[MAX_CLIENTS]; + + total = score = 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; + if (noscore->value) + { + j = total; + } + else + { + 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++; + } + + if (noscore->value) + { + strcpy(string, "xv 0 yv 32 string2 \"Player Time Ping\" " + "xv 0 yv 40 string2 \"--------------- ---- ----\" "); + } + else + { + strcpy(string, "xv 0 yv 32 string2 \"Frags Player Time Ping Damage\" " + "xv 0 yv 40 string2 \"----- --------------- ---- ---- ------\" "); + } + + for (i = 0; i < total; i++) + { + ping = game.clients[sorted[i]].ping; + if (ping > 999) + ping = 999; + if (noscore->value) + { + sprintf(string + strlen(string), + "xv 0 yv %d string \"%-15s %4d %4d\" ", + 48 + i * 8, + game.clients[sorted[i]].pers.netname, + (level.framenum - game.clients[sorted[i]].resp.enterframe)/600, + ping); + } + else + { + if (game.clients[sorted[i]].resp.damage_dealt < 1000000) + sprintf(damage, "%d", game.clients[sorted[i]].resp.damage_dealt); + else + strcpy(damage, "******"); + sprintf(string + strlen(string), + "xv 0 yv %d string \"%5d %-15s %4d %4d %6s\" ", + 48 + i * 8, + sortedscores[i], + game.clients[sorted[i]].pers.netname, + (level.framenum - game.clients[sorted[i]].resp.enterframe)/600, + ping, damage); + } + + if (strlen(string) > (maxsize - 200) && //RiEvEr was -100 + i < (total - 2)) + { + sprintf(string + strlen(string), + "xv 0 yv %d string \"..and %d more\" ", + 48 + (i + 1) * 8, + (total - i - 1)); + break; + } + } + } + + + if (strlen(string) > 1300) // for debugging... + gi.dprintf("Warning: scoreboard string neared or exceeded max length\nDump:\n%s\n---\n", + string); + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + +// called when we enter the intermission +void TallyEndOfLevelTeamScores(void) +{ + int i; + + team1_total = team2_total = 0; + for (i = 0; i < maxclients->value; i++) + { + if (!g_edicts[i+1].inuse) + continue; + if (game.clients[i].resp.team == TEAM1) + team1_total += game.clients[i].resp.score; + else if (game.clients[i].resp.team == TEAM2) + team2_total += game.clients[i].resp.score; + } +} + + +/* + * Teamplay spawning functions... + */ + +edict_t *SelectTeamplaySpawnPoint(edict_t *ent) +{ + return teamplay_spawns[ent->client->resp.team - 1]; +} + +// SpawnPointDistance: +// Returns the distance between two spawn points (or any entities, actually...) +float SpawnPointDistance(edict_t *spot1, edict_t *spot2) +{ + vec3_t v; + VectorSubtract (spot1->s.origin, spot2->s.origin, v); + return VectorLength(v); +} + +// GetSpawnPoints: +// Put the spawn points into our potential_spawns array so we can work with them easily. +void GetSpawnPoints() +{ + edict_t *spot; + + spot = NULL; + num_potential_spawns = 0; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + potential_spawns[num_potential_spawns] = spot; + num_potential_spawns++; + if (num_potential_spawns >= MAX_SPAWNS) + { + gi.dprintf("Warning: MAX_SPAWNS exceeded\n"); + return; + } + } +} + +// newrand returns n, where 0 >= n < top +int newrand(int top) +{ + return (int)(random() * top); +} + +// compare_spawn_distances is used by the qsort() call +int compare_spawn_distances(const void *sd1, const void *sd2) +{ + if (((spawn_distances_t *)sd1)->distance < + ((spawn_distances_t *)sd2)->distance) + return -1; + else if (((spawn_distances_t *)sd1)->distance > + ((spawn_distances_t *)sd2)->distance) + return 1; + else + return 0; +} + +void SelectRandomTeamplaySpawnPoint(int team, qboolean teams_assigned[]) +{ + int spawn_point; + spawn_point = newrand(num_potential_spawns); + teamplay_spawns[team] = potential_spawns[spawn_point]; + teams_assigned[team] = true; +} + +void SelectFarTeamplaySpawnPoint(int team, qboolean teams_assigned[]) +{ + int x, y, spawn_to_use, preferred_spawn_points, num_already_used, + total_good_spawn_points; + float closest_spawn_distance, distance; + spawn_distances_t *spawn_distances; + + spawn_distances = (spawn_distances_t *)gi.TagMalloc(num_potential_spawns * sizeof(spawn_distances_t), TAG_GAME); + + num_already_used = 0; + for (x = 0; x < num_potential_spawns; x++) + { + closest_spawn_distance = 2000000000; + + for (y = 0; y < num_teams; y++) + { + if (teams_assigned[y]) + { + distance = SpawnPointDistance(potential_spawns[x], teamplay_spawns[y]); + if (distance < closest_spawn_distance) + { + closest_spawn_distance = distance; + } + } + } + + if (closest_spawn_distance == 0) + num_already_used++; + + spawn_distances[x].s = potential_spawns[x]; + spawn_distances[x].distance = closest_spawn_distance; + } + + qsort(spawn_distances, num_potential_spawns, + sizeof(spawn_distances_t), compare_spawn_distances); + + total_good_spawn_points = num_potential_spawns - num_already_used; + + if (total_good_spawn_points <= 4) + preferred_spawn_points = 1; + else + if (total_good_spawn_points <= 10) + preferred_spawn_points = 2; + else + preferred_spawn_points = 3; + +//FB 6/1/99 - make DF_SPAWN_FARTHEST force far spawn points in TP + if ((int)dmflags->value & DF_SPAWN_FARTHEST) + preferred_spawn_points = 1; +//FB 6/1/99 + + spawn_to_use = newrand(preferred_spawn_points); + + if (team < 0 || team >= MAX_TEAMS) + { + gi.dprintf("Out-of-range teams value in SelectFarTeamplaySpawnPoint, skipping...\n"); + } + else + { + teams_assigned[team] = true; + teamplay_spawns[team] = spawn_distances[num_potential_spawns - spawn_to_use - 1].s; + } + + gi.TagFree(spawn_distances); +} + +// SetupTeamSpawnPoints: +// +// Setup the points at which the teams will spawn. +// +void SetupTeamSpawnPoints() +{ + qboolean teams_assigned[MAX_TEAMS]; + int started_at, l, firstassignment; + + for (l = 0; l < num_teams; l++) + { + teamplay_spawns[l] = NULL; + teams_assigned[l] = false; + } + + started_at = l = newrand(num_teams); + firstassignment = 1; + do + { + if (l < 0 || l >= MAX_TEAMS) + { + gi.dprintf("Warning: attempt to setup spawns for out-of-range team (%d)\n", l); + } + + if (firstassignment) + { + SelectRandomTeamplaySpawnPoint(l, teams_assigned); + firstassignment = 0; + } + else + { + SelectFarTeamplaySpawnPoint(l, teams_assigned); + } + l++; + if (l == num_teams) + l = 0; + } while (l != started_at); +} diff --git a/a_team.h b/a_team.h new file mode 100644 index 0000000..03e5db9 --- /dev/null +++ b/a_team.h @@ -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; diff --git a/acesrc/acebot.h b/acesrc/acebot.h new file mode 100644 index 0000000..5ae91ca --- /dev/null +++ b/acesrc/acebot.h @@ -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 \ No newline at end of file diff --git a/acesrc/acebot_ai.c b/acesrc/acebot_ai.c new file mode 100644 index 0000000..bfcf429 --- /dev/null +++ b/acesrc/acebot_ai.c @@ -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;isolid == 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;isolid == 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); +} diff --git a/acesrc/acebot_cmds.c b/acesrc/acebot_cmds.c new file mode 100644 index 0000000..921e923 --- /dev/null +++ b/acesrc/acebot_cmds.c @@ -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 ; ivalue ; 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 ; ivalue ; i++) + { + cl_ent = g_edicts + 1 + i; + if (!cl_ent->inuse || cl_ent->is_bot) + continue; + + gi.cprintf(cl_ent, printlevel, bigbuffer); + } +} + diff --git a/acesrc/acebot_compress.c b/acesrc/acebot_compress.c new file mode 100644 index 0000000..8d27211 --- /dev/null +++ b/acesrc/acebot_compress.c @@ -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 +#include +#include +#include +#include + +#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; +} +*/ \ No newline at end of file diff --git a/acesrc/acebot_items.c b/acesrc/acebot_items.c new file mode 100644 index 0000000..bec3a2b --- /dev/null +++ b/acesrc/acebot_items.c @@ -0,0 +1,1359 @@ +/////////////////////////////////////////////////////////////////////// +// +// 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_items.c 15 22/10/99 6:21 Riever $ + * + * $Log: /LicenseToKill/src/acesrc/acebot_items.c $ + * + * 15 22/10/99 6:21 Riever + * Modified item weightings + * + * 14 26/09/99 7:36 Riever + * HC reloading problem fixed - also changed sniper rifle reload code + * again. + * + * 13 21/09/99 14:08 Riever + * AMMO recognition code implemented - takes into account maximum allowed + * of each ammo type + * + * 12 21/09/99 12:49 Riever + * Removed a debug print that got left behind + * + * 11 21/09/99 7:30 Riever + * Found empty gun reload problem on special weapons - fix is now being + * tested (works on MP5) + * + * 10 20/09/99 21:30 Riever + * Persuaded bots to reload until last magazine. Need to review logic over + * full / empty weapons to fix the last niggle! + * + * 9 18/09/99 19:22 Riever + * Special handling added for NODE_DOOR to enable them to be successfully + * re-listed. + * + * 8 18/09/99 15:06 Riever + * Removed code that stopped door nodes being put in item list on reloads + * + * 7 18/09/99 9:50 Riever + * Corrected errors in MP5 code + * + * 6 18/09/99 8:43 Riever + * Integrated NODE_DOOR with item table system + * + * 5 18/09/99 8:06 Riever + * NODE_DOOR is now created correctly + * + * 4 14/09/99 21:53 Riever + * Used safe_bprintf method for all source files + * + * 3 14/09/99 14:07 Riever + * Added dual pistols to item collection list + * + * 2 13/09/99 19:52 Riever + * Added headers + * + */ + +/////////////////////////////////////////////////////////////////////// +// +// acebot_items.c - This file contains all of the +// item handling routines for the +// ACE bot, including fact table support +// +/////////////////////////////////////////////////////////////////////// + +#include "..\g_local.h" +#include "acebot.h" + +int num_players = 0; +int num_items = 0; +item_table_t item_table[MAX_EDICTS]; +edict_t *players[MAX_CLIENTS]; // pointers to all players in the game + +/////////////////////////////////////////////////////////////////////// +// Add the player to our list +/////////////////////////////////////////////////////////////////////// +void ACEIT_PlayerAdded(edict_t *ent) +{ + players[num_players++] = ent; +} + +/////////////////////////////////////////////////////////////////////// +// Remove player from list +/////////////////////////////////////////////////////////////////////// +void ACEIT_PlayerRemoved(edict_t *ent) +{ + int i; + int pos; + + // watch for 0 players + if(num_players == 0) + return; + + // special cas for only one player + if(num_players == 1) + { + num_players = 0; + return; + } + + // Find the player + for(i=0;imins,v); + v[2] += 18; // Stepsize + +//AQ2 trace = gi.trace (self->s.origin, v, self->maxs, goal, self, MASK_OPAQUE); + trace = gi.trace (self->s.origin, v, self->maxs, goal, self, MASK_SOLID|MASK_OPAQUE); + + // Yes we can see it + if (trace.fraction == 1.0) + return true; + else + return false; + +} + +/////////////////////////////////////////////////////////////////////// +// Visiblilty check +/////////////////////////////////////////////////////////////////////// +qboolean ACEIT_IsVisible(edict_t *self, vec3_t goal) +{ + trace_t trace; + +//AQ2 trace = gi.trace (self->s.origin, vec3_origin, vec3_origin, goal, self, MASK_OPAQUE); + trace = gi.trace (self->s.origin, vec3_origin, vec3_origin, goal, self, MASK_SOLID|MASK_OPAQUE); + + // Yes we can see it + if (trace.fraction == 1.0) + return true; + else + return false; + +} + +/////////////////////////////////////////////////////////////////////// +// Weapon changing support +/////////////////////////////////////////////////////////////////////// +//AQ2 CHANGE +// Completely rewritten for AQ2! +// +qboolean ACEIT_ChangeWeapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + qboolean loaded = true; + qboolean clips = true; + + // Has not picked up weapon yet + if(!ent->client->pers.inventory[ITEM_INDEX(item)]) + return false; + + // Do we have ammo for it? + if (item->ammo) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (!ent->client->pers.inventory[ammo_index] )//&& !g_select_empty->value) + clips = false; + else + clips = true; + } + + // see if we're already using it + if (item == ent->client->pers.weapon) + { + //Do we have one in the chamber? + if( ent->client->weaponstate == WEAPON_END_MAG) + { + loaded = false; + } + // No ammo - forget it! + if(!loaded && !clips) + return false; + // If it's not loaded - use a new clip + else if( !loaded ) + Cmd_New_Reload_f ( ent ); + return true; + } + + // Change to this weapon + ent->client->newweapon = item; + ChangeWeapon( ent ); + + return true; +} + +//=============================== +// Handling for MK23 (debugging) +//=============================== +qboolean ACEIT_ChangeMK23SpecialWeapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + qboolean loaded = true; + qboolean clips = true; + + // Has not picked up weapon yet + if(!ent->client->pers.inventory[ITEM_INDEX(item)]) + { +// safe_bprintf(PRINT_HIGH,"Not got MP23\n"); + return false; + } + + // Do we have ammo for it? + if (item->ammo) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (ent->client->pers.inventory[ammo_index] < 1) + clips = false; + else + clips = true; + } + // see if we're already using it + if (item == ent->client->pers.weapon) + { + //Do we have one in the chamber? + if( ent->client->weaponstate == WEAPON_END_MAG) + { + loaded = false; + } + // No ammo - forget it! + if(!loaded && !clips) + { +// safe_bprintf(PRINT_HIGH,"Not got MK23 Ammo\n"); + return false; + } + // If it's not loaded - use a new clip + else if( !loaded ) + { +// safe_bprintf(PRINT_HIGH,"Need to reload MK23\n"); + Cmd_New_Reload_f ( ent ); + } + return true; + } + + // Not current weapon, check ammo + if( (ent->client->mk23_rds < 1) && !clips ) + { +// safe_bprintf(PRINT_HIGH,"No change - No MK23 Ammo\n"); + return false; + } + + // Change to this weapon +// safe_bprintf(PRINT_HIGH,"Changing to MK23\n"); + ent->client->newweapon = item; + ChangeWeapon ( ent ); + return true; +} + +//=============================== +// Handling for HC (debugging) +//=============================== +qboolean ACEIT_ChangeHCSpecialWeapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + qboolean loaded = true; + qboolean clips = true; + + // Has not picked up weapon yet + if(!ent->client->pers.inventory[ITEM_INDEX(item)]) + { +// safe_bprintf(PRINT_HIGH,"Not got HC\n"); + return false; + } + + // Do we have ammo for it? + if (item->ammo) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (ent->client->pers.inventory[ammo_index] < 2) + clips = false; + else + clips = true; + } + // Check ammo + if( (ent->client->cannon_rds < 2) ) + { +// safe_bprintf(PRINT_HIGH,"No change - No HC Ammo\n"); + loaded = false; + } + // see if we're already using it + if (item == ent->client->pers.weapon) + { +/* //Do we have one in the chamber? + if( ent->client->weaponstate == WEAPON_END_MAG) + { + loaded = false; + }*/ + // No ammo - forget it! + if(!loaded && !clips) + { +// safe_bprintf(PRINT_HIGH,"Not got HC Ammo\n"); + DropSpecialWeapon ( ent ); + return false; + } + // If it's not loaded - use a new clip + else if( !loaded ) + { +// safe_bprintf(PRINT_HIGH,"Need to reload HC\n"); + Cmd_New_Reload_f ( ent ); + } + return true; + } + + // Not current weapon, check ammo + if( !loaded && !clips ) + { +// safe_bprintf(PRINT_HIGH,"No change - No HC Ammo\n"); + return false; + } + + // Change to this weapon +// safe_bprintf(PRINT_HIGH,"Changing to HandCannon\n"); + ent->client->newweapon = item; + ChangeWeapon ( ent ); + return true; +} + +//=============================== +// Handling for Sniper Rifle (debugging) +//=============================== +qboolean ACEIT_ChangeSniperSpecialWeapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + qboolean loaded = true; + qboolean clips = true; + + // Has not picked up weapon yet + if(!ent->client->pers.inventory[ITEM_INDEX(item)]) + { +// safe_bprintf(PRINT_HIGH,"Not got Sniper Rifle\n"); + return false; + } + + // Do we have ammo for it? + if (item->ammo) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (ent->client->pers.inventory[ammo_index] < 1) + clips = false; + else + clips = true; + } + // Check ammo + if( (ent->client->sniper_rds < 1) ) + { +// safe_bprintf(PRINT_HIGH,"No change - No Sniper Ammo\n"); + loaded = false; + } + // see if we're already using it + if (item == ent->client->pers.weapon) + { +/* //Do we have one in the chamber? + if( ent->client->weaponstate == WEAPON_END_MAG) + { + loaded = false; + }*/ + // No ammo - forget it! + if(!loaded && !clips) + { +// safe_bprintf(PRINT_HIGH,"Not got Sniper Ammo\n"); + DropSpecialWeapon ( ent ); + return false; + } + // If it's not loaded - use a new clip + else if( !loaded ) + { +// safe_bprintf(PRINT_HIGH,"Need to reload Sniper Rifle\n"); + Cmd_New_Reload_f ( ent ); + } + return true; + } + + // No ammo + if( !loaded && !clips ) + { +// safe_bprintf(PRINT_HIGH,"No change - No Sniper Ammo\n"); + return false; + } + + // Change to this weapon +// safe_bprintf(PRINT_HIGH,"Changing to Sniper Rifle\n"); + ent->client->newweapon = item; + ChangeWeapon ( ent ); + return true; +} +//=============================== +// Handling for M3 (debugging) +//=============================== +qboolean ACEIT_ChangeM3SpecialWeapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + qboolean loaded = true; + qboolean clips = true; + + // Has not picked up weapon yet + if(!ent->client->pers.inventory[ITEM_INDEX(item)]) + { +// safe_bprintf(PRINT_HIGH,"Not got M3\n"); + return false; + } + + // Do we have ammo for it? + if (item->ammo) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (ent->client->pers.inventory[ammo_index] < 1) + clips = false; + else + clips = true; + } + // Check ammo + if( (ent->client->shot_rds < 1) ) + { +// safe_bprintf(PRINT_HIGH,"No change - No M3 Ammo\n"); + loaded = false; + } + // see if we're already using it + if (item == ent->client->pers.weapon) + { +/* //Do we have one in the chamber? + if( ent->client->weaponstate == WEAPON_END_MAG) + { + loaded = false; + }*/ + // No ammo - forget it! + if(!loaded && !clips) + { +// safe_bprintf(PRINT_HIGH,"Not got M3 Ammo\n"); + DropSpecialWeapon ( ent ); + return false; + } + // If it's not loaded - use a new clip + else if( !loaded ) + { +// safe_bprintf(PRINT_HIGH,"Need to reload M3\n"); + Cmd_New_Reload_f ( ent ); + } + return true; + } + + // No ammo + if( !loaded && !clips ) + { +// safe_bprintf(PRINT_HIGH,"No change - No M3 Ammo\n"); + return false; + } + + // Change to this weapon +// safe_bprintf(PRINT_HIGH,"Changing to M3\n"); + ent->client->newweapon = item; + ChangeWeapon ( ent ); + return true; +} +//=============================== +// Handling for M4 (debugging) +//=============================== +qboolean ACEIT_ChangeM4SpecialWeapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + qboolean loaded = true; + qboolean clips = true; + + // Has not picked up weapon yet + if(!ent->client->pers.inventory[ITEM_INDEX(item)]) + { +// safe_bprintf(PRINT_HIGH,"Not got MP4\n"); + return false; + } + + // Do we have ammo for it? + if (item->ammo) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (ent->client->pers.inventory[ammo_index] < 1)//&& !g_select_empty->value) + clips = false; + else + clips = true; + } + // Check ammo + if( (ent->client->m4_rds < 1) ) + { +// safe_bprintf(PRINT_HIGH,"No change - No M4 Ammo\n"); + loaded = false; + } + // see if we're already using it + if (item == ent->client->pers.weapon) + { +/* //Do we have one in the chamber? + if( ent->client->weaponstate == WEAPON_END_MAG) + { + loaded = false; + }*/ + // No ammo - forget it! + if(!loaded && !clips) + { +// safe_bprintf(PRINT_HIGH,"Not got M4 Ammo\n"); + DropSpecialWeapon ( ent ); + return false; + } + // If it's not loaded - use a new clip + else if( !loaded ) + { +// safe_bprintf(PRINT_HIGH,"Need to reload M4\n"); + Cmd_New_Reload_f ( ent ); + } + return true; + } + + // No ammo + if( !loaded && !clips ) + { +// safe_bprintf(PRINT_HIGH,"No change - No M4 Ammo\n"); + return false; + } + + // Change to this weapon +// safe_bprintf(PRINT_HIGH,"Changing to M4\n"); + ent->client->newweapon = item; + ChangeWeapon ( ent ); + return true; +} +//=============================== +// Handling for MP5 (debugging) +//=============================== +qboolean ACEIT_ChangeMP5SpecialWeapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + qboolean loaded = true; + qboolean clips = true; + + // Has not picked up weapon yet + if(!ent->client->pers.inventory[ITEM_INDEX(item)]) + { +// safe_bprintf(PRINT_HIGH,"Not got MP5\n"); + return false; + } + + // Do we have ammo for it? +// if (item->ammo) +// { +// ammo_item = FindItem(item->ammo); + ammo_item = FindItem("Machinegun Magazine"); + ammo_index = ITEM_INDEX(ammo_item); + if (ent->client->pers.inventory[ammo_index] < 1) + { +// safe_bprintf(PRINT_HIGH,"Out of MP5 Clips\n"); + clips = false; + } + else + clips = true; +// } + // check ammo + if( (ent->client->mp5_rds < 1) ) + { +// safe_bprintf(PRINT_HIGH,"No MP5 Ammo\n"); + loaded = false; + } + // see if we're already using it + if (item == ent->client->pers.weapon) + { + // No ammo - forget it! + if(!loaded && !clips) + { +// safe_bprintf(PRINT_HIGH,"Stopping MP5 use - no ammo\n"); + DropSpecialWeapon ( ent ); + return false; + } + // If it's not loaded - use a new clip + else if( !loaded && clips) + { +// safe_bprintf(PRINT_HIGH,"Need to reload MP5\n"); + Cmd_New_Reload_f ( ent ); + } + return true; + } + + // Not current weapon, check ammo + if( !loaded && !clips ) + { +// safe_bprintf(PRINT_HIGH,"No change - No MP5 Ammo\n"); + return false; + } + + // Change to this weapon +// safe_bprintf(PRINT_HIGH,"Changing to MP5\n"); + ent->client->newweapon = item; + ChangeWeapon ( ent ); + return true; +} + +//=============================== +// Handling for DUAL PISTOLS (debugging) +//=============================== +qboolean ACEIT_ChangeDualSpecialWeapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + qboolean loaded = true; + qboolean clips = true; + + // Has not picked up weapon yet + if(!ent->client->pers.inventory[ITEM_INDEX(item)]) + { +// safe_bprintf(PRINT_HIGH,"Not got Dual Pistols\n"); + return false; + } + + // Do we have ammo for it? + if (item->ammo) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (ent->client->pers.inventory[ammo_index] < 2)//&& !g_select_empty->value) + clips = false; + else + clips = true; + } + // Not current weapon, check ammo + if( (ent->client->dual_rds < 2) ) + { +// safe_bprintf(PRINT_HIGH,"No change - No Dual Ammo\n"); + loaded = false; + } + + // see if we're already using it + if (item == ent->client->pers.weapon) + { +/* //Do we have one in the chamber? + if( ent->client->weaponstate == WEAPON_END_MAG) + { + loaded = false; + }*/ + // No ammo - forget it! + if(!loaded && !clips) + { +// safe_bprintf(PRINT_HIGH,"Not got Dual Ammo\n"); + DropSpecialWeapon ( ent ); + return false; + } + // If it's not loaded - use a new clip + else if( !loaded ) + { +// safe_bprintf(PRINT_HIGH,"Need to reload Dual\n"); + Cmd_New_Reload_f ( ent ); + } + return true; + } + + // Not current weapon, check ammo + if( !loaded && !clips ) + { +// safe_bprintf(PRINT_HIGH,"No change - No Dual Ammo\n"); + return false; + } + + // Change to this weapon +// safe_bprintf(PRINT_HIGH,"Changing to Dual\n"); + ent->client->newweapon = item; + ChangeWeapon ( ent ); + return true; +} +//AQ2 END + +extern gitem_armor_t jacketarmor_info; +extern gitem_armor_t combatarmor_info; +extern gitem_armor_t bodyarmor_info; + +/////////////////////////////////////////////////////////////////////// +// Check if we can use the armor +/////////////////////////////////////////////////////////////////////// +qboolean ACEIT_CanUseArmor (gitem_t *item, edict_t *other) +{ + int old_armor_index; + gitem_armor_t *oldinfo; + gitem_armor_t *newinfo; + int newcount; + float salvage; + int salvagecount; + + // get info on new armor + newinfo = (gitem_armor_t *)item->info; + + old_armor_index = ArmorIndex (other); + + // handle armor shards specially + if (item->tag == ARMOR_SHARD) + return true; + + // get info on old armor + if (old_armor_index == ITEM_INDEX(FindItem("Jacket Armor"))) + oldinfo = &jacketarmor_info; + else if (old_armor_index == ITEM_INDEX(FindItem("Combat Armor"))) + oldinfo = &combatarmor_info; + else // (old_armor_index == body_armor_index) + oldinfo = &bodyarmor_info; + + if (newinfo->normal_protection <= oldinfo->normal_protection) + { + // calc new armor values + salvage = newinfo->normal_protection / oldinfo->normal_protection; + salvagecount = salvage * newinfo->base_count; + newcount = other->client->pers.inventory[old_armor_index] + salvagecount; + + if (newcount > oldinfo->max_count) + newcount = oldinfo->max_count; + + // if we're already maxed out then we don't need the new armor + if (other->client->pers.inventory[old_armor_index] >= newcount) + return false; + + } + + return true; +} + + +/////////////////////////////////////////////////////////////////////// +// Determines the NEED for an item +// +// This function can be modified to support new items to pick up +// Any other logic that needs to be added for custom decision making +// can be added here. For now it is very simple. +/////////////////////////////////////////////////////////////////////// +float ACEIT_ItemNeed(edict_t *self, int item) +{ + gitem_t *haveband; + int band; + + // Make sure item is at least close to being valid + if(item < 0 || item > 100) + return 0.0; +//AQ2 ADD + // find out if they have a bandolier + haveband = FindItem(BAND_NAME); + if ( self->client->pers.inventory[ITEM_INDEX(haveband)] ) + band = 1; + else + band = 0; +//AQ2 END + + + switch(item) + { + // Health + case ITEMLIST_HEALTH_SMALL: + case ITEMLIST_HEALTH_MEDIUM: + case ITEMLIST_HEALTH_LARGE: + case ITEMLIST_HEALTH_MEGA: + if(self->health < 100) + return 1.0 - (float)self->health/100.0f; // worse off, higher priority + else + return 0.0; + + case ITEMLIST_AMMOPACK: + case ITEMLIST_QUADDAMAGE: + case ITEMLIST_INVULNERABILITY: + case ITEMLIST_SILENCER: + // case ITEMLIST_REBREATHER + // case ITEMLIST_ENVIRONMENTSUIT + case ITEMLIST_ADRENALINE: + case ITEMLIST_BANDOLIER: + return 0.5; +/* + // Weapons + case ITEMLIST_ROCKETLAUNCHER: + case ITEMLIST_RAILGUN: + case ITEMLIST_MACHINEGUN: + case ITEMLIST_CHAINGUN: + case ITEMLIST_SHOTGUN: + case ITEMLIST_SUPERSHOTGUN: + case ITEMLIST_BFG10K: + case ITEMLIST_GRENADELAUNCHER: + case ITEMLIST_HYPERBLASTER: + if(!self->client->pers.inventory[item]) + return 0.7; + else + return 0.0;*/ + + // Ammo + case ITEMLIST_SLUGS: + if(self->client->pers.inventory[ITEMLIST_SLUGS] < self->client->pers.max_slugs) + return 0.3; + else + return 0.0; + + case ITEMLIST_BULLETS: + if(self->client->pers.inventory[ITEMLIST_BULLETS] < self->client->pers.max_bullets) + return 0.3; + else + return 0.0; + + case ITEMLIST_SHELLS: + if(self->client->pers.inventory[ITEMLIST_SHELLS] < self->client->pers.max_shells) + return 0.3; + else + return 0.0; + + case ITEMLIST_CELLS: + if(self->client->pers.inventory[ITEMLIST_CELLS] < self->client->pers.max_cells) + return 0.3; + else + return 0.0; + + case ITEMLIST_ROCKETS: + if(self->client->pers.inventory[ITEMLIST_ROCKETS] < self->client->pers.max_rockets) + return 0.3; + else + return 0.0; + + case ITEMLIST_GRENADES: + if(self->client->pers.inventory[ITEMLIST_GRENADES] < self->client->pers.max_grenades) + return 0.3; + else + return 0.0; + + case ITEMLIST_BODYARMOR: + if(ACEIT_CanUseArmor (FindItem("Body Armor"), self)) + return 0.6; + else + return 0.0; + + case ITEMLIST_COMBATARMOR: + if(ACEIT_CanUseArmor (FindItem("Combat Armor"), self)) + return 0.6; + else + return 0.0; + + case ITEMLIST_JACKETARMOR: + if(ACEIT_CanUseArmor (FindItem("Jacket Armor"), self)) + return 0.6; + else + return 0.0; + + case ITEMLIST_POWERSCREEN: + case ITEMLIST_POWERSHIELD: + return 0.5; + +/* case ITEMLIST_FLAG1: + // If I am on team one, I want team two's flag + if(!self->client->pers.inventory[item] && self->client->resp.ctf_team == CTF_TEAM2) + return 10.0; + else + return 0.0; + + case ITEMLIST_FLAG2: + if(!self->client->pers.inventory[item] && self->client->resp.ctf_team == CTF_TEAM1) + return 10.0; + else + return 0.0; + + case ITEMLIST_RESISTANCETECH: + case ITEMLIST_STRENGTHTECH: + case ITEMLIST_HASTETECH: + case ITEMLIST_REGENERATIONTECH: + // Check for other tech + if(!self->client->pers.inventory[ITEMLIST_RESISTANCETECH] && + !self->client->pers.inventory[ITEMLIST_STRENGTHTECH] && + !self->client->pers.inventory[ITEMLIST_HASTETECH] && + !self->client->pers.inventory[ITEMLIST_REGENERATIONTECH]) + return 0.4; + else + return 0.0;*/ +//AQ2 ADD + case ITEMLIST_MP5: + case ITEMLIST_M4: + case ITEMLIST_M3: + case ITEMLIST_HC: + case ITEMLIST_SNIPER: + case ITEMLIST_DUAL: + if( + ( self->client->unique_weapon_total < unique_weapons->value + band ) + && (!self->client->pers.inventory[item]) + ) + return 0.7; + else + return 0.0; + + case ITEMLIST_AMMO_CLIP: + if(self->client->pers.inventory[ITEM_INDEX(FindItem(AMMO_CLIP_NAME))] + < self->client->pers.max_bullets) + return 0.3; + else + return 0.0; + + case ITEMLIST_AMMO_M4: + if(self->client->pers.inventory[ITEM_INDEX(FindItem(AMMO_M4_NAME))] + < self->client->pers.max_cells) + return 0.3; + else + return 0.0; + + case ITEMLIST_AMMO_MAG: + if(self->client->pers.inventory[ITEM_INDEX(FindItem(AMMO_MAG_NAME))] + < self->client->pers.max_rockets) + return 0.3; + else + return 0.0; + + case ITEMLIST_AMMO_SNIPER: + if(self->client->pers.inventory[ITEM_INDEX(FindItem(AMMO_SNIPER_NAME))] + < self->client->pers.max_slugs) + return 0.3; + else + return 0.0; + + case ITEMLIST_AMMO_M3: + if(self->client->pers.inventory[ITEM_INDEX(FindItem(AMMO_M3_NAME))] + < self->client->pers.max_shells) + return 0.3; + else + return 0.0; + +//AQ2 END + + default: + return 0.0; + + } + +} + +/////////////////////////////////////////////////////////////////////// +// Convert a classname to its index value +// +// I prefer to use integers/defines for simplicity sake. This routine +// can lead to some slowdowns I guess, but makes the rest of the code +// easier to deal with. +/////////////////////////////////////////////////////////////////////// +int ACEIT_ClassnameToIndex(char *classname) +{ + + if(strcmp(classname,"weapon_Mk23")==0) + return ITEMLIST_MK23; + + if(strcmp(classname,"weapon_MP5")==0) + return ITEMLIST_MP5; + + if(strcmp(classname,"weapon_M4")==0) + return ITEMLIST_M4; + + if(strcmp(classname,"weapon_M3")==0) + return ITEMLIST_M3; + + if(strcmp(classname,"weapon_HC")==0) + return ITEMLIST_HC; + + if(strcmp(classname,"weapon_Sniper")==0) + return ITEMLIST_SNIPER; + + if(strcmp(classname,"weapon_Dual")==0) + return ITEMLIST_DUAL; + + if(strcmp(classname,"weapon_Knife")==0) + return ITEMLIST_KNIFE; + + if(strcmp(classname,"weapon_Grenade")==0) + return ITEMLIST_GRENADE; + + if(strcmp(classname,"item_quiet")==0) + return ITEMLIST_SIL; + + if(strcmp(classname,"item_slippers")==0) + return ITEMLIST_SLIP; + + if(strcmp(classname,"item_band")==0) + return ITEMLIST_BAND; + + if(strcmp(classname,"item_vest")==0) + return ITEMLIST_KEV; + + if(strcmp(classname,"item_lasersight")==0) + return ITEMLIST_LASER; + + if(strcmp(classname,"ammo_clip")==0) + return ITEMLIST_AMMO_CLIP; + + if(strcmp(classname,"ammo_m4")==0) + return ITEMLIST_AMMO_M4; + + if(strcmp(classname,"ammo_mag")==0) + return ITEMLIST_AMMO_MAG; + + if(strcmp(classname,"ammo_sniper")==0) + return ITEMLIST_AMMO_SNIPER; + + if(strcmp(classname,"ammo_m3")==0) + return ITEMLIST_AMMO_M3; + + // Normal quake stuff: + if(strcmp(classname,"item_armor_body")==0) + return ITEMLIST_BODYARMOR; + + if(strcmp(classname,"item_armor_combat")==0) + return ITEMLIST_COMBATARMOR; + + if(strcmp(classname,"item_armor_jacket")==0) + return ITEMLIST_JACKETARMOR; + + if(strcmp(classname,"item_armor_shard")==0) + return ITEMLIST_ARMORSHARD; + + if(strcmp(classname,"item_power_screen")==0) + return ITEMLIST_POWERSCREEN; + + if(strcmp(classname,"item_power_shield")==0) + return ITEMLIST_POWERSHIELD; + + if(strcmp(classname,"weapon_grapple")==0) + return ITEMLIST_GRAPPLE; + + if(strcmp(classname,"weapon_blaster")==0) + return ITEMLIST_BLASTER; + + if(strcmp(classname,"weapon_shotgun")==0) + return ITEMLIST_SHOTGUN; + + if(strcmp(classname,"weapon_supershotgun")==0) + return ITEMLIST_SUPERSHOTGUN; + + if(strcmp(classname,"weapon_machinegun")==0) + return ITEMLIST_MACHINEGUN; + + if(strcmp(classname,"weapon_chaingun")==0) + return ITEMLIST_CHAINGUN; + + if(strcmp(classname,"weapon_chaingun")==0) + return ITEMLIST_CHAINGUN; + + if(strcmp(classname,"ammo_grenades")==0) + return ITEMLIST_GRENADES; + + if(strcmp(classname,"weapon_grenadelauncher")==0) + return ITEMLIST_GRENADELAUNCHER; + + if(strcmp(classname,"weapon_rocketlauncher")==0) + return ITEMLIST_ROCKETLAUNCHER; + + if(strcmp(classname,"weapon_hyperblaster")==0) + return ITEMLIST_HYPERBLASTER; + + if(strcmp(classname,"weapon_railgun")==0) + return ITEMLIST_RAILGUN; + + if(strcmp(classname,"weapon_bfg10k")==0) + return ITEMLIST_BFG10K; + + if(strcmp(classname,"ammo_shells")==0) + return ITEMLIST_SHELLS; + + if(strcmp(classname,"ammo_bullets")==0) + return ITEMLIST_BULLETS; + + if(strcmp(classname,"ammo_cells")==0) + return ITEMLIST_CELLS; + + if(strcmp(classname,"ammo_rockets")==0) + return ITEMLIST_ROCKETS; + + if(strcmp(classname,"ammo_slugs")==0) + return ITEMLIST_SLUGS; + + if(strcmp(classname,"item_quad")==0) + return ITEMLIST_QUADDAMAGE; + + if(strcmp(classname,"item_invunerability")==0) + return ITEMLIST_INVULNERABILITY; + + if(strcmp(classname,"item_silencer")==0) + return ITEMLIST_SILENCER; + + if(strcmp(classname,"item_rebreather")==0) + return ITEMLIST_REBREATHER; + + if(strcmp(classname,"item_enviornmentsuit")==0) + return ITEMLIST_ENVIRONMENTSUIT; + + if(strcmp(classname,"item_ancienthead")==0) + return ITEMLIST_ANCIENTHEAD; + + if(strcmp(classname,"item_adrenaline")==0) + return ITEMLIST_ADRENALINE; + + if(strcmp(classname,"item_bandolier")==0) + return ITEMLIST_BANDOLIER; + + if(strcmp(classname,"item_pack")==0) + return ITEMLIST_AMMOPACK; + + if(strcmp(classname,"item_datacd")==0) + return ITEMLIST_DATACD; + + if(strcmp(classname,"item_powercube")==0) + return ITEMLIST_POWERCUBE; + + if(strcmp(classname,"item_pyramidkey")==0) + return ITEMLIST_PYRAMIDKEY; + + if(strcmp(classname,"item_dataspinner")==0) + return ITEMLIST_DATASPINNER; + + if(strcmp(classname,"item_securitypass")==0) + return ITEMLIST_SECURITYPASS; + + if(strcmp(classname,"item_bluekey")==0) + return ITEMLIST_BLUEKEY; + + if(strcmp(classname,"item_redkey")==0) + return ITEMLIST_REDKEY; + + if(strcmp(classname,"item_commandershead")==0) + return ITEMLIST_COMMANDERSHEAD; + + if(strcmp(classname,"item_airstrikemarker")==0) + return ITEMLIST_AIRSTRIKEMARKER; + + if(strcmp(classname,"item_health")==0) // ?? + return ITEMLIST_HEALTH; + + if(strcmp(classname,"item_flag_team1")==0) + return ITEMLIST_FLAG1; + + if(strcmp(classname,"item_flag_team2")==0) + return ITEMLIST_FLAG2; + + if(strcmp(classname,"item_tech1")==0) + return ITEMLIST_RESISTANCETECH; + + if(strcmp(classname,"item_tech2")==0) + return ITEMLIST_STRENGTHTECH; + + if(strcmp(classname,"item_tech3")==0) + return ITEMLIST_HASTETECH; + + if(strcmp(classname,"item_tech4")==0) + return ITEMLIST_REGENERATIONTECH; + + if(strcmp(classname,"item_health_small")==0) + return ITEMLIST_HEALTH_SMALL; + + if(strcmp(classname,"item_health_medium")==0) + return ITEMLIST_HEALTH_MEDIUM; + + if(strcmp(classname,"item_health_large")==0) + return ITEMLIST_HEALTH_LARGE; + + if(strcmp(classname,"item_health_mega")==0) + return ITEMLIST_HEALTH_MEGA; + + return INVALID; +} + + +/////////////////////////////////////////////////////////////////////// +// Only called once per level, when saved will not be called again +// +// Downside of the routine is that items can not move about. If the level +// has been saved before and reloaded, it could cause a problem if there +// are items that spawn at random locations. +// +#define DEBUG // uncomment to write out items to a file. +/////////////////////////////////////////////////////////////////////// +void ACEIT_BuildItemNodeTable (qboolean rebuild) +{ + edict_t *items; + int i,item_index; + vec3_t v,v1,v2; + +#ifdef DEBUG + FILE *pOut; // for testing + if((pOut = fopen("items.txt","wt"))==NULL) + return; +#endif + + num_items = 0; + + // Add game items + for(items = g_edicts; items < &g_edicts[globals.num_edicts]; items++) + { + // filter out crap + if(items->solid == SOLID_NOT) + continue; + + if(!items->classname) + continue; + + ///////////////////////////////////////////////////////////////// + // Items + ///////////////////////////////////////////////////////////////// + item_index = ACEIT_ClassnameToIndex(items->classname); + + //////////////////////////////////////////////////////////////// + // SPECIAL NAV NODE DROPPING CODE + //////////////////////////////////////////////////////////////// + // Special node dropping for platforms + if(strcmp(items->classname,"func_plat")==0) + { + if(!rebuild) + ACEND_AddNode(items,NODE_PLATFORM); + item_index = 99; // to allow to pass the item index test + } + + // Special node dropping for teleporters + if(strcmp(items->classname,"misc_teleporter_dest")==0 || strcmp(items->classname,"misc_teleporter")==0) + { + if(!rebuild) + ACEND_AddNode(items,NODE_TELEPORTER); + item_index = 99; + } + + // Special node dropping for doors - RiEvEr + if(strcmp(items->classname,"func_door_rotating")==0 ) + { + item_index = 99; + if(!rebuild) + { + // add a pointer to the item entity + item_table[num_items].ent = items; + item_table[num_items].item = item_index; + // create the node + item_table[num_items].node = ACEND_AddNode(items,NODE_DOOR); +#ifdef DEBUG + fprintf(pOut,"item: %s node: %d pos: %f %f %f\n",items->classname, + item_table[num_items].node, + items->s.origin[0],items->s.origin[1],items->s.origin[2]); +#endif + num_items++; + continue; + } + } + + #ifdef DEBUG + if(item_index == INVALID) + fprintf(pOut,"Rejected item: %s node: %d pos: %f %f %f\n",items->classname,item_table[num_items].node,items->s.origin[0],items->s.origin[1],items->s.origin[2]); +// else +// fprintf(pOut,"item: %s node: %d pos: %f %f %f\n",items->classname,item_table[num_items].node,items->s.origin[0],items->s.origin[1],items->s.origin[2]); + #endif + + if(item_index == INVALID) + continue; + + // add a pointer to the item entity + item_table[num_items].ent = items; + item_table[num_items].item = item_index; + + // If new, add nodes for items + if(!rebuild) + { + item_table[num_items].node = ACEND_AddNode(items,NODE_ITEM); +#ifdef DEBUG + fprintf(pOut,"item: %s node: %d pos: %f %f %f\n",items->classname,item_table[num_items].node,items->s.origin[0],items->s.origin[1],items->s.origin[2]); +#endif + num_items++; + } + else // Now if rebuilding, just relink ent structures + { + // Find stored location + for(i=0;is.origin,v); + + // Add 16 to item type nodes + if(nodes[i].type == NODE_ITEM) + v[2] += 16; + + // Add 32 to teleporter + if(nodes[i].type == NODE_TELEPORTER) + v[2] += 32; + + // Door handling + if( nodes[i].type == NODE_DOOR) + { + vec3_t position; + // Find mid point of door max and min and put the node there + VectorCopy(items->s.origin, position); + // find center of door + position[0] = position[0] + items->mins[0] + ((items->maxs[0] - items->mins[0]) /2); + position[1] = position[1] + items->mins[1] + ((items->maxs[1] - items->mins[1]) /2); + position[2] -= 16; // lower it a little + // Set location + VectorCopy(position, v); + } + + if(nodes[i].type == NODE_PLATFORM) + { + VectorCopy(items->maxs,v1); + VectorCopy(items->mins,v2); + + // To get the center + v[0] = (v1[0] - v2[0]) / 2 + v2[0]; + v[1] = (v1[1] - v2[1]) / 2 + v2[1]; + v[2] = items->mins[2]+64; + } + + if(v[0] == nodes[i].origin[0] && + v[1] == nodes[i].origin[1] && + v[2] == nodes[i].origin[2]) + { + // found a match now link to facts + item_table[num_items].node = i; +#ifdef DEBUG + fprintf(pOut,"Relink item: %s node: %d pos: %f %f %f\n",items->classname,item_table[num_items].node,items->s.origin[0],items->s.origin[1],items->s.origin[2]); +#endif + num_items++; + + } + } + } + } + + + } + +#ifdef DEBUG + fclose(pOut); +#endif + +} diff --git a/acesrc/acebot_movement.c b/acesrc/acebot_movement.c new file mode 100644 index 0000000..2c4d9e4 --- /dev/null +++ b/acesrc/acebot_movement.c @@ -0,0 +1,1038 @@ +/////////////////////////////////////////////////////////////////////// +// +// 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_movement.c 27 22/10/99 7:13 Riever $ + * + * $Log: /LicenseToKill/src/acesrc/acebot_movement.c $ + * + * 27 22/10/99 7:13 Riever + * New ladder detection function enables creation of ladder nodes. + * Distance checks added for ladder climbing. + * Specfial handling to reduce velocity at ladder mount point. + * Ledge handling code allows jumping up small obstacles. + * Air Walking codce found and removed. + * Ladder climbing code refined and working. + * + * 26 20/10/99 20:33 Riever + * Removed a velocity hack + * + * 25 16/10/99 18:10 Riever + * Messing with wander code - marked up. + * + * 24 16/10/99 17:03 Riever + * Added jump to shoot when line of sight is blocked. + * + * 23 16/10/99 13:11 Riever + * Changed special move code to get jumps working better. + * + * 22 10/10/99 9:34 Riever + * Anglechange before random move added + * Changed special move code. + * + * 21 6/10/99 20:46 Riever + * Taught bots to aim round teammates and not fire at walls. + * + * 20 6/10/99 17:40 Riever + * Added TeamPlay state STATE_POSITION to enable bots to seperate and + * avoid centipede formations. + * + * 19 1/10/99 18:19 Riever + * Removed a debug print + * + * 18 30/09/99 8:03 Riever + * Put a distance limit on where crouching starts in the random combat + * code. + * + * 17 27/09/99 14:28 Riever + * Added (UNUSED) code for AntPathMove as a reference for anyone who feels + * like experimenting with it in another bot. + * + * 16 21/09/99 22:35 Riever + * Comments... + * + * 15 21/09/99 21:29 Riever + * Changed checkeyes focalpoint to 64 + * + * 14 21/09/99 21:25 Riever + * Steve was messing with the drown time so I took that bit out. + * + * 13 21/09/99 20:19 Riever + * Set teamPauseTime in attack routine. + * + * 12 21/09/99 20:14 Riever + * Reduced backwards movement on ledge avoidance. + * + * 11 21/09/99 12:21 Riever + * Ledge checking code inserted in movement functions to try to prevent + * sky diving... + * + * 10 21/09/99 11:18 Riever + * Changed logic checking in ledge code to avoid jumping off roofs. + * + * 9 18/09/99 19:20 Riever + * NODE_DOOR special handling added. I sweated blood over this so there's + * better not be any complaints! + * + * 8 18/09/99 9:51 Riever + * Reduced ledge check to 100 (was 400) + * Added some DroneBot code + * + * 7 14/09/99 14:37 Riever + * ForwardMove is now forced if only weapon available is the knife + * + * 6 14/09/99 14:15 Riever + * Made check for WEAPON_READY before firing + * + * 5 14/09/99 14:09 Riever + * Modified bot stupidity to always be off target a little if not + * ltk_skill == 10 + * + * 4 14/09/99 8:35 Riever + * Updated comments - tested - works + * + * 3 14/09/99 8:13 Riever + * Now using ltk_skill cvar to control bot accuracy + * + * 2 13/09/99 19:52 Riever + * Added headers + * + */ + +/////////////////////////////////////////////////////////////////////// +// acebot_movement.c - This file contains all of the +// movement routines for the ACE bot +// +/////////////////////////////////////////////////////////////////////// + +#include "..\g_local.h" +#include "acebot.h" + +qboolean ACEAI_CheckShot(edict_t *self); +void ACEMV_ChangeBotAngle (edict_t *ent); +qboolean ACEND_LadderForward( edict_t *self ); + +//============================================================= +// CanMoveSafely (dronebot) +//============================================================= +// Checks for lava and slime +#define MASK_DEADLY (CONTENTS_LAVA|CONTENTS_SLIME) +#define TRACE_DIST_SHORT 48 +#define TRACE_DOWN 96 +#define VEC_ORIGIN tv(0,0,0) +// + +qboolean CanMoveSafely(edict_t *self, vec3_t angles) +{ + vec3_t dir, angle, dest1, dest2; + trace_t trace; + float this_dist; + +// self->bot_ai.next_safety_time = level.time + EYES_FREQ; + + VectorClear(angle); + angle[1] = angles[1]; + + AngleVectors(angle, dir, NULL, NULL); + + // create a position in front of the bot + VectorMA(self->s.origin, TRACE_DIST_SHORT, dir, dest1); + + // Modified to check for crawl direction + //trace = gi.trace(self->s.origin, mins, self->maxs, dest, self, MASK_SOLID); + //TempLaser (self->s.origin, dest1); + trace = gi.trace(self->s.origin, tv(-16,-16,0), tv(16,16,0), dest1, self, MASK_PLAYERSOLID); + + // Check if we are looking inside a wall! + if (trace.startsolid) + return (true); + + if (trace.fraction > 0) + { // check that destination is onground, or not above lava/slime + dest1[0] = trace.endpos[0]; + dest1[1] = trace.endpos[1]; + dest1[2] = trace.endpos[2] - 28; + this_dist = trace.fraction * TRACE_DIST_SHORT; + + if (gi.pointcontents(dest1) & MASK_PLAYERSOLID) + return (true); + + + // create a position a distance below it + VectorCopy( trace.endpos, dest2); + dest2[2] -= TRACE_DOWN; + //TempLaser (trace.endpos, dest2); + trace = gi.trace(trace.endpos, VEC_ORIGIN, VEC_ORIGIN, dest2, self, MASK_PLAYERSOLID | MASK_DEADLY); + + if( (trace.fraction == 1.0) // long drop! + || (trace.contents & MASK_DEADLY) ) // avoid SLIME or LAVA + { + return (false); + } + else + { + return (true); + } + } + //gi.bprintf(PRINT_HIGH,"Default failure from LAVACHECK\n"); + return (true); +} +/////////////////////////////////////////////////////////////////////// +// Checks if bot can move (really just checking the ground) +// Also, this is not a real accurate check, but does a +// pretty good job and looks for lava/slime. +/////////////////////////////////////////////////////////////////////// +qboolean ACEMV_CanMove(edict_t *self, int direction) +{ + vec3_t forward, right; + vec3_t offset,start,end; + vec3_t angles; + trace_t tr; + + // Now check to see if move will move us off an edge + VectorCopy(self->s.angles,angles); + + if(direction == MOVE_LEFT) + angles[1] += 90; + else if(direction == MOVE_RIGHT) + angles[1] -= 90; + else if(direction == MOVE_BACK) + angles[1] -=180; + + // Set up the vectors + AngleVectors (angles, forward, right, NULL); + + VectorSet(offset, 36, 0, 24); + G_ProjectSource (self->s.origin, offset, forward, right, start); + + VectorSet(offset, 36, 0, -100); // RiEvEr reduced drop distance + G_ProjectSource (self->s.origin, offset, forward, right, end); + + //AQ2 ADDED MASK_SOLID + tr = gi.trace(start, NULL, NULL, end, self, MASK_SOLID|MASK_OPAQUE); + + if(tr.fraction == 1.0 || tr.contents & (CONTENTS_LAVA|CONTENTS_SLIME)) + { + if(debug_mode) + debug_printf("%s: move blocked\n",self->client->pers.netname); + return false; + } + + return true; // yup, can move +} + +/////////////////////////////////////////////////////////////////////// +// Handle special cases of crouch/jump +// +// If the move is resolved here, this function returns +// true. +/////////////////////////////////////////////////////////////////////// +qboolean ACEMV_SpecialMove(edict_t *self, usercmd_t *ucmd) +{ + vec3_t dir,forward,right,start,end,offset; + vec3_t top; + trace_t tr; + + // Get current direction + VectorCopy(self->client->ps.viewangles,dir); + dir[YAW] = self->s.angles[YAW]; + AngleVectors (dir, forward, right, NULL); + + VectorSet(offset, 0, 0, 0); // changed from 18,0,0 + G_ProjectSource (self->s.origin, offset, forward, right, start); + offset[0] += 18; + G_ProjectSource (self->s.origin, offset, forward, right, end); + + // trace it + start[2] += 18; // so they are not jumping all the time + end[2] += 18; + tr = gi.trace (start, self->mins, self->maxs, end, self, MASK_MONSTERSOLID); + +//RiEvEr if(tr.allsolid) + if( tr.fraction < 1.0) + { + // Check for crouching + start[2] -= 14; + end[2] -= 14; + + // Set up for crouching check + VectorCopy(self->maxs,top); + top[2] = 0.0; // crouching height + tr = gi.trace (start, self->mins, top, end, self, MASK_PLAYERSOLID); + + // Crouch +//RiEvEr if(!tr.allsolid) + if( tr.fraction == 1.0 ) + { + ucmd->forwardmove = 400; + ucmd->upmove = -400; + return true; + } + + // Check for jump + start[2] += 32; + end[2] += 32; + tr = gi.trace (start, self->mins, self->maxs, end, self, MASK_MONSTERSOLID); + +//RiEvEr if(!tr.allsolid) + if( tr.fraction == 1.0) + { + ucmd->forwardmove = 400; + ucmd->upmove = 400; + return true; + } + } + + return false; // We did not resolve a move here +} + + +/////////////////////////////////////////////////////////////////////// +// Checks for obstructions in front of bot +// +// This is a function I created origianlly for ACE that +// tries to help steer the bot around obstructions. +// +// If the move is resolved here, this function returns true. +/////////////////////////////////////////////////////////////////////// +qboolean ACEMV_CheckEyes(edict_t *self, usercmd_t *ucmd) +{ + vec3_t forward, right; + vec3_t leftstart, rightstart,focalpoint; + vec3_t /* upstart,*/upend; + vec3_t dir,offset; + + trace_t traceRight,traceLeft,/*traceUp,*/ traceFront; // for eyesight + + // Get current angle and set up "eyes" + VectorCopy(self->s.angles,dir); + AngleVectors (dir, forward, right, NULL); + + // Let them move to targets by walls + if(!self->movetarget) +// VectorSet(offset,200,0,4); // focalpoint + VectorSet(offset,64,0,4); // focalpoint + else + VectorSet(offset,36,0,4); // focalpoint + + G_ProjectSource (self->s.origin, offset, forward, right, focalpoint); + + // Check from self to focalpoint + // Ladder code + VectorSet(offset,36,0,0); // set as high as possible + G_ProjectSource (self->s.origin, offset, forward, right, upend); + //AQ2 ADDED MASK_SOLID + traceFront = gi.trace(self->s.origin, self->mins, self->maxs, upend, self, MASK_SOLID|MASK_OPAQUE); + + if(traceFront.contents & 0x8000000) // using detail brush here cuz sometimes it does not pick up ladders...?? + { + ucmd->upmove = 400; + ucmd->forwardmove = 400; + return true; + } + + // If this check fails we need to continue on with more detailed checks + if(traceFront.fraction == 1) + { + ucmd->forwardmove = 400; + return true; + } + + VectorSet(offset, 0, 18, 4); + G_ProjectSource (self->s.origin, offset, forward, right, leftstart); + + offset[1] -= 36; // want to make sure this is correct + //VectorSet(offset, 0, -18, 4); + G_ProjectSource (self->s.origin, offset, forward, right, rightstart); + + traceRight = gi.trace(rightstart, NULL, NULL, focalpoint, self, MASK_OPAQUE); + traceLeft = gi.trace(leftstart, NULL, NULL, focalpoint, self, MASK_OPAQUE); + + // Wall checking code, this will degenerate progressivly so the least cost + // check will be done first. + + // If open space move ok + if(traceRight.fraction != 1 || traceLeft.fraction != 1 || strcmp(traceLeft.ent->classname,"func_door")!=0) + { +//@@ weird code... +/* // Special uppoint logic to check for slopes/stairs/jumping etc. + VectorSet(offset, 0, 18, 24); + G_ProjectSource (self->s.origin, offset, forward, right, upstart); + + VectorSet(offset,0,0,200); // scan for height above head + G_ProjectSource (self->s.origin, offset, forward, right, upend); + traceUp = gi.trace(upstart, NULL, NULL, upend, self, MASK_OPAQUE); + + VectorSet(offset,200,0,200*traceUp.fraction-5); // set as high as possible + G_ProjectSource (self->s.origin, offset, forward, right, upend); + traceUp = gi.trace(upstart, NULL, NULL, upend, self, MASK_OPAQUE); + + // If the upper trace is not open, we need to turn. + if(traceUp.fraction != 1)*/ + { + if(traceRight.fraction > traceLeft.fraction) + self->s.angles[YAW] += (1.0 - traceLeft.fraction) * 45.0; + else + self->s.angles[YAW] += -(1.0 - traceRight.fraction) * 45.0; + + ucmd->forwardmove = 400; + ACEMV_ChangeBotAngle(self); + return true; + } + } + + return false; +} + +/////////////////////////////////////////////////////////////////////// +// Make the change in angles a little more gradual, not so snappy +// Subtle, but noticeable. +// +// Modified from the original id ChangeYaw code... +/////////////////////////////////////////////////////////////////////// +void ACEMV_ChangeBotAngle (edict_t *ent) +{ + float ideal_yaw; + float ideal_pitch; + float current_yaw; + float current_pitch; + float move; + float speed; + vec3_t ideal_angle; + + // Normalize the move angle first + VectorNormalize(ent->move_vector); + + current_yaw = anglemod(ent->s.angles[YAW]); + current_pitch = anglemod(ent->s.angles[PITCH]); + + vectoangles (ent->move_vector, ideal_angle); + + ideal_yaw = anglemod(ideal_angle[YAW]); + ideal_pitch = anglemod(ideal_angle[PITCH]); + + // Yaw + if (current_yaw != ideal_yaw) + { + move = ideal_yaw - current_yaw; + speed = ent->yaw_speed; + if (ideal_yaw > current_yaw) + { + 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_yaw + move); + } + + // Pitch + if (current_pitch != ideal_pitch) + { + move = ideal_pitch - current_pitch; + speed = ent->yaw_speed; + if (ideal_pitch > current_pitch) + { + 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[PITCH] = anglemod (current_pitch + move); + } +} + +/////////////////////////////////////////////////////////////////////// +// Set bot to move to it's movetarget. (following node path) +/////////////////////////////////////////////////////////////////////// +void ACEMV_MoveToGoal(edict_t *self, usercmd_t *ucmd) +{ + // If a rocket or grenade is around deal with it + // Simple, but effective (could be rewritten to be more accurate) + if(strcmp(self->movetarget->classname,"rocket")==0 || + strcmp(self->movetarget->classname,"grenade")==0) + { + VectorSubtract (self->movetarget->s.origin, self->s.origin, self->move_vector); + ACEMV_ChangeBotAngle(self); + if(debug_mode) + debug_printf("%s: Oh crap a rocket!\n",self->client->pers.netname); + + // strafe left/right + if(rand()%1 && ACEMV_CanMove(self, MOVE_LEFT)) + ucmd->sidemove = -400; + else if(ACEMV_CanMove(self, MOVE_RIGHT)) + ucmd->sidemove = 400; + return; + + } + else + { + // Set bot's movement direction + VectorSubtract (self->movetarget->s.origin, self->s.origin, self->move_vector); + ACEMV_ChangeBotAngle(self); + ucmd->forwardmove = 400; + return; + } +} + +/////////////////////////////////////////////////////////////////////// +// Main movement code. (following node path) +/////////////////////////////////////////////////////////////////////// +void ACEMV_Move(edict_t *self, usercmd_t *ucmd) +{ + vec3_t dist; + int current_node_type=-1; + int next_node_type=-1; + int i; + float distance; + + // Get current and next node back from nav code. + if(!ACEND_FollowPath(self)) + { + if( + ( !teamplay->value ) || + (teamplay->value && level.time >= (self->teamPauseTime)) + ) + { + self->state = STATE_WANDER; + self->wander_timeout = level.time + 1.0; + return; + } + else + { + // Teamplay mode - just fan out and chill + self->state = STATE_POSITION; + self->goal_node = INVALID; + self->wander_timeout = level.time + 1.0; + return; + } + } + + current_node_type = nodes[self->current_node].type; + next_node_type = nodes[self->next_node].type; + + /////////////////////////// + // Move To Goal + /////////////////////////// + if (self->movetarget) + ACEMV_MoveToGoal(self,ucmd); + +/* //////////////////////////////////////////////////////// + // Grapple + /////////////////////////////////////////////////////// + if(next_node_type == NODE_GRAPPLE) + { + ACEMV_ChangeBotAngle(self); + ACEIT_ChangeWeapon(self,FindItem("grapple")); + ucmd->buttons = BUTTON_ATTACK; + return; + } + // Reset the grapple if hangin on a graple node + if(current_node_type == NODE_GRAPPLE) + { + CTFPlayerResetGrapple(self); + return; + }*/ + //////////////////////////////////////////////////////// + // Doors - RiEvEr + /////////////////////////////////////////////////////// + if(current_node_type == NODE_DOOR) + { + // check to see if door is open? + for(i=0;icurrent_node) + { + if( (item_table[i].ent->moveinfo.state != STATE_TOP) && + (item_table[i].ent->moveinfo.state != STATE_UP) ) + { + vec3_t difference; + float distance; + + // Find distance to the door + VectorSubtract (self->s.origin, nodes[self->current_node].origin, difference); + distance = VectorLength(difference); + + // If not close enough carry on moving + if( (distance > 64.0) + && ( VectorLength(item_table[i].ent->avelocity) == 0.0) ) + { + ucmd->forwardmove = 200; + return; + } + + // Within range + if( self->last_door_time < (level.time - 2.0) ) + { + Cmd_OpenDoor_f ( self ); // Open the door + self->last_door_time = level.time; // wait! + } + ucmd->forwardmove = -200; //walk backwards a little + return; // Wait for door to open + } + else if( VectorLength(item_table[i].ent->avelocity) > 0.0) + { + ucmd->forwardmove = -200; //walk backwards a little + return; + } + } + } + } + + //////////////////////////////////////////////////////// + // Platforms + /////////////////////////////////////////////////////// + if(current_node_type != NODE_PLATFORM && next_node_type == NODE_PLATFORM) + { + // check to see if lift is down? + for(i=0;inext_node) + if(item_table[i].ent->moveinfo.state != STATE_BOTTOM) + return; // Wait for elevator + } + if(current_node_type == NODE_PLATFORM && next_node_type == NODE_PLATFORM) + { + // Move to the center + self->move_vector[2] = 0; // kill z movement + if(VectorLength(self->move_vector) > 10) + ucmd->forwardmove = 200; // walk to center + + ACEMV_ChangeBotAngle(self); + + return; // No move, riding elevator + } + + //////////////////////////////////////////////////////// + // Jumpto Nodes + /////////////////////////////////////////////////////// + if(next_node_type == NODE_JUMP || + (current_node_type == NODE_JUMP && next_node_type != NODE_ITEM && nodes[self->next_node].origin[2] > self->s.origin[2])) + { + // Set up a jump move + ucmd->forwardmove = 400; + ucmd->upmove = 400; + + ACEMV_ChangeBotAngle(self); + + // RiEvEr - kill the velocity hack +/* VectorCopy(self->move_vector,dist); + VectorScale(dist,440,self->velocity);*/ + + return; + } + + //////////////////////////////////////////////////////// + // Ladder Nodes + /////////////////////////////////////////////////////// + + // Find the distance vector to the next node + VectorSubtract( nodes[self->next_node].origin, self->s.origin, dist); + // Lose the vertical component + dist[2] = 0; + // Get the absolute length + distance = VectorLength(dist); + + if(next_node_type == NODE_LADDER && //(gi.pointcontents(self->s.origin) & CONTENTS_LADDER) && + nodes[self->next_node].origin[2] > self->s.origin[2] && + distance < NODE_DENSITY) + { + // Otherwise move as fast as we can + ucmd->forwardmove = 100; // Reduced from 400 + self->velocity[2] = 320; // Reduced from 320 + ucmd->sidemove = 0; + + ACEMV_ChangeBotAngle(self); + + return; + + } + // If getting off the ladder + if( + (current_node_type == NODE_LADDER && next_node_type != NODE_LADDER && + nodes[self->next_node].origin[2] > self->s.origin[2]) + ) + { + ucmd->forwardmove = 200; // Reduced from 400 +// ucmd->upmove = 200; + self->velocity[2] = 200; + ACEMV_ChangeBotAngle(self); + return; + } + // If getting onto the ladder + if( + (current_node_type != NODE_LADDER && next_node_type == NODE_LADDER )//&& +// (nodes[self->next_node].origin[2] > self->s.origin[2]) + ) + { + ucmd->forwardmove = 100; // Reduced from 400 +// ucmd->upmove = 200; +// self->velocity[2] = 200; + ACEMV_ChangeBotAngle(self); + return; + } + + //============================== + // LEDGES etc.. + // If trying to jump up a ledge + if( + (current_node_type == NODE_MOVE && + nodes[self->next_node].origin[2] > self->s.origin[2]+16 && distance < NODE_DENSITY) + ) + { + ucmd->forwardmove = 400; + ucmd->upmove = 400; + ACEMV_ChangeBotAngle(self); + return; + } + //////////////////////////////////////////////////////// + // Water Nodes + /////////////////////////////////////////////////////// + if(current_node_type == NODE_WATER) + { + // We need to be pointed up/down + ACEMV_ChangeBotAngle(self); + + // If the next node is not in the water, then move up to get out. + if(next_node_type != NODE_WATER && !(gi.pointcontents(nodes[self->next_node].origin) & MASK_WATER)) // Exit water + ucmd->upmove = 400; + + ucmd->forwardmove = 300; + return; + + } + + // Falling off ledge? +/* if(!self->groundentity) + { + ACEMV_ChangeBotAngle(self); + + self->velocity[0] = self->move_vector[0] * 360; + self->velocity[1] = self->move_vector[1] * 360; + + return; + }*/ + + // Check to see if stuck, and if so try to free us + // Also handles crouching + if(VectorLength(self->velocity) < 37) + { + // Keep a random factor just in case.... + if(random() > 0.1 && ACEMV_SpecialMove(self, ucmd)) + return; + + self->s.angles[YAW] += random() * 180 - 90; + + ACEMV_ChangeBotAngle(self); + + ucmd->forwardmove = 400; + + return; + } + + ACEMV_ChangeBotAngle(self); + + // Otherwise move as fast as we can + // If it's safe to move forward (I can't believe ACE didn't check this! + if( ACEMV_CanMove( self, MOVE_FORWARD) || (current_node_type == NODE_LADDER) ) + { + ucmd->forwardmove = 400; + } + else + { + // Forget about this route! +// ucmd->forwardmove = -100; + self->movetarget = NULL; + } +} + + +/////////////////////////////////////////////////////////////////////// +// Wandering code (based on old ACE movement code) +/////////////////////////////////////////////////////////////////////// +// +// RiEvEr - this routine is of a very poor standard and has a LOT of problems +// Maybe replace this with the DroneBot or ReDeMpTiOn wander code? +// +void ACEMV_Wander(edict_t *self, usercmd_t *ucmd) +{ + vec3_t temp; + + // Do not move + if(self->next_move_time > level.time) + return; + + // Special check for elevators, stand still until the ride comes to a complete stop. + if(self->groundentity != NULL && self->groundentity->use == Use_Plat) + if(self->groundentity->moveinfo.state == STATE_UP || + self->groundentity->moveinfo.state == STATE_DOWN) // only move when platform not + { + self->velocity[0] = 0; + self->velocity[1] = 0; + self->velocity[2] = 0; + self->next_move_time = level.time + 0.5; + return; + } + + + // Is there a target to move to + if (self->movetarget) + ACEMV_MoveToGoal(self,ucmd); + + //////////////////////////////// + // Swimming? + //////////////////////////////// + VectorCopy(self->s.origin,temp); + temp[2]+=24; + + if(gi.pointcontents (temp) & MASK_WATER) + { + // If drowning and no node, move up + if(self->client->next_drown_time > 0) + { + ucmd->upmove = 1; + self->s.angles[PITCH] = -45; + } + else + ucmd->upmove = 15; + + ucmd->forwardmove = 300; + } +// else +// self->client->next_drown_time = 0; // probably shound not be messing with this, but + + //////////////////////////////// + // Lava? + //////////////////////////////// + temp[2]-=48; + if(gi.pointcontents(temp) & (CONTENTS_LAVA|CONTENTS_SLIME)) + { + // safe_bprintf(PRINT_MEDIUM,"lava jump\n"); + self->s.angles[YAW] += random() * 360 - 180; + ucmd->forwardmove = 400; + ucmd->upmove = 400; + return; + } + +//@@ if(ACEMV_CheckEyes(self,ucmd)) +// return; + + // Check for special movement if we have a normal move (have to test) + if(VectorLength(self->velocity) < 37) + { + if(random() > 0.1 && ACEMV_SpecialMove(self,ucmd)) + return; + + self->s.angles[YAW] += random() * 180 - 90; + + if(!M_CheckBottom && !self->groundentity) // if there is ground continue otherwise wait for next move + ucmd->forwardmove = 0; + else if( ACEMV_CanMove( self, MOVE_FORWARD)) + ucmd->forwardmove = 200; + + return; + } + + // Otherwise move as fast as we can + // If it's safe to move forward (I can't believe ACE didn't check this! + if( ACEMV_CanMove( self, MOVE_FORWARD)) + { + ucmd->forwardmove = 400; + } + else + { + // Need a "findbestdirection" routine in here + // Forget about this route! + ucmd->forwardmove = -100; + self->movetarget = NULL; + } + +} + +/////////////////////////////////////////////////////////////////////// +// Attack movement routine +// +// NOTE: Very simple for now, just a basic move about avoidance. +// Change this routine for more advanced attack movement. +/////////////////////////////////////////////////////////////////////// +void ACEMV_Attack (edict_t *self, usercmd_t *ucmd) +{ + float c; + vec3_t target; + vec3_t angles; + vec3_t attackvector; + float dist; + + // Randomly choose a movement direction + c = random(); + + if(c < 0.2 && ACEMV_CanMove(self,MOVE_LEFT)) + ucmd->sidemove -= 400; + else if(c < 0.4 && ACEMV_CanMove(self,MOVE_RIGHT)) + ucmd->sidemove += 400; + +//AQ2 CHANGE + // Don't stand around if all you have is a knife! + if( self->client->pers.weapon == FindItem(KNIFE_NAME) ) + { + ucmd->forwardmove += 400; + } + else + { + if(c < 0.6 && ACEMV_CanMove(self,MOVE_FORWARD)) + ucmd->forwardmove += 400; + else if(c < 0.8 && ACEMV_CanMove(self,MOVE_FORWARD)) + ucmd->forwardmove -= 400; + } +//AQ2 END + // Check distance to enemy + VectorSubtract( self->s.origin, self->enemy->s.origin, attackvector); + dist = VectorLength( attackvector); + + if( (dist < 600) && !(self->client->pers.weapon == FindItem(KNIFE_NAME)) ) + { + // Randomly choose a vertical movement direction + c = random(); + + if(c < 0.15) + ucmd->upmove += 200; + else + ucmd->upmove -= 200; + } + + // Set the attack + //@@ Check this doesn't break grenades! + if((self->client->weaponstate == WEAPON_READY)||(self->client->weaponstate == WEAPON_FIRING)) + { + // Only shoot if the weapon is ready and we can hit the target! + //@@ Removed to try to help RobbieBoy! +// if( ACEAI_CheckShot( self )) + ucmd->buttons = BUTTON_ATTACK; +// else +// ucmd->upmove = 200; + } + + // Aim + VectorCopy(self->enemy->s.origin,target); + + // modify attack angles based on accuracy (mess this up to make the bot's aim not so deadly) +// target[0] += (random()-0.5) * 20; +// target[1] += (random()-0.5) * 20; + +//AQ2 ADD - RiEvEr + // Alter aiming based on skill level + if( ltk_skill->value < 10 ) + { + short int up, right; + up = (random() < 0.5)? -1 :1; + right = (random() < 0.5)? -1 : 1; + + // Not that complex. We miss by 0 to 80 units based on skill value and random factor + target[0] += ( right * (10 - ltk_skill->value +((8*(10 - ltk_skill->value)) *random())) ); + target[2] += ( up * (10 - ltk_skill->value +((8*(10 - ltk_skill->value)) *random())) ); + } +//AQ2 END + + // Set direction + VectorSubtract (target, self->s.origin, self->move_vector); + vectoangles (self->move_vector, angles); + VectorCopy(angles,self->s.angles); + + // Store time we last saw an enemy + // This value is used to decide if we initiate a long range search or not. + self->teamPauseTime = level.time; + +// if(debug_mode) +// debug_printf("%s attacking %s\n",self->client->pers.netname,self->enemy->client->pers.netname); +} + +//========================== +// AntPathMove +//========================== +// +qboolean AntPathMove( edict_t *self ) +{ + node_t *temp = &nodes[self->current_node]; // For checking our position + + if( level.time == (float)((int)level.time) ) + { + if( + !AntLinkExists( self->current_node, SLLfront(&self->pathList) ) + && ( self->current_node != SLLfront(&self->pathList) ) + ) + { + // We are off the path - clear out the lists + AntInitSearch( self ); + } + } + + // Boot in our new pathing algorithm + // This will fill ai.pathList with the information we need + if( + SLLempty(&self->pathList) // We have no path and + && (self->current_node != self->goal_node) // we're not at our destination + ) + { + if( !AntStartSearch( self, self->current_node, self->goal_node)) // Set up our pathList + { + // Failed to find a path +// gi.bprintf(PRINT_HIGH,"%s: Target at(%i) - No Path \n", +// self->client->pers.netname, self->goal_node, self->next_node); + return false; + } + return true; + } + + // And change our pathfinding to use it + if ( !SLLempty(&self->pathList) ) // We have a path + { +// if( AtNextNode( self->s.origin, SLLfront(&self->pathList)) // we're at the nextnode + if( ACEND_FindClosestReachableNode(self,NODE_DENSITY*0.66,NODE_ALL) == SLLfront(&self->pathList) // we're at the nextnode + || self->current_node == SLLfront(&self->pathList) + ) + { + self->current_node = SLLfront(&self->pathList); // Show we're there + SLLpop_front(&self->pathList); // Remove the top node + } + if( !SLLempty(&self->pathList) ) + self->next_node = SLLfront(&self->pathList); // Set our next destination + else + self->next_node = INVALID; // We're at the target location + return true; + } + return true; // Pathlist is emptyand we are at our destination +} + diff --git a/acesrc/acebot_nodes.c b/acesrc/acebot_nodes.c new file mode 100644 index 0000000..01d6ce1 --- /dev/null +++ b/acesrc/acebot_nodes.c @@ -0,0 +1,1245 @@ +/////////////////////////////////////////////////////////////////////// +// +// 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_nodes.c 29 26/11/99 0:48 Riever $ + * + * $Log: /LicenseToKill/src/acesrc/acebot_nodes.c $ + * + * 29 26/11/99 0:48 Riever + * Link creation safety checks.. + * + * 28 27/10/99 18:49 Riever + * Added some debug code to handle lightlevel checks. + * + * 27 22/10/99 20:53 Riever + * Placed own path showing code under ltk_showpath control. + * + * 26 22/10/99 20:30 Riever + * Checking ltk_showpath set up right. + * + * 25 22/10/99 7:36 Riever + * Safety check to prevent falling damage links being created. + * + * 24 22/10/99 7:27 Riever + * Updated node file version to 3 + * + * 23 22/10/99 7:17 Riever + * Stopped node files of less than 100 nodes being saved - this will + * prevent good node files being over-written when the game glitches. + * + * 22 21/10/99 8:15 Riever + * Added ltk_showpath CVAR to toggle display of bot path selection + * information. + * + * 21 20/10/99 20:26 Riever + * Fixed Ladder node creation. + * + * 20 10/10/99 9:33 Riever + * Found a bug in reverse link - fixed! + * + * 19 6/10/99 17:54 Riever + * ... and another! + * + * 18 6/10/99 17:53 Riever + * Removed another debug print. + * + * 17 6/10/99 17:51 Riever + * Removed debug prints and disabled path visibility code. + * + * 16 6/10/99 17:40 Riever + * Added TeamPlay state STATE_POSITION to enable bots to seperate and + * avoid centipede formations. + * + * 15 29/09/99 13:32 Riever + * Changed all node linkage code to check a trace. + * Changed nearest node code to find a reliable reachable node. + * Changed ShowPath function to show the AntPath to the destination, not + * the array path. + * + * 14 28/09/99 7:47 Riever + * Switched out a couple of debug prints that were appearing in non debug + * mode + * + * 13 27/09/99 20:46 Riever + * Changed trace tests for node linkage and detection to use vec3_origins + * + * 12 27/09/99 16:01 Riever + * Added "self" to ACEND_UpdateNodeEdge calls + * + * 11 27/09/99 14:59 Riever + * Changed ACE link code to TRACE IT FIRST!!! I know, no-one else will + * believe that this wasn't in there either *grin*.... + * + * 10 27/09/99 14:53 Riever + * Changed nearest node check to mask ALL (temporary until I find the + * problem with thin walls in this game!) + * Increased node table version number to 2 because node density increased + * in acebot.h to 96. + * + * 9 27/09/99 14:29 Riever + * Modified ACEND_FollowPath to utilise the AntPath code + * + * 8 26/09/99 8:01 Riever + * ACEND_ReverseLink function in place ready for new path system. + * + * 7 21/09/99 11:58 Riever + * fixed INVALID node searches + * + * 6 18/09/99 19:21 Riever + * NODE_DOOR creation fixed to place the node in the center of the closed + * door. + * + * 5 18/09/99 8:08 Riever + * NODE_DOOR creation now places node at center of the door to help bot + * navigation. Still need to sort out non rotating doors. + * + * 4 17/09/99 17:06 Riever + * Changed node table save directory to be "terrain" as advised by William + * + * 3 17/09/99 17:04 Riever + * New node structure implemented + * Link structure added + * Found nodetable creation bug at timelimit and fixed it. + * Changed load and save code to be "game directory" friendly. + * Made node table version number a CONSTANT + * + * 2 13/09/99 19:52 Riever + * Added headers + * + */ + +/////////////////////////////////////////////////////////////////////// +// +// acebot_nodes.c - This file contains all of the +// pathing routines for the ACE bot. +// +/////////////////////////////////////////////////////////////////////// + +#include "..\g_local.h" +#include "acebot.h" + +#define LTK_NODEVERSION 3 +#define TRACE_DIST_LADDER 16 + +// flags +qboolean newmap=true; + +// Total number of nodes that are items +int numitemnodes; + +// Total number of nodes +int numnodes; + +// For debugging paths +int show_path_from = -1; +int show_path_to = -1; + +// array for node data +node_t nodes[MAX_NODES]; +short int path_table[MAX_NODES][MAX_NODES]; + +/////////////////////////////////////////////////////////////////////// +// NODE INFORMATION FUNCTIONS +/////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////// +// Determin cost of moving from one node to another +/////////////////////////////////////////////////////////////////////// +int ACEND_FindCost(int from, int to) +{ + // RiEvEr - Bug Hunting + int curnode = INVALID; + int cost=1; // Shortest possible is 1 + + // If we can not get there then return invalid + if( (from == INVALID) || (to == INVALID) || + (path_table[from][to] == INVALID) ) + return INVALID; + + // Otherwise check the path and return the cost + curnode = path_table[from][to]; + + // Find a path (linear time, very fast) + while(curnode != to) + { + curnode = path_table[curnode][to]; + if(curnode == INVALID) // something has corrupted the path abort + return INVALID; + cost++; + } + + return cost; +} + +/////////////////////////////////////////////////////////////////////// +// Find a close node to the player within dist. +// +// Faster than looking for the closest node, but not very +// accurate. +/////////////////////////////////////////////////////////////////////// +int ACEND_FindCloseReachableNode(edict_t *self, int range, int type) +{ + vec3_t v; + int i; + trace_t tr; + float dist; + vec3_t maxs,mins; + + VectorCopy(self->mins,mins); + mins[2] += 16; + VectorCopy(self->maxs,maxs); + maxs[2] -= 16; + + range *= range; + + for(i=0;is.origin,v); // subtract first + + dist = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + + if(dist < range) // square range instead of sqrt + { + // make sure it is visible + //AQ2 ADDED MASK_SOLID + //trace = gi.trace (self->s.origin, vec3_origin, vec3_origin, nodes[i].origin, self, MASK_SOLID|MASK_OPAQUE); + tr = gi.trace (self->s.origin, mins, maxs, nodes[i].origin, self, MASK_ALL); + + if(tr.fraction == 1.0) + return i; + } + } + } + + return -1; +} + +/////////////////////////////////////////////////////////////////////// +// Find the closest node to the player within a certain range +/////////////////////////////////////////////////////////////////////// +int ACEND_FindClosestReachableNode(edict_t *self, int range, int type) +{ + int i; + float closest = 99999; + float dist; + int node=-1; + vec3_t v; + trace_t tr; + float rng; + vec3_t maxs,mins; + + VectorCopy(self->mins,mins); + VectorCopy(self->maxs,maxs); + + // For Ladders, do not worry so much about reachability + if(type == NODE_LADDER) + { + VectorCopy(vec3_origin,maxs); + VectorCopy(vec3_origin,mins); + } + else + { + mins[2] += 18; // Stepsize + maxs[2] -= 16; // Duck a little.. + } + + rng = (float)(range * range); // square range for distance comparison (eliminate sqrt) + + for(i=0;is.origin,v); // subtract first + + dist = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + + if(dist < closest && dist < rng) + { + // make sure it is visible + //AQ2 added MASK_SOLID + tr = gi.trace (self->s.origin, mins, maxs, nodes[i].origin, self, MASK_SOLID|MASK_OPAQUE); +// tr = gi.trace (self->s.origin, vec3_origin, vec3_origin, nodes[i].origin, self, MASK_ALL); + if( (tr.fraction == 1.0) || + ( (tr.fraction > 0.9) // may be blocked by the door itself! + && (Q_stricmp(tr.ent->classname, "func_door_rotating") == 0) ) + ) + { + node = i; + closest = dist; + } + } + } + } + + return node; +} + +/////////////////////////////////////////////////////////////////////// +// BOT NAVIGATION ROUTINES +/////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////// +// Set up the goal +/////////////////////////////////////////////////////////////////////// +void ACEND_SetGoal(edict_t *self, int goal_node) +{ + int node; + + self->goal_node = goal_node; + node = ACEND_FindClosestReachableNode(self, NODE_DENSITY*3, NODE_ALL); + + if(node == -1) + return; + + if(debug_mode) + debug_printf("%s new start node selected %d\n",self->client->pers.netname,node); + + + self->current_node = node; + self->next_node = self->current_node; // make sure we get to the nearest node first + self->node_timeout = 0; + +} + +/////////////////////////////////////////////////////////////////////// +// Move closer to goal by pointing the bot to the next node +// that is closer to the goal +/////////////////////////////////////////////////////////////////////// +qboolean ACEND_FollowPath(edict_t *self) +{ + vec3_t v; + + ////////////////////////////////////////// + // Show the path (uncomment for debugging) + show_path_from = self->current_node; + show_path_to = self->goal_node; + + if( ltk_showpath->value ) + { + ACEND_DrawPath(self); + } + ////////////////////////////////////////// + + // Try again? + if(self->node_timeout ++ > 30) + { + if(self->tries++ > 3) + return false; + else + ACEND_SetGoal(self,self->goal_node); + } + + //RiEvEr - new path code & algorithm + // This part checks if we are off course + if( level.time == (float)((int)level.time) ) + { + if( + !AntLinkExists( self->current_node, SLLfront(&self->pathList) ) + && ( self->current_node != SLLfront(&self->pathList) ) + ) + { + // We are off the path - clear out the lists + AntInitSearch( self ); + } + } + // Boot in our new pathing algorithm + // This will fill self->pathList with the information we need + if( + SLLempty(&self->pathList) // We have no path and + && (self->current_node != self->goal_node) // we're not at our destination + ) + { + if( !AntStartSearch( self, self->current_node, self->goal_node)) // Set up our pathList + { + // Failed to find a path + if( debug_mode ) + gi.bprintf(PRINT_HIGH,"%s: Target at(%i) - No Path \n", + self->client->pers.netname, self->goal_node, self->next_node); + return false; + } +// return true; + } + //R + + // Are we there yet? + VectorSubtract(self->s.origin,nodes[self->next_node].origin,v); + + if(VectorLength(v) < 32) + { + // reset timeout + self->node_timeout = 0; + + if(self->next_node == self->goal_node) + { + if(debug_mode) + debug_printf("%s reached goal!\n",self->client->pers.netname); + + ACEAI_PickLongRangeGoal(self); // Pick a new goal + } + else + { + self->current_node = self->next_node; +// self->next_node = path_table[self->current_node][self->goal_node]; + // Removethe front entry from the list + SLLpop_front(&self->pathList); + // Get the next node - if there is one! + if( !SLLempty(&self->pathList)) + self->next_node = SLLfront( &self->pathList); + else + { + // We messed up... + if( debug_mode) + gi.bprintf(PRINT_HIGH, "Trying to read an empty SLL nodelist!\n"); + self->next_node = INVALID; + } + } + } + + if(self->current_node == -1 || self->next_node ==-1) + return false; + + // Set bot's movement vector + VectorSubtract (nodes[self->next_node].origin, self->s.origin , self->move_vector); + + return true; +} + + +/////////////////////////////////////////////////////////////////////// +// MAPPING CODE +/////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////// +// Capture when the grappling hook has been fired for mapping purposes. +/////////////////////////////////////////////////////////////////////// +void ACEND_GrapFired(edict_t *self) +{ +/* int closest_node; + + if(!self->owner) + return; // should not be here + + // Check to see if the grapple is in pull mode + if(self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL) + { + // Look for the closest node of type grapple + closest_node = ACEND_FindClosestReachableNode(self,NODE_DENSITY,NODE_GRAPPLE); + if(closest_node == -1 ) // we need to drop a node + { + closest_node = ACEND_AddNode(self,NODE_GRAPPLE); + + // Add an edge + ACEND_UpdateNodeEdge(self, self->owner->last_node,closest_node); + + self->owner->last_node = closest_node; + } + else + self->owner->last_node = closest_node; // zero out so other nodes will not be linked + }*/ +} + + +/////////////////////////////////////////////////////////////////////// +// Check for adding ladder nodes +/////////////////////////////////////////////////////////////////////// +qboolean ACEND_CheckForLadder(edict_t *self) +{ + int closest_node; + + // If there is a ladder and we are moving up, see if we should add a ladder node + if (gi.pointcontents(self->s.origin) & CONTENTS_LADDER && self->velocity[2] > 0) + { + //debug_printf("contents: %x\n",tr.contents); + + closest_node = ACEND_FindClosestReachableNode(self,NODE_DENSITY,NODE_LADDER); + if(closest_node == -1) + { + closest_node = ACEND_AddNode(self,NODE_LADDER); + + // Now add link + ACEND_UpdateNodeEdge(self, self->last_node,closest_node); + + // Set current to last + self->last_node = closest_node; + } + else + { + ACEND_UpdateNodeEdge(self, self->last_node,closest_node); + self->last_node = closest_node; // set visited to last + } + return true; + } + return false; +} + +//======================================================= +// LadderForward +//======================================================= +// +// The ACE code version of this doesn't work! + +qboolean ACEND_LadderForward( edict_t *self )//, vec3_t angles ) +{ + vec3_t dir, angle, dest, min, max; + trace_t trace; + int closest_node; + + + VectorClear(angle); + angle[1] = self->s.angles[1]; + + AngleVectors(angle, dir, NULL, NULL); + VectorCopy(self->mins,min); + min[2] += 22; + VectorCopy(self->maxs,max); + VectorMA(self->s.origin, TRACE_DIST_LADDER, dir, dest); + + trace = gi.trace(self->s.origin, min, max, dest, self, MASK_ALL); + + //TempLaser(self->s.origin, dest); + if (trace.fraction == 1.0) + return (false); + +// safe_bprintf(PRINT_HIGH,"Contents forward are %d\n", trace.contents); + if (trace.contents & CONTENTS_LADDER || trace.contents &CONTENTS_DETAIL) + { + // Debug print +// safe_bprintf(PRINT_HIGH,"contents: %x\n",trace.contents); + + closest_node = ACEND_FindClosestReachableNode(self,NODE_DENSITY,NODE_LADDER); + if(closest_node == -1) + { + closest_node = ACEND_AddNode(self,NODE_LADDER); + + // Now add link + ACEND_UpdateNodeEdge(self, self->last_node,closest_node); + + // Set current to last + self->last_node = closest_node; + } + else + { + ACEND_UpdateNodeEdge(self, self->last_node,closest_node); + self->last_node = closest_node; // set visited to last + } + return (true); + } + return (false); +} + + +/////////////////////////////////////////////////////////////////////// +// This routine is called to hook in the pathing code and sets +// the current node if valid. +/////////////////////////////////////////////////////////////////////// +void ACEND_PathMap(edict_t *self) +{ + int closest_node; + // Removed last_update checks since this stopped multiple node files being built + vec3_t v; + + // Special node drawing code for debugging + if( ltk_showpath->value ) + { + if(show_path_to != -1) + ACEND_DrawPath( self ); + } + + // Just checking lightlevels - uncomment to use +// if( debug_mode && !self->is_bot) +// safe_bprintf(PRINT_HIGH,"LightLevel = %d\n", self->light_level); + + //////////////////////////////////////////////////////// + // Special check for ladder nodes + /////////////////////////////////////////////////////// + // Replace non-working ACE version with mine. +// if(ACEND_CheckForLadder(self)) // check for ladder nodes + if(ACEND_LadderForward(self)) // check for ladder nodes + return; + + // Not on ground, and not in the water, so bail + if(!self->groundentity && !self->waterlevel) + return; + + //////////////////////////////////////////////////////// + // Lava/Slime + //////////////////////////////////////////////////////// + VectorCopy(self->s.origin,v); + v[2] -= 18; + if(gi.pointcontents(v) & (CONTENTS_LAVA|CONTENTS_SLIME)) + return; // no nodes in slime + + //////////////////////////////////////////////////////// + // Jumping + /////////////////////////////////////////////////////// + if(self->is_jumping) + { + // See if there is a closeby jump landing node (prevent adding too many) + closest_node = ACEND_FindClosestReachableNode(self, 64, NODE_JUMP); + + if(closest_node == INVALID) + closest_node = ACEND_AddNode(self,NODE_JUMP); + + // Now add link + if(self->last_node != -1) + ACEND_UpdateNodeEdge(self, self->last_node, closest_node); + + self->is_jumping = false; + return; + } + +/* //////////////////////////////////////////////////////////// + // Grapple + // Do not add nodes during grapple, added elsewhere manually + //////////////////////////////////////////////////////////// + if(ctf->value && self->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL) + return;*/ + + // Iterate through all nodes to make sure far enough apart + closest_node = ACEND_FindClosestReachableNode(self, NODE_DENSITY, NODE_ALL); + + //////////////////////////////////////////////////////// + // Special Check for Platforms + //////////////////////////////////////////////////////// + if(self->groundentity && self->groundentity->use == Use_Plat) + { + if(closest_node == INVALID) + return; // Do not want to do anything here. + + // Here we want to add links + if(closest_node != self->last_node && self->last_node != INVALID) + ACEND_UpdateNodeEdge(self, self->last_node,closest_node); + + self->last_node = closest_node; // set visited to last + return; + } + + //////////////////////////////////////////////////////// + // Add Nodes as needed + //////////////////////////////////////////////////////// + if(closest_node == INVALID) + { + // Add nodes in the water as needed + if(self->waterlevel) + closest_node = ACEND_AddNode(self,NODE_WATER); + else + closest_node = ACEND_AddNode(self,NODE_MOVE); + + // Now add link + if(self->last_node != -1) + ACEND_UpdateNodeEdge(self, self->last_node, closest_node); + + } + else if(closest_node != self->last_node && self->last_node != INVALID) + ACEND_UpdateNodeEdge(self, self->last_node,closest_node); + + self->last_node = closest_node; // set visited to last + +} + +/////////////////////////////////////////////////////////////////////// +// Init node array (set all to INVALID) +/////////////////////////////////////////////////////////////////////// +void ACEND_InitNodes(void) +{ + numnodes = 1; + numitemnodes = 1; + memset(nodes,0,sizeof(node_t) * MAX_NODES); + memset(path_table,INVALID,sizeof(short int)*MAX_NODES*MAX_NODES); + +} + +/////////////////////////////////////////////////////////////////////// +// Show the node for debugging (utility function) +/////////////////////////////////////////////////////////////////////// +void ACEND_ShowNode(int node) +{ + edict_t *ent; + +// return; // commented out for now. uncommend to show nodes during debugging, + // but too many will cause overflows. You have been warned. + + ent = G_Spawn(); + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + + if(nodes[node].type == NODE_MOVE) + ent->s.renderfx = RF_SHELL_BLUE; + else if (nodes[node].type == NODE_WATER) + ent->s.renderfx = RF_SHELL_RED; + else + ent->s.renderfx = RF_SHELL_GREEN; // action nodes + + ent->s.modelindex = gi.modelindex ("models/items/ammo/grenades/medium/tris.md2"); + ent->owner = ent; + ent->nextthink = level.time + 600.0; // 10 minutes is long enough! + ent->think = G_FreeEdict; + ent->dmg = 0; + + VectorCopy(nodes[node].origin,ent->s.origin); + gi.linkentity (ent); + +} + +/////////////////////////////////////////////////////////////////////// +// Draws the current path (utility function) +/////////////////////////////////////////////////////////////////////// +void ACEND_DrawPath(edict_t *self) +{ + int current_node, goal_node, next_node; + + current_node = show_path_from; + goal_node = show_path_to; + + // RiEvEr - rewritten to use Ant system + AntStartSearch( self, current_node, goal_node); + + next_node = SLLfront(&self->pathList); + + // Now set up and display the path + while( current_node != goal_node && current_node != INVALID) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (nodes[current_node].origin); + gi.WritePosition (nodes[next_node].origin); + gi.multicast (nodes[current_node].origin, MULTICAST_PVS); + current_node = next_node; + SLLpop_front( &self->pathList); + next_node = SLLfront(&self->pathList); + } + +/* + next_node = path_table[current_node][goal_node]; + + // Now set up and display the path + while(current_node != goal_node && current_node != -1) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (nodes[current_node].origin); + gi.WritePosition (nodes[next_node].origin); + gi.multicast (nodes[current_node].origin, MULTICAST_PVS); + current_node = next_node; + next_node = path_table[current_node][goal_node]; + }*/ +} + +/////////////////////////////////////////////////////////////////////// +// Turns on showing of the path, set goal to -1 to +// shut off. (utility function) +/////////////////////////////////////////////////////////////////////// +void ACEND_ShowPath(edict_t *self, int goal_node) +{ + show_path_from = ACEND_FindClosestReachableNode(self, NODE_DENSITY, NODE_ALL); + show_path_to = goal_node; +} + +/////////////////////////////////////////////////////////////////////// +// Add a node of type ? +/////////////////////////////////////////////////////////////////////// +int ACEND_AddNode(edict_t *self, int type) +{ + vec3_t v1,v2; + int i; + + // Block if we exceed maximum + if (numnodes + 1 > MAX_NODES) + return false; + + // Set location + VectorCopy(self->s.origin, nodes[numnodes].origin); + + // Set type + nodes[numnodes].type = type; + // Set number - RiEvEr + nodes[numnodes].nodenum = numnodes; + + // Clear out the link information - RiEvEr + for( i = 0; i< MAXLINKS; i++) + { + nodes[numnodes].links[i].targetNode = INVALID; + } + + ///////////////////////////////////////////////////// + // ITEMS + // Move the z location up just a bit. + if(type == NODE_ITEM) + { + nodes[numnodes].origin[2] += 16; + numitemnodes++; + } + + // Teleporters + if(type == NODE_TELEPORTER) + { + // Up 32 + nodes[numnodes].origin[2] += 32; + } + + // Doors + if(type == NODE_DOOR) + { + vec3_t position; + // Find mid point of door max and min and put the node there + VectorCopy(self->s.origin, position); + // find center of door + position[0] = position[0] + self->mins[0] + ((self->maxs[0] - self->mins[0]) /2); + position[1] = position[1] + self->mins[1] + ((self->maxs[1] - self->mins[1]) /2); + position[2] -= 16; // lower it a little + // Set location + VectorCopy(position, nodes[numnodes].origin); + } + + if(type == NODE_LADDER) + { + nodes[numnodes].type = NODE_LADDER; + + if(debug_mode) + { + debug_printf("Node added %d type: Ladder\n",numnodes); + ACEND_ShowNode(numnodes); + } + + numnodes++; + return numnodes-1; // return the node added + + } + + // For platforms drop two nodes one at top, one at bottom + if(type == NODE_PLATFORM) + { + VectorCopy(self->maxs,v1); + VectorCopy(self->mins,v2); + + // To get the center + nodes[numnodes].origin[0] = (v1[0] - v2[0]) / 2 + v2[0]; + nodes[numnodes].origin[1] = (v1[1] - v2[1]) / 2 + v2[1]; + nodes[numnodes].origin[2] = self->maxs[2]; + + if(debug_mode) + ACEND_ShowNode(numnodes); + + numnodes++; + + nodes[numnodes].origin[0] = nodes[numnodes-1].origin[0]; + nodes[numnodes].origin[1] = nodes[numnodes-1].origin[1]; + nodes[numnodes].origin[2] = self->mins[2]+64; + + nodes[numnodes].type = NODE_PLATFORM; + + // Add a link + //RiEvEr modified to pass in calling entity + ACEND_UpdateNodeEdge(self, numnodes, numnodes-1); + + if(debug_mode) + { + debug_printf("Node added %d type: Platform\n",numnodes); + ACEND_ShowNode(numnodes); + } + + numnodes++; + + return numnodes -1; + } + + if(debug_mode) + { + if(nodes[numnodes].type == NODE_MOVE) + debug_printf("Node added %d type: Move\n",numnodes); + else if(nodes[numnodes].type == NODE_TELEPORTER) + debug_printf("Node added %d type: Teleporter\n",numnodes); + else if(nodes[numnodes].type == NODE_ITEM) + debug_printf("Node added %d type: Item\n",numnodes); + else if(nodes[numnodes].type == NODE_WATER) + debug_printf("Node added %d type: Water\n",numnodes); + else if(nodes[numnodes].type == NODE_GRAPPLE) + debug_printf("Node added %d type: Grapple\n",numnodes); + + ACEND_ShowNode(numnodes); + } + + numnodes++; + + return numnodes-1; // return the node added +} + +// RiEvEr +//======================================= +// ReverseLink +//======================================= +// Takes the path BACK to where we came from +// and tries to link the two nodes +// This helps make good path files +// +void ACEND_ReverseLink( edict_t *self, int from, int to ) +{ + int i; + trace_t trace; + vec3_t min,max; + + if(from == INVALID || to == INVALID || from == to) + return; // safety + + // Need to trace from -> to and check heights + // if from is much lower than to, forget it + if( (nodes[from].origin[2]+32.0) < (nodes[to].origin[2]) ) + { + // May not be able to jump that high so do not allow the return link + return; + } + VectorCopy(self->mins, min); +// if( (nodes[from].origin[2]) < (nodes[2].origin[2]) ) + min[2] =0; // Allow for steps etc. + VectorCopy(self->maxs, max); +// if( (nodes[from].origin[2]) > (nodes[2].origin[2]) ) + max[2] =0; // Could be a downward sloping feature above our head + + + // This should not be necessary, but I've heard that before! + // Now trace it again + trace = gi.trace( nodes[from].origin, min, max, nodes[to].origin, self, MASK_SOLID); + if( trace.fraction < 1.0) + { + // can't get there for some reason + return; + } + // Add the link + path_table[from][to] = to; + + // Checks if the link exists and then may create a new one - RiEvEr + for( i=0; i %d\n", from, to); +} +//R + +/////////////////////////////////////////////////////////////////////// +// Add/Update node connections (paths) +/////////////////////////////////////////////////////////////////////// +void ACEND_UpdateNodeEdge(edict_t *self, int from, int to) +{ + int i; + trace_t trace; + vec3_t min,max; + + if(from == INVALID || to == INVALID || from == to) + return; // safety + + // Try to stop impossible links! + // If it looks higher than a jump... + if( nodes[to].origin[2] > nodes[from].origin[2]+36) + { + // If we are coming from a move or jump node + if( (nodes[from].type == NODE_MOVE) || + (nodes[from].type == NODE_JUMP) ) + { + // No if the to node is the same, it's illegal + if( (nodes[to].type == NODE_MOVE) || + (nodes[to].type == NODE_JUMP) ) + { + // Too high - not possible!! + return; + } + } + } + // Do not allow creation of nodes where the falling distance would kill you! + if( (nodes[from].origin[2]) > (nodes[to].origin[2] + 180) ) + return; + +/* VectorCopy(self->mins, min); + // If going up +// if( (nodes[from].origin[2]) < (nodes[to].origin[2]) ) + min[2] = 0; // Allow for steps up etc. + VectorCopy(self->maxs, max); + // If going down +// if( (nodes[from].origin[2]) > (nodes[to].origin[2]) ) + max[2] = 0; // door node linking*/ + VectorCopy( vec3_origin, min); + VectorCopy( vec3_origin, max); + + // Now trace it - more safety stuff! + trace = gi.trace( nodes[from].origin, min, max, nodes[to].origin, self, MASK_SOLID); + + if( trace.fraction < 1.0) + { + // can't do it + return; + } + // Add the link + path_table[from][to] = to; + + // Checks if the link exists and then may create a new one - RiEvEr + for( i=0; i %d\n", from, to); + // RiEvEr - check for the link going back the other way + // Reverse the input data so it works properly! + ACEND_ReverseLink( self, to, from ); + // R +} + +/////////////////////////////////////////////////////////////////////// +// Remove a node edge +/////////////////////////////////////////////////////////////////////// +void ACEND_RemoveNodeEdge(edict_t *self, int from, int to) +{ + int i; + + if(debug_mode) + debug_printf("%s: Removing Edge %d -> %d\n", self->client->pers.netname, from, to); + + path_table[from][to] = INVALID; // set to invalid + + // Make sure this gets updated in our path array + for(i=0;istring); + i += sprintf(filename + i, "\\terrain\\"); + i += sprintf(filename + i, level.mapname); + i += sprintf(filename + i, ".ltk"); +#else + strcpy(filename, "./"); + strcat(filename, game_dir->string); + strcat(filename, "/terrain/"); + strcat(filename,level.mapname); + strcat(filename,".ltk"); +#endif + + // Resolve paths + ACEND_ResolveAllPaths(); + + safe_bprintf(PRINT_MEDIUM,"Saving node table..."); + +/* strcpy(filename,"action\\nav\\"); + strcat(filename,level.mapname); + strcat(filename,".nod");*/ + + if((pOut = fopen(filename, "wb" )) == NULL) + return; // bail + + fwrite(&version,sizeof(int),1,pOut); // write version + fwrite(&numnodes,sizeof(int),1,pOut); // write count + fwrite(&num_items,sizeof(int),1,pOut); // write facts count + + fwrite(nodes,sizeof(node_t),numnodes,pOut); // write nodes + + for(i=0;istring); + i += sprintf(filename + i, "\\terrain\\"); + i += sprintf(filename + i, level.mapname); + i += sprintf(filename + i, ".ltk"); +#else + strcpy(filename, "./"); + strcat(filename, game_dir->string); + strcat(filename, "/terrain/"); + strcat(filename,level.mapname); + strcat(filename,".ltk"); +#endif +/* + strcpy(filename,"action\\nav\\"); + strcat(filename,level.mapname); + strcat(filename,".nod");*/ + + if((pIn = fopen(filename, "rb" )) == NULL) + { + // Create item table + safe_bprintf(PRINT_MEDIUM, "ACE: No node file found, creating new one..."); + ACEIT_BuildItemNodeTable(false); + safe_bprintf(PRINT_MEDIUM, "done.\n"); + return; + } + + // determin version + fread(&version,sizeof(int),1,pIn); // read version + + if(version == LTK_NODEVERSION) + { + safe_bprintf(PRINT_MEDIUM,"ACE: Loading node table..."); + + fread(&numnodes,sizeof(int),1,pIn); // read count + fread(&num_items,sizeof(int),1,pIn); // read facts count + + fread(nodes,sizeof(node_t),numnodes,pIn); + + for(i=0;ivalue ) + return 0; + +// gi.bprintf(PRINT_HIGH, "Checking team balance..\n"); + for (i = 1; i < maxclients->value+1; i++) + { + e = g_edicts + i; + if (e->inuse) + { + if (e->client->resp.team == TEAM1) + onteam1++; + else if (e->client->resp.team == TEAM2) + onteam2++; + } + } + // Return the team number that needs the next bot + if (onteam1 > onteam2) + return (2); + else if (onteam2 >= onteam1) + return (1); + //default + return (1); +} + +//========================== +// Join a Team +//========================== +void ACESP_JoinTeam(edict_t *ent, int desired_team) +{ + char *s, *a; + + + if (ent->client->resp.team == desired_team) + return; + + a = (ent->client->resp.team == NOTEAM) ? "joined" : "changed to"; + + ent->client->resp.team = desired_team; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + AssignSkin(ent, s); + + if (ent->solid != SOLID_NOT) // alive, in game + { + ent->health = 0; + player_die (ent, ent, ent, 100000, vec3_origin); + ent->deadflag = DEAD_DEAD; + } + + safe_bprintf(PRINT_HIGH, "%s %s %s.\n", + ent->client->pers.netname, a, TeamName(desired_team)); + + ent->client->resp.joined_team = level.framenum; + + CheckForUnevenTeams(); +} + +//====================================== +// ACESP_LoadBotConfig() +//====================================== +// Using RiEvEr's new config file +// +void ACESP_LoadBotConfig() +{ + FILE *pIn; + cvar_t *game_dir; + int i; + char filename[60]; + // Scanner stuff + int fileVersion = 0; + char inString[81]; + char tokenString[81]; + char *sp, *tp; + int ttype; + + game_dir = gi.cvar ("game", "", 0); + + // Try to load the file for THIS level +#ifdef _WIN32 + i = sprintf(filename, ".\\"); + i += sprintf(filename + i, game_dir->string); + i += sprintf(filename + i, "\\bots\\"); + i += sprintf(filename + i, level.mapname); + i += sprintf(filename + i, ".cfg"); +#else + strcpy(filename, "./"); + strcat(filename, game_dir->string); + strcat(filename, "/bots/"); + strcat(filename, level.mapname); + strcat(filename,".cfg"); +#endif + + // If there's no specific file for this level then get the normal one + if((pIn = fopen(filename, "rb" )) == NULL) + { +#ifdef _WIN32 + i = sprintf(filename, ".\\"); + i += sprintf(filename + i, game_dir->string); + i += sprintf(filename + i, "\\bots\\botdata.cfg"); +#else + strcpy(filename, "./"); + strcat(filename, game_dir->string); + strcat(filename,"/bots/botdata.cfg"); +#endif + + // No bot file available, get out of here! + if((pIn = fopen(filename, "rb" )) == NULL) + return; // bail + } + + // Now scan each line for information + // First line should be the file version number + fgets( inString, 80, pIn ); + sp = inString; + tp = tokenString; + ttype = UNDEF; + + // Scan it for the version number + scanner( &sp, tp, &ttype ); + if(ttype == BANG) + { + scanner( &sp, tp, &ttype ); + if(ttype == INTLIT) + { + fileVersion = atoi( tokenString ); + } + if( fileVersion != CONFIG_FILE_VERSION ) + { + // ERROR! + safe_bprintf(PRINT_HIGH, "Bot Config file is out of date!\n"); + fclose(pIn); + return; + } + } + + // Now process each line of the config file + while( fgets(inString, 80, pIn) ) + { + ACESP_SpawnBotFromConfig( inString ); + } + +/* fread(&count,sizeof (int),1,pIn); + + for(i=0;iyaw_speed = 100; // yaw speed + bot->inuse = true; + bot->is_bot = true; + + // To allow bots to respawn + // initialise userinfo + memset (userinfo, 0, sizeof(userinfo)); + + // add bot's name/skin/hand to userinfo + Info_SetValueForKey (userinfo, "name", name); + Info_SetValueForKey (userinfo, "skin", modelskin); + Info_SetValueForKey (userinfo, "hand", "2"); // bot is center handed for now! + Info_SetValueForKey (userinfo, "spectator", "0"); // NOT a spectator + + ClientConnect (bot, userinfo); + + G_InitEdict (bot); + + // locate ent at a spawn point + if(teamplay->value) + { + // Make sure we have a team + if(!team) + team = GetNextTeamNumber(); + } + + // Set up the preferred weapon & equipment + bot->weaponchoice = weaponchoice; + bot->equipchoice = equipchoice; + + InitClientResp (bot->client); + + if(teamplay->value) + { + ACESP_PutClientInServer (bot,true, team); + } + else + ACESP_PutClientInServer (bot,true,0); + + + // make sure all view stuff is valid + ClientEndServerFrame (bot); + + ACEIT_PlayerAdded (bot); // let the world know we added another + + ACEAI_PickLongRangeGoal(bot); // pick a new goal + + // LTK chat stuff + if( random() < 0.33) + { + // Store current enemies available + int i, counter = 0; + edict_t *myplayer[MAX_BOTS]; + + for(i=0;i<=num_players;i++) + { + // Find all available enemies to insult + if(players[i] == NULL || players[i] == bot || + players[i]->solid == SOLID_NOT) + continue; + + if(teamplay->value && OnSameTeam( bot, players[i]) ) + continue; + myplayer[counter++] = players[i]; + } + if(counter > 0) + { + // Say something insulting to them! + if(ltk_chat->value) // Some people don't want this *sigh* + LTK_Chat( bot, myplayer[rand()%counter], DBC_WELCOME); + } + } +} +//AQ2 END + + +/////////////////////////////////////////////////////////////////////// +// Called by PutClient in Server to actually release the bot into the game +// Keep from killin' each other when all spawned at once +/////////////////////////////////////////////////////////////////////// +void ACESP_HoldSpawn(edict_t *self) +{ + if (!KillBox (self)) + { // could't spawn in? + } + + gi.linkentity (self); + + self->think = ACEAI_Think; + self->nextthink = level.time + FRAMETIME; + + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (self->s.origin, MULTICAST_PVS); + +/* if(ctf->value) + safe_bprintf(PRINT_MEDIUM, "%s joined the %s team.\n", + self->client->pers.netname, CTFTeamName(self->client->resp.ctf_team)); + else*/ + safe_bprintf (PRINT_MEDIUM, "%s entered the game\n", self->client->pers.netname); + +} + +/////////////////////////////////////////////////////////////////////// +// Modified version of id's code +/////////////////////////////////////////////////////////////////////// +void ACESP_PutClientInServer (edict_t *bot, qboolean respawn, int team) +{ + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i, counter; + client_persistant_t saved; + client_respawn_t resp; + char *s; + int going_observer;//AQ2 + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + SelectSpawnPoint (bot, spawn_origin, spawn_angles); + + index = bot-g_edicts-1; + client = bot->client; + + // deathmatch wipes most client data every spawn + if (deathmatch->value) + { + char userinfo[MAX_INFO_STRING]; + + resp = bot->client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + InitClientPersistant (client); + ClientUserinfoChanged (bot, userinfo); + } + else + memset (&resp, 0, sizeof(resp)); + + // clear everything but the persistant data + saved = client->pers; + memset (client, 0, sizeof(*client)); + client->pers = saved; + client->resp = resp; + + // copy some data from the client to the entity + FetchClientEntData (bot); + + // clear entity values + bot->groundentity = NULL; + bot->client = &game.clients[index]; + bot->takedamage = DAMAGE_AIM; + bot->movetype = MOVETYPE_WALK; + bot->viewheight = 24; + bot->classname = "bot"; + bot->mass = 200; + bot->solid = SOLID_BBOX; + bot->deadflag = DEAD_NO; + bot->air_finished = level.time + 12; + bot->clipmask = MASK_PLAYERSOLID; + bot->model = "players/male/tris.md2"; + bot->pain = player_pain; + bot->die = player_die; + bot->waterlevel = 0; + bot->watertype = 0; + bot->flags &= ~FL_NO_KNOCKBACK; + bot->svflags &= ~SVF_DEADMONSTER; + bot->is_jumping = false; + +//AQ2 ADD + if (!teamplay->value || bot->client->resp.team != NOTEAM) + { + bot->flags &= ~FL_GODMODE; + bot->svflags &= ~SVF_NOCLIENT; + } + +//AQ2 END + + VectorCopy (mins, bot->mins); + VectorCopy (maxs, bot->maxs); + VectorClear (bot->velocity); + + // clear playerstate values + memset (&bot->client->ps, 0, sizeof(client->ps)); + + client->ps.pmove.origin[0] = spawn_origin[0]*8; + client->ps.pmove.origin[1] = spawn_origin[1]*8; + client->ps.pmove.origin[2] = spawn_origin[2]*8; + +//ZOID +//AQ2 client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; +//ZOID + + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + client->ps.fov = 90; + } + else + { + client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov")); + if (client->ps.fov < 1) + client->ps.fov = 90; + else if (client->ps.fov > 160) + client->ps.fov = 160; + } + + client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); + + // clear entity state values + bot->s.effects = 0; + bot->s.skinnum = bot - g_edicts - 1; + bot->s.modelindex = 255; // will use the skin specified model +//AQ2 bot->s.modelindex2 = 255; // custom gun model +//AQ2 ADD + ShowGun(bot); +//AQ2 END + bot->s.frame = 0; + VectorCopy (spawn_origin, bot->s.origin); + bot->s.origin[2] += 1; // make sure off ground + VectorCopy (bot->s.origin, bot->s.old_origin);//AQ2 + + // set the delta angle + for (i=0 ; i<3 ; i++) + client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]); + + bot->s.angles[PITCH] = 0; + bot->s.angles[YAW] = spawn_angles[YAW]; + bot->s.angles[ROLL] = 0; + VectorCopy (bot->s.angles, client->ps.viewangles); + VectorCopy (bot->s.angles, client->v_angle); + + // force the current weapon up + client->newweapon = client->pers.weapon; + ChangeWeapon (bot); + + bot->enemy = NULL; + bot->movetarget = NULL; + if( !teamplay->value) + bot->state = STATE_MOVE; + else + bot->state = STATE_POSITION; + + // Set the current node + bot->current_node = ACEND_FindClosestReachableNode(bot,NODE_DENSITY, NODE_ALL); + bot->goal_node = bot->current_node; + bot->next_node = bot->current_node; + bot->next_move_time = level.time; + bot->suicide_timeout = level.time + 15.0; + + // If we are not respawning hold off for up to three seconds before releasing into game + if(!respawn) + { + bot->think = ACESP_HoldSpawn; + bot->nextthink = level.time + 0.1; + bot->nextthink = level.time + random()*3.0; // up to three seconds + } + else + { +//AQ2 ADD + //@@ check this out! + // Sets to an observer unless joined a team! (ent->client->resp.team != NOTEAM) + if (teamplay->value) + { + going_observer = StartClient(bot); + } + else + { + going_observer = bot->client->pers.spectator; + if (going_observer) + { + bot->movetype = MOVETYPE_NOCLIP; + bot->solid = SOLID_NOT; + bot->svflags |= SVF_NOCLIENT; + bot->client->resp.team = NOTEAM; + bot->client->ps.gunindex = 0; + } + } +//FIREBLADE + if (!going_observer && !teamplay->value) + { // this handles telefrags... + KillBox(bot); + } +//FIREBLADE + + gi.linkentity (bot); + + bot->think = ACEAI_Think; + bot->nextthink = level.time + FRAMETIME; + +//AQ2 ADD + client->mk23_max = 12; + client->mp5_max = 30; + client->m4_max = 24; + client->shot_max = 7; + client->sniper_max = 6; + client->cannon_max = 2; + client->dual_max = 24; + client->mk23_rds = client->mk23_max; + client->dual_rds = client->mk23_max; + client->knife_max = 10; + client->grenade_max = 2; + + bot->lasersight = NULL; + + //other + client->bandaging = 0; + client->leg_damage = 0; + client->leg_noise = 0; + client->leg_dam_count = 0; + client->desired_fov = 90; + client->ps.fov = 90; + client->idle_weapon = 0; + client->drop_knife = 0; + client->no_sniper_display = 0; + client->knife_sound = 0; + client->doortoggle = 0; + client->have_laser = 0; + + // Choose Teamplay weapon + if( bot->weaponchoice == 0) + counter = rand() % 5; + else + counter = bot->weaponchoice -1; // Range is 1..5 + + switch(counter) + { + case 0: + ACEAI_Cmd_Choose( bot, MP5_NAME); + break; + case 1: + ACEAI_Cmd_Choose( bot, M4_NAME); + break; + case 2: + ACEAI_Cmd_Choose( bot, M3_NAME); + break; + case 3: + ACEAI_Cmd_Choose( bot, HC_NAME); + break; + case 4: + ACEAI_Cmd_Choose( bot, SNIPER_NAME); + break; + default: + ACEAI_Cmd_Choose( bot, M3_NAME); + break; + } + + // Choose Teamplay equipment + if(bot->equipchoice == 0) + counter = rand() % 5; + else + counter = bot->equipchoice - 1; // Range is 1..5 + + switch(counter) + { + case 0: + ACEAI_Cmd_Choose( bot, SIL_NAME); + break; + case 1: + ACEAI_Cmd_Choose( bot, SLIP_NAME); + break; + case 2: + ACEAI_Cmd_Choose( bot, BAND_NAME); + break; + case 3: + ACEAI_Cmd_Choose( bot, KEV_NAME); + break; + case 4: + ACEAI_Cmd_Choose( bot, LASER_NAME); + break; + default: + ACEAI_Cmd_Choose( bot, KEV_NAME); + break; + } + +//FIREBLADE + if (!going_observer) + { + // items up here so that the bandolier will change equipclient below + if ( allitem->value ) + { + AllItems( bot ); + } + + + if (teamplay->value) + EquipClient(bot); + + if (bot->client->menu) + { + PMenu_Close(bot); + return; + } +//FIREBLADE + if ( allweapon->value ) + { + AllWeapons( bot ); + } + + // force the current weapon up + client->newweapon = client->pers.weapon; + ChangeWeapon (bot); + +//FIREBLADE + if (teamplay->value) + { + bot->solid = SOLID_TRIGGER; + gi.linkentity(bot); + } +//FIREBLADE + } + if(teamplay->value) + { + int randomnode; + bot->client->resp.team = team; + s = Info_ValueForKey (bot->client->pers.userinfo, "skin"); + AssignSkin(bot, s); + // Anti centipede timer + bot->teamPauseTime = level.time + 3.0 + (rand() % 7); + // Change facing angle for each bot + randomnode = (int)(num_players * random() ); + VectorSubtract (nodes[randomnode].origin, bot->s.origin, bot->move_vector); + bot->move_vector[2] = 0; + } + else + bot->teamPauseTime = level.time; + +//AQ2 END + + //RiEvEr - new node pathing system + memset(&bot->pathList, 0, sizeof(bot->pathList) ); + bot->pathList.head = bot->pathList.tail = NULL; + //R + /* +//AQ2 REMOVED + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (bot-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (bot->s.origin, MULTICAST_PVS); +//AQ2 END + */ + } + +} + +/////////////////////////////////////////////////////////////////////// +// Respawn the bot +/////////////////////////////////////////////////////////////////////// +void ACESP_Respawn (edict_t *self) +{ +// AQ2 CHANGED + if (self->solid != SOLID_NOT || self->deadflag == DEAD_DEAD) + { + CopyToBodyQue (self); + } + + if(teamplay->value) + ACESP_PutClientInServer (self,true, self->client->resp.team); + else + ACESP_PutClientInServer (self,true,0); + + self->svflags &= ~SVF_NOCLIENT; +//AQ2 END + + // add a teleportation effect +//AQ2 self->s.event = EV_PLAYER_TELEPORT; + + // hold in place briefly +//AQ2 self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; +//AQ2 self->client->ps.pmove.pm_time = 14; + + self->client->respawn_time = level.time; + + if( random() < 0.15) + { + // Store current enemies available + int i, counter = 0; + edict_t *myplayer[MAX_BOTS]; + + if( self->lastkilledby) + { + // Have a comeback line! + if(ltk_chat->value) // Some people don't want this *sigh* + LTK_Chat( self, self->lastkilledby, DBC_KILLED); + self->lastkilledby = NULL; + } + else + { + // Pick someone at random to insult + for(i=0;i<=num_players;i++) + { + // Find all available enemies to insult + if(players[i] == NULL || players[i] == self || + players[i]->solid == SOLID_NOT) + continue; + + if(teamplay->value && OnSameTeam( self, players[i]) ) + continue; + myplayer[counter++] = players[i]; + } + if(counter > 0) + { + // Say something insulting to them! + if(ltk_chat->value) // Some people don't want this *sigh* + LTK_Chat( self, myplayer[rand()%counter], DBC_INSULT); + } + } + } +} + +/////////////////////////////////////////////////////////////////////// +// Find a free client spot +/////////////////////////////////////////////////////////////////////// +edict_t *ACESP_FindFreeClient (void) +{ + edict_t *bot; + int i; + int max_count=0; + + // This is for the naming of the bots + for (i = maxclients->value; i > 0; i--) + { + bot = g_edicts + i + 1; + + if(bot->count > max_count) + max_count = bot->count; + } + + // Check for free spot + for (i = maxclients->value; i > 0; i--) + { + bot = g_edicts + i + 1; + + if (!bot->inuse) + break; + } + + bot->count = max_count + 1; // Will become bot name... + + if (bot->inuse) + bot = NULL; + + return bot; +} + +/////////////////////////////////////////////////////////////////////// +// Set the name of the bot and update the userinfo +/////////////////////////////////////////////////////////////////////// +void ACESP_SetName(edict_t *bot, char *name, char *skin, char *team) +{ + float rnd; + char userinfo[MAX_INFO_STRING]; + char bot_skin[MAX_INFO_STRING]; + char bot_name[MAX_INFO_STRING]; + + // Set the name for the bot. + // name + if(strlen(name) == 0) + { + // RiEvEr - new code to get random bot names + LTKsetBotName(bot_name); + } + else + strcpy(bot_name,name); + + // skin + if(strlen(skin) == 0) + { + // randomly choose skin + rnd = random(); + if(rnd < 0.05) + sprintf(bot_skin,"male/bluebeard"); + else if(rnd < 0.1) + sprintf(bot_skin,"female/brianna"); + else if(rnd < 0.15) + sprintf(bot_skin,"male/blues"); + else if(rnd < 0.2) + sprintf(bot_skin,"female/ensign"); + else if(rnd < 0.25) + sprintf(bot_skin,"female/jezebel"); + else if(rnd < 0.3) + sprintf(bot_skin,"female/jungle"); + else if(rnd < 0.35) + sprintf(bot_skin,"sas/sasurban"); + else if(rnd < 0.4) + sprintf(bot_skin,"terror/urbanterr"); + else if(rnd < 0.45) + sprintf(bot_skin,"female/venus"); + else if(rnd < 0.5) + sprintf(bot_skin,"sydney/sydney"); + else if(rnd < 0.55) + sprintf(bot_skin,"male/cajin"); + else if(rnd < 0.6) + sprintf(bot_skin,"male/commando"); + else if(rnd < 0.65) + sprintf(bot_skin,"male/grunt"); + else if(rnd < 0.7) + sprintf(bot_skin,"male/mclaine"); + else if(rnd < 0.75) + sprintf(bot_skin,"male/robber"); + else if(rnd < 0.8) + sprintf(bot_skin,"male/snowcamo"); + else if(rnd < 0.85) + sprintf(bot_skin,"terror/swat"); + else if(rnd < 0.9) + sprintf(bot_skin,"terror/jungleterr"); + else if(rnd < 0.95) + sprintf(bot_skin,"sas/saspolice"); + else + sprintf(bot_skin,"sas/sasuc"); + } + else + strcpy(bot_skin,skin); + + // initialise userinfo + memset (userinfo, 0, sizeof(userinfo)); + + // add bot's name/skin/hand to userinfo + Info_SetValueForKey (userinfo, "name", bot_name); + Info_SetValueForKey (userinfo, "skin", bot_skin); + Info_SetValueForKey (userinfo, "hand", "2"); // bot is center handed for now! +//AQ2 ADD + Info_SetValueForKey (userinfo, "spectator", "0"); // NOT a spectator +//AQ2 END + + ClientConnect (bot, userinfo); + +// ACESP_SaveBots(); // make sure to save the bots +} +//RiEvEr - new global to enable bot self-loading of routes +extern char current_map[55]; +// + +char *LocalTeamNames[3] = {"spectator", "1", "2" }; + +/////////////////////////////////////////////////////////////////////// +// Spawn the bot +/////////////////////////////////////////////////////////////////////// +void ACESP_SpawnBot (char *team, char *name, char *skin, char *userinfo) +{ + edict_t *bot; + + bot = ACESP_FindFreeClient (); + + if (!bot) + { + safe_bprintf (PRINT_MEDIUM, "Server is full, increase Maxclients.\n"); + return; + } + + bot->yaw_speed = 100; // yaw speed + bot->inuse = true; + bot->is_bot = true; + + // To allow bots to respawn + if(userinfo == NULL) + ACESP_SetName(bot, name, skin, team); + else + ClientConnect (bot, userinfo); + + G_InitEdict (bot); + + // Balance the teams! + if(teamplay->value) + { + if( (team == NULL) || (strlen(team) < 1) ) + { + if( GetNextTeamNumber() == 1 ) + { + gi.bprintf(PRINT_HIGH, "Assigned to team 1\n"); + team = LocalTeamNames[1]; + } + else + { + team = LocalTeamNames[2]; + gi.bprintf(PRINT_HIGH, "Assigned to team 2\n"); + } + } + } + + InitClientResp (bot->client); + + +//AQ2 CHANGE + // Set up the preferred weapon & equipment + bot->weaponchoice = 0; + bot->equipchoice = 0; +// ACESP_PutClientInServer (bot,false,0); + // locate ent at a spawn point + if(teamplay->value) + { + if ((team != NULL) && (strcmp(team,"1")==0) ) + ACESP_PutClientInServer (bot,true, TEAM1); + else + ACESP_PutClientInServer (bot,true, TEAM2); + } + else + ACESP_PutClientInServer (bot,true,0); +//AQ2 END + + // make sure all view stuff is valid + ClientEndServerFrame (bot); + + ACEIT_PlayerAdded (bot); // let the world know we added another + + ACEAI_PickLongRangeGoal(bot); // pick a new goal + + // LTK chat stuff + if( random() < 0.33) + { + // Store current enemies available + int i, counter = 0; + edict_t *myplayer[MAX_BOTS]; + + for(i=0;i<=num_players;i++) + { + // Find all available enemies to insult + if(players[i] == NULL || players[i] == bot || + players[i]->solid == SOLID_NOT) + continue; + + if(teamplay->value && OnSameTeam( bot, players[i]) ) + continue; + myplayer[counter++] = players[i]; + } + if(counter > 0) + { + // Say something insulting to them! + if(ltk_chat->value) // Some people don't want this *sigh* + LTK_Chat( bot, myplayer[rand()%counter], DBC_WELCOME); + } + } + +} + +void ClientDisconnect( edict_t *ent ); + +/////////////////////////////////////////////////////////////////////// +// Remove a bot by name or all bots +/////////////////////////////////////////////////////////////////////// +void ACESP_RemoveBot(char *name) +{ + int i; + qboolean freed=false; + edict_t *bot; + + for(i=0;ivalue;i++) + { + bot = g_edicts + i + 1; + if(bot->inuse) + { + if(bot->is_bot && (strcmp(bot->client->pers.netname,name)==0 || strcmp(name,"all")==0)) + { + bot->health = 0; + player_die (bot, bot, bot, 100000, vec3_origin); + // don't even bother waiting for death frames +// bot->deadflag = DEAD_DEAD; +// bot->inuse = false; + freed = true; + ClientDisconnect( bot ); +// ACEIT_PlayerRemoved (bot); +// safe_bprintf (PRINT_MEDIUM, "%s removed\n", bot->client->pers.netname); + } + } + } + + if(!freed) + safe_bprintf (PRINT_MEDIUM, "%s not found\n", name); + +// ACESP_SaveBots(); // Save them again +} + +//==================================== +// Stuff to generate pseudo-random names +//==================================== +#define NUMNAMES 10 +char *names1[NUMNAMES] = { + "Bad", "d3th", "L33t", "Fasst", "mAx", "l3thal", "kw1k", "Hard", "Angel", "Red"}; + +char *names2[NUMNAMES] = { + "Moon", "eevil", "wakko", "d00d", "killa", "dog", "sodja", "joos", "frags", "akimbo" }; + +char *names3[NUMNAMES] = { + "An", "Bal", "Calen", "Cor", "Fan", "Gil", "Hal", "Lin", "Mal", "Per"}; + +char *names4[NUMNAMES] = { + "adan", "rog", "born", "dor", "fing", "galad", "iel", "loss", "orch", "riel" }; + +qboolean nameused[NUMNAMES][NUMNAMES]; + +//==================================== +// New random bot naming routine +//==================================== +void LTKsetBotName( char *bot_name ) +{ + int part1,part2; + + part1 = part2 = 0; + + do + { + part1 = rand()% NUMNAMES; + part2 = rand()% NUMNAMES; + }while( nameused[part1][part2]); + + // Mark that name as used + nameused[part1][part2] = true; + // Now put the name together + if( random() < 0.5 ) + { + strcpy( bot_name, names1[part1]); + strcat( bot_name, names2[part2]); + } + else + { + strcpy( bot_name, names3[part1]); + strcat( bot_name, names4[part2]); + } +} diff --git a/acesrc/botchat.c b/acesrc/botchat.c new file mode 100644 index 0000000..c7ee259 --- /dev/null +++ b/acesrc/botchat.c @@ -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); + } +} + diff --git a/acesrc/botchat.h b/acesrc/botchat.h new file mode 100644 index 0000000..34a4f23 --- /dev/null +++ b/acesrc/botchat.h @@ -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__ diff --git a/acesrc/botnav.c b/acesrc/botnav.c new file mode 100644 index 0000000..e31397c --- /dev/null +++ b/acesrc/botnav.c @@ -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 ); + } +} + + diff --git a/acesrc/botnav.h b/acesrc/botnav.h new file mode 100644 index 0000000..1314494 --- /dev/null +++ b/acesrc/botnav.h @@ -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 \ No newline at end of file diff --git a/acesrc/botscan.c b/acesrc/botscan.c new file mode 100644 index 0000000..ce98593 --- /dev/null +++ b/acesrc/botscan.c @@ -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 +#include + +//============================== +// 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 ); + } + } +} +//************************************************************************** +*/ \ No newline at end of file diff --git a/acesrc/botscan.h b/acesrc/botscan.h new file mode 100644 index 0000000..1469321 --- /dev/null +++ b/acesrc/botscan.h @@ -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 ); diff --git a/acesrc/cgf_sfx_fog.c b/acesrc/cgf_sfx_fog.c new file mode 100644 index 0000000..6dffd2d --- /dev/null +++ b/acesrc/cgf_sfx_fog.c @@ -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 // prevent problems between C and STL + +extern "C" +{ +#include "cgf_sfx_fog.h" +#include "../g_local.h" +//#include "sabin_debug.h" +#include +#include + +/* +#ifdef CGF +// AI update related to fog +#include "cgf_tactics_visibility.h" +#endif +*/ +} + +#include +#include +#include +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\.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 diff --git a/acesrc/cgf_sfx_fog.cpp b/acesrc/cgf_sfx_fog.cpp new file mode 100644 index 0000000..8f761f1 --- /dev/null +++ b/acesrc/cgf_sfx_fog.cpp @@ -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 // prevent problems between C and STL + +extern "C" +{ +#include "cgf_sfx_fog.h" +#include "../g_local.h" +//#include "sabin_debug.h" +#include +#include + +/* +#ifdef CGF +// AI update related to fog +#include "cgf_tactics_visibility.h" +#endif +*/ +} + +#include +#include +#include +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\.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 diff --git a/acesrc/cgf_sfx_fog.h b/acesrc/cgf_sfx_fog.h new file mode 100644 index 0000000..c2dfe77 --- /dev/null +++ b/acesrc/cgf_sfx_fog.h @@ -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_ diff --git a/acesrc/ltklist.c b/acesrc/ltklist.c new file mode 100644 index 0000000..bb65e1f --- /dev/null +++ b/acesrc/ltklist.c @@ -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); +} diff --git a/acesrc/vssver.scc b/acesrc/vssver.scc new file mode 100644 index 0000000..9f740f4 Binary files /dev/null and b/acesrc/vssver.scc differ diff --git a/cgf_sfx_glass.c b/cgf_sfx_glass.c new file mode 100644 index 0000000..ff7bc7f --- /dev/null +++ b/cgf_sfx_glass.c @@ -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 // 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; +} + + diff --git a/cgf_sfx_glass.h b/cgf_sfx_glass.h new file mode 100644 index 0000000..2f101d2 --- /dev/null +++ b/cgf_sfx_glass.h @@ -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; +*/ diff --git a/docs/CHANGES b/docs/CHANGES new file mode 100644 index 0000000..b12692a --- /dev/null +++ b/docs/CHANGES @@ -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. diff --git a/docs/LICENSE.TXT b/docs/LICENSE.TXT new file mode 100644 index 0000000..6d93dc2 --- /dev/null +++ b/docs/LICENSE.TXT @@ -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 + + diff --git a/docs/README b/docs/README new file mode 100644 index 0000000..a67135c --- /dev/null +++ b/docs/README @@ -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 " and "sv removeip ". 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. diff --git a/docs/acebot.txt b/docs/acebot.txt new file mode 100644 index 0000000..f5a6285 --- /dev/null +++ b/docs/acebot.txt @@ -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 :\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 + + + + + + diff --git a/g_ai.c b/g_ai.c new file mode 100644 index 0000000..c84c9cb --- /dev/null +++ b/g_ai.c @@ -0,0 +1,1098 @@ +// g_ai.c + +#include "g_local.h" + +qboolean FindTarget (edict_t *self); +extern cvar_t *maxclients; + +qboolean ai_checkattack (edict_t *self, float dist); + +qboolean enemy_vis; +qboolean enemy_infront; +int enemy_range; +float enemy_yaw; + +//============================================================================ + + +/* +================= +AI_SetSightClient + +Called once each frame to set level.sight_client to the +player to be checked for in findtarget. + +If all clients are either dead or in notarget, sight_client +will be null. + +In coop games, sight_client will cycle between the clients. +================= +*/ +void AI_SetSightClient (void) +{ + edict_t *ent; + int start, check; + + if (level.sight_client == NULL) + start = 1; + else + start = level.sight_client - g_edicts; + + check = start; + while (1) + { + check++; + if (check > game.maxclients) + check = 1; + ent = &g_edicts[check]; + if (ent->inuse + && ent->health > 0 + && !(ent->flags & FL_NOTARGET) ) + { + level.sight_client = ent; + return; // got one + } + if (check == start) + { + level.sight_client = NULL; + return; // nobody to see + } + } +} + +//============================================================================ + +/* +============= +ai_move + +Move the specified distance at current facing. +This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward +============== +*/ +void ai_move (edict_t *self, float dist) +{ + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_stand + +Used for standing around and looking for players +Distance is for slight position adjustments needed by the animations +============== +*/ +void ai_stand (edict_t *self, float dist) +{ + vec3_t v; + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + if (self->enemy) + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + { + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.run (self); + } + M_ChangeYaw (self); + ai_checkattack (self, 0); + } + else + FindTarget (self); + return; + } + + if (FindTarget (self)) + return; + + if (level.time > self->monsterinfo.pausetime) + { + self->monsterinfo.walk (self); + return; + } + + if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.idle (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_walk + +The monster is walking it's beat +============= +*/ +void ai_walk (edict_t *self, float dist) +{ + M_MoveToGoal (self, dist); + + // check for noticing a player + if (FindTarget (self)) + return; + + if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.search (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_charge + +Turns towards target and advances +Use this call with a distnace of 0 to replace ai_face +============== +*/ +void ai_charge (edict_t *self, float dist) +{ + vec3_t v; + + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw (self); + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_turn + +don't move, but turn towards ideal_yaw +Distance is for slight position adjustments needed by the animations +============= +*/ +void ai_turn (edict_t *self, float dist) +{ + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (FindTarget (self)) + return; + + M_ChangeYaw (self); +} + + +/* + +.enemy +Will be world if not currently angry at anyone. + +.movetarget +The next path spot to walk toward. If .enemy, ignore .movetarget. +When an enemy is killed, the monster will try to return to it's path. + +.hunt_time +Set to time + something when the player is in sight, but movement straight for +him is blocked. This causes the monster to use wall following code for +movement direction instead of sighting on the player. + +.ideal_yaw +A yaw angle of the intended direction, which will be turned towards at up +to 45 deg / state. If the enemy is in view and hunt_time is not active, +this will be the exact line towards the enemy. + +.pausetime +A monster will leave it's stand state and head towards it's .movetarget when +time > .pausetime. + +walkmove(angle, speed) primitive is all or nothing +*/ + +/* +============= +range + +returns the range catagorization of an entity reletive to self +0 melee range, will become hostile even if back is turned +1 visibility and infront, or visibility and show hostile +2 infront and show hostile +3 only triggered by damage +============= +*/ +int range (edict_t *self, edict_t *other) +{ + vec3_t v; + float len; + + VectorSubtract (self->s.origin, other->s.origin, v); + len = VectorLength (v); + if (len < MELEE_DISTANCE) + return RANGE_MELEE; + if (len < 500) + return RANGE_NEAR; + if (len < 1000) + return RANGE_MID; + return RANGE_FAR; +} + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +qboolean visible (edict_t *self, edict_t *other) +{ + vec3_t spot1; + vec3_t spot2; + trace_t trace; + + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (other->s.origin, spot2); + spot2[2] += other->viewheight; + trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE); + + if (trace.fraction == 1.0) + return true; + return false; +} + + +/* +============= +infront + +returns 1 if the entity is in front (in sight) of self +============= +*/ +qboolean infront (edict_t *self, edict_t *other) +{ + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorSubtract (other->s.origin, self->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + + if (dot > 0.3) + return true; + return false; +} + + +//============================================================================ + +void HuntTarget (edict_t *self) +{ + vec3_t vec; + + self->goalentity = self->enemy; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.stand (self); + else + self->monsterinfo.run (self); + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + // wait a while before first attack + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + AttackFinished (self, 1); +} + +void FoundTarget (edict_t *self) +{ + // let other monsters see this monster for a while + if (self->enemy->client) + { + level.sight_entity = self; + level.sight_entity_framenum = level.framenum; + level.sight_entity->light_level = 128; + } + + self->show_hostile = level.time + 1; // wake up other monsters + + VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + + if (!self->combattarget) + { + HuntTarget (self); + return; + } + + self->goalentity = self->movetarget = G_PickTarget(self->combattarget); + if (!self->movetarget) + { + self->goalentity = self->movetarget = self->enemy; + HuntTarget (self); + gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget); + return; + } + + // clear out our combattarget, these are a one shot deal + self->combattarget = NULL; + self->monsterinfo.aiflags |= AI_COMBAT_POINT; + + // clear the targetname, that point is ours! + self->movetarget->targetname = NULL; + self->monsterinfo.pausetime = 0; + + // run for it + self->monsterinfo.run (self); +} + + +/* +=========== +FindTarget + +Self is currently not attacking anything, so try to find a target + +Returns TRUE if an enemy was sighted + +When a player fires a missile, the point of impact becomes a fakeplayer so +that monsters that see the impact will respond as if they had seen the +player. + +To avoid spending too much time, only a single client (or fakeclient) is +checked each frame. This means multi player games will have slightly +slower noticing monsters. +============ +*/ +qboolean FindTarget (edict_t *self) +{ + edict_t *client; + qboolean heardit; + int r; + + if (self->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (self->goalentity && self->goalentity->inuse && self->goalentity->classname) + { + if (strcmp(self->goalentity->classname, "target_actor") == 0) + return false; + } + + //FIXME look for monsters? + return false; + } + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + +// if the first spawnflag bit is set, the monster will only wake up on +// really seeing the player, not another monster getting angry or hearing +// something + +// revised behavior so they will wake up if they "see" a player make a noise +// but not weapon impact/explosion noises + + heardit = false; + if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sight_entity; + if (client->enemy == self->enemy) + { + return false; + } + } + else if (level.sound_entity_framenum >= (level.framenum - 1)) + { + client = level.sound_entity; + heardit = true; + } + else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sound2_entity; + heardit = true; + } + else + { + client = level.sight_client; + if (!client) + return false; // no clients to get mad at + } + + // if the entity went away, forget it + if (!client->inuse) + return false; + + if (client == self->enemy) + return true; // JDC false; + + if (client->client) + { + if (client->flags & FL_NOTARGET) + return false; + } + else if (client->svflags & SVF_MONSTER) + { + if (!client->enemy) + return false; + if (client->enemy->flags & FL_NOTARGET) + return false; + } + else if (heardit) + { + if (client->owner->flags & FL_NOTARGET) + return false; + } + else + return false; + + if (!heardit) + { + r = range (self, client); + + if (r == RANGE_FAR) + return false; + +// this is where we would check invisibility + + // is client in an spot too dark to be seen? + if (client->light_level <= 5) + return false; + + if (!visible (self, client)) + { + return false; + } + + if (r == RANGE_NEAR) + { + if (client->show_hostile < level.time && !infront (self, client)) + { + return false; + } + } + else if (r == RANGE_MID) + { + if (!infront (self, client)) + { + return false; + } + } + + self->enemy = client; + + if (strcmp(self->enemy->classname, "player_noise") != 0) + { + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + if (!self->enemy->client) + { + self->enemy = self->enemy->enemy; + if (!self->enemy->client) + { + self->enemy = NULL; + return false; + } + } + } + } + else // heardit + { + vec3_t temp; + + if (self->spawnflags & 1) + { + if (!visible (self, client)) + return false; + } + else + { + if (!gi.inPHS(self->s.origin, client->s.origin)) + return false; + } + + VectorSubtract (client->s.origin, self->s.origin, temp); + + if (VectorLength(temp) > 1000) // too far to hear + { + return false; + } + + // check area portals - if they are different and not connected then we can't hear it + if (client->areanum != self->areanum) + if (!gi.AreasConnected(self->areanum, client->areanum)) + return false; + + self->ideal_yaw = vectoyaw(temp); + M_ChangeYaw (self); + + // hunt the sound for a bit; hopefully find the real player + self->monsterinfo.aiflags |= AI_SOUND_TARGET; + self->enemy = client; + } + +// +// got one +// + FoundTarget (self); + + if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight)) + self->monsterinfo.sight (self, self->enemy); + + return true; +} + + +//============================================================================= + +/* +============ +FacingIdeal + +============ +*/ +qboolean FacingIdeal(edict_t *self) +{ + float delta; + + delta = anglemod(self->s.angles[YAW] - self->ideal_yaw); + if (delta > 45 && delta < 315) + return false; + return true; +} + + +//============================================================================= + +qboolean M_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + float chance; + trace_t tr; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + // melee attack + if (enemy_range == RANGE_MELEE) + { + // don't always melee in easy mode + if (skill->value == 0 && (rand()&3) ) + return false; + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.2; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.1; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.02; + } + else + { + return false; + } + + if (skill->value == 0) + chance *= 0.5; + else if (skill->value >= 2) + chance *= 2; + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + +/* +============= +ai_run_melee + +Turn and close until within an angle to launch a melee attack +============= +*/ +void ai_run_melee(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.melee (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +} + + +/* +============= +ai_run_missile + +Turn in place until within an angle to launch a missile attack +============= +*/ +void ai_run_missile(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.attack (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_slide + +Strafe sideways, but stay at aproximately the same range +============= +*/ +void ai_run_slide(edict_t *self, float distance) +{ + float ofs; + + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (self->monsterinfo.lefty) + ofs = 90; + else + ofs = -90; + + if (M_walkmove (self, self->ideal_yaw + ofs, distance)) + return; + + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + M_walkmove (self, self->ideal_yaw - ofs, distance); +} + + +/* +============= +ai_checkattack + +Decides if we're going to attack or do something else +used by ai_run and ai_stand +============= +*/ +qboolean ai_checkattack (edict_t *self, float dist) +{ + vec3_t temp; + qboolean hesDeadJim; + +// this causes monsters to run blindly to the combat point w/o firing + if (self->goalentity) + { + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + if ((level.time - self->enemy->teleport_time) > 5.0) + { + if (self->goalentity == self->enemy) + if (self->movetarget) + self->goalentity = self->movetarget; + else + self->goalentity = NULL; + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + } + else + { + self->show_hostile = level.time + 1; + return false; + } + } + } + + enemy_vis = false; + +// see if the enemy is dead + hesDeadJim = false; + if ((!self->enemy) || (!self->enemy->inuse)) + { + hesDeadJim = true; + } + else if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (self->enemy->health > 0) + { + hesDeadJim = true; + self->monsterinfo.aiflags &= ~AI_MEDIC; + } + } + else + { + if (self->monsterinfo.aiflags & AI_BRUTAL) + { + if (self->enemy->health <= -80) + hesDeadJim = true; + } + else + { + if (self->enemy->health <= 0) + hesDeadJim = true; + } + } + + if (hesDeadJim) + { + self->enemy = NULL; + // FIXME: look all around for other targets + if (self->oldenemy && self->oldenemy->health > 0) + { + self->enemy = self->oldenemy; + self->oldenemy = NULL; + HuntTarget (self); + } + else + { + if (self->movetarget) + { + self->goalentity = self->movetarget; + self->monsterinfo.walk (self); + } + else + { + // we need the pausetime otherwise the stand code + // will just revert to walking with no target and + // the monsters will wonder around aimlessly trying + // to hunt the world entity + self->monsterinfo.pausetime = level.time + 100000000; + self->monsterinfo.stand (self); + } + return true; + } + } + + self->show_hostile = level.time + 1; // wake up other monsters + +// check knowledge of enemy + enemy_vis = visible(self, self->enemy); + if (enemy_vis) + { + self->monsterinfo.search_time = level.time + 5; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + } + +// look for other coop players here +// if (coop && self->monsterinfo.search_time < level.time) +// { +// if (FindTarget (self)) +// return true; +// } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + + // JDC self->ideal_yaw = enemy_yaw; + + if (self->monsterinfo.attack_state == AS_MISSILE) + { + ai_run_missile (self); + return true; + } + if (self->monsterinfo.attack_state == AS_MELEE) + { + ai_run_melee (self); + return true; + } + + // if enemy is not currently visible, we will never attack + if (!enemy_vis) + return false; + + return self->monsterinfo.checkattack (self); +} + + +/* +============= +ai_run + +The monster has an enemy it is trying to kill +============= +*/ +void ai_run (edict_t *self, float dist) +{ + vec3_t v; + edict_t *tempgoal; + edict_t *save; + qboolean new; + edict_t *marker; + float d1, d2; + trace_t tr; + vec3_t v_forward, v_right; + float left, center, right; + vec3_t left_target, right_target; + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + { + M_MoveToGoal (self, dist); + return; + } + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + VectorSubtract (self->s.origin, self->enemy->s.origin, v); + if (VectorLength(v) < 64) + { + self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.stand (self); + return; + } + + M_MoveToGoal (self, dist); + + if (!FindTarget (self)) + return; + } + + if (ai_checkattack (self, dist)) + return; + + if (self->monsterinfo.attack_state == AS_SLIDING) + { + ai_run_slide (self, dist); + return; + } + + if (enemy_vis) + { +// if (self.aiflags & AI_LOST_SIGHT) +// dprint("regained sight\n"); + M_MoveToGoal (self, dist); + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + return; + } + + // coop will change to another enemy if visible + if (coop->value) + { // FIXME: insane guys get mad with this, which causes crashes! + if (FindTarget (self)) + return; + } + + if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20))) + { + M_MoveToGoal (self, dist); + self->monsterinfo.search_time = 0; +// dprint("search timeout\n"); + return; + } + + save = self->goalentity; + tempgoal = G_Spawn(); + self->goalentity = tempgoal; + + new = false; + + if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) + { + // just lost sight of the player, decide where to go first +// dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n"); + self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN); + self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP); + new = true; + } + + if (self->monsterinfo.aiflags & AI_PURSUE_NEXT) + { + self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT; +// dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n"); + + // give ourself more time since we got this far + self->monsterinfo.search_time = level.time + 5; + + if (self->monsterinfo.aiflags & AI_PURSUE_TEMP) + { +// dprint("was temp goal; retrying original\n"); + self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP; + marker = NULL; + VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting); + new = true; + } + else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN) + { + self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN; + marker = PlayerTrail_PickFirst (self); + } + else + { + marker = PlayerTrail_PickNext (self); + } + + if (marker) + { + VectorCopy (marker->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = marker->timestamp; + self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW]; +// dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n"); + +// debug_drawline(self.origin, self.last_sighting, 52); + new = true; + } + } + + VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v); + d1 = VectorLength(v); + if (d1 <= dist) + { + self->monsterinfo.aiflags |= AI_PURSUE_NEXT; + dist = d1; + } + + VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin); + + if (new) + { +// gi.dprintf("checking for course correction\n"); + + tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID); + if (tr.fraction < 1) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + d1 = VectorLength(v); + center = tr.fraction; + d2 = d1 * ((center+1)/2); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); + AngleVectors(self->s.angles, v_forward, v_right, NULL); + + VectorSet(v, d2, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID); + left = tr.fraction; + + VectorSet(v, d2, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID); + right = tr.fraction; + + center = (d1*center)/d2; + if (left >= center && left > right) + { + if (left < 1) + { + VectorSet(v, d2 * left * 0.5, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (left_target, self->goalentity->s.origin); + VectorCopy (left_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted left\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + else if (right >= center && right > left) + { + if (right < 1) + { + VectorSet(v, d2 * right * 0.5, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (right_target, self->goalentity->s.origin); + VectorCopy (right_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted right\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + } +// else gi.dprintf("course was fine\n"); + } + + M_MoveToGoal (self, dist); + + G_FreeEdict(tempgoal); + + if (self) + self->goalentity = save; +} diff --git a/g_chase.c b/g_chase.c new file mode 100644 index 0000000..5244802 --- /dev/null +++ b/g_chase.c @@ -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; + } +} diff --git a/g_cmds.c b/g_cmds.c new file mode 100644 index 0000000..71b9cd0 --- /dev/null +++ b/g_cmds.c @@ -0,0 +1,1315 @@ +#include "g_local.h" +#include "m_player.h" + + +char *ClientTeam (edict_t *ent) +{ + char *p; + static char value[512]; + + value[0] = 0; + + if (!ent->client) + return value; + + strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin")); + p = strchr(value, '/'); + if (!p) + return value; + + if ((int)(dmflags->value) & DF_MODELTEAMS) + { + *p = 0; + return value; + } + + // if ((int)(dmflags->value) & DF_SKINTEAMS) + return ++p; +} + +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2) +{ + char ent1Team [512]; + char ent2Team [512]; + +//FIREBLADE + if (!ent1->client || !ent2->client) + return false; + + if (teamplay->value) + { + return ent1->client->resp.team == ent2->client->resp.team; + } +//FIREBLADE + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + return false; + + strcpy (ent1Team, ClientTeam (ent1)); + strcpy (ent2Team, ClientTeam (ent2)); + + if (strcmp(ent1Team, ent2Team) == 0) + return true; + return false; +} + + +void SelectNextItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + +//FIREBLADE + if (cl->menu) + { + PMenu_Next(ent); + return; + } +//FIREBLADE + + if (ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) + return; + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void SelectPrevItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + +//FIREBLADE + if (cl->menu) + { + PMenu_Prev(ent); + return; + } +//FIREBLADE + + if (ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) + return; + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void ValidateSelectedItem (edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (cl->pers.inventory[cl->pers.selected_item]) + return; // valid + + SelectNextItem (ent, -1); +} + + +//================================================================================= + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (edict_t *ent) +{ + char *name; + gitem_t *it; + int index; + int i; + qboolean give_all; + edict_t *it_ent; + edict_t etemp; + + if (deathmatch->value && !sv_cheats->value) + { + safe_cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + if (ent->solid == SOLID_NOT) + { + safe_cprintf(ent, PRINT_HIGH, "This command can't be used by spectators.\n"); + return; + } + + name = gi.args(); + + if (Q_stricmp(name, "all") == 0) + give_all = true; + else + give_all = false; + + if (Q_stricmp(gi.argv(1), "health") == 0) + { + /* if (gi.argc() == 3) + ent->health = atoi(gi.argv(2)); + else + ent->health = ent->max_health; + if (!give_all) */ + return; + } + + if (give_all || Q_stricmp(name, "weapons") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_WEAPON)) + continue; + ent->client->pers.inventory[i] += 1; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "items") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_ITEM)) + continue; + etemp.item = it; + + if ( ent->client->unique_item_total >= unique_items->value ) + ent->client->unique_item_total = unique_items->value - 1; + + Pickup_Special ( &etemp, ent ); + } + if (!give_all) + return; + } + + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + ent->client->mk23_rds = ent->client->mk23_max; + ent->client->dual_rds = ent->client->dual_max; + ent->client->mp5_rds = ent->client->mp5_max; + ent->client->m4_rds = ent->client->m4_max; + ent->client->shot_rds = ent->client->shot_max; + ent->client->sniper_rds = ent->client->sniper_max; + ent->client->cannon_rds = ent->client->cannon_max; + + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_AMMO)) + continue; + Add_Ammo (ent, it, 1000); + } + if (!give_all) + return; + } + + if (Q_stricmp(name, "armor") == 0) + { + /* + gitem_armor_t *info; + + it = FindItem("Jacket Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Combat Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Body Armor"); + info = (gitem_armor_t *)it->info; + ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count; + + if (!give_all) + */ + return; + } + + if (Q_stricmp(name, "Power Shield") == 0) + { + /*it = FindItem("Power Shield"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + + if (!give_all) + */ + return; + } + + /*if (give_all) + { + for (i=0 ; ipickup) + continue; + if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO)) + continue; + ent->client->pers.inventory[i] = 1; + } + return; + }*/ + + if ( give_all ) + { + return; + } + + + it = FindItem (name); + if (!it) + { + name = gi.argv(1); + it = FindItem (name); + if (!it) + { + gi.dprintf ("unknown item\n"); + return; + } + if ( !(it->flags & IT_AMMO || it->flags & IT_WEAPON || it->flags & IT_ITEM) ) + return; + } + + if ( !(it->flags & IT_AMMO || it->flags & IT_WEAPON || it->flags & IT_ITEM) ) + return; + + + if (!it->pickup) + { + gi.dprintf ("non-pickup item\n"); + return; + } + + + index = ITEM_INDEX(it); + + if (it->flags & IT_AMMO) + { + /* if (gi.argc() == 5) + ent->client->pers.inventory[index] = atoi(gi.argv(4)); + else if ( (gi.argc() == 4) && !(stricmp(it->pickup_name, "12 Gauge Shells")) ) + ent->client->pers.inventory[index] = atoi(gi.argv(3)); + else */ + ent->client->pers.inventory[index] += it->quantity; + } + else if ( it->flags & IT_ITEM) + { + + etemp.item = it; + if ( ent->client->unique_item_total >= unique_items->value ) + ent->client->unique_item_total = unique_items->value - 1; + + Pickup_Special ( &etemp, ent ); + } + + else + { + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + safe_cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + safe_cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + safe_cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + safe_cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + safe_cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + if (ent->movetype == MOVETYPE_NOCLIP) + { + ent->movetype = MOVETYPE_WALK; + msg = "noclip OFF\n"; + } + else + { + ent->movetype = MOVETYPE_NOCLIP; + msg = "noclip ON\n"; + } + + safe_cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Use_f + +Use an inventory item +================== +*/ +void Cmd_Use_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + + //zucc - check for "special" + if ( stricmp(s, "special") == 0 ) + { + ReadySpecialWeapon( ent ); + return; + } + + //zucc - alias names + if (!stricmp(s, "blaster") || !stricmp(s, "mark 23 pistol")) + s = MK23_NAME; + 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; + // zucc - let people pull up a knife ready to be thrown + if (!stricmp(s, "throwing combat knife")) + { + if ( ent->client->curr_weap != KNIFE_NUM ) + { + ent->client->resp.knife_mode = 1; + } + // switch to throwing mode if a knife is already out + else + { + Cmd_New_Weapon_f( ent ); + } + s = KNIFE_NAME; + } + if (!stricmp(s, "slashing combat knife")) + { + if ( ent->client->curr_weap != KNIFE_NUM ) + { + ent->client->resp.knife_mode = 0; + } + // switch to slashing mode if a knife is already out + else + { + Cmd_New_Weapon_f( ent ); + } + s = KNIFE_NAME; + } + if (!stricmp(s, "grenade launcher")) + s = M4_NAME; + if (!stricmp(s, "grenades")) + s = GRENADE_NAME; + + it = FindItem (s); + +//FIREBLADE + if (!it || (ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD)) +//FIREBLADE + { + safe_cprintf (ent, PRINT_HIGH, "Unknown item: %s\n", s); // fixed capitalization -FB + return; + } + + if (!it->use) + { + safe_cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + safe_cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + + it->use (ent, it); +} + + +/* +================== +Cmd_Drop_f + +Drop an inventory item +================== +*/ +void Cmd_Drop_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + + //zucc check to see if the string is weapon + if ( stricmp(s, "weapon") == 0 ) + { + DropSpecialWeapon ( ent ); + return; + } + + //zucc now for item + if ( stricmp(s, "item") == 0 ) + { + DropSpecialItem ( ent ); + return; + } + + it = FindItem (s); + if (!it) + { + safe_cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->drop) + { + safe_cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + safe_cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + + it->drop (ent, it); +} + + +/* +================= +Cmd_Inven_f +================= +*/ +void Cmd_Inven_f (edict_t *ent) +{ + int i; + gclient_t *cl; + + cl = ent->client; + + cl->showscores = false; + cl->showhelp = false; + +//FIREBLADE + if (ent->client->menu) + { + PMenu_Close(ent); + return; + } +//FIREBLADE + + if (cl->showinventory) + { + cl->showinventory = false; + return; + } + +//FIREBLADE + if (teamplay->value) + { + if (ent->client->resp.team == NOTEAM) + OpenJoinMenu(ent); + else + OpenWeaponMenu(ent); + return; + } +//FIREBLADE + + cl->showinventory = true; + + gi.WriteByte (svc_inventory); + for (i=0 ; ipers.inventory[i]); + } + gi.unicast (ent, true); +} + +/* +================= +Cmd_InvUse_f +================= +*/ +void Cmd_InvUse_f (edict_t *ent) +{ + gitem_t *it; + +//FIREBLADE + if (ent->client->menu) + { + PMenu_Select(ent); + return; + } +//FIREBLADE + + if (ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) + return; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + safe_cprintf (ent, PRINT_HIGH, "No item to use.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->use) + { + safe_cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + it->use (ent, it); +} + +/* +================= +Cmd_WeapPrev_f +================= +*/ +void Cmd_WeapPrev_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + if (ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) + return; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapNext_f +================= +*/ +void Cmd_WeapNext_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + if (ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) + return; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapLast_f +================= +*/ +void Cmd_WeapLast_f (edict_t *ent) +{ + gclient_t *cl; + int index; + gitem_t *it; + + if (ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) + return; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + index = ITEM_INDEX(cl->pers.lastweapon); + if (!cl->pers.inventory[index]) + return; + it = &itemlist[index]; + if (!it->use) + return; + if (! (it->flags & IT_WEAPON) ) + return; + it->use (ent, it); +} + +/* +================= +Cmd_InvDrop_f +================= +*/ +void Cmd_InvDrop_f (edict_t *ent) +{ + gitem_t *it; + + if (ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) + return; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + safe_cprintf (ent, PRINT_HIGH, "No item to drop.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->drop) + { + safe_cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + it->drop (ent, it); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f (edict_t *ent) +{ +//FIREBLADE + if (ent->solid == SOLID_NOT || ent->deadflag == DEAD_DEAD) + return; +//FIREBLADE + + if((level.time - ent->client->respawn_time) < 5) + return; + ent->flags &= ~FL_GODMODE; + ent->health = 0; + meansOfDeath = MOD_SUICIDE; + player_die (ent, ent, ent, 100000, vec3_origin); +// Forget all this... -FB +// // don't even bother waiting for death frames +// ent->deadflag = DEAD_DEAD; +////FIREBLADE +// if (!teamplay->value) +////FIREBLADE +// respawn (ent); +} + +/* +================= +Cmd_PutAway_f +================= +*/ +void Cmd_PutAway_f (edict_t *ent) +{ + ent->client->showscores = false; + ent->client->showhelp = false; + ent->client->showinventory = false; +//FIREBLADE + if (ent->client->menu) + PMenu_Close(ent); +//FIREBLADE +} + + +int PlayerSort (void const *a, void const *b) +{ + int anum, bnum; + + anum = *(int *)a; + bnum = *(int *)b; + + anum = game.clients[anum].ps.stats[STAT_FRAGS]; + bnum = game.clients[bnum].ps.stats[STAT_FRAGS]; + + if (anum < bnum) + return -1; + if (anum > bnum) + return 1; + return 0; +} + +/* +================= +Cmd_Players_f +================= +*/ +void Cmd_Players_f (edict_t *ent) +{ + int i; + int count; + char small[64]; + char large[1280]; + int index[256]; + + count = 0; + for (i = 0 ; i < maxclients->value ; i++) + { + if (game.clients[i].pers.connected) + { + index[count] = i; + count++; + } + } + + if (!teamplay->value || !noscore->value) + { + // sort by frags + qsort (index, count, sizeof(index[0]), PlayerSort); + } + + // print information + large[0] = 0; + + for (i = 0 ; i < count ; i++) + { + if (!teamplay->value || !noscore->value) + Com_sprintf (small, sizeof(small), "%3i %s\n", + game.clients[index[i]].ps.stats[STAT_FRAGS], + game.clients[index[i]].pers.netname); + else + Com_sprintf (small, sizeof(small), "%s\n", + game.clients[index[i]].pers.netname); + + if (strlen (small) + strlen(large) > sizeof(large) - 100 ) + { // can't print all of them in one packet + strcat (large, "...\n"); + break; + } + strcat (large, small); + } + + safe_cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count); +} + +/* +================= +Cmd_Wave_f +================= +*/ +void Cmd_Wave_f (edict_t *ent) +{ + int i; + + i = atoi (gi.argv(1)); + + // can't wave when ducked + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + return; + + if (ent->client->anim_priority > ANIM_WAVE) + return; + + ent->client->anim_priority = ANIM_WAVE; + + switch (i) + { + case 0: + safe_cprintf (ent, PRINT_HIGH, "flipoff\n"); + ent->s.frame = FRAME_flip01-1; + ent->client->anim_end = FRAME_flip12; + break; + case 1: + safe_cprintf (ent, PRINT_HIGH, "salute\n"); + ent->s.frame = FRAME_salute01-1; + ent->client->anim_end = FRAME_salute11; + break; + case 2: + safe_cprintf (ent, PRINT_HIGH, "taunt\n"); + ent->s.frame = FRAME_taunt01-1; + ent->client->anim_end = FRAME_taunt17; + break; + case 3: + safe_cprintf (ent, PRINT_HIGH, "wave\n"); + ent->s.frame = FRAME_wave01-1; + ent->client->anim_end = FRAME_wave11; + break; + case 4: + default: + safe_cprintf (ent, PRINT_HIGH, "point\n"); + ent->s.frame = FRAME_point01-1; + ent->client->anim_end = FRAME_point12; + break; + } +} + +/* +================== +Cmd_Say_f +================== +*/ +void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0, qboolean partner_msg) +{ + int j, i, offset_of_text; + edict_t *other; + char *p; + char text[2048]; + gclient_t *cl; + + if (gi.argc () < 2 && !arg0) + return; + +//FIREBLADE + if (!teamplay->value) + { +//FIREBLADE + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + team = false; + } + + if (team) + { + if (ent->client->resp.team == NOTEAM) + { + safe_cprintf(ent, PRINT_HIGH, "You're not on a team.\n"); + return; + } + Com_sprintf (text, sizeof(text), "%s(%s): ", + (teamplay->value && (ent->solid == SOLID_NOT || ent->deadflag == DEAD_DEAD)) ? "[DEAD] " : "", + ent->client->pers.netname); + } + else if (partner_msg) + { + if (ent->client->resp.radio_partner == NULL) + { + safe_cprintf(ent, PRINT_HIGH, "You don't have a partner.\n"); + return; + } + Com_sprintf (text, sizeof(text), "[%sPARTNER] %s: ", + (teamplay->value && (ent->solid == SOLID_NOT || ent->deadflag == DEAD_DEAD)) ? "DEAD " : "", + ent->client->pers.netname); + } + else + { + Com_sprintf (text, sizeof(text), "%s%s: ", + (teamplay->value && (ent->solid == SOLID_NOT || ent->deadflag == DEAD_DEAD)) ? "[DEAD] " : "", + ent->client->pers.netname); + } + + offset_of_text = strlen(text); //FB 5/31/99 + + if (arg0) + { + strcat (text, gi.argv(0)); + strcat (text, " "); + strcat (text, gi.args()); + } + else + { + p = gi.args(); + + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + strcat(text, p); + } + + // don't let text be too long for malicious reasons + // ...doubled this limit for Axshun -FB + if (strlen(text) > 300) + text[300] = 0; + + if (ent->solid != SOLID_NOT && ent->deadflag != DEAD_DEAD) + ParseSayText(ent, text + offset_of_text); //FB 5/31/99 - offset change + // this will parse the % variables, + // and again check 300 limit afterwards -FB + // (although it checks it without the name in front, oh well) + + strcat(text, "\n"); + + if (flood_msgs->value) + { + cl = ent->client; + + if (level.time < cl->flood_locktill) + { + safe_cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds.\n", + (int)(cl->flood_locktill - level.time)); + return; + } + i = cl->flood_whenhead - flood_msgs->value + 1; + if (i < 0) + i = (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])) + i; + if (cl->flood_when[i] && + level.time - cl->flood_when[i] < flood_persecond->value) + { + cl->flood_locktill = level.time + flood_waitdelay->value; + safe_cprintf(ent, PRINT_HIGH, "You can't talk for %d seconds.\n", + (int)flood_waitdelay->value); + return; + } + cl->flood_whenhead = (cl->flood_whenhead + 1) % + (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])); + cl->flood_when[cl->flood_whenhead] = level.time; + } + + if (dedicated->value) + safe_cprintf(NULL, PRINT_CHAT, "%s", text); + + for (j = 1; j <= game.maxclients; j++) + { + other = &g_edicts[j]; + if (!other->inuse) + continue; + if (!other->client) + continue; + if (team) + { + if (!OnSameTeam(ent, other)) + continue; + } + if (partner_msg) + { + if (other != ent->client->resp.radio_partner && other != ent) + continue; + } +//FIREBLADE + if (teamplay->value && team_round_going) + { + if ((ent->solid == SOLID_NOT || ent->deadflag == DEAD_DEAD) && + (other->solid != SOLID_NOT && other->deadflag != DEAD_DEAD)) + continue; + } +//FIREBLADE + safe_cprintf(other, PRINT_CHAT, "%s", text); + } +} + +void Cmd_PlayerList_f(edict_t *ent) +{ + int i; + char st[80]; + char text[1280]; + edict_t *e2; + + // connect time, ping, score, name + *text = 0; + for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++) { + if (!e2->inuse) + continue; + + if (!teamplay->value || !noscore->value) + Com_sprintf(st, sizeof(st), "%02d:%02d %4d %3d %s%s\n", + (level.framenum - e2->client->resp.enterframe) / 600, + ((level.framenum - e2->client->resp.enterframe) % 600)/10, + e2->client->ping, + e2->client->resp.score, + e2->client->pers.netname, + (e2->solid == SOLID_NOT && e2->deadflag != DEAD_DEAD) ? " (spectator)" : ""); + else + Com_sprintf(st, sizeof(st), "%02d:%02d %4d %s%s\n", + (level.framenum - e2->client->resp.enterframe) / 600, + ((level.framenum - e2->client->resp.enterframe) % 600)/10, + e2->client->ping, + e2->client->pers.netname, + (e2->solid == SOLID_NOT && e2->deadflag != DEAD_DEAD) ? " (spectator)" : ""); + + if (strlen(text) + strlen(st) > sizeof(text) - 100) { + sprintf(text+strlen(text), "...\n"); + safe_cprintf(ent, PRINT_HIGH, "%s", text); + return; + } + strcat(text, st); + } + safe_cprintf(ent, PRINT_HIGH, "%s", text); +} + + +/* +================= +ClientCommand +================= +*/ +void ClientCommand (edict_t *ent) +{ + char *cmd; + + if (!ent->client) + return; // not fully in game yet +// ACEBOT_ADD + if(ACECM_Commands(ent)) + return; +// ACEBOT_END + + + cmd = gi.argv(0); + + if (Q_stricmp (cmd, "players") == 0) + { + Cmd_Players_f (ent); + return; + } + if (Q_stricmp (cmd, "say") == 0) + { + Cmd_Say_f (ent, false, false, false); + return; + } + if (Q_stricmp (cmd, "say_team") == 0) + { + Cmd_Say_f (ent, true, false, false); + return; + } + if (Q_stricmp (cmd, "score") == 0) + { + Cmd_Score_f (ent); + return; + } + if (Q_stricmp (cmd, "help") == 0) + { + Cmd_Help_f (ent); + return; + } + + if (level.intermissiontime) + return; + + if (Q_stricmp (cmd, "use") == 0) + Cmd_Use_f (ent); + else if (Q_stricmp (cmd, "drop") == 0) + Cmd_Drop_f (ent); + else if (Q_stricmp (cmd, "give") == 0) + Cmd_Give_f (ent); + else if (Q_stricmp (cmd, "god") == 0) + Cmd_God_f (ent); + else if (Q_stricmp (cmd, "notarget") == 0) + Cmd_Notarget_f (ent); + else if (Q_stricmp (cmd, "noclip") == 0) + Cmd_Noclip_f (ent); + else if (Q_stricmp (cmd, "inven") == 0) + Cmd_Inven_f (ent); + else if (Q_stricmp (cmd, "invnext") == 0) + SelectNextItem (ent, -1); + else if (Q_stricmp (cmd, "invprev") == 0) + SelectPrevItem (ent, -1); + else if (Q_stricmp (cmd, "invnextw") == 0) + SelectNextItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invprevw") == 0) + SelectPrevItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invnextp") == 0) + SelectNextItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invprevp") == 0) + SelectPrevItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invuse") == 0) + Cmd_InvUse_f (ent); + else if (Q_stricmp (cmd, "invdrop") == 0) + Cmd_InvDrop_f (ent); + else if (Q_stricmp (cmd, "weapprev") == 0) + Cmd_WeapPrev_f (ent); + else if (Q_stricmp (cmd, "weapnext") == 0) + Cmd_WeapNext_f (ent); + else if (Q_stricmp (cmd, "weaplast") == 0) + Cmd_WeapLast_f (ent); + else if (Q_stricmp (cmd, "kill") == 0) + Cmd_Kill_f (ent); + else if (Q_stricmp (cmd, "putaway") == 0) + Cmd_PutAway_f (ent); + else if (Q_stricmp (cmd, "wave") == 0) + Cmd_Wave_f (ent); +//zucc +// else if (Q_stricmp (cmd, "laser") == 0) +// SP_LaserSight (ent); + else if (Q_stricmp (cmd, "reload") == 0) + Cmd_New_Reload_f (ent); + else if (Q_stricmp (cmd, "weapon") == 0) + Cmd_New_Weapon_f (ent); + else if (Q_stricmp (cmd, "opendoor") == 0) + Cmd_OpenDoor_f (ent); + else if (Q_stricmp (cmd, "bandage") == 0) + Cmd_Bandage_f (ent); + else if (Q_stricmp (cmd, "id") == 0) + Cmd_ID_f (ent ); + else if (Q_stricmp (cmd, "irvision") == 0) + Cmd_IR_f (ent ); + else if (Q_stricmp(cmd, "playerlist") == 0) + Cmd_PlayerList_f(ent); +//FIREBLADE + else if (Q_stricmp(cmd, "team") == 0 && teamplay->value) + Team_f(ent); + else if (Q_stricmp(cmd, "radio") == 0) + Cmd_Radio_f(ent); + else if (Q_stricmp(cmd, "radiogender") == 0) + Cmd_Radiogender_f(ent); + else if (Q_stricmp(cmd, "radio_power") == 0) + Cmd_Radio_power_f(ent); + else if (Q_stricmp(cmd, "radiopartner") == 0) + Cmd_Radiopartner_f(ent); + else if (Q_stricmp(cmd, "radioteam") == 0) + Cmd_Radioteam_f(ent); + else if (Q_stricmp(cmd, "channel") == 0) + Cmd_Channel_f(ent); + else if (Q_stricmp(cmd, "say_partner") == 0) + Cmd_Say_partner_f(ent); + else if (Q_stricmp(cmd, "partner") == 0) + Cmd_Partner_f(ent); + else if (Q_stricmp(cmd, "unpartner") == 0) + Cmd_Unpartner_f(ent); + else if (Q_stricmp(cmd, "motd") == 0) + PrintMOTD(ent); + else if (Q_stricmp(cmd, "deny") == 0) + Cmd_Deny_f(ent); + else if (Q_stricmp(cmd, "choose") == 0) + Cmd_Choose_f(ent); +//FIREBLADE + else // anything that doesn't match a command will be a chat + Cmd_Say_f (ent, false, true, false); +} + + diff --git a/g_combat.c b/g_combat.c new file mode 100644 index 0000000..a10aa34 --- /dev/null +++ b/g_combat.c @@ -0,0 +1,1116 @@ +// g_combat.c + +#include "g_local.h" +#include "cgf_sfx_glass.h" + +void Add_TeamWound( edict_t *attacker, edict_t *victim, int mod); + +/* +============ +CanDamage + + Returns true if the inflictor can directly damage the target. Used for + explosions and melee attacks. + ============ +*/ +qboolean CanDamage (edict_t *targ, edict_t *inflictor) +{ + vec3_t dest; + trace_t trace; + + // bmodels need special checking because their origin is 0,0,0 +//GLASS FX + if ((targ->movetype == MOVETYPE_PUSH) + || + ((targ->movetype == MOVETYPE_FLYMISSILE) + && + (0 == Q_stricmp("func_explosive", targ->classname)) + ) + ) +//GLASS FX + { + VectorAdd (targ->absmin, targ->absmax, dest); + VectorScale (dest, 0.5, dest); + PRETRACE(); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + POSTTRACE(); + if (trace.fraction == 1.0) + return true; + if (trace.ent == targ) + return true; + return false; + } + + PRETRACE(); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID); + POSTTRACE(); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] += 15.0; + PRETRACE(); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + POSTTRACE(); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] -= 15.0; + PRETRACE(); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + POSTTRACE(); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] += 15.0; + PRETRACE(); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + POSTTRACE(); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] -= 15.0; + PRETRACE(); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + POSTTRACE(); + if (trace.fraction == 1.0) + return true; + + + return false; +} + + +/* +============ +Killed +============ +*/ +void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + if (targ->health < -999) + targ->health = -999; + + if ( targ->client ) + { + targ->client->bleeding = 0; + //targ->client->bleedcount = 0; + targ->client->bleed_remain = 0; + } + + targ->enemy = attacker; + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { + // targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type + if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) + { + level.killed_monsters++; + if (coop->value && attacker->client) + attacker->client->resp.score++; + // medics won't heal monsters that they kill themselves + if (strcmp(attacker->classname, "monster_medic") == 0) + targ->owner = attacker; + } + } + + if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE) + { // doors, triggers, etc + targ->die (targ, inflictor, attacker, damage, point); + return; + } + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { + targ->touch = NULL; + monster_death_use (targ); + } + + targ->die (targ, inflictor, attacker, damage, point); +} + + +/* +================ +SpawnDamage +================ +*/ +void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage) +{ + if (damage > 255) + damage = 255; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (type); + // gi.WriteByte (damage); + gi.WritePosition (origin); + gi.WriteDir (normal); + gi.multicast (origin, MULTICAST_PVS); +} + + +/* +============ +T_Damage + + targ entity that is being damaged + inflictor entity that is causing the damage + attacker entity that caused the inflictor to damage targ + example: targ=monster, inflictor=rocket, attacker=player + + dir direction of the attack + point point at which the damage is being inflicted + normal normal vector from that point + damage amount of damage being inflicted + knockback force to be applied against targ as a result of the damage + + dflags these flags are used to control how T_Damage works + DAMAGE_RADIUS damage was indirect (from a nearby explosion) + DAMAGE_NO_ARMOR armor does not protect from this damage + DAMAGE_ENERGY damage is from an energy based weapon + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_BULLET damage is from a bullet (used for ricochets) + DAMAGE_NO_PROTECTION kills godmode, armor, everything + ============ +*/ +static int CheckPowerArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags) +{ + gclient_t *client; + int save; + int power_armor_type; + int index; + int damagePerCell; + int pa_te_type; + int power; + int power_used; + + if (!damage) + return 0; + + client = ent->client; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + if (client) + { + power_armor_type = PowerArmorType (ent); + if (power_armor_type != POWER_ARMOR_NONE) + { + index = ITEM_INDEX(FindItem("Cells")); + power = client->pers.inventory[index]; + } + } + else if (ent->svflags & SVF_MONSTER) + { + power_armor_type = ent->monsterinfo.power_armor_type; + power = ent->monsterinfo.power_armor_power; + } + else + return 0; + + if (power_armor_type == POWER_ARMOR_NONE) + return 0; + if (!power) + return 0; + + if (power_armor_type == POWER_ARMOR_SCREEN) + { + vec3_t vec; + float dot; + vec3_t forward; + + // only works if damage point is in front + AngleVectors (ent->s.angles, forward, NULL, NULL); + VectorSubtract (point, ent->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + if (dot <= 0.3) + return 0; + + damagePerCell = 1; + pa_te_type = TE_SCREEN_SPARKS; + damage = damage / 3; + } + else + { + damagePerCell = 2; + pa_te_type = TE_SHIELD_SPARKS; + damage = (2 * damage) / 3; + } + + save = power * damagePerCell; + if (!save) + return 0; + if (save > damage) + save = damage; + + SpawnDamage (pa_te_type, point, normal, save); + ent->powerarmor_time = level.time + 0.2; + + power_used = save / damagePerCell; + + if (client) + client->pers.inventory[index] -= power_used; + else + ent->monsterinfo.power_armor_power -= power_used; + return save; +} + +static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags) +{ + gclient_t *client; + int save; + int index; + gitem_t *armor; + + if (!damage) + return 0; + + client = ent->client; + + if (!client) + return 0; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + index = ArmorIndex (ent); + if (!index) + return 0; + + armor = GetItemByIndex (index); + + if (dflags & DAMAGE_ENERGY) + save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage); + else + save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage); + if (save >= client->pers.inventory[index]) + save = client->pers.inventory[index]; + + if (!save) + return 0; + + client->pers.inventory[index] -= save; + SpawnDamage (te_sparks, point, normal, save); + + return save; +} + +void M_ReactToDamage (edict_t *targ, edict_t *attacker) +{ + if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER)) + return; + + if (attacker == targ || attacker == targ->enemy) + return; + + // if we are a good guy monster and our attacker is a player + // or another good guy, do not get mad at them + if (targ->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + } + + // we now know that we are not both good guys + + // if attacker is a client, get mad at them because he's good and we're not + if (attacker->client) + { + // this can only happen in coop (both new and old enemies are clients) + // only switch if can't see the current enemy + if (targ->enemy && targ->enemy->client) + { + if (visible(targ, targ->enemy)) + { + targ->oldenemy = attacker; + return; + } + targ->oldenemy = targ->enemy; + } + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + return; + } + + // it's the same base (walk/swim/fly) type and a different classname and it's not a tank + // (they spray too much), get mad at them + if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) && + (strcmp (targ->classname, attacker->classname) != 0) && + (strcmp(attacker->classname, "monster_tank") != 0) && + (strcmp(attacker->classname, "monster_supertank") != 0) && + (strcmp(attacker->classname, "monster_makron") != 0) && + (strcmp(attacker->classname, "monster_jorg") != 0) ) + { + if (targ->enemy) + if (targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + else + // otherwise get mad at whoever they are mad at (help our buddy) + { + if (targ->enemy) + if (targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker->enemy; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } +} + +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker) +{ + //FIXME make the next line real and uncomment this block + // if ((ability to damage a teammate == OFF) && (targ's team == attacker's team)) + return false; +} + + + +void BloodSprayThink (edict_t *self) +{ + + +/* if ( self->dmg > 0 ) +{ +self->dmg -= 10; +// SpawnDamage (TE_BLOOD, self->s.origin, self->movedir, self->dmg); +gi.WriteByte (svc_temp_entity); +gi.WriteByte (TE_SPLASH); +gi.WriteByte (6); +gi.WritePosition (self->s.origin); +gi.WriteDir (self->movedir); +gi.WriteByte (6); //blood +gi.multicast (self->s.origin, MULTICAST_PVS); + + } + else + { + self->think = G_FreeEdict; + } + + self->nextthink = level.time + 0.1; + gi.linkentity (self); + */ + + G_FreeEdict(self); + +} + +void blood_spray_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 0.1; +} + + + +void spray_blood (edict_t *self, vec3_t start, vec3_t dir, int damage, int mod ) +{ + edict_t *blood; + int speed; + + switch(mod) + { + case MOD_MK23: + speed = 1800; + break; + case MOD_MP5: + speed = 1500; + break; + case MOD_M4: + speed = 2400; + break; + case MOD_KNIFE: + speed = 0; + break; + case MOD_KNIFE_THROWN: + speed = 0; + break; + case MOD_DUAL: + speed = 1800; + break; + case MOD_SNIPER: + speed = 4000; + break; + default: + speed = 1800; + } + + + + + blood = G_Spawn(); + VectorNormalize(dir); + VectorCopy (start, blood->s.origin); + VectorCopy (dir, blood->movedir); + vectoangles (dir, blood->s.angles); + VectorScale (dir, speed, blood->velocity); + blood->movetype = MOVETYPE_BLOOD; + blood->clipmask = MASK_SHOT; + blood->solid = SOLID_BBOX; + blood->s.effects |= EF_GIB; + VectorClear (blood->mins); + VectorClear (blood->maxs); + blood->s.modelindex = gi.modelindex ("sprites/null.sp2"); + blood->owner = self; + blood->nextthink = level.time + speed/1000;//3.2; + blood->touch = blood_spray_touch; + blood->think = BloodSprayThink; + blood->dmg = damage; + blood->classname = "blood_spray"; + + gi.linkentity (blood); +} + + +// zucc based on some code in Action Quake +void spray_sniper_blood( edict_t *self, vec3_t start, vec3_t dir ) +{ + vec3_t forward; + int mod = MOD_SNIPER; + + VectorCopy (dir, forward); + + forward[2] += .03; + + spray_blood(self, start, forward, 0, mod); + + + VectorCopy (dir, forward); + forward[2] -= .03; + spray_blood(self, start, forward, 0, mod); + + + VectorCopy (dir, forward); + if ( (forward[0] > 0) && (forward[1] > 0) ) + { + forward[0] -= .03; + forward[1] += .03; + } + if ( (forward[0] > 0) && (forward[1] < 0) ) + { + forward[0] += .03; + forward[1] += .03; + } + if ( (forward[0] < 0) && (forward[1] > 0) ) + { + forward[0] -= .03; + forward[1] -= .03; + } + if ( (forward[0] < 0) && (forward[1] < 0) ) + { + forward[0] += .03; + forward[1] -= .03; + } + spray_blood(self, start, forward, 0, mod); + + + VectorCopy (dir, forward); + if ( (forward[0] > 0) && (forward[1] > 0) ) + { + forward[0] += .03; + forward[1] -= .03; + } + if ( (forward[0] > 0) && (forward[1] < 0) ) + { + forward[0] -= .03; + forward[1] -= .03; + } + if ( (forward[0] < 0) && (forward[1] > 0) ) + { + forward[0] += .03; + forward[1] += .03; + } + if ( (forward[0] < 0) && (forward[1] < 0) ) + { + forward[0] -= .03; + forward[1] += .03; + } + spray_blood(self, start, forward, 0, mod); + + VectorCopy (dir, forward); + spray_blood(self, start, forward, 0, mod); + +} + + +void VerifyHeadShot( vec3_t point, vec3_t dir, float height, vec3_t newpoint) +{ + vec3_t normdir; + vec3_t normdir2; + + + VectorNormalize2(dir, normdir); + VectorScale( normdir, height, normdir2 ); + VectorAdd( point, normdir2, newpoint ); +} + + + +// zucc adding location hit code +// location hit code based off ideas by pyromage and shockman + +#define LEG_DAMAGE (height/2.2) - abs(targ->mins[2]) - 3 +#define STOMACH_DAMAGE (height/1.8) - abs(targ->mins[2]) +#define CHEST_DAMAGE (height/1.4) - abs(targ->mins[2]) + +#define HEAD_HEIGHT 12.0 +qboolean IsFemale (edict_t *ent); + + +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod) +{ + gclient_t *client; + int take; + int save; + int asave; + int psave; + int te_sparks; + int do_sparks = 0; + int damage_type = 0; // used for MOD later + int bleeding = 0; // damage causes bleeding + int head_success = 0; + int instant_dam = 1; + + float z_rel; + int height; + + gitem_t* item; + + float from_top; + + vec3_t line; + vec_t dist; + float targ_maxs2; //FB 6/1/99 + + // do this before teamplay check + if (!targ->takedamage) + return; + + //FIREBLADE + if (teamplay->value && mod != MOD_TELEFRAG) + { + if (lights_camera_action) + return; + + if (targ != attacker && targ->client && attacker->client && + (targ->client->resp.team == attacker->client->resp.team && + ((int)(dmflags->value) & (DF_NO_FRIENDLY_FIRE)))) + return; + } + //FIREBLADE + + + // damage reduction for shotgun + // if far away, reduce it to original action levels + if ( mod == MOD_M3 ) + { + VectorSubtract(targ->s.origin, inflictor->s.origin, line ); + dist = VectorLength( line ); + if ( dist > 450.0 ) + { + damage = damage - 2; + } + + } + + item = FindItem(KEV_NAME); + + targ_maxs2 = targ->maxs[2]; + if (targ_maxs2 == 4) + targ_maxs2 = CROUCHING_MAXS2; //FB 6/1/99 + + height = abs(targ->mins[2]) + targ_maxs2; + + // locational damage code + // base damage is head shot damage, so all the scaling is downwards + if (targ->client) + { + if (!((targ != attacker) && + ((deathmatch->value && ((int)(dmflags->value) + & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value) && + (attacker && attacker->client && + OnSameTeam (targ, attacker) && + ((int)(dmflags->value) & (DF_NO_FRIENDLY_FIRE))))) + { + + if ((mod == MOD_MK23) || + (mod == MOD_MP5) || + (mod == MOD_M4) || + (mod == MOD_SNIPER) || + (mod == MOD_DUAL) || + (mod == MOD_KNIFE) || + (mod == MOD_KNIFE_THROWN)) + { + z_rel = point[2] - targ->s.origin[2]; + from_top = targ_maxs2 - z_rel; + if (from_top < 0.0) //FB 6/1/99 + from_top = 0.0; //Slightly negative values were being handled wrong + bleeding = 1; + instant_dam = 0; + + // damage reduction for longer range pistol shots + if ( mod == MOD_MK23 || mod == MOD_DUAL ) + { + VectorSubtract(targ->s.origin, inflictor->s.origin, line ); + dist = VectorLength( line ); + if ( dist > 600.0 && dist < 1400.0 ) + { + damage = (int)(damage*2/3); + } + else if ( dist > 1400.0 ) + damage = (int)(damage*1/2); + } + + + //safe_cprintf(targ, PRINT_HIGH, "z_rel is %f\n leg: %f stomach: %f chest: %f\n", z_rel, LEG_DAMAGE, STOMACH_DAMAGE, CHEST_DAMAGE ); + //safe_cprintf(targ, PRINT_HIGH, "point[2]: %f targ->s.origin[2]: %f height: %d\n", point[2], targ->s.origin[2], height ); + //safe_cprintf(targ, PRINT_HIGH, "abs(trag->min[2]): %d targ_max[2] %d\n", (int)abs(targ->mins[2]), (int)targ_maxs2); + //safe_cprintf(attacker, PRINT_HIGH, "abs(trag->min[2]): %d targ_max[2] %d\n", (int)abs(targ->mins[2]), (int)targ_maxs2); + //safe_cprintf(attacker, PRINT_HIGH, "abs(trag->min[0]): %d targ_max[0] %d\n", (int)abs(targ->mins[0]), (int)targ->maxs[0]); + //safe_cprintf(attacker, PRINT_HIGH, "abs(trag->min[1]): %d targ_max[1] %d\n", (int)abs(targ->mins[1]), (int)targ->maxs[1]); + + + if ( from_top < 2*HEAD_HEIGHT ) + { + vec3_t new_point; + VerifyHeadShot( point, dir, HEAD_HEIGHT, new_point ); + VectorSubtract( new_point, targ->s.origin, new_point ); + //safe_cprintf(attacker, PRINT_HIGH, "z: %d y: %d x: %d\n", (int)(targ_maxs2 - new_point[2]),(int)(new_point[1]) , (int)(new_point[0]) ); + + if ( (targ_maxs2 - new_point[2]) < HEAD_HEIGHT + && (abs(new_point[1])) < HEAD_HEIGHT*.8 + && (abs(new_point[0])) < HEAD_HEIGHT*.8 ) + + { + head_success = 1; + } + } + + if ( head_success ) + { + + damage = damage*1.8 + 1; + safe_cprintf(targ, PRINT_HIGH, "Head damage\n"); + if (attacker->client) + safe_cprintf(attacker, PRINT_HIGH, "You hit %s in the head\n", targ->client->pers.netname); + damage_type = LOC_HDAM; + if ( mod != MOD_KNIFE && mod != MOD_KNIFE_THROWN ) + gi.sound(targ, CHAN_VOICE, gi.soundindex("misc/headshot.wav"), 1, ATTN_NORM, 0); + //else + // gi.sound(targ, CHAN_VOICE, gi.soundindex("misc/glurp.wav"), 1, ATTN_NORM, 0); + } + + else if (z_rel < LEG_DAMAGE) + { + damage = damage * .25; + safe_cprintf(targ, PRINT_HIGH, "Leg damage\n"); + if (attacker->client) + safe_cprintf(attacker, PRINT_HIGH, "You hit %s in the legs\n", targ->client->pers.netname); + damage_type = LOC_LDAM; + targ->client->leg_damage = 1; + targ->client->leghits++; + } + else if (z_rel < STOMACH_DAMAGE) + { + damage = damage * .4; + safe_cprintf(targ, PRINT_HIGH, "Stomach damage\n"); + if (attacker->client) + safe_cprintf(attacker, PRINT_HIGH, "You hit %s in the stomach\n", targ->client->pers.netname); + damage_type = LOC_SDAM; + } + else //(z_rel < CHEST_DAMAGE) + { + if ( (targ->client->pers.inventory[ITEM_INDEX(item)]) + && mod != MOD_KNIFE + && mod != MOD_KNIFE_THROWN + && mod != MOD_SNIPER ) + { + if (attacker->client) + { + safe_cprintf(attacker, PRINT_HIGH, "%s has a Kevlar Vest - AIM FOR THE HEAD!\n", + targ->client->pers.netname); + safe_cprintf(targ, PRINT_HIGH, "Kevlar Vest absorbed most of %s's shot\n", + attacker->client->pers.netname); + /* + if (IsFemale(targ)) + safe_cprintf(attacker, PRINT_HIGH, "You bruised %s through her Kevlar Vest\n", targ->client->pers.netname); + else + safe_cprintf(attacker, PRINT_HIGH, "You bruised %s through his Kevlar Vest\n", targ->client->pers.netname); + */ + } + gi.sound(targ, CHAN_ITEM, gi.soundindex("misc/vest.wav"), 1, ATTN_NORM, 0); + damage = (int)(damage/10); + damage_type = LOC_CDAM; + bleeding = 0; + instant_dam = 1; + stopAP = 1; do_sparks = 1; + } + else if ( (targ->client->pers.inventory[ITEM_INDEX(item)]) + && mod == MOD_SNIPER ) + { + if ( attacker->client ) + { + safe_cprintf(attacker, PRINT_HIGH, "%s has a Kevlar Vest, too bad you have AP rounds...\n", + targ->client->pers.netname); + safe_cprintf(targ, PRINT_HIGH, "Kevlar Vest absorbed some of %s's AP sniper round\n", + attacker->client->pers.netname); + } + damage = damage * .325; + damage_type = LOC_CDAM; + } + else + { + damage = damage * .65; + safe_cprintf(targ, PRINT_HIGH, "Chest damage\n"); + if (attacker->client) + safe_cprintf(attacker, PRINT_HIGH, "You hit %s in the chest\n", targ->client->pers.netname); + damage_type = LOC_CDAM; + } + + } + /*else + { + + // no mod to damage + safe_cprintf(targ, PRINT_HIGH, "Head damage\n"); + if (attacker->client) + safe_cprintf(attacker, PRINT_HIGH, "You hit %s in the head\n", targ->client->pers.netname); + damage_type = LOC_HDAM; + gi.sound(targ, CHAN_VOICE, gi.soundindex("misc/headshot.wav"), 1, ATTN_NORM, 0); + } */ + } + if (team_round_going && attacker->client && targ != attacker && OnSameTeam(targ, attacker)) + { + Add_TeamWound(attacker, targ, mod); + } + } + } + + + if ( damage_type && !instant_dam) // bullets but not vest hits + { + vec3_t temp; + vec3_t temporig; + //vec3_t forward; + VectorMA (targ->s.origin, 50, dir, temp); + //AngleVectors (attacker->client->v_angle, forward, NULL, NULL); + VectorScale( dir, 20, temp); + VectorAdd( point, temp, temporig ); + if ( mod != MOD_SNIPER ) + spray_blood (targ, temporig, dir, damage, mod ); + else + { + spray_sniper_blood( targ, temporig, dir ); + } + } + + if ( mod == MOD_FALLING && !(targ->flags & FL_GODMODE)) + { + if ( targ->client && targ->health > 0) + { + safe_cprintf(targ, PRINT_HIGH, "Leg damage\n"); + targ->client->leg_damage = 1; + targ->client->leghits++; + // bleeding = 1; for testing + } + } + + + // friendly fire avoidance + // if enabled you can't hurt teammates (but you can hurt yourself) + // knockback still occurs + if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value)) + { + if (OnSameTeam (targ, attacker)) + { + if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) + damage = 0; + else + mod |= MOD_FRIENDLY_FIRE; + } + } + + meansOfDeath = mod; + locOfDeath = damage_type; // location + + client = targ->client; + + if (dflags & DAMAGE_BULLET) + te_sparks = TE_BULLET_SPARKS; + else + te_sparks = TE_SPARKS; + + VectorNormalize(dir); + + // bonus damage for suprising a monster + // if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0)) + // damage *= 2; + + if (targ->flags & FL_NO_KNOCKBACK) + knockback = 0; + + // figure momentum add + if (!(dflags & DAMAGE_NO_KNOCKBACK)) + { + if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) + { + vec3_t kvel, flydir; + float mass; + + if ( mod != MOD_FALLING ) + { + VectorCopy(dir, flydir); + flydir[2] += 0.4; + } + + if (targ->mass < 50) + mass = 50; + else + mass = targ->mass; + + if (targ->client && attacker == targ) + VectorScale (flydir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack... + else + VectorScale (flydir, 500.0 * (float)knockback / mass, kvel); + + // FB + //if (mod == MOD_KICK ) + //{ + // kvel[2] = 0; + //} + + VectorAdd (targ->velocity, kvel, targ->velocity); + } + } + + take = damage; + save = 0; + + // check for godmode + if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) ) + { + take = 0; + save = damage; + SpawnDamage (te_sparks, point, normal, save); + } + + // zucc don't need this stuff, but to remove it need to change how damagefeedback works with colors + + // check for invincibility + if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION)) + { + if (targ->pain_debounce_time < level.time) + { + gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0); + targ->pain_debounce_time = level.time + 2; + } + take = 0; + save = damage; + } + + psave = CheckPowerArmor (targ, point, normal, take, dflags); + take -= psave; + + asave = CheckArmor (targ, point, normal, take, te_sparks, dflags); + take -= asave; + + //treat cheat/powerup savings the same as armor + asave += save; + + // team damage avoidance + if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker)) + return; + if ( (mod == MOD_M3) + || (mod == MOD_HC) + || (mod == MOD_HELD_GRENADE) + || (mod == MOD_HG_SPLASH) + || (mod == MOD_G_SPLASH) + || (mod == MOD_BREAKINGGLASS) + ) + { +//FB 6/3/99 - shotgun damage report stuff + int playernum = targ - g_edicts; + playernum--; + if (playernum >= 0 && + playernum <= game.maxclients - 1) + *(took_damage + playernum) = 1; +//FB 6/3/99 + + bleeding = 1; + instant_dam = 0; + } + + /* if ( (mod == MOD_M3) || (mod == MOD_HC) ) + { + instant_dam = 1; + remain = take % 2; + take = (int)(take/2); // balances out difference in how action and axshun handle damage/bleeding + + } + */ + // do the damage + if (take) + { + // zucc added check for stopAP, if it hit a vest we want sparks + if (((targ->svflags & SVF_MONSTER) || (client)) && !do_sparks ) + SpawnDamage (TE_BLOOD, point, normal, take); + else + SpawnDamage (te_sparks, point, normal, take); + + // all things that have at least some instantaneous damage, i.e. bruising/falling + if ( instant_dam ) + targ->health = targ->health - take; + + if (targ->health <= 0) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + targ->flags |= FL_NO_KNOCKBACK; + Killed (targ, inflictor, attacker, take, point); + return; + } + } + + if (targ->svflags & SVF_MONSTER) + { + M_ReactToDamage (targ, attacker); + if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) + { + targ->pain (targ, attacker, knockback, take); + // nightmare mode monsters don't go into pain frames often + if (skill->value == 3) + targ->pain_debounce_time = level.time + 5; + } + } + else if (client) + { + if (!(targ->flags & FL_GODMODE) && (take)) + targ->pain (targ, attacker, knockback, take); + } + else if (take) + { + if (targ->pain) + targ->pain (targ, attacker, knockback, take); + } + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if (client) + { + client->damage_parmor += psave; + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + //zucc handle adding bleeding here + if ( damage_type && bleeding ) // one of the hit location weapons + { + /* zucc add in partial bleeding, changed + if ( client->bleeding < 4*damage*BLEED_TIME ) + { + client->bleeding = 4*damage*BLEED_TIME + client->bleeding/2; + + } + else + { + client->bleeding += damage*BLEED_TIME*2; + + }*/ + client->bleeding += damage*BLEED_TIME; + VectorSubtract (point, targ->absmax, targ->client->bleedloc_offset); + //VectorSubtract(point, targ->s.origin, client->bleedloc_offset); + + } + else if ( bleeding ) + { + /* + if ( client->bleeding < damage*BLEED_TIME ) + { + client->bleeding = damage*BLEED_TIME; + //client->bleedcount = 0; + }*/ + client->bleeding += damage*BLEED_TIME; + VectorSubtract (point, targ->absmax, targ->client->bleedloc_offset); + //VectorSubtract(point, targ->s.origin, client->bleedloc_offset); + + } + if ( attacker->client ) + { + attacker->client->resp.damage_dealt += damage; + client->attacker = attacker; + client->attacker_mod = mod; + client->attacker_loc = damage_type; + client->push_timeout = 50; + //VectorCopy(dir, client->bleeddir ); + //VectorCopy(point, client->bleedpoint ); + //VectorCopy(normal, client->bleednormal); + + } + + VectorCopy (point, client->damage_from); + } +} + + +/* +============ +T_RadiusDamage +============ +*/ +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod) +{ + float points; + edict_t *ent = NULL; + vec3_t v; + vec3_t dir; + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) + { + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = damage - 0.5 * VectorLength (v); + //zucc reduce damage for crouching, max is 32 when standing + if (ent->maxs[2] < 20 ) + { + points = points * 0.5; // hefty reduction in damage + } + //if (ent == attacker) + //points = points * 0.5; + if (points > 0) + { + #ifdef _DEBUG + if (0 == Q_stricmp(ent->classname, "func_explosive")) + { + CGF_SFX_ShootBreakableGlass(ent, inflictor, 0, mod); + } + else + #endif + if (CanDamage (ent, inflictor)) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); + // zucc scaled up knockback(kick) of grenades + T_Damage (ent, inflictor, attacker, dir, ent->s.origin, vec3_origin, (int)(points*.75), (int)(points*.75), DAMAGE_RADIUS, mod); + } + } + } +} diff --git a/g_func.c b/g_func.c new file mode 100644 index 0000000..8d86af3 --- /dev/null +++ b/g_func.c @@ -0,0 +1,2119 @@ +#include "g_local.h" + +/* +========================================================= + + PLATS + + movement options: + + linear + smooth start, hard stop + smooth start, smooth stop + + start + end + acceleration + speed + deceleration + begin sound + end sound + target fired when reaching end + wait at end + + object characteristics that use move segments + --------------------------------------------- + movetype_push, or movetype_stop + action when touched + action when blocked + action when used + disabled? + auto trigger spawning + + +========================================================= +*/ + +#define PLAT_LOW_TRIGGER 1 + +#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 +#define DOOR_CRUSHER 4 +#define DOOR_NOMONSTER 8 +#define DOOR_TOGGLE 32 +#define DOOR_X_AXIS 64 +#define DOOR_Y_AXIS 128 + + +// zucc function to deal with special items that might get destroyed +void Handle_Unique_Items( edict_t *ent ) +{ + if ( !ent->item ) + return; + + if ( stricmp(ent->item->pickup_name, MP5_NAME) == 0 ) + ThinkSpecWeap(ent); + else if ( stricmp(ent->item->pickup_name, M4_NAME) == 0 ) + ThinkSpecWeap(ent); + else if ( stricmp(ent->item->pickup_name, M3_NAME) == 0 ) + ThinkSpecWeap(ent); + else if ( stricmp(ent->item->pickup_name, HC_NAME) == 0 ) + ThinkSpecWeap(ent); + else if ( stricmp(ent->item->pickup_name, SNIPER_NAME) == 0 ) + ThinkSpecWeap(ent); + else if ( stricmp(ent->item->pickup_name, SIL_NAME) == 0 ) + RespawnSpec(ent); + else if ( stricmp(ent->item->pickup_name, SLIP_NAME) == 0 ) + RespawnSpec(ent); + else if ( stricmp(ent->item->pickup_name, BAND_NAME) == 0 ) + RespawnSpec(ent); + else if ( stricmp(ent->item->pickup_name, KEV_NAME) == 0 ) + RespawnSpec(ent); + else if ( stricmp(ent->item->pickup_name, LASER_NAME) == 0 ) + RespawnSpec(ent); +} + + + + + + + + +// +// Support routines for movement (changes in origin using velocity) +// + +void Move_Done (edict_t *ent) +{ + VectorClear (ent->velocity); + ent->moveinfo.endfunc (ent); +} + +void Move_Final (edict_t *ent) +{ + if (ent->moveinfo.remaining_distance == 0) + { + Move_Done (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity); + + ent->think = Move_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void Move_Begin (edict_t *ent) +{ + float frames; + + if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance) + { + Move_Final (ent); + return; + } + VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity); + frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME); + ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME; + ent->nextthink = level.time + (frames * FRAMETIME); + ent->think = Move_Final; +} + +void Think_AccelMove (edict_t *ent); + +void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*)) +{ + VectorClear (ent->velocity); + VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir); + ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir); + ent->moveinfo.endfunc = func; + + if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel) + { + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + Move_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = Move_Begin; + } + } + else + { + // accelerative + ent->moveinfo.current_speed = 0; + ent->think = Think_AccelMove; + ent->nextthink = level.time + FRAMETIME; + } +} + + +// +// Support routines for angular movement (changes in angle using avelocity) +// + +void AngleMove_Done (edict_t *ent) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc (ent); +} + +void AngleMove_Final (edict_t *ent) +{ + vec3_t move; + + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move); + + if (VectorCompare (move, vec3_origin)) + { + AngleMove_Done (ent); + return; + } + + VectorScale (move, 1.0/FRAMETIME, ent->avelocity); + + ent->think = AngleMove_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void AngleMove_Begin (edict_t *ent) +{ + vec3_t destdelta; + float len; + float traveltime; + float frames; + + // set destdelta to the vector needed to move + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta); + + // calculate length of vector + len = VectorLength (destdelta); + + // divide by speed to get time to reach dest + traveltime = len / ent->moveinfo.speed; + + if (traveltime < FRAMETIME) + { + AngleMove_Final (ent); + return; + } + + frames = floor(traveltime / FRAMETIME); + + // scale the destdelta vector by the time spent traveling to get velocity + VectorScale (destdelta, 1.0 / traveltime, ent->avelocity); + + // set nextthink to trigger a think when dest is reached + ent->nextthink = level.time + frames * FRAMETIME; + ent->think = AngleMove_Final; +} + +void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*)) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc = func; + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + AngleMove_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = AngleMove_Begin; + } +} + + +/* +============== +Think_AccelMove + +The team has completed a frame of movement, so +change the speed for the next frame +============== +*/ +#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) + +void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) +{ + float accel_dist; + float decel_dist; + + moveinfo->move_speed = moveinfo->speed; + + if (moveinfo->remaining_distance < moveinfo->accel) + { + moveinfo->current_speed = moveinfo->remaining_distance; + return; + } + + accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel); + decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel); + + if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0) + { + float f; + + f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); + moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); + decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel); + } + + moveinfo->decel_distance = decel_dist; +}; + +void plat_Accelerate (moveinfo_t *moveinfo) +{ + // are we decelerating? + if (moveinfo->remaining_distance <= moveinfo->decel_distance) + { + if (moveinfo->remaining_distance < moveinfo->decel_distance) + { + if (moveinfo->next_speed) + { + moveinfo->current_speed = moveinfo->next_speed; + moveinfo->next_speed = 0; + return; + } + if (moveinfo->current_speed > moveinfo->decel) + moveinfo->current_speed -= moveinfo->decel; + } + return; + } + + // are we at full speed and need to start decelerating during this move? + if (moveinfo->current_speed == moveinfo->move_speed) + if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance) + { + float p1_distance; + float p2_distance; + float distance; + + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = moveinfo->move_speed; + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // are we accelerating? + if (moveinfo->current_speed < moveinfo->speed) + { + float old_speed; + float p1_distance; + float p1_speed; + float p2_distance; + float distance; + + old_speed = moveinfo->current_speed; + + // figure simple acceleration up to move_speed + moveinfo->current_speed += moveinfo->accel; + if (moveinfo->current_speed > moveinfo->speed) + moveinfo->current_speed = moveinfo->speed; + + // are we accelerating throughout this entire move? + if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance) + return; + + // during this move we will accelrate from current_speed to move_speed + // and cross over the decel_distance; figure the average speed for the + // entire move + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p1_speed = (old_speed + moveinfo->move_speed) / 2.0; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance)); + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // we are at constant velocity (move_speed) + return; +}; + +void Think_AccelMove (edict_t *ent) +{ + ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed; + + if (ent->moveinfo.current_speed == 0) // starting or blocked + plat_CalcAcceleratedMove(&ent->moveinfo); + + plat_Accelerate (&ent->moveinfo); + + // will the entire move complete on next frame? + if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed) + { + Move_Final (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity); + ent->nextthink = level.time + FRAMETIME; + ent->think = Think_AccelMove; +} + + +void plat_go_down (edict_t *ent); + +void plat_hit_top (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_TOP; + + ent->think = plat_go_down; + ent->nextthink = level.time + 3; +} + +void plat_hit_bottom (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_BOTTOM; +} + +void plat_go_down (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_DOWN; + Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom); +} + +void plat_go_up (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_UP; + Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top); +} + +void plat_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + { + // zucc just free it, we don't need explosions + Handle_Unique_Items(other); + if ( other ) + G_FreeEdict(other); + //BecomeExplosion1 (other); + } + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->moveinfo.state == STATE_UP) + plat_go_down (self); + else if (self->moveinfo.state == STATE_DOWN) + plat_go_up (self); +} + + +void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->think) + return; // already down + plat_go_down (ent); +} + + +void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + ent = ent->enemy; // now point at the plat, not the trigger + if (ent->moveinfo.state == STATE_BOTTOM) + plat_go_up (ent); + else if (ent->moveinfo.state == STATE_TOP) + ent->nextthink = level.time + 1; // the player is still on the plat, so delay going down +} + +void plat_spawn_inside_trigger (edict_t *ent) +{ + edict_t *trigger; + vec3_t tmin, tmax; + +// +// middle trigger +// + trigger = G_Spawn(); + trigger->touch = Touch_Plat_Center; + trigger->movetype = MOVETYPE_NONE; + trigger->solid = SOLID_TRIGGER; + trigger->enemy = ent; + + tmin[0] = ent->mins[0] + 25; + tmin[1] = ent->mins[1] + 25; + tmin[2] = ent->mins[2]; + + tmax[0] = ent->maxs[0] - 25; + tmax[1] = ent->maxs[1] - 25; + tmax[2] = ent->maxs[2] + 8; + + tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip); + + if (ent->spawnflags & PLAT_LOW_TRIGGER) + tmax[2] = tmin[2] + 8; + + if (tmax[0] - tmin[0] <= 0) + { + tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5; + tmax[0] = tmin[0] + 1; + } + if (tmax[1] - tmin[1] <= 0) + { + tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5; + tmax[1] = tmin[1] + 1; + } + + VectorCopy (tmin, trigger->mins); + VectorCopy (tmax, trigger->maxs); + + gi.linkentity (trigger); +} + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat. + +"speed" overrides default 200. +"accel" overrides default 500 +"lip" overrides default 8 pixel lip + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ +void SP_func_plat (edict_t *ent) +{ + VectorClear (ent->s.angles); + ent->solid = SOLID_BSP; + ent->movetype = MOVETYPE_PUSH; + + gi.setmodel (ent, ent->model); + + ent->blocked = plat_blocked; + + if (!ent->speed) + ent->speed = 20; + else + ent->speed *= 0.1; + + if (!ent->accel) + ent->accel = 5; + else + ent->accel *= 0.1; + + if (!ent->decel) + ent->decel = 5; + else + ent->decel *= 0.1; + + if (!ent->dmg) + ent->dmg = 2; + + if (!st.lip) + st.lip = 8; + + // pos1 is the top position, pos2 is the bottom + VectorCopy (ent->s.origin, ent->pos1); + VectorCopy (ent->s.origin, ent->pos2); + if (st.height) + ent->pos2[2] -= st.height; + else + ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; + + ent->use = Use_Plat; + + plat_spawn_inside_trigger (ent); // the "start moving" trigger + + if (ent->targetname) + { + ent->moveinfo.state = STATE_UP; + } + else + { + VectorCopy (ent->pos2, ent->s.origin); + gi.linkentity (ent); + ent->moveinfo.state = STATE_BOTTOM; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav"); +} + +//==================================================================== + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +STOP mean it will stop moving instead of pushing entities +*/ + +void rotating_blocked (edict_t *self, edict_t *other) +{ + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2]) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!VectorCompare (self->avelocity, vec3_origin)) + { + self->s.sound = 0; + VectorClear (self->avelocity); + self->touch = NULL; + } + else + { + self->s.sound = self->moveinfo.sound_middle; + VectorScale (self->movedir, self->speed, self->avelocity); + if (self->spawnflags & 16) + self->touch = rotating_touch; + } +} + +void SP_func_rotating (edict_t *ent) +{ + ent->solid = SOLID_BSP; + if (ent->spawnflags & 32) + ent->movetype = MOVETYPE_STOP; + else + ent->movetype = MOVETYPE_PUSH; + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & 4) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & 8) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & 2) + VectorNegate (ent->movedir, ent->movedir); + + if (!ent->speed) + ent->speed = 100; + if (!ent->dmg) + ent->dmg = 2; + +// ent->moveinfo.sound_middle = "doors/hydro1.wav"; + + ent->use = rotating_use; + if (ent->dmg) + ent->blocked = rotating_blocked; + + if (ent->spawnflags & 1) + ent->use (ent, NULL, NULL); + + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 128) + ent->s.effects |= EF_ANIM_ALLFAST; + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + +/* +====================================================================== + +BUTTONS + +====================================================================== +*/ + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +1) silent +2) steam metal +3) wooden clunk +4) metallic click +5) in-out +*/ + +void button_done (edict_t *self) +{ + self->moveinfo.state = STATE_BOTTOM; + self->s.effects &= ~EF_ANIM23; + self->s.effects |= EF_ANIM01; +} + +void button_return (edict_t *self) +{ + self->moveinfo.state = STATE_DOWN; + + Move_Calc (self, self->moveinfo.start_origin, button_done); + + self->s.frame = 0; + + if (self->health) + self->takedamage = DAMAGE_YES; +} + +void button_wait (edict_t *self) +{ + self->moveinfo.state = STATE_TOP; + self->s.effects &= ~EF_ANIM01; + self->s.effects |= EF_ANIM23; + + G_UseTargets (self, self->activator); + self->s.frame = 1; + if (self->moveinfo.wait >= 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = button_return; + } +} + +void button_fire (edict_t *self) +{ + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + return; + + self->moveinfo.state = STATE_UP; + if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE)) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + Move_Calc (self, self->moveinfo.end_origin, button_wait); +} + +void button_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + button_fire (self); +} + +void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + self->activator = other; + button_fire (self); +} + +void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->activator = attacker; + self->health = self->max_health; + self->takedamage = DAMAGE_NO; + button_fire (self); +} + +void SP_func_button (edict_t *ent) +{ + vec3_t abs_movedir; + float dist; + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_STOP; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + if (ent->sounds != 1) + ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav"); + + if (!ent->speed) + ent->speed = 40; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 4; + + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, dist, ent->movedir, ent->pos2); + + ent->use = button_use; + ent->s.effects |= EF_ANIM01; + + if (ent->health) + { + ent->max_health = ent->health; + ent->die = button_killed; + ent->takedamage = DAMAGE_YES; + } + else if (! ent->targetname) + ent->touch = button_touch; + + ent->moveinfo.state = STATE_BOTTOM; + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + gi.linkentity (ent); +} + +/* +====================================================================== + +DOORS + + spawn a trigger surrounding the entire team unless it is + already targeted by another + +====================================================================== +*/ + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void door_use_areaportals (edict_t *self, qboolean open) +{ + edict_t *t = NULL; + + if (!self->target) + return; + + while ((t = G_Find (t, FOFS(targetname), self->target))) + { + if (Q_stricmp(t->classname, "func_areaportal") == 0) + { + gi.SetAreaPortalState (t->style, open); + } + } +} + +void door_go_down (edict_t *self); + +void door_hit_top (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_TOP; + if (self->spawnflags & DOOR_TOGGLE) + return; + if (self->moveinfo.wait >= 0) + { + self->think = door_go_down; + self->nextthink = level.time + self->moveinfo.wait; + } +} + +void door_hit_bottom (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_BOTTOM; + door_use_areaportals (self, false); +} + +void door_go_down (edict_t *self) +{ + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + if (self->max_health) + { + self->takedamage = DAMAGE_YES; + self->health = self->max_health; + } + + self->moveinfo.state = STATE_DOWN; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_bottom); +} + +void door_go_up (edict_t *self, edict_t *activator) +{ + if (self->moveinfo.state == STATE_UP) + return; // already going up + + if (self->moveinfo.state == STATE_TOP) + { // reset top wait time + if (self->moveinfo.wait >= 0) + self->nextthink = level.time + self->moveinfo.wait; + return; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + self->moveinfo.state = STATE_UP; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.end_origin, door_hit_top); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_top); + + G_UseTargets (self, activator); + door_use_areaportals (self, true); +} + +void door_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + if (self->flags & FL_TEAMSLAVE) + return; + + + if (self->spawnflags & DOOR_TOGGLE) + { + + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + { +// if ( other->client ) +// safe_cprintf(other, PRINT_HIGH, "Client sending door down.\n"); + + + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_down (ent); + } + return; + } + } + + // if ( other->client ) +// safe_cprintf(other, PRINT_HIGH, "Client sending door up.\n"); + + + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_up (ent, activator); + } +}; + +void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + // zucc needed so the doors respond quickly + if ( other->client && other->client->doortoggle ) + { + //safe_cprintf(other, PRINT_HIGH, "Door trigger called.\n"); + self->touch_debounce_time = 0.0; + } + if (other->health <= 0) + return; + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + return; + + if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER)) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 1.0; +// zucc + if ( other->client ) + { + if (other->client->doortoggle == 0 ) // player not trying to open the door + { + //safe_cprintf(other, PRINT_HIGH, "Client failed to open door.\n"); + return; + } + else + { + //other->client->doortoggle = 0; + door_use (self->owner, other, other); + } + } + + +} + +void Think_CalcMoveSpeed (edict_t *self) +{ + edict_t *ent; + float min; + float time; + float newspeed; + float ratio; + float dist; + + if (self->flags & FL_TEAMSLAVE) + return; // only the team master does this + + // find the smallest distance any member of the team will be moving + min = fabs(self->moveinfo.distance); + for (ent = self->teamchain; ent; ent = ent->teamchain) + { + dist = fabs(ent->moveinfo.distance); + if (dist < min) + min = dist; + } + + time = min / self->moveinfo.speed; + + // adjust speeds so they will all complete at the same time + for (ent = self; ent; ent = ent->teamchain) + { + newspeed = fabs(ent->moveinfo.distance) / time; + ratio = newspeed / ent->moveinfo.speed; + if (ent->moveinfo.accel == ent->moveinfo.speed) + ent->moveinfo.accel = newspeed; + else + ent->moveinfo.accel *= ratio; + if (ent->moveinfo.decel == ent->moveinfo.speed) + ent->moveinfo.decel = newspeed; + else + ent->moveinfo.decel *= ratio; + ent->moveinfo.speed = newspeed; + } +} + +void Think_SpawnDoorTrigger (edict_t *ent) +{ + edict_t *other; + vec3_t mins, maxs; + + if (ent->flags & FL_TEAMSLAVE) + return; // only the team leader spawns a trigger + + VectorCopy (ent->absmin, mins); + VectorCopy (ent->absmax, maxs); + + for (other = ent->teamchain ; other ; other=other->teamchain) + { + AddPointToBounds (other->absmin, mins, maxs); + AddPointToBounds (other->absmax, mins, maxs); + } + + // expand + mins[0] -= 60; + mins[1] -= 60; + maxs[0] += 60; + maxs[1] += 60; + + other = G_Spawn (); + VectorCopy (mins, other->mins); + VectorCopy (maxs, other->maxs); + other->owner = ent; + other->solid = SOLID_TRIGGER; + other->movetype = MOVETYPE_NONE; + other->touch = Touch_DoorTrigger; + gi.linkentity (other); + + if (ent->spawnflags & DOOR_START_OPEN) + door_use_areaportals (ent, true); + + Think_CalcMoveSpeed (ent); +} + +void door_blocked (edict_t *self, edict_t *other) +{ + edict_t *ent; + + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + { + // zucc we don't need explosions + Handle_Unique_Items(other); + if ( other ) + G_FreeEdict(other); + + // BecomeExplosion1 (other); + } + return; + } + + //zucc stop the doors from damaging people + //T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->spawnflags & DOOR_CRUSHER) + return; + + +// if a door has a negative wait, it would never come back if blocked, +// so let it just squash the object to death real fast + if (self->moveinfo.wait >= 0) + { + if (self->moveinfo.state == STATE_DOWN) + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_up (ent, ent->activator); + } + else + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_down (ent); + } + } +} + +void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *ent; + + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + { + ent->health = ent->max_health; + ent->takedamage = DAMAGE_NO; + } + door_use (self->teammaster, attacker, attacker); +} + +void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + + safe_centerprintf (other, "%s", self->message); + gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); +} + +void SP_func_door (edict_t *ent) +{ + vec3_t abs_movedir; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (deathmatch->value) + ent->speed *= 2; + + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 8; + if (!ent->dmg) + ent->dmg = 2; + + // calculate second position + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2); + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.origin); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.origin, ent->pos1); + } + + ent->moveinfo.state = STATE_BOTTOM; + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALLFAST; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void SP_func_door_rotating (edict_t *ent) +{ + VectorClear (ent->s.angles); + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & DOOR_X_AXIS) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & DOOR_Y_AXIS) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & DOOR_REVERSE) + VectorNegate (ent->movedir, ent->movedir); + + if (!st.distance) + { + gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin)); + st.distance = 90; + } + + VectorCopy (ent->s.angles, ent->pos1); + VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2); + ent->moveinfo.distance = st.distance; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!ent->dmg) + ent->dmg = 2; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.angles); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.angles, ent->pos1); + VectorNegate (ent->movedir, ent->movedir); + } + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + + if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.state = STATE_BOTTOM; + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->s.origin, ent->moveinfo.start_origin); + VectorCopy (ent->pos1, ent->moveinfo.start_angles); + VectorCopy (ent->s.origin, ent->moveinfo.end_origin); + VectorCopy (ent->pos2, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_water (0 .5 .8) ? START_OPEN +func_water is a moveable water brush. It must be targeted to operate. Use a non-water texture at your own risk. + +START_OPEN causes the water to move to its destination when spawned and operate in reverse. + +"angle" determines the opening direction (up or down only) +"speed" movement speed (25 default) +"wait" wait before returning (-1 default, -1 = TOGGLE) +"lip" lip remaining at end of move (0 default) +"sounds" (yes, these need to be changed) +0) no sound +1) water +2) lava +*/ + +void SP_func_water (edict_t *self) +{ + vec3_t abs_movedir; + + G_SetMovedir (self->s.angles, self->movedir); + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + switch (self->sounds) + { + default: + break; + + case 1: // water + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + + case 2: // lava + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + } + + // calculate second position + VectorCopy (self->s.origin, self->pos1); + abs_movedir[0] = fabs(self->movedir[0]); + abs_movedir[1] = fabs(self->movedir[1]); + abs_movedir[2] = fabs(self->movedir[2]); + self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip; + VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2); + + // if it starts open, switch the positions + if (self->spawnflags & DOOR_START_OPEN) + { + VectorCopy (self->pos2, self->s.origin); + VectorCopy (self->pos1, self->pos2); + VectorCopy (self->s.origin, self->pos1); + } + + VectorCopy (self->pos1, self->moveinfo.start_origin); + VectorCopy (self->s.angles, self->moveinfo.start_angles); + VectorCopy (self->pos2, self->moveinfo.end_origin); + VectorCopy (self->s.angles, self->moveinfo.end_angles); + + self->moveinfo.state = STATE_BOTTOM; + + if (!self->speed) + self->speed = 25; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed; + + if (!self->wait) + self->wait = -1; + self->moveinfo.wait = self->wait; + + self->use = door_use; + + if (self->wait == -1) + self->spawnflags |= DOOR_TOGGLE; + + self->classname = "func_door"; + + gi.linkentity (self); +} + + +#define TRAIN_START_ON 1 +#define TRAIN_TOGGLE 2 +#define TRAIN_BLOCK_STOPS 4 + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +noise looping sound to play when the train is in motion + +*/ +void train_next (edict_t *self); + +void train_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + { + Handle_Unique_Items(other); + if ( other ) + G_FreeEdict(other); + //BecomeExplosion1 (other); + } + return; + } + + if (level.time < self->touch_debounce_time) + return; + + if (!self->dmg) + return; + self->touch_debounce_time = level.time + 0.5; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void train_wait (edict_t *self) +{ + if (self->target_ent->pathtarget) + { + char *savetarget; + edict_t *ent; + + ent = self->target_ent; + savetarget = ent->target; + ent->target = ent->pathtarget; + G_UseTargets (ent, self->activator); + ent->target = savetarget; + + // make sure we didn't get killed by a killtarget + if (!self->inuse) + return; + } + + if (self->moveinfo.wait) + { + if (self->moveinfo.wait > 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = train_next; + } + else if (self->spawnflags & TRAIN_TOGGLE) // && wait < 0 + { + train_next (self); + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + } + else + { + train_next (self); + } + +} + +void train_next (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + qboolean first; + + first = true; +again: + if (!self->target) + { +// gi.dprintf ("train_next: no next target\n"); + return; + } + + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_next: bad target %s\n", self->target); + return; + } + + self->target = ent->target; + + // check for a teleport path_corner + if (ent->spawnflags & 1) + { + if (!first) + { + gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin)); + return; + } + first = false; + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + VectorCopy (self->s.origin, self->s.old_origin); + gi.linkentity (self); + goto again; + } + + self->moveinfo.wait = ent->wait; + self->target_ent = ent; + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void train_resume (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + + ent = self->target_ent; + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void func_train_find (edict_t *self) +{ + edict_t *ent; + + if (!self->target) + { + gi.dprintf ("train_find: no target\n"); + return; + } + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_find: target %s not found\n", self->target); + return; + } + self->target = ent->target; + + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + gi.linkentity (self); + + // if not triggered, start immediately + if (!self->targetname) + self->spawnflags |= TRAIN_START_ON; + + if (self->spawnflags & TRAIN_START_ON) + { + self->nextthink = level.time + FRAMETIME; + self->think = train_next; + self->activator = self; + } +} + +void train_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (self->spawnflags & TRAIN_START_ON) + { + if (!(self->spawnflags & TRAIN_TOGGLE)) + return; + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + else + { + if (self->target_ent) + train_resume(self); + else + train_next(self); + } +} + +void SP_func_train (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + + VectorClear (self->s.angles); + self->blocked = train_blocked; + if (self->spawnflags & TRAIN_BLOCK_STOPS) + self->dmg = 0; + else + { + if (!self->dmg) + self->dmg = 100; + } + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + if (st.noise) + self->moveinfo.sound_middle = gi.soundindex (st.noise); + + if (!self->speed) + self->speed = 100; + + self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; + + self->use = train_use; + + gi.linkentity (self); + + if (self->target) + { + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + FRAMETIME; + self->think = func_train_find; + } + else + { + gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin)); + } +} + + +/*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) +*/ +void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *target; + + if (self->movetarget->nextthink) + { +// gi.dprintf("elevator busy\n"); + return; + } + + if (!other->pathtarget) + { + gi.dprintf("elevator used with no pathtarget\n"); + return; + } + + target = G_PickTarget (other->pathtarget); + if (!target) + { + gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget); + return; + } + + self->movetarget->target_ent = target; + train_resume (self->movetarget); +} + +void trigger_elevator_init (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("trigger_elevator has no target\n"); + return; + } + self->movetarget = G_PickTarget (self->target); + if (!self->movetarget) + { + gi.dprintf("trigger_elevator unable to find target %s\n", self->target); + return; + } + if (strcmp(self->movetarget->classname, "func_train") != 0) + { + gi.dprintf("trigger_elevator target %s is not a train\n", self->target); + return; + } + + self->use = trigger_elevator_use; + self->svflags = SVF_NOCLIENT; + +} + +void SP_trigger_elevator (edict_t *self) +{ + self->think = trigger_elevator_init; + self->nextthink = level.time + FRAMETIME; +} + + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +"wait" base time between triggering all targets, default is 1 +"random" wait variance, default is 0 + +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +"delay" delay before first firing when turned on, default is 0 + +"pausetime" additional delay used only the very first time + and only if spawned with START_ON + +These can used but not touched. +*/ +void func_timer_think (edict_t *self) +{ + G_UseTargets (self, self->activator); + self->nextthink = level.time + self->wait + crandom() * self->random; +} + +void func_timer_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + // if on, turn it off + if (self->nextthink) + { + self->nextthink = 0; + return; + } + + // turn it on + if (self->delay) + self->nextthink = level.time + self->delay; + else + func_timer_think (self); +} + +void SP_func_timer (edict_t *self) +{ + if (!self->wait) + self->wait = 1.0; + + self->use = func_timer_use; + self->think = func_timer_think; + + if (self->random >= self->wait) + { + self->random = self->wait - FRAMETIME; + gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); + } + + if (self->spawnflags & 1) + { + self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random; + self->activator = self; + } + + self->svflags = SVF_NOCLIENT; +} + + +/*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE +Conveyors are stationary brushes that move what's on them. +The brush should be have a surface with at least one current content enabled. +speed default 100 +*/ + +void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & 1) + { + self->speed = 0; + self->spawnflags &= ~1; + } + else + { + self->speed = self->count; + self->spawnflags |= 1; + } + + if (!(self->spawnflags & 2)) + self->count = 0; +} + +void SP_func_conveyor (edict_t *self) +{ + if (!self->speed) + self->speed = 100; + + if (!(self->spawnflags & 1)) + { + self->count = self->speed; + self->speed = 0; + } + + self->use = func_conveyor_use; + + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + gi.linkentity (self); +} + + +/*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down +A secret door. Slide back and then to the side. + +open_once doors never closes +1st_left 1st move is left of arrow +1st_down 1st move is down from arrow +always_shoot door is shootebale even if targeted + +"angle" determines the direction +"dmg" damage to inflic when blocked (default 2) +"wait" how long to hold in the open position (default 5, -1 means hold) +*/ + +#define SECRET_ALWAYS_SHOOT 1 +#define SECRET_1ST_LEFT 2 +#define SECRET_1ST_DOWN 4 + +void door_secret_move1 (edict_t *self); +void door_secret_move2 (edict_t *self); +void door_secret_move3 (edict_t *self); +void door_secret_move4 (edict_t *self); +void door_secret_move5 (edict_t *self); +void door_secret_move6 (edict_t *self); +void door_secret_done (edict_t *self); + +void door_secret_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // make sure we're not already moving + if (!VectorCompare(self->s.origin, vec3_origin)) + return; + + Move_Calc (self, self->pos1, door_secret_move1); + door_use_areaportals (self, true); +} + +void door_secret_move1 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move2; +} + +void door_secret_move2 (edict_t *self) +{ + Move_Calc (self, self->pos2, door_secret_move3); +} + +void door_secret_move3 (edict_t *self) +{ + if (self->wait == -1) + return; + self->nextthink = level.time + self->wait; + self->think = door_secret_move4; +} + +void door_secret_move4 (edict_t *self) +{ + Move_Calc (self, self->pos1, door_secret_move5); +} + +void door_secret_move5 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move6; +} + +void door_secret_move6 (edict_t *self) +{ + Move_Calc (self, vec3_origin, door_secret_done); +} + +void door_secret_done (edict_t *self) +{ + if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT)) + { + self->health = 0; + self->takedamage = DAMAGE_YES; + } + door_use_areaportals (self, false); +} + +void door_secret_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + { + // zucc no explosions + Handle_Unique_Items(other); + if ( other ) + G_FreeEdict(other); + // BecomeExplosion1 (other); + } + return; + } + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 0.5; + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + door_secret_use (self, attacker, attacker); +} + +void SP_func_door_secret (edict_t *ent) +{ + vec3_t forward, right, up; + float side; + float width; + float length; + + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_secret_blocked; + ent->use = door_secret_use; + + if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT)) + { + ent->health = 0; + ent->takedamage = DAMAGE_YES; + ent->die = door_secret_die; + } + + if (!ent->dmg) + ent->dmg = 2; + + if (!ent->wait) + ent->wait = 5; + + ent->moveinfo.accel = + ent->moveinfo.decel = + ent->moveinfo.speed = 50; + + // calculate positions + AngleVectors (ent->s.angles, forward, right, up); + VectorClear (ent->s.angles); + side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT); + if (ent->spawnflags & SECRET_1ST_DOWN) + width = fabs(DotProduct(up, ent->size)); + else + width = fabs(DotProduct(right, ent->size)); + length = fabs(DotProduct(forward, ent->size)); + if (ent->spawnflags & SECRET_1ST_DOWN) + VectorMA (ent->s.origin, -1 * width, up, ent->pos1); + else + VectorMA (ent->s.origin, side * width, right, ent->pos1); + VectorMA (ent->pos1, length, forward, ent->pos2); + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->classname = "func_door"; + + gi.linkentity (ent); +} + + +/*QUAKED func_killbox (1 0 0) ? +Kills everything inside when fired, irrespective of protection. +*/ +void use_killbox (edict_t *self, edict_t *other, edict_t *activator) +{ + KillBox (self); +} + +void SP_func_killbox (edict_t *ent) +{ + gi.setmodel (ent, ent->model); + ent->use = use_killbox; + ent->svflags = SVF_NOCLIENT; +} + diff --git a/g_items.c b/g_items.c new file mode 100644 index 0000000..62d1af7 --- /dev/null +++ b/g_items.c @@ -0,0 +1,2747 @@ +#include "g_local.h" + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other); +void Use_Weapon (edict_t *ent, gitem_t *inv); +void Drop_Weapon (edict_t *ent, gitem_t *inv); + +void Weapon_Blaster (edict_t *ent); +void Weapon_Shotgun (edict_t *ent); +void Weapon_SuperShotgun (edict_t *ent); +void Weapon_Machinegun (edict_t *ent); +void Weapon_Chaingun (edict_t *ent); +void Weapon_HyperBlaster (edict_t *ent); +void Weapon_RocketLauncher (edict_t *ent); +void Weapon_Grenade (edict_t *ent); +void Weapon_GrenadeLauncher (edict_t *ent); +void Weapon_Railgun (edict_t *ent); +void Weapon_BFG (edict_t *ent); + +// zucc +void Weapon_MK23 (edict_t *ent); +void Weapon_MP5 (edict_t *ent); +void Weapon_M4 (edict_t *ent); +void Weapon_M3 (edict_t *ent); +void Weapon_HC (edict_t *ent); +void Weapon_Sniper (edict_t *ent); +void Weapon_Dual (edict_t *ent); +void Weapon_Knife (edict_t *ent); +void Weapon_Gas (edict_t *ent); + +gitem_armor_t jacketarmor_info = { 25, 50, .30, .00, ARMOR_JACKET}; +gitem_armor_t combatarmor_info = { 50, 100, .60, .30, ARMOR_COMBAT}; +gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY}; + +static int jacket_armor_index; +static int combat_armor_index; +static int body_armor_index; +static int power_screen_index; +static int power_shield_index; + +#define HEALTH_IGNORE_MAX 1 +#define HEALTH_TIMED 2 + +void Use_Quad (edict_t *ent, gitem_t *item); +static int quad_drop_timeout_hack; + +//====================================================================== + +/* +=============== +GetItemByIndex +=============== +*/ +gitem_t *GetItemByIndex (int index) +{ + if (index == 0 || index >= game.num_items) + return NULL; + + return &itemlist[index]; +} + + +/* +=============== +FindItemByClassname + +=============== +*/ +gitem_t *FindItemByClassname (char *classname) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; iclassname) + continue; + if (!Q_stricmp(it->classname, classname)) + return it; + } + + return NULL; +} + +/* +=============== +FindItem + +=============== +*/ +gitem_t *FindItem (char *pickup_name) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; ipickup_name) + continue; + if (!Q_stricmp(it->pickup_name, pickup_name)) + return it; + } + + return NULL; +} + +//====================================================================== + +void DoRespawn (edict_t *ent) +{ + if (ent->team) + { + edict_t *master; + int count; + int choice; + + master = ent->teammaster; + + for (count = 0, ent = master; ent; ent = ent->chain, count++) + ; + + choice = rand() % count; + + for (count = 0, ent = master; count < choice; ent = ent->chain, count++) + ; + } + + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity (ent); + + // send an effect + ent->s.event = EV_ITEM_RESPAWN; +} + +void SetRespawn (edict_t *ent, float delay) +{ + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->nextthink = level.time + delay; + ent->think = DoRespawn; + gi.linkentity (ent); +} + + +//====================================================================== + +qboolean Pickup_Powerup (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1)) + return false; + + if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))) + { + if ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM)) + quad_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME; + ent->item->use (other, ent->item); + } + } + + return true; +} + +//zucc pickup function for special items +qboolean Pickup_Special ( edict_t *ent, edict_t *other ) +{ + if ( other->client->unique_item_total >= unique_items->value ) + return false; + else + { + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + other->client->unique_item_total++; + if ( stricmp( ent->item->pickup_name, LASER_NAME ) == 0 ) + { + other->client->have_laser = 1; + SP_LaserSight(other, ent->item);//ent->item->use(other, ent->item); + } + if ( stricmp( ent->item->pickup_name, BAND_NAME ) == 0 ) + { + + if (other->client->pers.max_bullets < 4) + other->client->pers.max_bullets = 4; + if (other->client->pers.max_shells < 28) + other->client->pers.max_shells = 28; + if (other->client->pers.max_cells < 2) + other->client->pers.max_cells = 2; + if (other->client->pers.max_slugs < 40) + other->client->pers.max_slugs = 40; + if (other->client->pers.max_grenades < 6) + other->client->pers.max_grenades = 6; + if (other->client->pers.max_rockets < 4) + other->client->pers.max_rockets = 4; + if (other->client->knife_max < 20 ) + other->client->knife_max = 20; + if (other->client->grenade_max < 4 ) + other->client->grenade_max = 4; + // zucc for ir +/* if ( ir->value && other->client->resp.ir == 0 ) + { + other->client->ps.rdflags |= RDF_IRGOGGLES; + } +*/ + } + + + } + return true; +} + + + +void Drop_Special( edict_t *ent, gitem_t *item) +{ + ent->client->unique_item_total--; +/* if ( stricmp( item->pickup_name, LASER_NAME ) == 0 + && ent->client->pers.inventory[ITEM_INDEX(item)] <= 1 ) + { + ent->client->have_laser = 0; + item->use(ent, item); + } + */ + if ( stricmp( item->pickup_name, BAND_NAME ) == 0 + && ent->client->pers.inventory[ITEM_INDEX(item)] <= 1 ) + { + ent->client->pers.max_bullets = 2; + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("Pistol Clip"))] > 2 ) + ent->client->pers.inventory[ITEM_INDEX(FindItem("Pistol Clip"))] = 2; + ent->client->pers.max_shells = 14; + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("12 Gauge Shells"))] > 14 ) + ent->client->pers.inventory[ITEM_INDEX(FindItem("12 Gauge Shells"))] = 14; + ent->client->pers.max_cells = 1; + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("M4 Clip"))] > 1 ) + ent->client->pers.inventory[ITEM_INDEX(FindItem("M4 Clip"))] = 1; + + ent->client->grenade_max = 2; + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(GRENADE_NAME))] > 2 ) + ent->client->pers.inventory[ITEM_INDEX(FindItem(GRENADE_NAME))] = 2; + + ent->client->pers.max_rockets = 2; + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("Machinegun Magazine"))] > 2 ) + ent->client->pers.inventory[ITEM_INDEX(FindItem("Machinegun Magazine"))] = 2; + + ent->client->knife_max = 10; + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(KNIFE_NAME))] > 10 ) + ent->client->pers.inventory[ITEM_INDEX(FindItem(KNIFE_NAME))] = 10; + + ent->client->pers.max_slugs = 20; + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("AP Sniper Ammo"))] > 20 ) + ent->client->pers.inventory[ITEM_INDEX(FindItem("AP Sniper Ammo"))] = 20; + + if ( ent->client->unique_weapon_total > unique_weapons->value && !allweapon->value ) + { + DropExtraSpecial(ent); + safe_cprintf(ent, PRINT_HIGH, "One of your guns is dropped with the bandolier.\n"); + } + } + Drop_Spec (ent, item); + ValidateSelectedItem (ent); + SP_LaserSight(ent, item); + +} + +// called by the "drop item" command +void DropSpecialItem ( edict_t* ent ) +{ + +// this is the order I'd probably want to drop them in... + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(BAND_NAME))] ) + Drop_Special (ent, FindItem(BAND_NAME)); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SLIP_NAME))] ) + Drop_Special (ent, FindItem(SLIP_NAME)); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SIL_NAME))] ) + Drop_Special (ent, FindItem(SIL_NAME)); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(LASER_NAME))] ) + Drop_Special (ent, FindItem(LASER_NAME)); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(KEV_NAME))] ) + Drop_Special (ent, FindItem(KEV_NAME)); + +} + + +void Drop_General (edict_t *ent, gitem_t *item) +{ + Drop_Item (ent, item); + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other) +{ + if (!deathmatch->value) + other->max_health += 1; + + if (other->health < other->max_health) + other->health = other->max_health; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_AncientHead (edict_t *ent, edict_t *other) +{ + other->max_health += 2; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Bandolier (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 250) + other->client->pers.max_bullets = 250; + if (other->client->pers.max_shells < 150) + other->client->pers.max_shells = 150; + if (other->client->pers.max_cells < 250) + other->client->pers.max_cells = 250; + if (other->client->pers.max_slugs < 75) + other->client->pers.max_slugs = 75; + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Pack (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 300) + other->client->pers.max_bullets = 300; + if (other->client->pers.max_shells < 200) + other->client->pers.max_shells = 200; + if (other->client->pers.max_rockets < 100) + other->client->pers.max_rockets = 100; + if (other->client->pers.max_grenades < 100) + other->client->pers.max_grenades = 100; + if (other->client->pers.max_cells < 300) + other->client->pers.max_cells = 300; + if (other->client->pers.max_slugs < 100) + other->client->pers.max_slugs = 100; + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + item = FindItem("Cells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_cells) + other->client->pers.inventory[index] = other->client->pers.max_cells; + } + + item = FindItem("Grenades"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_grenades) + other->client->pers.inventory[index] = other->client->pers.max_grenades; + } + + item = FindItem("Rockets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_rockets) + other->client->pers.inventory[index] = other->client->pers.max_rockets; + } + + item = FindItem("Slugs"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_slugs) + other->client->pers.inventory[index] = other->client->pers.max_slugs; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +//====================================================================== + +void Use_Quad (edict_t *ent, gitem_t *item) +{ + int timeout; + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (quad_drop_timeout_hack) + { + timeout = quad_drop_timeout_hack; + quad_drop_timeout_hack = 0; + } + else + { + timeout = 300; + } + + if (ent->client->quad_framenum > level.framenum) + ent->client->quad_framenum += timeout; + else + ent->client->quad_framenum = level.framenum + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Breather (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->breather_framenum > level.framenum) + ent->client->breather_framenum += 300; + else + ent->client->breather_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Envirosuit (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->enviro_framenum > level.framenum) + ent->client->enviro_framenum += 300; + else + ent->client->enviro_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Invulnerability (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->invincible_framenum > level.framenum) + ent->client->invincible_framenum += 300; + else + ent->client->invincible_framenum = level.framenum + 300; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Silencer (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + ent->client->silencer_shots += 30; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +qboolean Pickup_Key (edict_t *ent, edict_t *other) +{ + if (coop->value) + { + if (strcmp(ent->classname, "key_power_cube") == 0) + { + if (other->client->pers.power_cubes & ((ent->spawnflags & 0x0000ff00)>> 8)) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + other->client->pers.power_cubes |= ((ent->spawnflags & 0x0000ff00) >> 8); + } + else + { + if (other->client->pers.inventory[ITEM_INDEX(ent->item)]) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1; + } + return true; + } + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + return true; +} + +//====================================================================== + +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count) +{ + int index; + int max; + + if (!ent->client) + return false; + + if (item->tag == AMMO_BULLETS) + max = ent->client->pers.max_bullets; + else if (item->tag == AMMO_SHELLS) + max = ent->client->pers.max_shells; + else if (item->tag == AMMO_ROCKETS) + max = ent->client->pers.max_rockets; + else if (item->tag == AMMO_GRENADES) + max = ent->client->pers.max_grenades; + else if (item->tag == AMMO_CELLS) + max = ent->client->pers.max_cells; + else if (item->tag == AMMO_SLUGS) + max = ent->client->pers.max_slugs; + else + return false; + + index = ITEM_INDEX(item); + + if (ent->client->pers.inventory[index] == max) + return false; + + ent->client->pers.inventory[index] += count; + + if (ent->client->pers.inventory[index] > max) + ent->client->pers.inventory[index] = max; + + return true; +} + +qboolean Pickup_Ammo (edict_t *ent, edict_t *other) +{ + int oldcount; + int count; + qboolean weapon; + + weapon = (ent->item->flags & IT_WEAPON); + if ( (weapon) && ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + count = 1000; + else if (ent->count) + count = ent->count; + else + count = ent->item->quantity; + + oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + if (!Add_Ammo (other, ent->item, count)) + return false; + + if (weapon && !oldcount) + { + if (other->client->pers.weapon != ent->item && ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) ) + other->client->newweapon = ent->item; + } + + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value)) + SetRespawn (ent, 30); + return true; +} + +void Drop_Ammo (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + int index; + + index = ITEM_INDEX(item); + dropped = Drop_Item (ent, item); + if (ent->client->pers.inventory[index] >= item->quantity) + dropped->count = item->quantity; + else + dropped->count = ent->client->pers.inventory[index]; + ent->client->pers.inventory[index] -= dropped->count; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +void MegaHealth_think (edict_t *self) +{ + if (self->owner->health > self->owner->max_health) + { + self->nextthink = level.time + 1; + self->owner->health -= 1; + return; + } + + if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (self, 20); + else + G_FreeEdict (self); +} + +qboolean Pickup_Health (edict_t *ent, edict_t *other) +{ + if (!(ent->style & HEALTH_IGNORE_MAX)) + if (other->health >= other->max_health) + return false; + + other->health += ent->count; + + if (ent->count == 2) + ent->item->pickup_sound = "items/s_health.wav"; + else if (ent->count == 10) + ent->item->pickup_sound = "items/n_health.wav"; + else if (ent->count == 25) + ent->item->pickup_sound = "items/l_health.wav"; + else // (ent->count == 100) + ent->item->pickup_sound = "items/m_health.wav"; + + if (!(ent->style & HEALTH_IGNORE_MAX)) + { + if (other->health > other->max_health) + other->health = other->max_health; + } + + if (ent->style & HEALTH_TIMED) + { + ent->think = MegaHealth_think; + ent->nextthink = level.time + 5; + ent->owner = other; + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + else + { + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 30); + } + + return true; +} + +//====================================================================== + +int ArmorIndex (edict_t *ent) +{ + if (!ent->client) + return 0; + + if (ent->client->pers.inventory[jacket_armor_index] > 0) + return jacket_armor_index; + + if (ent->client->pers.inventory[combat_armor_index] > 0) + return combat_armor_index; + + if (ent->client->pers.inventory[body_armor_index] > 0) + return body_armor_index; + + return 0; +} + +qboolean Pickup_Armor (edict_t *ent, edict_t *other) +{ + int old_armor_index; + gitem_armor_t *oldinfo; + gitem_armor_t *newinfo; + int newcount; + float salvage; + int salvagecount; + + // get info on new armor + newinfo = (gitem_armor_t *)ent->item->info; + + old_armor_index = ArmorIndex (other); + + // handle armor shards specially + if (ent->item->tag == ARMOR_SHARD) + { + if (!old_armor_index) + other->client->pers.inventory[jacket_armor_index] = 2; + else + other->client->pers.inventory[old_armor_index] += 2; + } + + // if player has no armor, just use it + else if (!old_armor_index) + { + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newinfo->base_count; + } + + // use the better armor + else + { + // get info on old armor + if (old_armor_index == jacket_armor_index) + oldinfo = &jacketarmor_info; + else if (old_armor_index == combat_armor_index) + oldinfo = &combatarmor_info; + else // (old_armor_index == body_armor_index) + oldinfo = &bodyarmor_info; + + if (newinfo->normal_protection > oldinfo->normal_protection) + { + // calc new armor values + salvage = oldinfo->normal_protection / newinfo->normal_protection; + salvagecount = salvage * other->client->pers.inventory[old_armor_index]; + newcount = newinfo->base_count + salvagecount; + if (newcount > newinfo->max_count) + newcount = newinfo->max_count; + + // zero count of old armor so it goes away + other->client->pers.inventory[old_armor_index] = 0; + + // change armor to new item with computed value + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount; + } + else + { + // calc new armor values + salvage = newinfo->normal_protection / oldinfo->normal_protection; + salvagecount = salvage * newinfo->base_count; + newcount = other->client->pers.inventory[old_armor_index] + salvagecount; + if (newcount > oldinfo->max_count) + newcount = oldinfo->max_count; + + // if we're already maxed out then we don't need the new armor + if (other->client->pers.inventory[old_armor_index] >= newcount) + return false; + + // update current armor value + other->client->pers.inventory[old_armor_index] = newcount; + } + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 20); + + return true; +} + +//====================================================================== + +int PowerArmorType (edict_t *ent) +{ + if (!ent->client) + return POWER_ARMOR_NONE; + + if (!(ent->flags & FL_POWER_ARMOR)) + return POWER_ARMOR_NONE; + + if (ent->client->pers.inventory[power_shield_index] > 0) + return POWER_ARMOR_SHIELD; + + if (ent->client->pers.inventory[power_screen_index] > 0) + return POWER_ARMOR_SCREEN; + + return POWER_ARMOR_NONE; +} + +void Use_PowerArmor (edict_t *ent, gitem_t *item) +{ + int index; + + if (ent->flags & FL_POWER_ARMOR) + { + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + } + else + { + index = ITEM_INDEX(FindItem("cells")); + if (!ent->client->pers.inventory[index]) + { + safe_cprintf (ent, PRINT_HIGH, "No cells for power armor.\n"); + return; + } + ent->flags |= FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0); + } +} + +qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + // auto-use for DM only if we didn't already have one + if (!quantity) + ent->item->use (other, ent->item); + } + + return true; +} + +void Drop_PowerArmor (edict_t *ent, gitem_t *item) +{ + if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[ITEM_INDEX(item)] == 1)) + Use_PowerArmor (ent, item); + Drop_General (ent, item); +} + +//====================================================================== + +/* +=============== +Touch_Item +=============== +*/ +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + qboolean taken; + + if (!other->client) + return; + if (other->health < 1) + return; // dead people can't pickup + if (!ent->item->pickup) + return; // not a grabbable item? + + taken = ent->item->pickup(ent, other); + + if (taken) + { + // flash the screen + other->client->bonus_alpha = 0.25; + + // show icon and name on status bar +//FIREBLADE (debug code) + if (!ent->item->icon || strlen(ent->item->icon) == 0) + { + if (ent->item->classname) + gi.dprintf("Warning: null icon filename (classname = %s)\n", + ent->item->classname); + else + gi.dprintf("Warning: null icon filename (no classname)\n"); + + } +//FIREBLADE + other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon); + other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS+ITEM_INDEX(ent->item); + other->client->pickup_msg_time = level.time + 3.0; + + // change selected item + if (ent->item->use) + other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item); + + gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0); + } + + if (!(ent->spawnflags & ITEM_TARGETS_USED)) + { + G_UseTargets (ent, other); + ent->spawnflags |= ITEM_TARGETS_USED; + } + + if (!taken) + return; + + if (!((coop->value) && (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) + { + if (ent->flags & FL_RESPAWN) + ent->flags &= ~FL_RESPAWN; + else + G_FreeEdict (ent); + } +} + +//====================================================================== + +static void drop_temp_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void drop_make_touchable (edict_t *ent) +{ + ent->touch = Touch_Item; + if (deathmatch->value) + { + ent->nextthink = level.time + 119; + ent->think = G_FreeEdict; + } +} + +edict_t *Drop_Item (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + vec3_t forward, right; + vec3_t offset; + + dropped = G_Spawn(); + + dropped->classname = item->classname; + dropped->item = item; + dropped->spawnflags = DROPPED_ITEM; + dropped->s.effects = item->world_model_flags; + dropped->s.renderfx = RF_GLOW; + VectorSet (dropped->mins, -15, -15, -15); + VectorSet (dropped->maxs, 15, 15, 15); + // zucc dumb hack to make knife look like it is on the ground + if ( (stricmp(item->pickup_name, KNIFE_NAME) == 0) + || (stricmp(item->pickup_name, LASER_NAME) == 0) + || (stricmp(item->pickup_name, GRENADE_NAME) == 0 ) ) + { + VectorSet (dropped->mins, -15, -15, -1); + VectorSet (dropped->maxs, 15, 15, 1); + } + // spin? + VectorSet (dropped->avelocity, 0, 600, 0); + gi.setmodel (dropped, dropped->item->world_model); + dropped->solid = SOLID_TRIGGER; + dropped->movetype = MOVETYPE_TOSS; + + dropped->touch = drop_temp_touch; + dropped->owner = ent; + + if (ent->client) + { + trace_t trace; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 0, -16); + G_ProjectSource (ent->s.origin, offset, forward, right, dropped->s.origin); + PRETRACE(); + trace = gi.trace (ent->s.origin, dropped->mins, dropped->maxs, + dropped->s.origin, ent, CONTENTS_SOLID); + POSTTRACE(); + VectorCopy (trace.endpos, dropped->s.origin); + } + else + { + AngleVectors (ent->s.angles, forward, right, NULL); + VectorCopy (ent->s.origin, dropped->s.origin); + } + + VectorScale (forward, 100, dropped->velocity); + dropped->velocity[2] = 300; + + dropped->think = drop_make_touchable; + dropped->nextthink = level.time + 1; + + gi.linkentity (dropped); + + return dropped; +} + +void Use_Item (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->svflags &= ~SVF_NOCLIENT; + ent->use = NULL; + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->touch = Touch_Item; + } + + gi.linkentity (ent); +} + +//====================================================================== + +/* +================ +droptofloor +================ +*/ +void droptofloor (edict_t *ent) +{ + trace_t tr; + vec3_t dest; + float *v; + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + if ( ent->item ) + { + if ( (stricmp(ent->item->pickup_name, KNIFE_NAME) == 0) + || (stricmp(ent->item->pickup_name, LASER_NAME) == 0) + || (stricmp(ent->item->pickup_name, GRENADE_NAME) == 0 ) ) + { + VectorSet (ent->mins, -15, -15, -1); + VectorSet (ent->maxs, 15, 15, 1); + } + } + + if (ent->model) + gi.setmodel (ent, ent->model); + else + gi.setmodel (ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + PRETRACE(); + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + POSTTRACE(); + if (tr.startsolid) + { + gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + VectorCopy (tr.endpos, ent->s.origin); + + if (ent->team) + { + ent->flags &= ~FL_TEAMSLAVE; + ent->chain = ent->teamchain; + ent->teamchain = NULL; + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + if (ent == ent->teammaster) + { + ent->nextthink = level.time + FRAMETIME; + ent->think = DoRespawn; + } + } + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + ent->s.effects &= ~EF_ROTATE; + ent->s.renderfx &= ~RF_GLOW; + } + + if (ent->spawnflags & ITEM_TRIGGER_SPAWN) + { + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->use = Use_Item; + } + + gi.linkentity (ent); +} + + +/* +=============== +PrecacheItem + +Precaches all data needed for a given item. +This will be called for each item spawned in a level, +and for each item in each client's inventory. +=============== +*/ +void PrecacheItem (gitem_t *it) +{ + char *s, *start; + char data[MAX_QPATH]; + int len; + gitem_t *ammo; + + if (!it) + return; + + if (it->pickup_sound) + gi.soundindex (it->pickup_sound); + if (it->world_model) + gi.modelindex (it->world_model); + if (it->view_model) + gi.modelindex (it->view_model); + if (it->icon) + gi.imageindex (it->icon); + + // parse everything for its ammo + if (it->ammo && it->ammo[0]) + { + ammo = FindItem (it->ammo); + if (ammo != it) + PrecacheItem (ammo); + } + + // parse the space seperated precache string for other items + s = it->precaches; + if (!s || !s[0]) + return; + + while (*s) + { + start = s; + while (*s && *s != ' ') + s++; + + len = s-start; + if (len >= MAX_QPATH || len < 5) + gi.error ("PrecacheItem: %s has bad precache string", it->classname); + memcpy (data, start, len); + data[len] = 0; + if (*s) + s++; + + // determine type based on extension + if (!strcmp(data+len-3, "md2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "sp2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "wav")) + gi.soundindex (data); + if (!strcmp(data+len-3, "pcx")) + gi.imageindex (data); + } +} + +/* +============ +SpawnItem + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void SpawnItem (edict_t *ent, gitem_t *item) +{ + PrecacheItem (item); + + if (ent->spawnflags) + { + if (strcmp(ent->classname, "key_power_cube") != 0) + { + ent->spawnflags = 0; + gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin)); + } + } + + // some items will be prevented in deathmatch + if (deathmatch->value) + { + if ( (int)dmflags->value & DF_NO_ARMOR ) + { + if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_ITEMS ) + { + if (item->pickup == Pickup_Powerup) + { + G_FreeEdict (ent); + return; + } + } + // zucc remove health from the game + if ( 1 /*(int)dmflags->value & DF_NO_HEALTH*/ ) + { + if (item->pickup == Pickup_Health || item->pickup == Pickup_Adrenaline || item->pickup == Pickup_AncientHead) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + { + if ( (item->flags == IT_AMMO) || (strcmp(ent->classname, "weapon_bfg") == 0) ) + { + G_FreeEdict (ent); + return; + } + } + } + + if (coop->value && (strcmp(ent->classname, "key_power_cube") == 0)) + { + ent->spawnflags |= (1 << (8 + level.power_cubes)); + level.power_cubes++; + } + + // don't let them drop items that stay in a coop game + if ((coop->value) && (item->flags & IT_STAY_COOP)) + { + item->drop = NULL; + } + + ent->item = item; + ent->nextthink = level.time + 2 * FRAMETIME; // items start after other solids + ent->think = droptofloor; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW; + if (ent->model) + gi.modelindex (ent->model); +} + +//====================================================================== + +gitem_t itemlist[] = +{ + { + NULL + }, // leave index 0 alone + + // + // ARMOR + // + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_body", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/body/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_bodyarmor", +/* pickup */ "Body Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + &bodyarmor_info, + ARMOR_BODY, +/* precache */ "" + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_combat", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/combat/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_combatarmor", +/* pickup */ "Combat Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + &combatarmor_info, + ARMOR_COMBAT, +/* precache */ "" + }, + +/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_jacket", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/jacket/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Jacket Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + &jacketarmor_info, + ARMOR_JACKET, +/* precache */ "" + }, + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_shard", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar2_pkup.wav", + "models/items/armor/shard/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Armor Shard", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + NULL, + ARMOR_SHARD, +/* precache */ "" + }, + + +/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_power_screen", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/screen/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powerscreen", +/* pickup */ "Power Screen", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_power_shield", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/shield/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powershield", +/* pickup */ "Power Shield", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + NULL, + 0, +/* precache */""// "misc/power2.wav misc/power1.wav" + }, + + + // + // WEAPONS + // + +/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_blaster", + NULL, + Use_Weapon, + NULL, + Weapon_Blaster, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Blaster", + 0, + 0, + NULL, + 0,//IT_WEAPON|IT_STAY_COOP, + NULL, + 0, + + /* precache */ ""//"weapons/blastf1a.wav misc/lasfly.wav" + }, + +/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_shotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Shotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg/tris.md2", EF_ROTATE, + "models/weapons/v_shotg/tris.md2", +/* icon */ "w_shotgun", +/* pickup */ "Shotgun", + 0, + 1, + "Shells", + 0,//IT_WEAPON|IT_STAY_COOP, + NULL, + 0, +/* precache */ ""//"weapons/shotgf1b.wav weapons/shotgr1b.wav" + }, + +/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_supershotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_SuperShotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg2/tris.md2", EF_ROTATE, + "models/weapons/v_shotg2/tris.md2", +/* icon */ "w_sshotgun", +/* pickup */ "Super Shotgun", + 0, + 2, + "Shells", + 0,//IT_WEAPON|IT_STAY_COOP, + NULL, + 0, +/* precache */ ""//"weapons/sshotf1b.wav" + }, + +/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_machinegun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Machinegun, + "misc/w_pkup.wav", + "models/weapons/g_machn/tris.md2", EF_ROTATE, + "models/weapons/v_machn/tris.md2", +/* icon */ "w_machinegun", +/* pickup */ "Machinegun", + 0, + 1, + "Bullets", + 0,//IT_WEAPON|IT_STAY_COOP, + NULL, + 0, +/* precache */ ""//"weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav" + }, + +/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_chaingun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Chaingun, + "misc/w_pkup.wav", + "models/weapons/g_chain/tris.md2", EF_ROTATE, + "models/weapons/v_chain/tris.md2", +/* icon */ "w_chaingun", +/* pickup */ "Chaingun", + 0, + 1, + "Bullets", + 0,//IT_WEAPON|IT_STAY_COOP, + NULL, + 0, +/* precache */ ""//"weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav" + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_grenades", + Pickup_Ammo, + Use_Weapon, + Drop_Ammo, + Weapon_Grenade, + "misc/am_pkup.wav", + "models/items/ammo/grenades/medium/tris.md2", 0, + "models/weapons/v_handgr/tris.md2", +/* icon */ "a_grenades", +/* pickup */ "Grenades", +/* width */ 3, + 5, + "grenades", + 0,//IT_AMMO|IT_WEAPON, + NULL, + AMMO_GRENADES, +/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_grenadelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_GrenadeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Grenade Launcher", + 0, + 1, + "Grenades", + 0,//IT_WEAPON|IT_STAY_COOP, + NULL, + 0, +/* precache */ ""//"models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + +/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_rocketlauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_RocketLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, + "models/weapons/v_rocket/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Rocket Launcher", + 0, + 1, + "Rockets", + 0,//IT_WEAPON|IT_STAY_COOP, + NULL, + 0, +/* precache */ ""//"models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + +/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_hyperblaster", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_HyperBlaster, + "misc/w_pkup.wav", + "models/weapons/g_hyperb/tris.md2", EF_ROTATE, + "models/weapons/v_hyperb/tris.md2", +/* icon */ "w_hyperblaster", +/* pickup */ "HyperBlaster", + 0, + 1, + "Cells", + 0,//IT_WEAPON|IT_STAY_COOP, + NULL, + 0, +/* precache */ ""//"weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav" + }, + +/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_railgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Railgun, + "misc/w_pkup.wav", + "models/weapons/g_rail/tris.md2", EF_ROTATE, + "models/weapons/v_rail/tris.md2", +/* icon */ "w_railgun", +/* pickup */ "Railgun", + 0, + 1, + "Slugs", + 0,//IT_WEAPON|IT_STAY_COOP, + NULL, + 0, +/* precache */ ""//"weapons/rg_hum.wav" + }, + + + + /*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_bfg", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_BFG, + "misc/w_pkup.wav", + "models/weapons/g_bfg/tris.md2", EF_ROTATE, + "models/weapons/v_bfg/tris.md2", +/* icon */ "w_bfg", +/* pickup */ "BFG10K", + 0, + 50, + "Cells", + 0,//IT_WEAPON|IT_STAY_COOP, + NULL, + 0, +/* precache */ ""//"sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav" + }, + +// zucc - New Weapons +/* +gitem_t + +referenced by 'entity_name->item.attribute' + +Name Type Notes + +ammo char * type of ammo to use +classname char * name when spawning it +count_width int number of digits to display by icon +drop void function called when entity dropped +flags int type of pickup : + IT_WEAPON, IT_AMMO, IT_ARMOR +icon char * filename of icon +info void * ? unused +pickup qboolean function called when entity picked up +pickup_name char * displayed onscreen when item picked up +pickup_sound char * filename of sound to play when picked up +precaches char * string containing all models, sounds etc. needed by this + item +quantity int ammo gained by item/ammo used per shot by item +tag int ? unused +use void function called when entity used +view_model char * filename of model when being held +weaponthink void unused function +world_model char * filename of model when item is sitting on level +world_model_flags int copied to 'ent->s.effects' (see s.effects for values) + + + + */ + { + "weapon_Mk23", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_MK23, + //"misc/w_pkup.wav", + NULL, + "models/weapons/g_dual/tris.md2", + 0, + "models/weapons/v_blast/tris.md2", + "w_mk23", + MK23_NAME, + 0, + 1, + "Pistol Clip", + IT_WEAPON, + NULL, + 0, + "weapons/mk23fire.wav weapons/mk23in.wav weapons/mk23out.wav weapons/mk23slap.wav weapons/mk23slide.wav misc/click.wav" + }, + + +{ + "weapon_MP5", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_MP5, + //"misc/w_pkup.wav", + NULL, + "models/weapons/g_machn/tris.md2", + 0, + "models/weapons/v_machn/tris.md2", + "w_mp5", + MP5_NAME, + 0, + 0, + "Machinegun Magazine", + IT_WEAPON, + NULL, + 0, + "weapons/mp5fire1.wav weapons/mp5in.wav weapons/mp5out.wav weapons/mp5slap.wav weapons/mp5slide.wav" + }, + + { + "weapon_M4", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_M4, + //"misc/w_pkup.wav", + NULL, + "models/weapons/g_m4/tris.md2", + 0, + "models/weapons/v_m4/tris.md2", + "w_m4", + M4_NAME, + 0, + 0, + "M4 Clip", + IT_WEAPON, + NULL, + 0, + "weapons/m4a1fire.wav weapons/m4a1in.wav weapons/m4a1out.wav weapons/m4a1slide.wav" + }, + { + "weapon_M3", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_M3, + //"misc/w_pkup.wav", + NULL, + "models/weapons/g_shotg/tris.md2", + 0, + "models/weapons/v_shotg/tris.md2", + "w_super90", + M3_NAME, + 0, + 0, + "12 Gauge Shells", + IT_WEAPON, + NULL, + 0, + "weapons/m3in.wav weapons/shotgf1b.wav" + }, + { + "weapon_HC", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_HC, + //"misc/w_pkup.wav", + NULL, + "models/weapons/g_cannon/tris.md2", + 0, + "models/weapons/v_cannon/tris.md2", + "w_cannon", + HC_NAME, + 0, + 0, + "12 Gauge Shells", + IT_WEAPON, + NULL, + 0, + "weapons/cannon_fire.wav weapons/cclose.wav weapons/cin.wav weapons/cout.wav weapons/copen.wav" + }, + { + "weapon_Sniper", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Sniper, + //"misc/w_pkup.wav", + NULL, + "models/weapons/g_sniper/tris.md2", + 0, + "models/weapons/v_sniper/tris.md2", + "w_sniper", + SNIPER_NAME, + 0, + 0, + "AP Sniper Ammo", + IT_WEAPON, + NULL, + 0, + "weapons/ssgbolt.wav weapons/ssgfire.wav weapons/ssgin.wav misc/lensflik.wav" + }, + { + "weapon_Dual", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Dual, + //"misc/w_pkup.wav", + NULL, + "models/weapons/g_dual/tris.md2", + 0, + "models/weapons/v_dual/tris.md2", + "w_akimbo", + DUAL_NAME, + 0, + 0, + "Pistol Clip", + IT_WEAPON, + NULL, + 0, + "weapons/mk23fire.wav weapons/mk23in.wav weapons/mk23out.wav weapons/mk23slap.wav weapons/mk23slide.wav" + }, + { + "weapon_Knife", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Knife, + NULL, + "models/objects/knife/tris.md2", + 0, + "models/weapons/v_knife/tris.md2", + "w_knife", + KNIFE_NAME, + 0, + 0, + NULL, + IT_WEAPON, + NULL, + 0, + "weapons/throw.wav weapons/stab.wav weapons/swish.wav weapons/clank.wav" + }, + { + "weapon_Grenade", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Gas, + NULL, + "models/objects/grenade2/tris.md2", + 0, + "models/weapons/v_handgr/tris.md2", + "a_m61frag", + GRENADE_NAME, + 0, + 0, + NULL, + IT_WEAPON, + NULL, + 0, + "misc/grenade.wav" + }, + + + + // + // AMMO ITEMS + // + +/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_shells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/shells/medium/tris.md2", 0, + NULL, +/* icon */ "a_shells", +/* pickup */ "Shells", +/* width */ 3, + 10, + NULL, + 0,//IT_AMMO, + NULL, + AMMO_SHELLS, +/* precache */ "" + }, + +/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_bullets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/bullets/medium/tris.md2", 0, + NULL, +/* icon */ "a_bullets", +/* pickup */ "Bullets", +/* width */ 3, + 50, + NULL, + 0,//IT_AMMO, + NULL, + AMMO_BULLETS, +/* precache */ "" + }, + +/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_cells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/cells/medium/tris.md2", 0, + NULL, +/* icon */ "a_cells", +/* pickup */ "Cells", +/* width */ 3, + 50, + NULL, + 0,//IT_AMMO, + NULL, + AMMO_CELLS, +/* precache */ "" + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_rockets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/rockets/medium/tris.md2", 0, + NULL, +/* icon */ "a_rockets", +/* pickup */ "Rockets", +/* width */ 3, + 5, + NULL, + 0,//IT_AMMO, + NULL, + AMMO_ROCKETS, +/* precache */ "" + }, + +/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_slugs", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/slugs/medium/tris.md2", 0, + NULL, +/* icon */ "a_slugs", +/* pickup */ "Slugs", +/* width */ 3, + 10, + NULL, + 0,//IT_AMMO, + NULL, + AMMO_SLUGS, +/* precache */ "" + }, + +// zucc new ammo + { + "ammo_clip", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + //"misc/click.wav", + NULL, + "models/items/ammo/clip/tris.md2", 0, + NULL, +/* icon */ "a_clip", +/* pickup */ "Pistol Clip", +/* width */ 3, + 1, + NULL, + IT_AMMO, + NULL, + AMMO_BULLETS, +/* precache */ "" + }, + + { + "ammo_mag", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + //"misc/click.wav", + NULL, + "models/items/ammo/mag/tris.md2", 0, + NULL, +/* icon */ "a_mag", +/* pickup */ "Machinegun Magazine", +/* width */ 3, + 1, + NULL, + IT_AMMO, + NULL, + AMMO_ROCKETS, +/* precache */ "" + }, + + { + "ammo_m4", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + //"misc/click.wav", + NULL, + "models/items/ammo/m4/tris.md2", 0, + NULL, +/* icon */ "a_m4", +/* pickup */ "M4 Clip", +/* width */ 3, + 1, + NULL, + IT_AMMO, + NULL, + AMMO_CELLS, +/* precache */ "" + }, + { + "ammo_m3", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + //"misc/click.wav", + NULL, + "models/items/ammo/shells/medium/tris.md2", 0, + NULL, +/* icon */ "a_shells", +/* pickup */ "12 Gauge Shells", +/* width */ 3, + 7, + NULL, + IT_AMMO, + NULL, + AMMO_SHELLS, +/* precache */ "" + }, + { + "ammo_sniper", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + //"misc/click.wav", + NULL, + "models/items/ammo/sniper/tris.md2", 0, + NULL, +/* icon */ "a_bullets", +/* pickup */ "AP Sniper Ammo", +/* width */ 3, + 10, + NULL, + IT_AMMO, + NULL, + AMMO_SLUGS, +/* precache */ "" + }, + + + + // + // POWERUP ITEMS + // + + // zucc the main items + { + "item_quiet", + Pickup_Special, + NULL, + Drop_Special, + NULL, + "misc/screw.wav", + "models/items/quiet/tris.md2", + 0, + NULL, + /* icon */ "p_silencer", + /* pickup */ "Silencer", + /* width */ 2, + 60, + NULL, + IT_ITEM, + NULL, + 0, + /* precache */ "" + }, + + { + "item_slippers", + Pickup_Special, + NULL, + Drop_Special, + NULL, + "misc/veston.wav", // sound + "models/items/slippers/slippers.md2", + 0, + NULL, +/* icon */ "slippers", +/* pickup */ "Stealth Slippers", +/* width */ 2, + 60, + NULL, + IT_ITEM, + NULL, + 0, + /* precache */ "" + }, + + { + "item_band", + Pickup_Special, + NULL, + Drop_Special, + NULL, + "misc/veston.wav", // sound + "models/items/band/tris.md2", + 0, + NULL, + /* icon */ "p_bandolier", + /* pickup */ "Bandolier", + /* width */ 2, + 60, + NULL, + IT_ITEM, + NULL, + 0, + /* precache */ "" + }, + { + "item_vest", + Pickup_Special, + NULL, + Drop_Special, + NULL, + "misc/veston.wav", // sound + "models/items/armor/jacket/tris.md2", + 0, + NULL, + /* icon */ "i_jacketarmor", + /* pickup */ "Kevlar Vest", + /* width */ 2, + 60, + NULL, + IT_ITEM, + NULL, + 0, + /* precache */ "" + }, + { + "item_lasersight", + Pickup_Special, + NULL, //SP_LaserSight, + Drop_Special, + NULL, + "misc/lasersight.wav", // sound + "models/items/laser/tris.md2", + 0, + NULL, + /* icon */ "p_laser", + /* pickup */ "Lasersight", + /* width */ 2, + 60, + NULL, + IT_ITEM, + NULL, + 0, + /* precache */ "" + }, + + +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_quad", + Pickup_Powerup, + Use_Quad, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/quaddama/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_quad", +/* pickup */ "Quad Damage", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + NULL, + 0, +/* precache */ "items/damage.wav items/damage2.wav items/damage3.wav" + }, + +/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_invulnerability", + Pickup_Powerup, + Use_Invulnerability, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/invulner/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_invulnerability", +/* pickup */ "Invulnerability", +/* width */ 2, + 300, + NULL, + IT_POWERUP, + NULL, + 0, +/* precache */ "items/protect.wav items/protect2.wav items/protect4.wav" + }, + +/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_silencer", + Pickup_Powerup, + Use_Silencer, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/silencer/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_silencer", +/* pickup */ "Silencer", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_breather", + Pickup_Powerup, + Use_Breather, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/breather/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_rebreather", +/* pickup */ "Rebreather", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_enviro", + Pickup_Powerup, + Use_Envirosuit, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/enviro/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_envirosuit", +/* pickup */ "Environment Suit", +/* width */ 2, + 60, + NULL, + 0,//IT_STAY_COOP|IT_POWERUP, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) +Special item that gives +2 to maximum health +*/ + { + "item_ancient_head", + Pickup_AncientHead, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/c_head/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_fixme", +/* pickup */ "Ancient Head", +/* width */ 2, + 60, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) +gives +1 to maximum health +*/ + { + "item_adrenaline", + Pickup_Adrenaline, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/adrenal/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_adrenaline", +/* pickup */ "Adrenaline", +/* width */ 2, + 60, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_bandolier", + Pickup_Bandolier, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/band/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_bandolier", +/* pickup */ "NogBandolier", +/* width */ 2, + 60, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_pack", + Pickup_Pack, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/pack/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_pack", +/* pickup */ "Ammo Pack", +/* width */ 2, + 180, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + + // + // KEYS + // +/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) +key for computer centers +*/ + { + "key_data_cd", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/data_cd/tris.md2", EF_ROTATE, + NULL, + "k_datacd", + "Data CD", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH +warehouse circuits +*/ + { + "key_power_cube", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/power/tris.md2", EF_ROTATE, + NULL, + "k_powercube", + "Power Cube", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the entrance of jail3 +*/ + { + "key_pyramid", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pyramid/tris.md2", EF_ROTATE, + NULL, + "k_pyramid", + "Pyramid Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the city computer +*/ + { + "key_data_spinner", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/spinner/tris.md2", EF_ROTATE, + NULL, + "k_dataspin", + "Data Spinner", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) +security pass for the security level +*/ + { + "key_pass", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pass/tris.md2", EF_ROTATE, + NULL, + "k_security", + "Security Pass", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - blue +*/ + { + "key_blue_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/key/tris.md2", EF_ROTATE, + NULL, + "k_bluekey", + "Blue Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - red +*/ + { + "key_red_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/red_key/tris.md2", EF_ROTATE, + NULL, + "k_redkey", + "Red Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_commander_head", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/monsters/commandr/head/tris.md2", EF_GIB, + NULL, +/* icon */ "k_comhead", +/* pickup */ "Commander's Head", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_airstrike_target", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/target/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_airstrike", +/* pickup */ "Airstrike Marker", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + NULL, + 0, +/* precache */ "" + }, + + { + NULL, + Pickup_Health, + NULL, + NULL, + NULL, + "items/pkup.wav", + NULL, 0, + NULL, +/* icon */ "i_health", +/* pickup */ "Health", +/* width */ 3, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + + // end of list marker + {NULL} +}; + + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health (edict_t *self) +{ + if ( 1 ) //deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/medium/tris.md2"; + self->count = 10; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/n_health.wav"); +} + +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_small (edict_t *self) +{ + if ( 1) //deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/stimpack/tris.md2"; + self->count = 2; + SpawnItem (self, FindItem ("Health")); + self->style = HEALTH_IGNORE_MAX; + gi.soundindex ("items/s_health.wav"); +} + +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_large (edict_t *self) +{ + if ( 1 )//deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/large/tris.md2"; + self->count = 25; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/l_health.wav"); +} + +/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_mega (edict_t *self) +{ + if ( 1 )//deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/mega_h/tris.md2"; + self->count = 100; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/m_health.wav"); + self->style = HEALTH_IGNORE_MAX|HEALTH_TIMED; +} + + +void InitItems (void) +{ + game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1; +} + + + +/* +=============== +SetItemNames + +Called by worldspawn +=============== +*/ +void SetItemNames (void) +{ + int i; + gitem_t *it; + + for (i=0 ; ipickup_name); + } + + jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor")); + combat_armor_index = ITEM_INDEX(FindItem("Combat Armor")); + body_armor_index = ITEM_INDEX(FindItem("Body Armor")); + power_screen_index = ITEM_INDEX(FindItem("Power Screen")); + power_shield_index = ITEM_INDEX(FindItem("Power Shield")); +} diff --git a/g_local.h b/g_local.h new file mode 100644 index 0000000..a5485e6 --- /dev/null +++ b/g_local.h @@ -0,0 +1,1464 @@ +#ifndef __G_LOCAL_H +#define __G_LOCAL_H +// g_local.h -- local definitions for game module + +#include "q_shared.h" + +// define GAME_INCLUDE so that game.h does not define the +// short, server-visible gclient_t and edict_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "game.h" +// RiEvEr LTKBOT +#define LTKVERSION "LTK 1.10 (Fog) Release" +#include "acesrc/botnav.h" +// END LTKBOT + +//FIREBLADE +#include "a_team.h" +#include "a_game.h" +#include "a_menu.h" +#include "a_radio.h" +//FIREBLADE + +// the "gameversion" client command will print this plus compile date +#define GAMEVERSION "action" + +// protocol bytes that can be directly added to messages +#define svc_muzzleflash 1 +#define svc_muzzleflash2 2 +#define svc_temp_entity 3 +#define svc_layout 4 +#define svc_inventory 5 +#define svc_stufftext 11 + +//================================================================== + +// view pitching times +#define DAMAGE_TIME 0.5 +#define FALL_TIME 0.3 + + +// edict->spawnflags +// these are set with checkboxes on each entity in the map editor +#define SPAWNFLAG_NOT_EASY 0x00000100 +#define SPAWNFLAG_NOT_MEDIUM 0x00000200 +#define SPAWNFLAG_NOT_HARD 0x00000400 +#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 +#define SPAWNFLAG_NOT_COOP 0x00001000 + +// edict->flags +#define FL_FLY 0x00000001 +#define FL_SWIM 0x00000002 // implied immunity to drowining +#define FL_IMMUNE_LASER 0x00000004 +#define FL_INWATER 0x00000008 +#define FL_GODMODE 0x00000010 +#define FL_NOTARGET 0x00000020 +#define FL_IMMUNE_SLIME 0x00000040 +#define FL_IMMUNE_LAVA 0x00000080 +#define FL_PARTIALGROUND 0x00000100 // not all corners are valid +#define FL_WATERJUMP 0x00000200 // player jumping out of water +#define FL_TEAMSLAVE 0x00000400 // not the first on the team +#define FL_NO_KNOCKBACK 0x00000800 +#define FL_POWER_ARMOR 0x00001000 // power armor (if any) is active +#define FL_RESPAWN 0x80000000 // used for item respawning + + +#define FRAMETIME 0.1 + +// memory tags to allow dynamic memory to be cleaned up +#define TAG_GAME 765 // clear when unloading the dll +#define TAG_LEVEL 766 // clear when loading a new level + + +#define MELEE_DISTANCE 80 + +#define BODY_QUEUE_SIZE 8 + +typedef enum +{ + DAMAGE_NO, + DAMAGE_YES, // will take damage if hit + DAMAGE_AIM // auto targeting recognizes this +} damage_t; + +typedef enum +{ + WEAPON_READY, + WEAPON_ACTIVATING, + WEAPON_DROPPING, + WEAPON_FIRING, +// zucc + WEAPON_END_MAG, + WEAPON_RELOADING, + WEAPON_BURSTING, + WEAPON_BUSY, // used by sniper rifle when engaging zoom + // if I want to make laser sight toggle on/off + // this could be used for that too... + WEAPON_BANDAGING +} weaponstate_t; + +typedef enum +{ + AMMO_BULLETS, + AMMO_SHELLS, + AMMO_ROCKETS, + AMMO_GRENADES, + AMMO_CELLS, + AMMO_SLUGS +} ammo_t; + + +//deadflag +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 +#define DEAD_RESPAWNABLE 3 + +//range +#define RANGE_MELEE 0 +#define RANGE_NEAR 1 +#define RANGE_MID 2 +#define RANGE_FAR 3 + +//gib types +#define GIB_ORGANIC 0 +#define GIB_METALLIC 1 + +//monster ai flags +#define AI_STAND_GROUND 0x00000001 +#define AI_TEMP_STAND_GROUND 0x00000002 +#define AI_SOUND_TARGET 0x00000004 +#define AI_LOST_SIGHT 0x00000008 +#define AI_PURSUIT_LAST_SEEN 0x00000010 +#define AI_PURSUE_NEXT 0x00000020 +#define AI_PURSUE_TEMP 0x00000040 +#define AI_HOLD_FRAME 0x00000080 +#define AI_GOOD_GUY 0x00000100 +#define AI_BRUTAL 0x00000200 +#define AI_NOSTEP 0x00000400 +#define AI_DUCKED 0x00000800 +#define AI_COMBAT_POINT 0x00001000 +#define AI_MEDIC 0x00002000 +#define AI_RESURRECTING 0x00004000 + +//monster attack state +#define AS_STRAIGHT 1 +#define AS_SLIDING 2 +#define AS_MELEE 3 +#define AS_MISSILE 4 + +// armor types +#define ARMOR_NONE 0 +#define ARMOR_JACKET 1 +#define ARMOR_COMBAT 2 +#define ARMOR_BODY 3 +#define ARMOR_SHARD 4 + +// power armor types +#define POWER_ARMOR_NONE 0 +#define POWER_ARMOR_SCREEN 1 +#define POWER_ARMOR_SHIELD 2 + +// handedness values +#define RIGHT_HANDED 0 +#define LEFT_HANDED 1 +#define CENTER_HANDED 2 + + +// game.serverflags values +#define SFL_CROSS_TRIGGER_1 0x00000001 +#define SFL_CROSS_TRIGGER_2 0x00000002 +#define SFL_CROSS_TRIGGER_3 0x00000004 +#define SFL_CROSS_TRIGGER_4 0x00000008 +#define SFL_CROSS_TRIGGER_5 0x00000010 +#define SFL_CROSS_TRIGGER_6 0x00000020 +#define SFL_CROSS_TRIGGER_7 0x00000040 +#define SFL_CROSS_TRIGGER_8 0x00000080 +#define SFL_CROSS_TRIGGER_MASK 0x000000ff + + +// noise types for PlayerNoise +#define PNOISE_SELF 0 +#define PNOISE_WEAPON 1 +#define PNOISE_IMPACT 2 + + +// edict->movetype values +typedef enum +{ +MOVETYPE_NONE, // never moves +MOVETYPE_NOCLIP, // origin and angles change with no interaction +MOVETYPE_PUSH, // no clip to world, push on box contact +MOVETYPE_STOP, // no clip to world, stops on box contact + +MOVETYPE_WALK, // gravity +MOVETYPE_STEP, // gravity, special edge handling +MOVETYPE_FLY, +MOVETYPE_TOSS, // gravity +MOVETYPE_FLYMISSILE, // extra size to monsters +MOVETYPE_BOUNCE, +MOVETYPE_BLOOD +} movetype_t; + + + +typedef struct +{ + int base_count; + int max_count; + float normal_protection; + float energy_protection; + int armor; +} gitem_armor_t; + + +// gitem_t->flags +#define IT_WEAPON 1 // use makes active weapon +#define IT_AMMO 2 +#define IT_ARMOR 4 +#define IT_STAY_COOP 8 +#define IT_KEY 16 +#define IT_POWERUP 32 +#define IT_ITEM 64 + +typedef struct gitem_s +{ + char *classname; // spawning name + qboolean (*pickup)(struct edict_s *ent, struct edict_s *other); + void (*use)(struct edict_s *ent, struct gitem_s *item); + void (*drop)(struct edict_s *ent, struct gitem_s *item); + void (*weaponthink)(struct edict_s *ent); + char *pickup_sound; + char *world_model; + int world_model_flags; + char *view_model; + + // client side info + char *icon; + char *pickup_name; // for printing on pickup + int count_width; // number of digits to display by icon + + int quantity; // for ammo how much, for weapons how much is used per shot + char *ammo; // for weapons + int flags; // IT_* flags + + void *info; + int tag; + + char *precaches; // string of all models, sounds, and images this item will use +} gitem_t; + + + +// +// this structure is left intact through an entire game +// it should be initialized at dll load time, and read/written to +// the server.ssv file for savegames +// +typedef struct +{ + char helpmessage1[512]; + char helpmessage2[512]; + int helpchanged; // flash F1 icon if non 0, play sound + // and increment only if 1, 2, or 3 + + gclient_t *clients; // [maxclients] + + // can't store spawnpoint in level, because + // it would get overwritten by the savegame restore + char spawnpoint[512]; // needed for coop respawns + + // store latched cvars here that we want to get at often + int maxclients; + int maxentities; + + // cross level triggers + int serverflags; + + // items + int num_items; + + qboolean autosaved; +} game_locals_t; + + +// +// this structure is cleared as each map is entered +// it is read/written to the level.sav file for savegames +// +typedef struct +{ + int framenum; + float time; + + char level_name[MAX_QPATH]; // the descriptive name (Outer Base, etc) + char mapname[MAX_QPATH]; // the server name (base1, etc) + char nextmap[MAX_QPATH]; // go here when fraglimit is hit + + // intermission state + float intermissiontime; // time the intermission was started + char *changemap; + int exitintermission; + vec3_t intermission_origin; + vec3_t intermission_angle; + + edict_t *sight_client; // changed once each frame for coop games + + edict_t *sight_entity; + int sight_entity_framenum; + edict_t *sound_entity; + int sound_entity_framenum; + edict_t *sound2_entity; + int sound2_entity_framenum; + + int pic_health; + + int total_secrets; + int found_secrets; + + int total_goals; + int found_goals; + + int total_monsters; + int killed_monsters; + + edict_t *current_entity; // entity running from G_RunFrame + int body_que; // dead bodies + + int power_cubes; // ugly necessity for coop + int specspawn; // determines if initial spawning has occured +} level_locals_t; + + +// spawn_temp_t is only used to hold entity field values that +// can be set from the editor, but aren't actualy present +// in edict_t during gameplay +typedef struct +{ + // world vars + char *sky; + float skyrotate; + vec3_t skyaxis; + char *nextmap; + + int lip; + int distance; + int height; + char *noise; + float pausetime; + char *item; + char *gravity; + + float minyaw; + float maxyaw; + float minpitch; + float maxpitch; +} spawn_temp_t; + + +typedef struct +{ + // fixed data + vec3_t start_origin; + vec3_t start_angles; + vec3_t end_origin; + vec3_t end_angles; + + int sound_start; + int sound_middle; + int sound_end; + + float accel; + float speed; + float decel; + float distance; + + float wait; + + // state data + int state; + vec3_t dir; + float current_speed; + float move_speed; + float next_speed; + float remaining_distance; + float decel_distance; + void (*endfunc)(edict_t *); +} moveinfo_t; + + +typedef struct +{ + void (*aifunc)(edict_t *self, float dist); + float dist; + void (*thinkfunc)(edict_t *self); +} mframe_t; + +typedef struct +{ + int firstframe; + int lastframe; + mframe_t *frame; + void (*endfunc)(edict_t *self); +} mmove_t; + +typedef struct +{ + mmove_t *currentmove; + int aiflags; + int nextframe; + float scale; + + void (*stand)(edict_t *self); + void (*idle)(edict_t *self); + void (*search)(edict_t *self); + void (*walk)(edict_t *self); + void (*run)(edict_t *self); + void (*dodge)(edict_t *self, edict_t *other, float eta); + void (*attack)(edict_t *self); + void (*melee)(edict_t *self); + void (*sight)(edict_t *self, edict_t *other); + qboolean (*checkattack)(edict_t *self); + + float pausetime; + float attack_finished; + + vec3_t saved_goal; + float search_time; + float trail_time; + vec3_t last_sighting; + int attack_state; + int lefty; + float idle_time; + int linkcount; + + int power_armor_type; + int power_armor_power; +} monsterinfo_t; + + + +extern game_locals_t game; +extern level_locals_t level; +extern game_import_t gi; +extern game_export_t globals; +extern spawn_temp_t st; + +extern int sm_meat_index; +extern int snd_fry; + +extern int jacket_armor_index; +extern int combat_armor_index; +extern int body_armor_index; + + +// means of death +#define MOD_UNKNOWN 0 +#define MOD_BLASTER 1 +#define MOD_SHOTGUN 2 +#define MOD_SSHOTGUN 3 +#define MOD_MACHINEGUN 4 +#define MOD_CHAINGUN 5 +#define MOD_GRENADE 6 +#define MOD_G_SPLASH 7 +#define MOD_ROCKET 8 +#define MOD_R_SPLASH 9 +#define MOD_HYPERBLASTER 10 +#define MOD_RAILGUN 11 +#define MOD_BFG_LASER 12 +#define MOD_BFG_BLAST 13 +#define MOD_BFG_EFFECT 14 +#define MOD_HANDGRENADE 15 +#define MOD_HG_SPLASH 16 +#define MOD_WATER 17 +#define MOD_SLIME 18 +#define MOD_LAVA 19 +#define MOD_CRUSH 20 +#define MOD_TELEFRAG 21 +#define MOD_FALLING 22 +#define MOD_SUICIDE 23 +#define MOD_HELD_GRENADE 24 +#define MOD_EXPLOSIVE 25 +#define MOD_BARREL 26 +#define MOD_BOMB 27 +#define MOD_EXIT 28 +#define MOD_SPLASH 29 +#define MOD_TARGET_LASER 30 +#define MOD_TRIGGER_HURT 31 +#define MOD_HIT 32 +#define MOD_TARGET_BLASTER 33 +//zucc +#define MOD_MK23 34 +#define MOD_MP5 35 +#define MOD_M4 36 +#define MOD_M3 37 +#define MOD_HC 38 +#define MOD_SNIPER 39 +#define MOD_DUAL 40 +#define MOD_KNIFE 41 +#define MOD_KNIFE_THROWN 42 +#define MOD_BLEEDING 43 +#define MOD_GAS 44 +#define MOD_KICK 45 +#define MOD_FRIENDLY_FIRE 0x8000000 + + +extern int meansOfDeath; +// zucc for hitlocation of death +extern int locOfDeath; +// stop an armor piercing round that hits a vest +extern int stopAP; + +extern edict_t *g_edicts; + +#define FOFS(x) (int)&(((edict_t *)0)->x) +#define STOFS(x) (int)&(((spawn_temp_t *)0)->x) +#define LLOFS(x) (int)&(((level_locals_t *)0)->x) +#define CLOFS(x) (int)&(((gclient_t *)0)->x) + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0 * (random() - 0.5)) + +extern cvar_t *maxentities; +extern cvar_t *deathmatch; +extern cvar_t *coop; +extern cvar_t *dmflags; +//FIREBLADE +extern cvar_t *needpass; +extern cvar_t *hostname; +extern cvar_t *teamplay; +extern cvar_t *radiolog; +extern cvar_t *motd_time; +extern cvar_t *actionmaps; +extern cvar_t *roundtimelimit; +extern cvar_t *maxteamkills; +extern cvar_t *tkbanrounds; +extern cvar_t *twbanrounds; +extern cvar_t *limchasecam; +extern cvar_t *roundlimit; +extern cvar_t *skipmotd; +extern cvar_t *nohud; +extern cvar_t *noscore; +extern cvar_t *actionversion; +//FIREBLADE +extern cvar_t *skill; +extern cvar_t *fraglimit; +extern cvar_t *timelimit; +extern cvar_t *password; +extern cvar_t *g_select_empty; +extern cvar_t *dedicated; + +extern cvar_t *filterban; +extern cvar_t *flood_msgs; +extern cvar_t *flood_persecond; +extern cvar_t *flood_waitdelay; + +extern cvar_t *sv_gravity; +extern cvar_t *sv_maxvelocity; + +extern cvar_t *gun_x, *gun_y, *gun_z; +extern cvar_t *sv_rollspeed; +extern cvar_t *sv_rollangle; + +extern cvar_t *run_pitch; +extern cvar_t *run_roll; +extern cvar_t *bob_up; +extern cvar_t *bob_pitch; +extern cvar_t *bob_roll; + +extern cvar_t *sv_cheats; +extern cvar_t *maxclients; + +// zucc server variables + +extern cvar_t *unique_weapons; +extern cvar_t *unique_items; +extern cvar_t *ir; //toggles if bandolier works as infra-red sensor +extern cvar_t *knifelimit; +extern cvar_t *tgren; +extern cvar_t *allweapon; +extern cvar_t *allitem; + +// zucc from action +extern cvar_t *sv_shelloff; +extern cvar_t *splatlimit; +extern cvar_t *bholelimit; + +#define world (&g_edicts[0]) + +// item spawnflags +#define ITEM_TRIGGER_SPAWN 0x00000001 +#define ITEM_NO_TOUCH 0x00000002 +// 6 bits reserved for editor flags +// 8 bits used as power cube id bits for coop games +#define DROPPED_ITEM 0x00010000 +#define DROPPED_PLAYER_ITEM 0x00020000 +#define ITEM_TARGETS_USED 0x00040000 + +// +// fields are needed for spawning from the entity string +// and saving / loading games +// +#define FFL_SPAWNTEMP 1 +#define FFL_NOSPAWN 2 + +typedef enum { + F_INT, + F_FLOAT, + F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + F_GSTRING, // string on disk, pointer in memory, TAG_GAME + F_VECTOR, + F_ANGLEHACK, + F_EDICT, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_FUNCTION, + F_MMOVE, + F_IGNORE +} fieldtype_t; + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} field_t; + + +extern field_t fields[]; +extern gitem_t itemlist[]; + + +// +// g_cmds.c +// +void Cmd_Help_f (edict_t *ent); +void Cmd_Score_f (edict_t *ent); + +// +// g_items.c +// +void PrecacheItem (gitem_t *it); +void InitItems (void); +void SetItemNames (void); +gitem_t *FindItem (char *pickup_name); +gitem_t *FindItemByClassname (char *classname); +#define ITEM_INDEX(x) ((x)-itemlist) +edict_t *Drop_Item (edict_t *ent, gitem_t *item); +void SetRespawn (edict_t *ent, float delay); +void ChangeWeapon (edict_t *ent); +void SpawnItem (edict_t *ent, gitem_t *item); +void Think_Weapon (edict_t *ent); +int ArmorIndex (edict_t *ent); +int PowerArmorType (edict_t *ent); +gitem_t *GetItemByIndex (int index); +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count); +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +// +// g_utils.c +// +qboolean KillBox (edict_t *ent); +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +edict_t *G_Find (edict_t *from, int fieldofs, char *match); +edict_t *findradius (edict_t *from, vec3_t org, float rad); +edict_t *G_PickTarget (char *targetname); +void G_UseTargets (edict_t *ent, edict_t *activator); +void G_SetMovedir (vec3_t angles, vec3_t movedir); + +void G_InitEdict (edict_t *e); +edict_t *G_Spawn (void); +void G_FreeEdict (edict_t *e); + +void G_TouchTriggers (edict_t *ent); +void G_TouchSolids (edict_t *ent); + +char *G_CopyString (char *in); + +float *tv (float x, float y, float z); +char *vtos (vec3_t v); + +float vectoyaw (vec3_t vec); +void vectoangles (vec3_t vec, vec3_t angles); + +// +// g_combat.c +// +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2); +qboolean CanDamage (edict_t *targ, edict_t *inflictor); +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod); +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod); + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_ENERGY 0x00000004 // damage is from an energy based weapon +#define DAMAGE_NO_KNOCKBACK 0x00000008 // do not affect velocity, just view angles +#define DAMAGE_BULLET 0x00000010 // damage is from a bullet (used for ricochets) +#define DAMAGE_NO_PROTECTION 0x00000020 // armor, shields, invulnerability, and godmode have no effect + +#define DEFAULT_BULLET_HSPREAD 300 +#define DEFAULT_BULLET_VSPREAD 500 +#define DEFAULT_SHOTGUN_HSPREAD 1000 +#define DEFAULT_SHOTGUN_VSPREAD 500 +#define DEFAULT_DEATHMATCH_SHOTGUN_COUNT 12 +#define DEFAULT_SHOTGUN_COUNT 12 +#define DEFAULT_SSHOTGUN_COUNT 20 + +// +// g_monster.c +// +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype); +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); +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect); +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype); +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype); +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype); +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype); +void M_droptofloor (edict_t *ent); +void monster_think (edict_t *self); +void walkmonster_start (edict_t *self); +void swimmonster_start (edict_t *self); +void flymonster_start (edict_t *self); +void AttackFinished (edict_t *self, float time); +void monster_death_use (edict_t *self); +void M_CatagorizePosition (edict_t *ent); +qboolean M_CheckAttack (edict_t *self); +void M_FlyCheck (edict_t *self); +void M_CheckGround (edict_t *ent); + +// +// g_misc.c +// +void ThrowHead (edict_t *self, char *gibname, int damage, int type); +void ThrowClientHead (edict_t *self, int damage); +void ThrowGib (edict_t *self, char *gibname, int damage, int type); +void BecomeExplosion1(edict_t *self); + +// +// g_ai.c +// +void AI_SetSightClient (void); + +void ai_stand (edict_t *self, float dist); +void ai_move (edict_t *self, float dist); +void ai_walk (edict_t *self, float dist); +void ai_turn (edict_t *self, float dist); +void ai_run (edict_t *self, float dist); +void ai_charge (edict_t *self, float dist); +int range (edict_t *self, edict_t *other); + +void FoundTarget (edict_t *self); +qboolean infront (edict_t *self, edict_t *other); +qboolean visible (edict_t *self, edict_t *other); +qboolean FacingIdeal(edict_t *self); + +// +// g_weapon.c +// +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin); +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick); +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod); +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod); +void fire_blaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper); +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick); +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius); + +// zucc +int knife_attack ( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick); +void knife_throw (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed ); +void fire_bullet_sparks (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod); +void fire_bullet_sniper (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod); +void fire_grenade3 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed ); + +// +// g_ptrail.c +// +void PlayerTrail_Init (void); +void PlayerTrail_Add (vec3_t spot); +void PlayerTrail_New (vec3_t spot); +edict_t *PlayerTrail_PickFirst (edict_t *self); +edict_t *PlayerTrail_PickNext (edict_t *self); +edict_t *PlayerTrail_LastSpot (void); + + +// +// g_client.c +// +void respawn (edict_t *ent); +void BeginIntermission (edict_t *targ); +void PutClientInServer (edict_t *ent); +void InitClientPersistant (gclient_t *client); +void InitClientResp (gclient_t *client); +void InitBodyQue (void); +void ClientBeginServerFrame (edict_t *ent); + +// +// g_player.c +// +void player_pain (edict_t *self, edict_t *other, float kick, int damage); +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +// +// g_svcmds.c +// +void ServerCommand (void); +qboolean SV_FilterPacket (char *from); + +// +// p_view.c +// +void ClientEndServerFrame (edict_t *ent); + +// +// p_hud.c +// +void MoveClientToIntermission (edict_t *client); +void G_SetStats (edict_t *ent); +void G_SetSpectatorStats (edict_t *ent); +void G_CheckChaseStats (edict_t *ent); +void ValidateSelectedItem (edict_t *ent); +void DeathmatchScoreboardMessage (edict_t *client, edict_t *killer); + +// +// g_pweapon.c +// +void PlayerNoise(edict_t *who, vec3_t where, int type); + +// +// m_move.c +// +qboolean M_CheckBottom (edict_t *ent); +qboolean M_walkmove (edict_t *ent, float yaw, float dist); +void M_MoveToGoal (edict_t *ent, float dist); +void M_ChangeYaw (edict_t *ent); + +// +// g_phys.c +// +void G_RunEntity (edict_t *ent); + +// +// g_main.c +// +void SaveClientData (void); +void FetchClientEntData (edict_t *ent); + +// +// g_chase.c +// +void UpdateChaseCam(edict_t *ent); +int ChaseTargetGone(edict_t *ent); +void ChaseNext(edict_t *ent); +void ChasePrev(edict_t *ent); +void GetChaseTarget(edict_t *ent); + +//============================================================================ + +// client_t->anim_priority +#define ANIM_BASIC 0 // stand / run +#define ANIM_WAVE 1 +#define ANIM_JUMP 2 +#define ANIM_PAIN 3 +#define ANIM_ATTACK 4 +#define ANIM_DEATH 5 +// in 3.20 there is #define ANIM_REVERSE 6 -FB +// zucc vwep - based on info from Hentai +#define ANIM_REVERSE -1 + +// client data that stays across multiple level loads +typedef struct +{ + char userinfo[MAX_INFO_STRING]; + char netname[16]; + int hand; + + qboolean connected; // a loadgame will leave valid entities that + // just don't have a connection yet + + // values saved and restored from edicts when changing levels + int health; + int max_health; + qboolean powerArmorActive; + + int selected_item; + int inventory[MAX_ITEMS]; + + // ammo capacities + int max_bullets; + int max_shells; + int max_rockets; + int max_grenades; + int max_cells; + int max_slugs; + + gitem_t *weapon; + gitem_t *lastweapon; + + int power_cubes; // used for tracking the cubes in coop games + int score; // for calculating total unit score in coop games + +//FIREBLADE + qboolean spectator; + int firing_style; +//FIREBLADE +} client_persistant_t; + +// client data that stays across deathmatch respawns +typedef struct +{ + client_persistant_t coop_respawn; // what to set client->pers to on a respawn + int enterframe; // level.framenum the client entered the game + int score; // frags, etc + vec3_t cmd_angles; // angles sent over in the last command + int game_helpchanged; + int helpchanged; + int sniper_mode; //level of zoom + int kills; // real kills + int damage_dealt; // keep track of damage dealt by player to other players + int streak; // kills in a row + gitem_t *item; // item for teamplay + gitem_t *weapon; // weapon for teamplay +//FIREBLADE + int team; // team the player is on + int joined_team; // last frame # at which the player joined a team + +// radio/partners stuff... + int radio_delay; + radio_queue_entry_t radio_queue[MAX_RADIO_QUEUE_SIZE]; + int radio_queue_size; + edict_t *radio_partner; // current partner + edict_t *partner_last_offered_to; // last person I offered a partnership to + edict_t *partner_last_offered_from; // last person I received a partnership offer from + edict_t *partner_last_denied_from; // last person I denied a partnership offer from +// end of radio/partners stuff... + + int motd_refreshes; + int last_motd_refresh; + edict_t *last_chase_target; // last person they chased, to resume at the same place later... +//FIREBLADE +//Action + int mk23_mode; // firing mode, semi or auto + int mp5_mode; + int m4_mode; + int knife_mode; + int grenade_mode; + int id; // id command on or off + int ir; // ir on or off (only matters if player has ir device, currently bandolier) + qboolean radio_partner_mode; // 'radio' command using team or partner + qboolean radio_gender; // radiogender + qboolean radio_power_off; // radio_power +//--- +} client_respawn_t; + +// this structure is cleared on each PutClientInServer(), +// except for 'client->pers' +struct gclient_s +{ + // known to server + player_state_t ps; // communicated by server to clients + int ping; + + // private to game + client_persistant_t pers; + client_respawn_t resp; + pmove_state_t old_pmove; // for detecting out-of-pmove changes + + qboolean showscores; // set layout stat +//FIREBLADE + int rate; // their "rate" setting + int scoreboardnum; +//FIREBLADE + qboolean showinventory; // set layout stat + qboolean showhelp; + qboolean showhelpicon; + + int ammo_index; + + int buttons; + int oldbuttons; + int latched_buttons; + + qboolean weapon_thunk; + + gitem_t *newweapon; + + // sum up damage over an entire frame, so + // shotgun blasts give a single big kick + int damage_armor; // damage absorbed by armor + int damage_parmor; // damage absorbed by power armor + int damage_blood; // damage taken out of health + int damage_knockback; // impact damage + vec3_t damage_from; // origin for vector calculation + + float killer_yaw; // when dead, look at killer + + weaponstate_t weaponstate; + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + float v_dmg_roll, v_dmg_pitch, v_dmg_time; // damage kicks + float fall_time, fall_value; // for view drop on fall + float damage_alpha; + float bonus_alpha; + vec3_t damage_blend; + vec3_t v_angle; // aiming direction + float bobtime; // so off-ground doesn't change it + vec3_t oldviewangles; + vec3_t oldvelocity; + + float next_drown_time; + int old_waterlevel; + int breather_sound; + + int machinegun_shots; // for weapon raising + + // animation vars + int anim_end; + int anim_priority; + qboolean anim_duck; + qboolean anim_run; + + // powerup timers + float quad_framenum; + float invincible_framenum; + float breather_framenum; + float enviro_framenum; + + qboolean grenade_blew_up; + float grenade_time; + int silencer_shots; + int weapon_sound; + + float pickup_msg_time; + + float flood_locktill; // locked from talking + float flood_when[10]; // when messages were said + int flood_whenhead; // head pointer for when said + + float respawn_time; // can respawn when time > this +// zucc +// weapon ammo information + int mk23_max; + int mk23_rds; + + int dual_max; + int dual_rds; + int shot_max; + int shot_rds; + int sniper_max; + int sniper_rds; + + int mp5_max; + int mp5_rds; + + int m4_max; + int m4_rds; + + int cannon_max; + int cannon_rds; + int knife_max; + + int grenade_max; + + int curr_weap; // uses NAME_NUM values + +// other + int fired; // keep track of semi auto + int burst; // remember if player is bursting or not + int fast_reload; // for shotgun/sniper rifle + int idle_weapon; // how many frames to keep our weapon idle + int desired_fov; // what fov does the player want? (via zooming) + int unique_weapon_total; + int unique_item_total; + int drop_knife; + int knife_sound; // we attack several times when slashing but only want 1 sound + int no_sniper_display; + int bandaging; + int leg_damage; + int leg_dam_count; + int leg_noise; + int leghits; + int bleeding; //remaining points to bleed away + int bleed_remain; + int bleedloc; + vec3_t bleedloc_offset; // location of bleeding (from origin) + vec3_t bleednorm; + float bleeddelay; // how long until we bleed again + + int bandage_stopped; + + int have_laser; + + + int doortoggle; // set by player with opendoor command + + edict_t* attacker; // keep track of the last person to hit us + int attacker_mod; // and how they hit us + int attacker_loc; // location of the hit + int push_timeout; // timeout for how long an attacker will get fall death credit + + int jumping; + + int reload_attempts; + int weapon_attempts; + + +//FIREBLADE + qboolean inmenu; // in menu + pmenuhnd_t *menu; // current menu + edict_t *chase_target; + qboolean update_chase; + int chase_mode; +//FIREBLADE + +//AZEROV + // Number of team kills this game + int team_kills; +//AZEROV + +//EEK + // Number of teammate woundings this game and a "before attack" tracker + int team_wounds; + int team_wounds_before; + int ff_warning; + + // IP address of this host to be collected at Connection time. + // (getting at it later seems to be unreliable) + char ipaddr[100]; // changed to 100 -FB +//EEK +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; // NULL if not a player + // the server expects the first part + // of gclient_s to be a player_state_t + // but the rest of it is opaque + + 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; + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + + //================================ + int movetype; + int flags; + + char *model; + float freetime; // sv.time when the object was freed + + // + // only used locally in game, not by server + // + char *message; + char *classname; + int spawnflags; + + float timestamp; + + float angle; // set in qe3, -1 = up, -2 = down + char *target; + char *targetname; + char *killtarget; + char *team; + char *pathtarget; + char *deathtarget; + char *combattarget; + edict_t *target_ent; + + float speed, accel, decel; + vec3_t movedir; + vec3_t pos1, pos2; + + vec3_t velocity; + vec3_t avelocity; + int mass; + float air_finished; + float gravity; // per entity gravity multiplier (1.0 is normal) + // use for lowgrav artifact, flares + + edict_t *goalentity; + edict_t *movetarget; + float yaw_speed; + float ideal_yaw; + + float nextthink; + void (*prethink) (edict_t *ent); + void (*think)(edict_t *self); + void (*blocked)(edict_t *self, edict_t *other); //move to moveinfo? + void (*touch)(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); + void (*use)(edict_t *self, edict_t *other, edict_t *activator); + void (*pain)(edict_t *self, edict_t *other, float kick, int damage); + void (*die)(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + + float touch_debounce_time; // are all these legit? do we need more/less of them? + float pain_debounce_time; + float damage_debounce_time; + float fly_sound_debounce_time; //move to clientinfo + float last_move_time; + + int health; + int max_health; + int gib_health; + int deadflag; + qboolean show_hostile; + + float powerarmor_time; + + char *map; // target_changelevel + + int viewheight; // height above origin where eyesight is determined + int takedamage; + int dmg; + int radius_dmg; + float dmg_radius; + int sounds; //make this a spawntemp var? + int count; + + edict_t *chain; + edict_t *enemy; + edict_t *oldenemy; + edict_t *activator; + edict_t *groundentity; + int groundentity_linkcount; + edict_t *teamchain; + edict_t *teammaster; + + edict_t *mynoise; // can go in client only + edict_t *mynoise2; + + int noise_index; + int noise_index2; + float volume; + float attenuation; + + // timing variables + float wait; + float delay; // before firing targets + float random; + + float teleport_time; + + int watertype; + int waterlevel; + + vec3_t move_origin; + vec3_t move_angles; + + // move this to clientinfo? + int light_level; + + int style; // also used as areaportal number + + gitem_t *item; // for bonus items + + // common data blocks + moveinfo_t moveinfo; + monsterinfo_t monsterinfo; + + // laser + edict_t *lasersight; + + // action + qboolean splatted; + int classnum; + +// ACEBOT_ADD + qboolean is_bot; + qboolean is_jumping; + + // For movement + vec3_t move_vector; + float next_move_time; + float wander_timeout; + float suicide_timeout; + +//AQ2 ADD + // Door and pause time stuff. + float last_door_time; // Used to open doors without immediately closing them again! + float teamPauseTime; // To stop the centipede effect and seperate the team out a little + // Path to follow + ltklist_t pathList; // Single linked list of node numbers + float antLastCallTime; // Check for calling complex pathsearcher + // Who killed me? + edict_t *lastkilledby; // Set in ClientObituary... +//AQ2 END + + // For node code + int current_node; // current node + int goal_node; // current goal node + int next_node; // the node that will take us one step closer to our goal + int node_timeout; + int last_node; + int tries; + + // AI related stuff + int weaponchoice; + int equipchoice; + int state; +// ACEBOT_END + +}; + + +//zucc +void LaserSightThink (edict_t *self); +void SP_LaserSight(edict_t *self, gitem_t *item ); +void Cmd_Reload_f (edict_t *ent); +void Cmd_New_Reload_f (edict_t *ent); +void Cmd_New_Weapon_f (edict_t *ent); +void Cmd_Weapon_f ( edict_t *ent ); +void Cmd_OpenDoor_f (edict_t *ent ); +void Cmd_Bandage_f ( edict_t *ent ); +void Cmd_ID_f (edict_t *ent ); +void Cmd_IR_f (edict_t *ent ); +void Cmd_Choose_f (edict_t *ent); + +void DropSpecialWeapon (edict_t *ent ); +void ReadySpecialWeapon(edict_t *ent ); +void DropSpecialItem ( edict_t* ent ); +void Bandage( edict_t* ent ); +// hentai's vwep function added by zucc +void ShowGun(edict_t *ent); + + +// spec functions +void SetupSpecSpawn(void); +void RespawnSpec(edict_t *ent); +void Drop_Spec(edict_t *ent, gitem_t *item); +void SpecThink(edict_t *spec); +void DeadDropSpec(edict_t *ent); + +void temp_think_specweap( edict_t* ent ); // p_weapons.c +void ThinkSpecWeap( edict_t* ent ); +void DropExtraSpecial( edict_t* ent ); +void TransparentListSet(solid_t solid_type); + + +//local to g_combat but needed in p_view +void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage); +void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + + +void Add_Frag( edict_t *ent ); +void Subtract_Frag( edict_t *ent ); + +void kick_attack( edict_t *ent ); + +void SetIDView(edict_t *ent); + +void EndDMLevel (void); +qboolean Pickup_Special ( edict_t *ent, edict_t *other ); + + +// action function +edict_t *FindEdictByClassnum (char *classname, int classnum); + +void EjectBlooder (edict_t *self, vec3_t start, vec3_t veloc ); +void EjectShell (edict_t *self, vec3_t start, int toggle); +void AddDecal (edict_t *self, trace_t* tr); +void AddSplat (edict_t *self, vec3_t point, trace_t* tr); +// weapon names +/* +bind 2 "use M3 Super 90 Assault Shotgun;" +bind 3 "use MP5/10 Submachinegun" +bind 4 "use Handcannon" +bind 5 "use M4 Assault Rifle" +bind 6 "use Sniper Rifle" +*/ +#define MK23_NAME "MK23 Pistol" +#define MP5_NAME "MP5/10 Submachinegun" +#define M4_NAME "M4 Assault Rifle" +#define M3_NAME "M3 Super 90 Assault Shotgun" +#define HC_NAME "Handcannon" +#define SNIPER_NAME "Sniper Rifle" +#define DUAL_NAME "Dual MK23 Pistols" +#define KNIFE_NAME "Combat Knife" +#define GRENADE_NAME "M26 Fragmentation Grenade" + +#define SIL_NAME "Silencer" +#define SLIP_NAME "Stealth Slippers" +#define BAND_NAME "Bandolier" +#define KEV_NAME "Kevlar Vest" +#define LASER_NAME "Lasersight" + +#define AMMO_CLIP_NAME "Pistol Clip" +#define AMMO_M4_NAME "M4 Clip" +#define AMMO_MAG_NAME "Machinegun Magazine" +#define AMMO_SNIPER_NAME "AP Sniper Ammo" +#define AMMO_M3_NAME "12 Gauge Shells" + + + + +#define MK23_NUM 0 +#define MP5_NUM 1 +#define M4_NUM 2 +#define M3_NUM 3 +#define HC_NUM 4 +#define SNIPER_NUM 5 +#define DUAL_NUM 6 +#define KNIFE_NUM 7 +#define GRENADE_NUM 8 + +// types of locations that can be hit +#define LOC_HDAM 1 // head +#define LOC_CDAM 2 // chest +#define LOC_SDAM 3 // stomach +#define LOC_LDAM 4 // legs + + + +// sniper modes +#define SNIPER_1X 0 +#define SNIPER_2X 1 +#define SNIPER_4X 2 +#define SNIPER_6X 3 + + + +#define GRENADE_IDLE_FIRST 40 +#define GRENADE_IDLE_LAST 69 +#define GRENADE_THROW_FIRST 4 +#define GRENADE_THROW_LAST 9 // throw it on frame 8? + + +// these should be server variables, when I get around to it +//#define UNIQUE_WEAPONS_ALLOWED 2 +//#define UNIQUE_ITEMS_ALLOWED 1 +#define SPEC_WEAPON_RESPAWN 1 +#define BANDAGE_TIME 27 // 10 = 1 second +#define BLEED_TIME 10 // 10 = 1 second is time for losing 1 health at slowest bleed rate +#define GRENADE_DAMRAD 170 + +// ACEBOT_ADD +#include "acesrc\acebot.h" +// ACEBOT_END + +#endif \ No newline at end of file diff --git a/g_main.c b/g_main.c new file mode 100644 index 0000000..ce42115 --- /dev/null +++ b/g_main.c @@ -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 ; ivalue ; 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 ; ivalue ; 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 ; ivalue ; 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 ; iinuse) + 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 diff --git a/g_misc.c b/g_misc.c new file mode 100644 index 0000000..83feec1 --- /dev/null +++ b/g_misc.c @@ -0,0 +1,1860 @@ +// g_misc.c + +#include "g_local.h" +#include "cgf_sfx_glass.h" + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. +*/ + +//===================================================== + +void Use_Areaportal (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->count ^= 1; // toggle state +// gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count); + gi.SetAreaPortalState (ent->style, ent->count); +} + +/*QUAKED func_areaportal (0 0 0) ? + +This is a non-visible object that divides the world into +areas that are seperated when this portal is not activated. +Usually enclosed in the middle of a door. +*/ +void SP_func_areaportal (edict_t *ent) +{ + ent->use = Use_Areaportal; + ent->count = 0; // always start closed; +} + +//===================================================== + + +/* +================= +Misc functions +================= +*/ +void VelocityForDamage (int damage, vec3_t v) +{ + v[0] = 100.0 * crandom(); + v[1] = 100.0 * crandom(); + v[2] = 200.0 + 100.0 * random(); + + if (damage < 50) + VectorScale (v, 0.7, v); + else + VectorScale (v, 1.2, v); +} + +void ClipGibVelocity (edict_t *ent) +{ + if (ent->velocity[0] < -300) + ent->velocity[0] = -300; + else if (ent->velocity[0] > 300) + ent->velocity[0] = 300; + if (ent->velocity[1] < -300) + ent->velocity[1] = -300; + else if (ent->velocity[1] > 300) + ent->velocity[1] = 300; + if (ent->velocity[2] < 200) + ent->velocity[2] = 200; // always some upwards + else if (ent->velocity[2] > 500) + ent->velocity[2] = 500; +} + + +/* +================= +gibs +================= +*/ +void gib_think (edict_t *self) +{ + self->s.frame++; + self->nextthink = level.time + FRAMETIME; + + if (self->s.frame == 10) + { + self->think = G_FreeEdict; + self->nextthink = level.time + 8 + random()*10; + } +} + +void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t normal_angles, right; + + if (!self->groundentity) + return; + + self->touch = NULL; + + if (plane) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0); + + vectoangles (plane->normal, normal_angles); + AngleVectors (normal_angles, NULL, right, NULL); + vectoangles (right, self->s.angles); + + if (self->s.modelindex == sm_meat_index) + { + self->s.frame++; + self->think = gib_think; + self->nextthink = level.time + FRAMETIME; + } + } +} + +void gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowGib (edict_t *self, char *gibname, int damage, int type) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + gib = G_Spawn(); + + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + gib->s.origin[0] = origin[0] + crandom() * size[0]; + gib->s.origin[1] = origin[1] + crandom() * size[1]; + gib->s.origin[2] = origin[2] + crandom() * size[2]; + + gi.setmodel (gib, gibname); + gib->solid = SOLID_NOT; + gib->s.effects |= EF_GIB; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + + if (type == GIB_ORGANIC) + { + gib->movetype = MOVETYPE_TOSS; + gib->touch = gib_touch; + vscale = 0.5; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, gib->velocity); + ClipGibVelocity (gib); + gib->avelocity[0] = random()*600; + gib->avelocity[1] = random()*600; + gib->avelocity[2] = random()*600; + + gib->think = G_FreeEdict; + gib->nextthink = level.time + 10 + random()*10; + + gi.linkentity (gib); +} + +void ThrowHead (edict_t *self, char *gibname, int damage, int type) +{ + vec3_t vd; + float vscale; + + self->s.skinnum = 0; + self->s.frame = 0; + VectorClear (self->mins); + VectorClear (self->maxs); + + self->s.modelindex2 = 0; + gi.setmodel (self, gibname); + self->solid = SOLID_NOT; + self->s.effects |= EF_GIB; + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + self->svflags &= ~SVF_MONSTER; + self->takedamage = DAMAGE_YES; + self->die = gib_die; + + if (type == GIB_ORGANIC) + { + self->movetype = MOVETYPE_TOSS; + self->touch = gib_touch; + vscale = 0.5; + } + else + { + self->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, self->velocity); + ClipGibVelocity (self); + + self->avelocity[YAW] = crandom()*600; + + self->think = G_FreeEdict; + self->nextthink = level.time + 10 + random()*10; + + gi.linkentity (self); +} + + +void ThrowClientHead (edict_t *self, int damage) +{ + vec3_t vd; + char *gibname; + + if (rand()&1) + { + gibname = "models/objects/gibs/head2/tris.md2"; + self->s.skinnum = 1; // second skin is player + } + else + { + gibname = "models/objects/gibs/skull/tris.md2"; + self->s.skinnum = 0; + } + + self->s.origin[2] += 32; + self->s.frame = 0; + gi.setmodel (self, gibname); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 16); + + self->takedamage = DAMAGE_NO; + self->solid = SOLID_NOT; + self->s.effects = EF_GIB; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + + self->movetype = MOVETYPE_BOUNCE; + VelocityForDamage (damage, vd); + VectorAdd (self->velocity, vd, self->velocity); + + if (self->client) // bodies in the queue don't have a client anymore + { + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = self->s.frame; + } + + gi.linkentity (self); +} + + +/* +================= +debris +================= +*/ +void debris_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin) +{ + edict_t *chunk; + vec3_t v; + + 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); +} + + +void BecomeExplosion1 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +void BecomeExplosion2 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION2); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT +Target: next path corner +Pathtarget: gets used when an entity that has + this path_corner targeted touches it +*/ + +void path_corner_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t v; + edict_t *next; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets (self, other); + self->target = savetarget; + } + + if (self->target) + next = G_PickTarget(self->target); + else + next = NULL; + + if ((next) && (next->spawnflags & 1)) + { + VectorCopy (next->s.origin, v); + v[2] += next->mins[2]; + v[2] -= other->mins[2]; + VectorCopy (v, other->s.origin); + next = G_PickTarget(next->target); + } + + other->goalentity = other->movetarget = next; + + if (self->wait) + { + other->monsterinfo.pausetime = level.time + self->wait; + other->monsterinfo.stand (other); + return; + } + + if (!other->movetarget) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.stand (other); + } + else + { + VectorSubtract (other->goalentity->s.origin, other->s.origin, v); + other->ideal_yaw = vectoyaw (v); + } +} + +void SP_path_corner (edict_t *self) +{ + if (!self->targetname) + { + gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->solid = SOLID_TRIGGER; + self->touch = path_corner_touch; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + self->svflags |= SVF_NOCLIENT; + gi.linkentity (self); +} + + +/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold +Makes this the target of a monster and it will head here +when first activated before going after the activator. If +hold is selected, it will stay here. +*/ +void point_combat_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *activator; + + if (other->movetarget != self) + return; + + if (self->target) + { + other->target = self->target; + other->goalentity = other->movetarget = G_PickTarget(other->target); + if (!other->goalentity) + { + gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target); + other->movetarget = self; + } + self->target = NULL; + } + else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY))) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.aiflags |= AI_STAND_GROUND; + other->monsterinfo.stand (other); + } + + if (other->movetarget == self) + { + other->target = NULL; + other->movetarget = NULL; + other->goalentity = other->enemy; + other->monsterinfo.aiflags &= ~AI_COMBAT_POINT; + } + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + if (other->enemy && other->enemy->client) + activator = other->enemy; + else if (other->oldenemy && other->oldenemy->client) + activator = other->oldenemy; + else if (other->activator && other->activator->client) + activator = other->activator; + else + activator = other; + G_UseTargets (self, activator); + self->target = savetarget; + } +} + +void SP_point_combat (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + self->solid = SOLID_TRIGGER; + self->touch = point_combat_touch; + VectorSet (self->mins, -8, -8, -16); + VectorSet (self->maxs, 8, 8, 16); + self->svflags = SVF_NOCLIENT; + gi.linkentity (self); +}; + + +/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) +Just for the debugging level. Don't use +*/ +void TH_viewthing(edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 7; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_viewthing(edict_t *ent) +{ + gi.dprintf ("viewthing spawned\n"); + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.renderfx = RF_FRAMELERP; + VectorSet (ent->mins, -16, -16, -24); + VectorSet (ent->maxs, 16, 16, 32); + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + gi.linkentity (ent); + ent->nextthink = level.time + 0.5; + ent->think = TH_viewthing; + return; +} + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void SP_info_null (edict_t *self) +{ + G_FreeEdict (self); +}; + + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void SP_info_notnull (edict_t *self) +{ + VectorCopy (self->s.origin, self->absmin); + VectorCopy (self->s.origin, self->absmax); +}; + + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF +Non-displayed light. +Default light value is 300. +Default style is 0. +If targeted, will toggle between on and off. +Default _cone value is 10 (used to set size of light for spotlights) +*/ + +#define START_OFF 1 + +static void light_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & START_OFF) + { + gi.configstring (CS_LIGHTS+self->style, "m"); + self->spawnflags &= ~START_OFF; + } + else + { + gi.configstring (CS_LIGHTS+self->style, "a"); + self->spawnflags |= START_OFF; + } +} + +void SP_light (edict_t *self) +{ + // no targeted lights in deathmatch, because they cause global messages + if (!self->targetname || deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (self->style >= 32) + { + self->use = light_use; + if (self->spawnflags & START_OFF) + gi.configstring (CS_LIGHTS+self->style, "a"); + else + gi.configstring (CS_LIGHTS+self->style, "m"); + } +} + + +/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST +This is just a solid wall if not inhibited + +TRIGGER_SPAWN the wall will not be present until triggered + it will then blink in to existance; it will + kill anything that was in it's way + +TOGGLE only valid for TRIGGER_SPAWN walls + this allows the wall to be turned on and off + +START_ON only valid for TRIGGER_SPAWN walls + the wall will initially be present +*/ + +void func_wall_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + { + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + KillBox (self); + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + +void SP_func_wall (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + + if (self->spawnflags & 8) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 16) + self->s.effects |= EF_ANIM_ALLFAST; + + // just a wall + if ((self->spawnflags & 7) == 0) + { + self->solid = SOLID_BSP; + gi.linkentity (self); + return; + } + + // it must be TRIGGER_SPAWN + if (!(self->spawnflags & 1)) + { +// gi.dprintf("func_wall missing TRIGGER_SPAWN\n"); + self->spawnflags |= 1; + } + + // yell if the spawnflags are odd + if (self->spawnflags & 4) + { + if (!(self->spawnflags & 2)) + { + gi.dprintf("func_wall START_ON without TOGGLE\n"); + self->spawnflags |= 2; + } + } + + self->use = func_wall_use; + if (self->spawnflags & 4) + { + self->solid = SOLID_BSP; + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); +} + + +/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST +This is solid bmodel that will fall if it's support it removed. +*/ + +void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + // only squash thing we fall on top of + if (!plane) + return; + if (plane->normal[2] < 1.0) + return; + if (other->takedamage == DAMAGE_NO) + return; + T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void func_object_release (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->touch = func_object_touch; +} + +void func_object_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + func_object_release (self); +} + +void SP_func_object (edict_t *self) +{ + gi.setmodel (self, self->model); + + self->mins[0] += 1; + self->mins[1] += 1; + self->mins[2] += 1; + self->maxs[0] -= 1; + self->maxs[1] -= 1; + self->maxs[2] -= 1; + + if (!self->dmg) + self->dmg = 100; + + if (self->spawnflags == 0) + { + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + self->think = func_object_release; + self->nextthink = level.time + 2 * FRAMETIME; + } + else + { + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_PUSH; + self->use = func_object_use; + self->svflags |= SVF_NOCLIENT; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + self->clipmask = MASK_MONSTERSOLID; + + gi.linkentity (self); +} + + +/*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST +Any brush that you want to explode or break apart. If you want an +ex0plosion, set dmg and it will do a radius explosion of that amount +at the center of the bursh. + +If targeted it will not be shootable. + +health defaults to 100. + +mass defaults to 75. This determines how much debris is emitted when +it explodes. You get one large chunk per 100 of mass (up to 8) and +one small chunk per 25 of mass (up to 16). So 800 gives the most. +*/ +void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + vec3_t origin; + vec3_t chunkorigin; + vec3_t size; + int count; + int mass; + + // bmodel origins are (0 0 0), we need to adjust that here + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + VectorCopy (origin, self->s.origin); + + self->takedamage = DAMAGE_NO; + + if (self->dmg) + T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity); + VectorNormalize (self->velocity); + VectorScale (self->velocity, 150, self->velocity); + + // start chunks towards the center + VectorScale (size, 0.5, size); + + mass = self->mass; + if (!mass) + mass = 75; + + // big chunks + if (mass >= 100) + { + count = mass / 100; + if (count > 8) + count = 8; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin); + } + } + + // small chunks + count = mass / 25; + if (count > 16) + count = 16; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin); + } + + G_UseTargets (self, attacker); + + if (self->dmg) + BecomeExplosion1 (self); + else + G_FreeEdict (self); +} + +void func_explosive_use(edict_t *self, edict_t *other, edict_t *activator) +{ + func_explosive_explode (self, self, other, self->health, vec3_origin); +} + +void func_explosive_spawn (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + gi.linkentity (self); +} + +void SP_func_explosive (edict_t *self) +{ +/* removed for glass fx + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } +*/ + + self->movetype = MOVETYPE_PUSH; + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + + gi.setmodel (self, self->model); + + if (self->spawnflags & 1) + { + self->svflags |= SVF_NOCLIENT; + self->solid = SOLID_NOT; + self->use = func_explosive_spawn; + } + else + { + self->solid = SOLID_BSP; + if (self->targetname) + self->use = func_explosive_use; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + if (self->use != func_explosive_use) + { + if (!self->health) + self->health = 100; + self->die = func_explosive_explode; + self->takedamage = DAMAGE_YES; + } + + gi.linkentity (self); + self->think = CGF_SFX_TestBreakableGlassAndRemoveIfNot_Think; + self->nextthink = level.time + FRAMETIME; +} + + +/*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) +Large exploding box. You can override its mass (100), +health (80), and dmg (150). +*/ + +void barrel_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) + +{ + float ratio; + vec3_t v; + + if ((!other->groundentity) || (other->groundentity == self)) + return; + + ratio = (float)other->mass / (float)self->mass; + VectorSubtract (self->s.origin, other->s.origin, v); + M_walkmove (self, vectoyaw(v), 20 * ratio * FRAMETIME); +} + +void barrel_explode (edict_t *self) +{ + vec3_t org; + float spd; + vec3_t save; + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_BARREL); + + VectorCopy (self->s.origin, save); + VectorMA (self->absmin, 0.5, self->size, self->s.origin); + + // a few big chunks + spd = 1.5 * (float)self->dmg / 200.0; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + + // bottom corners + spd = 1.75 * (float)self->dmg / 200.0; + VectorCopy (self->absmin, org); + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + + // a bunch of little chunks + spd = 2 * self->dmg / 200; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + + VectorCopy (save, self->s.origin); + if (self->groundentity) + BecomeExplosion2 (self); + else + BecomeExplosion1 (self); +} + +void barrel_delay (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + 2 * FRAMETIME; + self->think = barrel_explode; + self->activator = attacker; +} + +void SP_misc_explobox (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + gi.modelindex ("models/objects/debris3/tris.md2"); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + + self->model = "models/objects/barrels/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 40); + + if (!self->mass) + self->mass = 400; + if (!self->health) + self->health = 10; + if (!self->dmg) + self->dmg = 150; + + self->die = barrel_delay; + self->takedamage = DAMAGE_YES; + self->monsterinfo.aiflags = AI_NOSTEP; + + self->touch = barrel_touch; + + self->think = M_droptofloor; + self->nextthink = level.time + 2 * FRAMETIME; + + gi.linkentity (self); +} + + +// +// miscellaneous specialty items +// + +/*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8) +*/ + +void misc_blackhole_use (edict_t *ent, edict_t *other, edict_t *activator) +{ + /* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + */ + G_FreeEdict (ent); +} + +void misc_blackhole_think (edict_t *self) +{ + if (++self->s.frame < 19) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 0; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_blackhole (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 8); + ent->s.modelindex = gi.modelindex ("models/objects/black/tris.md2"); + ent->s.renderfx = RF_TRANSLUCENT; + ent->use = misc_blackhole_use; + ent->think = misc_blackhole_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32) +*/ + +void misc_eastertank_think (edict_t *self) +{ + if (++self->s.frame < 293) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 254; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_eastertank (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, -16); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2"); + ent->s.frame = 254; + ent->think = misc_eastertank_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick_think (edict_t *self) +{ + if (++self->s.frame < 247) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 208; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 208; + ent->think = misc_easterchick_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick2_think (edict_t *self) +{ + if (++self->s.frame < 287) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 248; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 248; + ent->think = misc_easterchick2_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + + +/*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) +Not really a monster, this is the Tank Commander's decapitated body. +There should be a item_commander_head that has this as it's target. +*/ + +void commander_body_think (edict_t *self) +{ + if (++self->s.frame < 24) + self->nextthink = level.time + FRAMETIME; + else + self->nextthink = 0; + + if (self->s.frame == 22) + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/thud.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->think = commander_body_think; + self->nextthink = level.time + FRAMETIME; + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/pain.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_drop (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->s.origin[2] += 2; +} + +void SP_monster_commander_body (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + self->model = "models/monsters/commandr/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -32, -32, 0); + VectorSet (self->maxs, 32, 32, 48); + self->use = commander_body_use; + self->takedamage = DAMAGE_YES; + self->flags = FL_GODMODE; + self->s.renderfx |= RF_FRAMELERP; + gi.linkentity (self); + + gi.soundindex ("tank/thud.wav"); + gi.soundindex ("tank/pain.wav"); + + self->think = commander_body_drop; + self->nextthink = level.time + 5 * FRAMETIME; +} + + +/*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) +The origin is the bottom of the banner. +The banner is 128 tall. +*/ +void misc_banner_think (edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_misc_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED +This is the dead player model. Comes in 6 exciting different poses! +*/ +void misc_deadsoldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health > -80) + return; + + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); +} + +void SP_misc_deadsoldier (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex=gi.modelindex ("models/deadbods/dude/tris.md2"); + + // Defaults to frame 0 + if (ent->spawnflags & 2) + ent->s.frame = 1; + else if (ent->spawnflags & 4) + ent->s.frame = 2; + else if (ent->spawnflags & 8) + ent->s.frame = 3; + else if (ent->spawnflags & 16) + ent->s.frame = 4; + else if (ent->spawnflags & 32) + ent->s.frame = 5; + else + ent->s.frame = 0; + + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 16); + ent->deadflag = DEAD_DEAD; + ent->takedamage = DAMAGE_YES; + ent->svflags |= SVF_MONSTER|SVF_DEADMONSTER; + ent->die = misc_deadsoldier_die; + ent->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity (ent); +} + +/*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) +This is the Viper for the flyby bombing. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast the Viper should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_viper_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_viper (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_viper_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) +This is a large stationary viper as seen in Paul's intro +*/ +void SP_misc_bigviper (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -176, -120, -24); + VectorSet (ent->maxs, 176, 120, 72); + ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) +"dmg" how much boom should the bomb make? +*/ +void misc_viper_bomb_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + G_UseTargets (self, self->activator); + + self->s.origin[2] = self->absmin[2] + 1; + T_RadiusDamage (self, self, self->dmg, NULL, self->dmg+40, MOD_BOMB); + BecomeExplosion2 (self); +} + +void misc_viper_bomb_prethink (edict_t *self) +{ + vec3_t v; + float diff; + + self->groundentity = NULL; + + diff = self->timestamp - level.time; + if (diff < -1.0) + diff = -1.0; + + VectorScale (self->moveinfo.dir, 1.0 + diff, v); + v[2] = diff; + + diff = self->s.angles[2]; + vectoangles (v, self->s.angles); + self->s.angles[2] = diff + 10; +} + +void misc_viper_bomb_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *viper; + + self->solid = SOLID_BBOX; + self->svflags &= ~SVF_NOCLIENT; + self->s.effects |= EF_ROCKET; + self->use = NULL; + self->movetype = MOVETYPE_TOSS; + self->prethink = misc_viper_bomb_prethink; + self->touch = misc_viper_bomb_touch; + self->activator = activator; + + viper = G_Find (NULL, FOFS(classname), "misc_viper"); + VectorScale (viper->moveinfo.dir, viper->moveinfo.speed, self->velocity); + + self->timestamp = level.time; + VectorCopy (viper->moveinfo.dir, self->moveinfo.dir); +} + +void SP_misc_viper_bomb (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + + self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2"); + + if (!self->dmg) + self->dmg = 1000; + + self->use = misc_viper_bomb_use; + self->svflags |= SVF_NOCLIENT; + + gi.linkentity (self); +} + + +/*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) +This is a Storgg ship for the flybys. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast it should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_strogg_ship_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_strogg_ship (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/strogg1/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_strogg_ship_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128) +*/ +void misc_satellite_dish_think (edict_t *self) +{ + self->s.frame++; + if (self->s.frame < 38) + self->nextthink = level.time + FRAMETIME; +} + +void misc_satellite_dish_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->s.frame = 0; + self->think = misc_satellite_dish_think; + self->nextthink = level.time + FRAMETIME; +} + +void SP_misc_satellite_dish (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 128); + ent->s.modelindex = gi.modelindex ("models/objects/satellite/tris.md2"); + ent->use = misc_satellite_dish_use; + gi.linkentity (ent); +} + + +/*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine1 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light1/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light2/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_arm (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/arm/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_leg (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/leg/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_head (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/head/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +//===================================================== + +/*QUAKED target_character (0 0 1) ? +used with target_string (must be on same "team") +"count" is position in the string (starts at 1) +*/ + +void SP_target_character (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + self->s.frame = 12; + gi.linkentity (self); + return; +} + + +/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8) +*/ + +void target_string_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *e; + int n, l; + char c; + + l = strlen(self->message); + for (e = self->teammaster; e; e = e->teamchain) + { + if (!e->count) + continue; + n = e->count - 1; + if (n > l) + { + e->s.frame = 12; + continue; + } + + c = self->message[n]; + if (c >= '0' && c <= '9') + e->s.frame = c - '0'; + else if (c == '-') + e->s.frame = 10; + else if (c == ':') + e->s.frame = 11; + else + e->s.frame = 12; + } +} + +void SP_target_string (edict_t *self) +{ + if (!self->message) + self->message = ""; + self->use = target_string_use; +} + + +/*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE +target a target_string with this + +The default is to be a time of day clock + +TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget" +If START_OFF, this entity must be used before it starts + +"style" 0 "xx" + 1 "xx:xx" + 2 "xx:xx:xx" +*/ + +#define CLOCK_MESSAGE_SIZE 16 + +// don't let field width of any clock messages change, or it +// could cause an overwrite after a game load + +static void func_clock_reset (edict_t *self) +{ + self->activator = NULL; + if (self->spawnflags & 1) + { + self->health = 0; + self->wait = self->count; + } + else if (self->spawnflags & 2) + { + self->health = self->count; + self->wait = 0; + } +} + +static void func_clock_format_countdown (edict_t *self) +{ + if (self->style == 0) + { + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health); + return; + } + + if (self->style == 1) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + return; + } + + if (self->style == 2) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + return; + } +} + +void func_clock_think (edict_t *self) +{ + if (!self->enemy) + { + self->enemy = G_Find (NULL, FOFS(targetname), self->target); + if (!self->enemy) + return; + } + + if (self->spawnflags & 1) + { + func_clock_format_countdown (self); + self->health++; + } + else if (self->spawnflags & 2) + { + func_clock_format_countdown (self); + self->health--; + } + else + { + struct tm *ltime; + time_t gmtime; + + time(&gmtime); + ltime = localtime(&gmtime); + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", ltime->tm_hour, ltime->tm_min, ltime->tm_sec); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + } + + self->enemy->message = self->message; + self->enemy->use (self->enemy, self, self); + + if (((self->spawnflags & 1) && (self->health > self->wait)) || + ((self->spawnflags & 2) && (self->health < self->wait))) + { + if (self->pathtarget) + { + char *savetarget; + char *savemessage; + + savetarget = self->target; + savemessage = self->message; + self->target = self->pathtarget; + self->message = NULL; + G_UseTargets (self, self->activator); + self->target = savetarget; + self->message = savemessage; + } + + if (!(self->spawnflags & 8)) + return; + + func_clock_reset (self); + + if (self->spawnflags & 4) + return; + } + + self->nextthink = level.time + 1; +} + +void func_clock_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!(self->spawnflags & 8)) + self->use = NULL; + if (self->activator) + return; + self->activator = activator; + self->think (self); +} + +void SP_func_clock (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 2) && (!self->count)) + { + gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 1) && (!self->count)) + self->count = 60*60;; + + func_clock_reset (self); + + self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL); + + self->think = func_clock_think; + + if (self->spawnflags & 4) + self->use = func_clock_use; + else + self->nextthink = level.time + 1; +} + +//================================================================================= + +void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + + if (!other->client) + return; + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity (other); + + VectorCopy (dest->s.origin, other->s.origin); + VectorCopy (dest->s.origin, other->s.old_origin); + other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + VectorClear (other->velocity); + other->client->ps.pmove.pm_time = 160>>3; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + self->owner->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + for (i=0 ; i<3 ; i++) + other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); + + VectorClear (other->s.angles); + VectorClear (other->client->ps.viewangles); + VectorClear (other->client->v_angle); + + // kill anything at the destination + KillBox (other); + + gi.linkentity (other); +} + +/*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) +Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object. +*/ +void SP_misc_teleporter (edict_t *ent) +{ + edict_t *trig; + + if (!ent->target) + { + gi.dprintf ("teleporter without a target.\n"); + G_FreeEdict (ent); + return; + } + + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 1; + ent->s.effects = EF_TELEPORTER; + ent->s.sound = gi.soundindex ("world/amb10.wav"); + ent->solid = SOLID_BBOX; + + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); + + trig = G_Spawn (); + trig->touch = teleporter_touch; + trig->solid = SOLID_TRIGGER; + trig->target = ent->target; + trig->owner = ent; + VectorCopy (ent->s.origin, trig->s.origin); + VectorSet (trig->mins, -8, -8, 8); + VectorSet (trig->maxs, 8, 8, 24); + gi.linkentity (trig); + +} + +/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) +Point teleporters at these. +*/ +void SP_misc_teleporter_dest (edict_t *ent) +{ +// zucc modified to remove visible teleporter/dm start + //gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 0; + ent->solid = SOLID_NOT; + //ent->solid = SOLID_BBOX; +// ent->s.effects |= EF_FLIES; +// VectorSet (ent->mins, -32, -32, -24); +// VectorSet (ent->maxs, 32, 32, -16); + VectorSet (ent->mins, 0, 0, 0); + VectorSet (ent->maxs, 0, 0, 0); + + gi.linkentity (ent); +} + diff --git a/g_monster.c b/g_monster.c new file mode 100644 index 0000000..787cea1 --- /dev/null +++ b/g_monster.c @@ -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); +} diff --git a/g_phys.c b/g_phys.c new file mode 100644 index 0000000..234d67f --- /dev/null +++ b/g_phys.c @@ -0,0 +1,1101 @@ +// g_phys.c + +#include "g_local.h" +#include "cgf_sfx_glass.h" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + + +/* +============ +SV_TestEntityPosition + +============ +*/ +edict_t *SV_TestEntityPosition (edict_t *ent) +{ + trace_t trace; + int mask; + + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + PRETRACE(); + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask); + POSTTRACE(); + + if (trace.startsolid) + return g_edicts; + + return NULL; +} + + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (edict_t *ent) +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (ent->velocity[i] > sv_maxvelocity->value) + ent->velocity[i] = sv_maxvelocity->value; + else if (ent->velocity[i] < -sv_maxvelocity->value) + ent->velocity[i] = -sv_maxvelocity->value; + } +} + +/* +============= +SV_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +qboolean SV_RunThink (edict_t *ent) +{ + float thinktime; + + thinktime = ent->nextthink; + if (thinktime <= 0) + return true; + if (thinktime > level.time+0.001) + return true; + + ent->nextthink = 0; + if (!ent->think) + gi.error ("NULL ent->think"); + ent->think (ent); + + return false; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void SV_Impact (edict_t *e1, trace_t *trace) +{ + edict_t *e2; +// cplane_t backplane; + + e2 = trace->ent; + + if (e1->touch && e1->solid != SOLID_NOT) + e1->touch (e1, e2, &trace->plane, trace->surface); + + if (e2->touch && e2->solid != SOLID_NOT) + e2->touch (e2, e1, NULL, NULL); +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + int i, blocked; + + blocked = 0; + if (normal[2] > 0) + blocked |= 1; // floor + if (!normal[2]) + blocked |= 2; // step + + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + return blocked; +} + + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +============ +*/ +#define MAX_CLIP_PLANES 5 +int SV_FlyMove (edict_t *ent, float time, int mask) +{ + edict_t *hit; + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity, new_velocity; + int i, j; + trace_t trace; + vec3_t end; + float time_left; + int blocked; + + numbumps = 4; + + blocked = 0; + VectorCopy (ent->velocity, original_velocity); + VectorCopy (ent->velocity, primal_velocity); + numplanes = 0; + + time_left = time; + + ent->groundentity = NULL; + for (bumpcount=0 ; bumpcounts.origin[i] + time_left * ent->velocity[i]; + + PRETRACE(); + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask); + POSTTRACE(); + + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, ent->s.origin); + VectorCopy (ent->velocity, original_velocity); + numplanes = 0; + } + + if (trace.fraction == 1) + break; // moved the entire distance + + hit = trace.ent; + + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + if ( hit->solid == SOLID_BSP) + { + ent->groundentity = hit; + ent->groundentity_linkcount = hit->linkcount; + } + } + if (!trace.plane.normal[2]) + { + blocked |= 2; // step + } + +// +// run the impact function +// + SV_Impact (ent, &trace); + if (!ent->inuse) + break; // removed by the impact function + + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + +// +// modify original_velocity so it parallels all of the clip planes +// + for (i=0 ; ivelocity); + } + else + { // go along the crease + if (numplanes != 2) + { +// gi.dprintf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, ent->velocity); + return 7; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, ent->velocity); + VectorScale (dir, d, ent->velocity); + } + +// +// if original velocity is against the original velocity, stop dead +// to avoid tiny occilations in sloping corners +// + if (DotProduct (ent->velocity, primal_velocity) <= 0) + { + VectorCopy (vec3_origin, ent->velocity); + return blocked; + } + } + + return blocked; +} + + +/* +============ +SV_AddGravity + +============ +*/ +void SV_AddGravity (edict_t *ent) +{ + ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; +} + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t SV_PushEntity (edict_t *ent, vec3_t push) +{ + trace_t trace; + vec3_t start; + vec3_t end; + int mask; + + VectorCopy (ent->s.origin, start); + VectorAdd (start, push, end); + +retry: + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + + PRETRACE(); + trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask); + POSTTRACE(); + + VectorCopy (trace.endpos, ent->s.origin); + gi.linkentity (ent); + + if (trace.fraction != 1.0) + { + SV_Impact (ent, &trace); + + // if the pushed entity went away and the pusher is still there + if (!trace.ent->inuse && ent->inuse) + { + // move the pusher back and try again + VectorCopy (start, ent->s.origin); + gi.linkentity (ent); + goto retry; + } + } + + if (ent->inuse) + G_TouchTriggers (ent); + + return trace; +} + + +typedef struct +{ + edict_t *ent; + vec3_t origin; + vec3_t angles; + float deltayaw; +} pushed_t; +pushed_t pushed[MAX_EDICTS], *pushed_p; + +edict_t *obstacle; + +/* +============ +SV_Push + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +============ +*/ +qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove) +{ + int i, e; + edict_t *check, *block; + vec3_t mins, maxs; + pushed_t *p; + vec3_t org, org2, move2, forward, right, up; + + // clamp the move to 1/8 units, so the position will + // be accurate for client side prediction + for (i=0 ; i<3 ; i++) + { + float temp; + temp = move[i]*8.0; + if (temp > 0.0) + temp += 0.5; + else + temp -= 0.5; + move[i] = 0.125 * (int)temp; + } + + // find the bounding box + for (i=0 ; i<3 ; i++) + { + mins[i] = pusher->absmin[i] + move[i]; + maxs[i] = pusher->absmax[i] + move[i]; + } + +// we need this for pushing things later + VectorSubtract (vec3_origin, amove, org); + AngleVectors (org, forward, right, up); + +// save the pusher's original position + pushed_p->ent = pusher; + VectorCopy (pusher->s.origin, pushed_p->origin); + VectorCopy (pusher->s.angles, pushed_p->angles); + if (pusher->client) + pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW]; + pushed_p++; + +// move the pusher to it's final position + VectorAdd (pusher->s.origin, move, pusher->s.origin); + VectorAdd (pusher->s.angles, amove, pusher->s.angles); + gi.linkentity (pusher); + +// see if any solid entities are inside the final position + check = g_edicts+1; + for (e = 1; e < globals.num_edicts; e++, check++) + { + if (!check->inuse) + continue; + if (check->movetype == MOVETYPE_PUSH + || check->movetype == MOVETYPE_STOP + || check->movetype == MOVETYPE_NONE + || check->movetype == MOVETYPE_NOCLIP) + continue; + + if (!check->area.prev) + continue; // not linked in anywhere + + // if the entity is standing on the pusher, it will definitely be moved + if (check->groundentity != pusher) + { + // see if the ent needs to be tested + if ( check->absmin[0] >= maxs[0] + || check->absmin[1] >= maxs[1] + || check->absmin[2] >= maxs[2] + || check->absmax[0] <= mins[0] + || check->absmax[1] <= mins[1] + || check->absmax[2] <= mins[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (!SV_TestEntityPosition (check)) + continue; + } + + if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher)) + { + // move this entity + pushed_p->ent = check; + VectorCopy (check->s.origin, pushed_p->origin); + VectorCopy (check->s.angles, pushed_p->angles); + pushed_p++; + + // try moving the contacted entity + VectorAdd (check->s.origin, move, check->s.origin); + if (check->client) + { // FIXME: doesn't rotate monsters? + check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; + } + + // figure movement due to the pusher's amove + VectorSubtract (check->s.origin, pusher->s.origin, org); + org2[0] = DotProduct (org, forward); + org2[1] = -DotProduct (org, right); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move2); + VectorAdd (check->s.origin, move2, check->s.origin); + + // may have pushed them off an edge + if (check->groundentity != pusher) + check->groundentity = NULL; + + block = SV_TestEntityPosition (check); + if (!block) + { // pushed ok + gi.linkentity (check); + // impact? + continue; + } + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // FIXME: this doesn't acount for rotation + VectorSubtract (check->s.origin, move, check->s.origin); + block = SV_TestEntityPosition (check); + if (!block) + { + pushed_p--; + continue; + } + } + + // save off the obstacle so we can call the block function + obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for (p=pushed_p-1 ; p>=pushed ; p--) + { + VectorCopy (p->origin, p->ent->s.origin); + VectorCopy (p->angles, p->ent->s.angles); + if (p->ent->client) + { + p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw; + } + gi.linkentity (p->ent); + } + return false; + } + +//FIXME: is there a better way to handle this? + // see if anything we moved has touched a trigger + for (p=pushed_p-1 ; p>=pushed ; p--) + G_TouchTriggers (p->ent); + + return true; +} + +/* +================ +SV_Physics_Pusher + +Bmodel objects don't interact with each other, but +push all box objects +================ +*/ +void SV_Physics_Pusher (edict_t *ent) +{ + vec3_t move, amove; + edict_t *part, *mv; + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out +//retry: + pushed_p = pushed; + for (part = ent ; part ; part=part->teamchain) + { + if (part->velocity[0] || part->velocity[1] || part->velocity[2] || + part->avelocity[0] || part->avelocity[1] || part->avelocity[2] + ) + { // object is moving + VectorScale (part->velocity, FRAMETIME, move); + VectorScale (part->avelocity, FRAMETIME, amove); + + if (!SV_Push (part, move, amove)) + break; // move was blocked + } + } + if (pushed_p > &pushed[MAX_EDICTS]) + gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); + + if (part) + { + // the move failed, bump all nextthink times and back out moves + for (mv = ent ; mv ; mv=mv->teamchain) + { + if (mv->nextthink > 0) + mv->nextthink += FRAMETIME; + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (part->blocked) + part->blocked (part, obstacle); +#if 0 + // if the pushed entity went away and the pusher is still there + if (!obstacle->inuse && part->inuse) + goto retry; +#endif + } + else + { + // the move succeeded, so call all think functions + for (part = ent ; part ; part=part->teamchain) + { + SV_RunThink (part); + } + } +} + +//================================================================== + +/* +============= +SV_Physics_None + +Non moving objects can only think +============= +*/ +void SV_Physics_None (edict_t *ent) +{ +// regular thinking + SV_RunThink (ent); +} + +/* +============= +SV_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip (edict_t *ent) +{ +// regular thinking + if (!SV_RunThink (ent)) + return; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin); + + gi.linkentity (ent); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + + +/* + ============= + SV_Physics_Bounce + bounce movement. When onground, do nothing. + This is a copy of SV_Physics_Toss() modified to provide more realistic + bounce physics + ============= +*/ + + +void SV_Physics_Bounce (edict_t *ent); + + +void SV_Physics_Bounce (edict_t *ent) +{ + trace_t trace; + vec3_t move, planeVel, normalVel, temp; + edict_t *slave; + qboolean wasinwater; + qboolean isinwater; + vec3_t old_origin; + float movetime; + +// regular thinking + SV_RunThink (ent); + if (!ent->inuse) + return; + +// if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + if (ent->velocity[2] > 0) + ent->groundentity = NULL; +// check for the groundentity going away + if (ent->groundentity) + if (!ent->groundentity->inuse) + ent->groundentity = NULL; +// if onground, return without moving + if (ent->groundentity) + return; + VectorCopy (ent->s.origin, old_origin); + SV_CheckVelocity (ent); + + // move angles + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + + // add gravity + ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; + movetime = FRAMETIME; + while (movetime > 0) + { + // move origin + VectorScale (ent->velocity, movetime, move); + // do the move (checking to see if we hit anything) + trace = SV_PushEntity (ent, move); + // SV_PushEntity might have called touch() functions so its possible we disappeared + if (!ent->inuse) + return; + // we hit something so calculate rebound velocity etc. + if (trace.fraction < 1) + { + // find component of velocity in direction of normal + VectorScale (trace.plane.normal, DotProduct (trace.plane.normal, ent->velocity), normalVel); + // find component of velocity parallel to plane of impact + VectorSubtract (ent->velocity, normalVel, planeVel); + // apply friction (lose 25% of velocity parallel to impact plane) + VectorScale (planeVel, 0.75, planeVel); + // calculate final velocity (rebound with 60% of impact velocity) + // negative since rebound causes direction reversal + VectorMA (planeVel, -0.6, normalVel, ent->velocity); + // stop if on ground + // only consider stopping if impact plane is less than 45 degrees with respect to horizontal + // arccos(0.7) ~ 45 degrees + if (trace.plane.normal[2] > 0.7) + { + VectorCopy (ent->velocity, temp); + // zero out z component (vertical) since we want to consider horizontal and vertical motion +// separately + temp[2] = 0; + if ((VectorLength (temp) < 20) && (ent->velocity[2] < ent->gravity * sv_gravity->value * FRAMETIME * 1.5)) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + VectorCopy (vec3_origin, ent->velocity); + VectorCopy (vec3_origin, ent->avelocity); + break; // stopped, so no further simulation is necessary + } + } + } + // remaining amount of FRAMETIME left to simulate + movetime *= (1.0 - trace.fraction); + } + + // check for water transition + wasinwater = (ent->watertype & MASK_WATER); + ent->watertype = gi.pointcontents (ent->s.origin); + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = 1; + else + ent->waterlevel = 0; + + if (!wasinwater && isinwater) + gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + else + if (wasinwater && !isinwater) + gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + + // move teamslaves + for (slave = ent->teamchain; slave; slave = slave->teamchain) + { + VectorCopy (ent->s.origin, slave->s.origin); + gi.linkentity (slave); + } +} + + + + + + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss (edict_t *ent) +{ + trace_t trace; + vec3_t move; + float backoff; + edict_t *slave; + qboolean wasinwater; + qboolean isinwater; + vec3_t old_origin; + +// regular thinking + SV_RunThink (ent); + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + if (ent->velocity[2] > 0) + ent->groundentity = NULL; + +// check for the groundentity going away + if (ent->groundentity) + if (!ent->groundentity->inuse) + ent->groundentity = NULL; + +// if onground, return without moving + if ( ent->groundentity ) + return; + + VectorCopy (ent->s.origin, old_origin); + + SV_CheckVelocity (ent); + +// add gravity + if (ent->movetype != MOVETYPE_FLY + && ent->movetype != MOVETYPE_FLYMISSILE) + SV_AddGravity (ent); + +// move angles + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + +// move origin + VectorScale (ent->velocity, FRAMETIME, move); + trace = SV_PushEntity (ent, move); + if (!ent->inuse) + return; + + // zucc added from action to cause blood splatters + if (trace.fraction < 1 && ent->movetype == MOVETYPE_BLOOD) + { + if (!ent->splatted) + { + AddSplat (ent->owner, ent->s.origin, &trace); + ent->splatted = true; + } + + return; + } + + + + if (trace.fraction < 1) + { + if (ent->movetype == MOVETYPE_BOUNCE) + backoff = 1.4; // 1.5 is normal -Zucc/FB + else + backoff = 1; + + ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + // zucc moved velocity check down from 60 + if (ent->velocity[2] < 10 || ent->movetype != MOVETYPE_BOUNCE ) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + VectorCopy (vec3_origin, ent->velocity); + VectorCopy (vec3_origin, ent->avelocity); + } + } + +// if (ent->touch) +// ent->touch (ent, trace.ent, &trace.plane, trace.surface); + } + +// check for water transition + wasinwater = (ent->watertype & MASK_WATER); + ent->watertype = gi.pointcontents (ent->s.origin); + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = 1; + else + ent->waterlevel = 0; + + if (!wasinwater && isinwater) + gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + else if (wasinwater && !isinwater) + gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + +// move teamslaves + for (slave = ent->teamchain; slave; slave = slave->teamchain) + { + VectorCopy (ent->s.origin, slave->s.origin); + gi.linkentity (slave); + } +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +FIXME: is this true? +============= +*/ + +//FIXME: hacked in for E3 demo +#define sv_stopspeed 100 +#define sv_friction 6 +#define sv_waterfriction 1 + +void SV_AddRotationalFriction (edict_t *ent) +{ + int n; + float adjustment; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + adjustment = FRAMETIME * sv_stopspeed * sv_friction; + for (n = 0; n < 3; n++) + { + if (ent->avelocity[n] > 0) + { + ent->avelocity[n] -= adjustment; + if (ent->avelocity[n] < 0) + ent->avelocity[n] = 0; + } + else + { + ent->avelocity[n] += adjustment; + if (ent->avelocity[n] > 0) + ent->avelocity[n] = 0; + } + } +} + +void SV_Physics_Step (edict_t *ent) +{ + qboolean wasonground; + qboolean hitsound = false; + float *vel; + float speed, newspeed, control; + float friction; + edict_t *groundentity; + int mask; + + // airborn monsters should always check for ground + if (!ent->groundentity) + M_CheckGround (ent); + + groundentity = ent->groundentity; + + SV_CheckVelocity (ent); + + if (groundentity) + wasonground = true; + else + wasonground = false; + + if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + SV_AddRotationalFriction (ent); + + // add gravity except: + // flying monsters + // swimming monsters who are in the water + if (! wasonground) + if (!(ent->flags & FL_FLY)) + if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2))) + { + if (ent->velocity[2] < sv_gravity->value*-0.1) + hitsound = true; + if (ent->waterlevel == 0) + SV_AddGravity (ent); + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + friction = sv_friction/3; + newspeed = speed - (FRAMETIME * control * friction); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) + { + // apply friction + // let dead monsters who aren't completely onground slide + if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) + if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) + { + vel = ent->velocity; + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); + if (speed) + { + friction = sv_friction; + + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - FRAMETIME*control*friction; + + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + vel[0] *= newspeed; + vel[1] *= newspeed; + } + } + + if (ent->svflags & SVF_MONSTER) + mask = MASK_MONSTERSOLID; + else + mask = MASK_SOLID; + SV_FlyMove (ent, FRAMETIME, mask); + + gi.linkentity (ent); + G_TouchTriggers (ent); + +// Following check was added in 3.20. Adding here. -FB + if (!ent->inuse) + return; + + if (ent->groundentity) + if (!wasonground) + if (hitsound) + gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0); + } + +// regular thinking + SV_RunThink (ent); +} + +//============================================================================ +/* +================ +G_RunEntity + +================ +*/ +void G_RunEntity (edict_t *ent) +{ + if (ent->prethink) + ent->prethink (ent); + + switch ( (int)ent->movetype) + { +// ACEBOT_ADD + case MOVETYPE_WALK: + SV_RunThink (ent); + break; +// ACEBOT_END + case MOVETYPE_PUSH: + case MOVETYPE_STOP: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + SV_Physics_None (ent); + break; + case MOVETYPE_NOCLIP: + SV_Physics_Noclip (ent); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + + case MOVETYPE_BOUNCE: + SV_Physics_Bounce (ent); // provided by siris + break; + case MOVETYPE_TOSS: + case MOVETYPE_FLY: + // zucc added for blood splatting + case MOVETYPE_BLOOD: + // zucc + case MOVETYPE_FLYMISSILE: + SV_Physics_Toss (ent); + break; + default: + gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype); + } +} diff --git a/g_save.c b/g_save.c new file mode 100644 index 0000000..7c6e965 --- /dev/null +++ b/g_save.c @@ -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 ; iname ; 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 ; iinuse) + 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 ; ivalue ; 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 ; iinuse) + continue; + + // fire any cross-level triggers + if (ent->classname) + if (strcmp(ent->classname, "target_crosslevel_target") == 0) + ent->nextthink = level.time + ent->delay; + } +} diff --git a/g_spawn.c b/g_spawn.c new file mode 100644 index 0000000..7decfd4 --- /dev/null +++ b/g_spawn.c @@ -0,0 +1,1362 @@ +/* + * $Header: /LicenseToKill/src/g_spawn.c 6 2/11/99 12:58 Riever $ + * + * $Log: /LicenseToKill/src/g_spawn.c $ + * + * 6 2/11/99 12:58 Riever + * Fog include file and call added. + * + * + */ + +#include "g_local.h" +// CGF_FOG ADD +#include "acesrc/cgf_sfx_fog.h" +// CGF_FOG END + +typedef struct +{ + char *name; + void (*spawn)(edict_t *ent); +} spawn_t; + + +void SP_item_health (edict_t *self); +void SP_item_health_small (edict_t *self); +void SP_item_health_large (edict_t *self); +void SP_item_health_mega (edict_t *self); + +void SP_info_player_start (edict_t *ent); +void SP_info_player_deathmatch (edict_t *ent); +void SP_info_player_coop (edict_t *ent); +void SP_info_player_intermission (edict_t *ent); + +void SP_func_plat (edict_t *ent); +void SP_func_rotating (edict_t *ent); +void SP_func_button (edict_t *ent); +void SP_func_door (edict_t *ent); +void SP_func_door_secret (edict_t *ent); +void SP_func_door_rotating (edict_t *ent); +void SP_func_water (edict_t *ent); +void SP_func_train (edict_t *ent); +void SP_func_conveyor (edict_t *self); +void SP_func_wall (edict_t *self); +void SP_func_object (edict_t *self); +void SP_func_explosive (edict_t *self); +void SP_func_timer (edict_t *self); +void SP_func_areaportal (edict_t *ent); +void SP_func_clock (edict_t *ent); +void SP_func_killbox (edict_t *ent); + +void SP_trigger_always (edict_t *ent); +void SP_trigger_once (edict_t *ent); +void SP_trigger_multiple (edict_t *ent); +void SP_trigger_relay (edict_t *ent); +void SP_trigger_push (edict_t *ent); +void SP_trigger_hurt (edict_t *ent); +void SP_trigger_key (edict_t *ent); +void SP_trigger_counter (edict_t *ent); +void SP_trigger_elevator (edict_t *ent); +void SP_trigger_gravity (edict_t *ent); +void SP_trigger_monsterjump (edict_t *ent); + +void SP_target_temp_entity (edict_t *ent); +void SP_target_speaker (edict_t *ent); +void SP_target_explosion (edict_t *ent); +void SP_target_changelevel (edict_t *ent); +void SP_target_secret (edict_t *ent); +void SP_target_goal (edict_t *ent); +void SP_target_splash (edict_t *ent); +void SP_target_spawner (edict_t *ent); +void SP_target_blaster (edict_t *ent); +void SP_target_crosslevel_trigger (edict_t *ent); +void SP_target_crosslevel_target (edict_t *ent); +void SP_target_laser (edict_t *self); +void SP_target_help (edict_t *ent); +void SP_target_actor (edict_t *ent); +void SP_target_lightramp (edict_t *self); +void SP_target_earthquake (edict_t *ent); +void SP_target_character (edict_t *ent); +void SP_target_string (edict_t *ent); + +void SP_worldspawn (edict_t *ent); +void SP_viewthing (edict_t *ent); + +void SP_light (edict_t *self); +void SP_light_mine1 (edict_t *ent); +void SP_light_mine2 (edict_t *ent); +void SP_info_null (edict_t *self); +void SP_info_notnull (edict_t *self); +void SP_path_corner (edict_t *self); +void SP_point_combat (edict_t *self); + +void SP_misc_explobox (edict_t *self); +void SP_misc_banner (edict_t *self); +void SP_misc_satellite_dish (edict_t *self); +void SP_misc_actor (edict_t *self); +void SP_misc_gib_arm (edict_t *self); +void SP_misc_gib_leg (edict_t *self); +void SP_misc_gib_head (edict_t *self); +void SP_misc_insane (edict_t *self); +void SP_misc_deadsoldier (edict_t *self); +void SP_misc_viper (edict_t *self); +void SP_misc_viper_bomb (edict_t *self); +void SP_misc_bigviper (edict_t *self); +void SP_misc_strogg_ship (edict_t *self); +void SP_misc_teleporter (edict_t *self); +void SP_misc_teleporter_dest (edict_t *self); +void SP_misc_blackhole (edict_t *self); +void SP_misc_eastertank (edict_t *self); +void SP_misc_easterchick (edict_t *self); +void SP_misc_easterchick2 (edict_t *self); + + +void SP_monster_berserk (edict_t *self); +void SP_monster_gladiator (edict_t *self); +void SP_monster_gunner (edict_t *self); +void SP_monster_infantry (edict_t *self); +void SP_monster_soldier_light (edict_t *self); +void SP_monster_soldier (edict_t *self); +void SP_monster_soldier_ss (edict_t *self); +void SP_monster_tank (edict_t *self); +void SP_monster_medic (edict_t *self); +void SP_monster_flipper (edict_t *self); +void SP_monster_chick (edict_t *self); +void SP_monster_parasite (edict_t *self); +void SP_monster_flyer (edict_t *self); +void SP_monster_brain (edict_t *self); +void SP_monster_floater (edict_t *self); +void SP_monster_hover (edict_t *self); +void SP_monster_mutant (edict_t *self); +void SP_monster_supertank (edict_t *self); +void SP_monster_boss2 (edict_t *self); +void SP_monster_jorg (edict_t *self); +void SP_monster_boss3_stand (edict_t *self); + +void SP_monster_commander_body (edict_t *self); + + +void SP_turret_breach (edict_t *self); +void SP_turret_base (edict_t *self); +void SP_turret_driver (edict_t *self); + +//zucc - item replacement function +void CheckItem(edict_t *ent, gitem_t *item); + +spawn_t spawns[] = { + {"item_health", SP_item_health}, + {"item_health_small", SP_item_health_small}, + {"item_health_large", SP_item_health_large}, + {"item_health_mega", SP_item_health_mega}, + + {"info_player_start", SP_info_player_start}, + {"info_player_deathmatch", SP_info_player_deathmatch}, + {"info_player_coop", SP_info_player_coop}, + {"info_player_intermission", SP_info_player_intermission}, + + {"func_plat", SP_func_plat}, + {"func_button", SP_func_button}, + {"func_door", SP_func_door}, + {"func_door_secret", SP_func_door_secret}, + {"func_door_rotating", SP_func_door_rotating}, + {"func_rotating", SP_func_rotating}, + {"func_train", SP_func_train}, + {"func_water", SP_func_water}, + {"func_conveyor", SP_func_conveyor}, + {"func_areaportal", SP_func_areaportal}, + {"func_clock", SP_func_clock}, + {"func_wall", SP_func_wall}, + {"func_object", SP_func_object}, + {"func_timer", SP_func_timer}, + {"func_explosive", SP_func_explosive}, + {"func_killbox", SP_func_killbox}, + + {"trigger_always", SP_trigger_always}, + {"trigger_once", SP_trigger_once}, + {"trigger_multiple", SP_trigger_multiple}, + {"trigger_relay", SP_trigger_relay}, + {"trigger_push", SP_trigger_push}, + {"trigger_hurt", SP_trigger_hurt}, + {"trigger_key", SP_trigger_key}, + {"trigger_counter", SP_trigger_counter}, + {"trigger_elevator", SP_trigger_elevator}, + {"trigger_gravity", SP_trigger_gravity}, + {"trigger_monsterjump", SP_trigger_monsterjump}, + + {"target_temp_entity", SP_target_temp_entity}, + {"target_speaker", SP_target_speaker}, + {"target_explosion", SP_target_explosion}, + {"target_changelevel", SP_target_changelevel}, + {"target_secret", SP_target_secret}, + {"target_goal", SP_target_goal}, + {"target_splash", SP_target_splash}, + {"target_spawner", SP_target_spawner}, + {"target_blaster", SP_target_blaster}, + {"target_crosslevel_trigger", SP_target_crosslevel_trigger}, + {"target_crosslevel_target", SP_target_crosslevel_target}, + {"target_laser", SP_target_laser}, + {"target_help", SP_target_help}, +// monster {"target_actor", SP_target_actor}, + {"target_lightramp", SP_target_lightramp}, + {"target_earthquake", SP_target_earthquake}, + {"target_character", SP_target_character}, + {"target_string", SP_target_string}, + + {"worldspawn", SP_worldspawn}, + {"viewthing", SP_viewthing}, + + {"light", SP_light}, + {"light_mine1", SP_light_mine1}, + {"light_mine2", SP_light_mine2}, + {"info_null", SP_info_null}, + {"func_group", SP_info_null}, + {"info_notnull", SP_info_notnull}, + {"path_corner", SP_path_corner}, + {"point_combat", SP_point_combat}, + + {"misc_explobox", SP_misc_explobox}, + {"misc_banner", SP_misc_banner}, + {"misc_satellite_dish", SP_misc_satellite_dish}, + // monster {"misc_actor", SP_misc_actor}, + {"misc_gib_arm", SP_misc_gib_arm}, + {"misc_gib_leg", SP_misc_gib_leg}, + {"misc_gib_head", SP_misc_gib_head}, + // monster {"misc_insane", SP_misc_insane}, + {"misc_deadsoldier", SP_misc_deadsoldier}, + {"misc_viper", SP_misc_viper}, + {"misc_viper_bomb", SP_misc_viper_bomb}, + {"misc_bigviper", SP_misc_bigviper}, + {"misc_strogg_ship", SP_misc_strogg_ship}, + {"misc_teleporter", SP_misc_teleporter}, + {"misc_teleporter_dest", SP_misc_teleporter_dest}, + {"misc_blackhole", SP_misc_blackhole}, + {"misc_eastertank", SP_misc_eastertank}, + {"misc_easterchick", SP_misc_easterchick}, + {"misc_easterchick2", SP_misc_easterchick2}, +/* // monsters + {"monster_berserk", SP_monster_berserk}, + {"monster_gladiator", SP_monster_gladiator}, + {"monster_gunner", SP_monster_gunner}, + {"monster_infantry", SP_monster_infantry}, + {"monster_soldier_light", SP_monster_soldier_light}, + {"monster_soldier", SP_monster_soldier}, + {"monster_soldier_ss", SP_monster_soldier_ss}, + {"monster_tank", SP_monster_tank}, + {"monster_tank_commander", SP_monster_tank}, + {"monster_medic", SP_monster_medic}, + {"monster_flipper", SP_monster_flipper}, + {"monster_chick", SP_monster_chick}, + {"monster_parasite", SP_monster_parasite}, + {"monster_flyer", SP_monster_flyer}, + {"monster_brain", SP_monster_brain}, + {"monster_floater", SP_monster_floater}, + {"monster_hover", SP_monster_hover}, + {"monster_mutant", SP_monster_mutant}, + {"monster_supertank", SP_monster_supertank}, + {"monster_boss2", SP_monster_boss2}, + {"monster_boss3_stand", SP_monster_boss3_stand}, + {"monster_jorg", SP_monster_jorg}, + + {"monster_commander_body", SP_monster_commander_body}, + + {"turret_breach", SP_turret_breach}, + {"turret_base", SP_turret_base}, + {"turret_driver", SP_turret_driver}, +*/ + {NULL, NULL} +}; + + +/* +=============== +ED_CallSpawn + +Finds the spawn function for the entity and calls it +=============== +*/ +void ED_CallSpawn (edict_t *ent) +{ + spawn_t *s; + gitem_t *item; + int i; + + if (!ent->classname) + { + gi.dprintf ("ED_CallSpawn: NULL classname\n"); + return; + } + + // check item spawn functions + for (i=0,item=itemlist ; iclassname) + continue; + if (!strcmp(item->classname, ent->classname)) + { // found it +//FIREBLADE + if (!teamplay->value) + { +//FIREBLADE + if ( item->flags == IT_AMMO || item->flags == IT_WEAPON ) + SpawnItem (ent, item); + } + + return; + } + // zucc - BD's item replacement idea + else + { + CheckItem(ent, item); + } + } + + + // check normal spawn functions + for (s=spawns ; s->name ; s++) + { + if (!strcmp(s->name, ent->classname)) + { // found it + s->spawn (ent); + return; + } + } + gi.dprintf ("%s doesn't have a spawn function\n", ent->classname); +} + +// zucc BD's checkitem function +//An 2D array of items to look for and replace with... + //item[i][0] = the Q2 item to look for + //item[i][1] = the NS2 item to actually spawn + +#define ITEM_SWITCH_COUNT 15 + +char *sp_item[ITEM_SWITCH_COUNT][2]= +{ + {"weapon_machinegun","weapon_MP5"}, + //{"weapon_supershotgun","weapon_HC"}, + {"weapon_bfg", "weapon_M4"}, + {"weapon_shotgun","weapon_M3"}, + //{"weapon_grenadelauncher","weapon_M203"}, + {"weapon_chaingun","weapon_Sniper"}, + {"weapon_rocketlauncher","weapon_HC"}, + {"weapon_railgun","weapon_Dual"}, + {"ammo_bullets","ammo_clip"}, + {"ammo_rockets","ammo_mag"}, + {"ammo_cells", "ammo_m4"}, + {"ammo_slugs", "ammo_sniper"}, + {"ammo_shells", "ammo_m3"}, + {"ammo_grenades", "weapon_Grenade"} + , + {"ammo_box", "ammo_m3"}, + {"weapon_cannon", "weapon_HC"}, + {"weapon_sniper", "weapon_Sniper"} + +}; + +void CheckItem(edict_t *ent, gitem_t *item) +{ + int i; + + for(i = 0;i < ITEM_SWITCH_COUNT; i++) + { + //If it's a null entry, bypass it + if(!sp_item[i][0]) + continue; + //Do the passed ent and our list match? + if(strcmp(ent->classname,sp_item[i][0])==0) + { + //Yep. Replace the Q2 entity with our own. + ent->classname = item->classname = sp_item[i][1]; +//FIREBLADE + if (!teamplay->value) + { +//FIREBLADE + SpawnItem(ent,item); + } + //We found it, so don't waste time looking for more. + //gi.bprintf(PRINT_HIGH,"Found %s\nReplaced with %s\n",ent->classname,test); + return; + } + } +} + + +/* +============= +ED_NewString +============= +*/ +char *ED_NewString (char *string) +{ + char *newb, *new_p; + int i,l; + + l = strlen(string) + 1; + + newb = gi.TagMalloc (l, TAG_LEVEL); + + new_p = newb; + + for (i=0 ; i< l ; i++) + { + if (string[i] == '\\' && i < l-1) + { + i++; + if (string[i] == 'n') + *new_p++ = '\n'; + else + *new_p++ = '\\'; + } + else + *new_p++ = string[i]; + } + + return newb; +} + + + + +/* +=============== +ED_ParseField + +Takes a key/value pair and sets the binary values +in an edict +=============== +*/ +void ED_ParseField (char *key, char *value, edict_t *ent) +{ + field_t *f; + byte *b; + float v; + vec3_t vec; + + for (f=fields ; f->name ; f++) + { +// FFL_NOSPAWN check in the following added in 3.20. Adding here. -FB + if (!(f->flags & FFL_NOSPAWN) && !Q_stricmp(f->name, key)) + { // found it + if (f->flags & FFL_SPAWNTEMP) + b = (byte *)&st; + else + b = (byte *)ent; + + switch (f->type) + { + case F_LSTRING: + *(char **)(b+f->ofs) = ED_NewString (value); + break; + case F_VECTOR: + sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + ((float *)(b+f->ofs))[0] = vec[0]; + ((float *)(b+f->ofs))[1] = vec[1]; + ((float *)(b+f->ofs))[2] = vec[2]; + break; + case F_INT: + *(int *)(b+f->ofs) = atoi(value); + break; + case F_FLOAT: + *(float *)(b+f->ofs) = atof(value); + break; + case F_ANGLEHACK: + v = atof(value); + ((float *)(b+f->ofs))[0] = 0; + ((float *)(b+f->ofs))[1] = v; + ((float *)(b+f->ofs))[2] = 0; + break; + case F_IGNORE: + break; + } + return; + } + } + gi.dprintf ("%s is not a field\n", key); +} + +/* +==================== +ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +==================== +*/ +char *ED_ParseEdict (char *data, edict_t *ent) +{ + qboolean init; + char keyname[256]; + char *com_token; + + init = false; + memset (&st, 0, sizeof(st)); + +// go through all the dictionary pairs + while (1) + { + // parse key + com_token = COM_Parse (&data); + if (com_token[0] == '}') + break; + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + strncpy (keyname, com_token, sizeof(keyname)-1); + + // parse value + com_token = COM_Parse (&data); + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + if (com_token[0] == '}') + gi.error ("ED_ParseEntity: closing brace without data"); + + init = true; + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by quake + if (keyname[0] == '_') + continue; + + ED_ParseField (keyname, com_token, ent); + } + + if (!init) + memset (ent, 0, sizeof(*ent)); + + return data; +} + + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. + +All but the first will have the FL_TEAMSLAVE flag set. +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FindTeams (void) +{ + edict_t *e, *e2, *chain; + int i, j; + int c, c2; + + c = 0; + c2 = 0; + for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++) + { + if (!e->inuse) + continue; + if (!e->team) + continue; + if (e->flags & FL_TEAMSLAVE) + continue; + chain = e; + e->teammaster = e; + c++; + c2++; + for (j=i+1, e2=e+1 ; j < globals.num_edicts ; j++,e2++) + { + if (!e2->inuse) + continue; + if (!e2->team) + continue; + if (e2->flags & FL_TEAMSLAVE) + continue; + if (!strcmp(e->team, e2->team)) + { + c2++; + chain->teamchain = e2; + e2->teammaster = e; + chain = e2; + e2->flags |= FL_TEAMSLAVE; + } + } + } + + gi.dprintf ("%i teams with %i entities\n", c, c2); +} + +/* +============== +SpawnEntities + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. +============== +*/ +void SpawnEntities (char *mapname, char *entities, char *spawnpoint) +{ + edict_t *ent; + int inhibit; + char *com_token; + int i; + float skill_level; + + skill_level = floor (skill->value); + if (skill_level < 0) + skill_level = 0; + if (skill_level > 3) + skill_level = 3; + if (skill->value != skill_level) + gi.cvar_forceset("skill", va("%f", skill_level)); + + SaveClientData (); + + gi.FreeTags (TAG_LEVEL); + + memset (&level, 0, sizeof(level)); + memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0])); + + strncpy (level.mapname, mapname, sizeof(level.mapname)-1); + strncpy (game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)-1); + + // set client fields on player ents + for (i=0 ; iclassname, "trigger_once") && !stricmp(ent->model, "*27")) + ent->spawnflags &= ~SPAWNFLAG_NOT_HARD; + + // remove things (except the world) from different skill levels or deathmatch + if (ent != g_edicts) + { + if (deathmatch->value) + { + if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + else + { + if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */ + ((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || + ((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || + (((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)) + ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + + ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH); + } + + ED_CallSpawn (ent); + } + + gi.dprintf ("%i entities inhibited\n", inhibit); + + G_FindTeams (); + + PlayerTrail_Init (); + +//FIREBLADE + if (!teamplay->value) + { +//FIREBLADE + //zucc for special items + SetupSpecSpawn(); + } +// CGF_FOG ADD + CGF_SFX_AdjustFogForMap(level.mapname); +// CGF_FOG END +} + + +//=================================================================== + +#if 0 + // cursor positioning + xl + xr + yb + yt + xv + yv + + // drawing + statpic + pic + num + string + + // control + if + ifeq + ifbit + endif + +#endif + +char *single_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +/* +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " +*/ + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 262 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " + +// zucc + +// sniper zoom graphic/icon + +"if 18 " +" xr 0 " +" yb 0 " +" xv 0 " +" yv 0 " +" pic 18 " +"endif " + + + + + +// clip(s) +// puts them all the way on the right side of the screen +"if 16 " +" xv 0 " +" yv 0 " +" yb -24 " +" xr -60 " +" num 2 17 " +" xr -24 " +" pic 16 " +"endif " + +// zucc special item ( vest etc ) +"if 19 " +" xv 0 " +" yv 0 " +" yb -72 " +" xr -24 " +" pic 19 " +"endif " + +// zucc special weapon +"if 20 " +" xv 0 " +" yv 0 " +" yb -48 " +" xr -24 " +" pic 20 " +"endif " + +// zucc grenades + +"if 28 " +" xv 0 " +" yv 0 " +" yb -96 " +" xr -60 " +" num 2 29 " +" xr -24 " +" pic 28 " +"endif " + + +"if 21 " + "xv 0 " + "yb -58 " + "string \"Viewing\" " + "xv 64 " + "stat_string 21 " +"endif " +; + + +char *dm_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + + +/* +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " +*/ +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +/* +// timer +"if 9 " +" xv 246 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " +*/ +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " + +// zucc +// clip(s) +"if 16 " +" xv 0 " +" yv 0 " +" yb -24 " +" xr -60 " +" num 2 17 " +" xr -24 " +" pic 16 " +"endif " + +// zucc special item ( vest etc ) +"if 19 " +" xv 0 " +" yv 0 " +" yb -72 " +" xr -24 " +" pic 19 " +"endif " + +// zucc special weapon +"if 20 " +" xv 0 " +" yv 0 " +" yb -48 " +" xr -24 " +" pic 20 " +"endif " + +// zucc grenades + +"if 28 " +" xv 0 " +" yv 0 " +" yb -96 " +" xr -60 " +" num 2 29 " +" xr -24 " +" pic 28 " +"endif " + +"if 21 " + "xv 0 " + "yb -58 " + "string \"Viewing\" " + "xv 64 " + "stat_string 21 " +"endif " + +// sniper graphic/icon + +"if 18 " +" xr 0 " +" yb 0 " +" xv 0 " +" yv 0 " +" pic 18 " +"endif " + +// frags +"xr -50 " +"yt 2 " +"num 3 14" +; + + + +/* DM status bar for teamplay without individual scores -FB: */ +char *dm_noscore_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + + +/* +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " +*/ +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +/* +// timer +"if 9 " +" xv 246 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " +*/ +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " + +// zucc +// clip(s) +"if 16 " +" xv 0 " +" yv 0 " +" yb -24 " +" xr -60 " +" num 2 17 " +" xr -24 " +" pic 16 " +"endif " + +// zucc special item ( vest etc ) +"if 19 " +" xv 0 " +" yv 0 " +" yb -72 " +" xr -24 " +" pic 19 " +"endif " + +// zucc special weapon +"if 20 " +" xv 0 " +" yv 0 " +" yb -48 " +" xr -24 " +" pic 20 " +"endif " + +// zucc grenades + +"if 28 " +" xv 0 " +" yv 0 " +" yb -96 " +" xr -60 " +" num 2 29 " +" xr -24 " +" pic 28 " +"endif " + +"if 21 " + "xv 0 " + "yb -58 " + "string \"Viewing\" " + "xv 64 " + "stat_string 21 " +"endif " + +// sniper graphic/icon + +"if 18 " +" xr 0 " +" yb 0 " +" xv 0 " +" yv 0 " +" pic 18 " +"endif " + +/* +// frags +"xr -50 " +"yt 2 " +"num 3 14" +*/ +; +// END FB + + +/*QUAKED worldspawn (0 0 0) ? + +Only used for the world. +"sky" environment map name +"skyaxis" vector axis for rotating sky +"skyrotate" speed of rotation in degrees/second +"sounds" music cd track number +"gravity" 800 is default gravity +"message" text to print at user logon +*/ +void SP_worldspawn (edict_t *ent) +{ + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + ent->inuse = true; // since the world doesn't use G_Spawn() + ent->s.modelindex = 1; // world model is always index 1 + + //--------------- + + // reserve some spots for dead player bodies for coop / deathmatch + InitBodyQue (); + + // set configstrings for items + SetItemNames (); + + if (st.nextmap) + strcpy (level.nextmap, st.nextmap); + + // make some data visible to the server + + if (ent->message && ent->message[0]) + { + gi.configstring (CS_NAME, ent->message); + strncpy (level.level_name, ent->message, sizeof(level.level_name)); + } + else + strncpy (level.level_name, level.mapname, sizeof(level.level_name)); + + if (st.sky && st.sky[0]) + gi.configstring (CS_SKY, st.sky); + else + gi.configstring (CS_SKY, "unit1_"); + + gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) ); + + gi.configstring (CS_SKYAXIS, va("%f %f %f", + st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) ); + + gi.configstring (CS_CDTRACK, va("%i", ent->sounds) ); + + gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) ); + +//FIREBLADE + if (nohud->value) + { + gi.configstring(CS_STATUSBAR, ""); + } + else +//FIREBLADE + { + // status bar program + if (deathmatch->value) +//FIREBLADE + { + if (noscore->value && teamplay->value) + { + gi.configstring (CS_STATUSBAR, dm_noscore_statusbar); + } + else + { + gi.configstring (CS_STATUSBAR, dm_statusbar); + } + } +//FIREBLADE + else + gi.configstring (CS_STATUSBAR, single_statusbar); + } + + //--------------- + + + // help icon for statusbar + gi.imageindex ("i_help"); + level.pic_health = gi.imageindex ("i_health"); + gi.imageindex ("help"); + gi.imageindex ("field_3"); + + // zucc - preload sniper stuff + gi.imageindex ("scope2x"); + gi.imageindex ("scope4x"); + gi.imageindex ("scope6x"); + +//FIREBLADE + gi.soundindex("atl/lights.wav"); + gi.soundindex("atl/camera.wav"); + gi.soundindex("atl/action.wav"); + gi.imageindex("tag1"); + gi.imageindex("tag2"); + gi.imageindex("tag3"); + if (teamplay->value) + { + if (team1_skin_index[0] == 0) + { + gi.dprintf("No skin was specified for team 1 in config file. Exiting.\n"); + exit(1); + } + gi.imageindex(team1_skin_index); + if (team2_skin_index[0] == 0) + { + gi.dprintf("No skin was specified for team 2 in config file. Exiting.\n"); + exit(1); + } + gi.imageindex(team2_skin_index); + } + PrecacheRadioSounds(); + + team_round_going = 0; + lights_camera_action = 0; + holding_on_tie_check = 0; + team_round_countdown = 0; +//FIREBLADE + + if (!st.gravity) + gi.cvar_set("sv_gravity", "800"); + else + gi.cvar_set("sv_gravity", st.gravity); + + snd_fry = gi.soundindex ("player/fry.wav"); // standing in lava / slime + + PrecacheItem (FindItem ("Blaster")); + + gi.soundindex ("player/lava1.wav"); + gi.soundindex ("player/lava2.wav"); + + gi.soundindex ("misc/pc_up.wav"); + gi.soundindex ("misc/talk1.wav"); + + gi.soundindex ("misc/udeath.wav"); + + // gibs + gi.soundindex ("items/respawn1.wav"); + + // sexed sounds + gi.soundindex ("*death1.wav"); + gi.soundindex ("*death2.wav"); + gi.soundindex ("*death3.wav"); + gi.soundindex ("*death4.wav"); + gi.soundindex ("*fall1.wav"); + gi.soundindex ("*fall2.wav"); + gi.soundindex ("*gurp1.wav"); // drowning damage + gi.soundindex ("*gurp2.wav"); + gi.soundindex ("*jump1.wav"); // player jump + gi.soundindex ("*pain25_1.wav"); + gi.soundindex ("*pain25_2.wav"); + gi.soundindex ("*pain50_1.wav"); + gi.soundindex ("*pain50_2.wav"); + gi.soundindex ("*pain75_1.wav"); + gi.soundindex ("*pain75_2.wav"); + gi.soundindex ("*pain100_1.wav"); + gi.soundindex ("*pain100_2.wav"); + + //------------------- + + // precache vwep models + gi.modelindex( "#w_mk23.md2"); + gi.modelindex( "#w_mp5.md2"); + gi.modelindex( "#w_m4.md2"); + gi.modelindex( "#w_cannon.md2"); + gi.modelindex( "#w_super90.md2"); + gi.modelindex( "#w_sniper.md2"); + gi.modelindex( "#w_akimbo.md2"); + gi.modelindex( "#w_knife.md2"); + gi.modelindex( "#a_m61frag.md2"); + + gi.soundindex ("player/gasp1.wav"); // gasping for air + gi.soundindex ("player/gasp2.wav"); // head breaking surface, not gasping + + gi.soundindex ("player/watr_in.wav"); // feet hitting water + gi.soundindex ("player/watr_out.wav"); // feet leaving water + + gi.soundindex ("player/watr_un.wav"); // head going underwater + + gi.soundindex ("player/u_breath1.wav"); + gi.soundindex ("player/u_breath2.wav"); + + gi.soundindex ("items/pkup.wav"); // bonus item pickup + gi.soundindex ("world/land.wav"); // landing thud + gi.soundindex ("misc/h2ohit1.wav"); // landing splash + + gi.soundindex ("items/damage.wav"); + gi.soundindex ("items/protect.wav"); + gi.soundindex ("items/protect4.wav"); + gi.soundindex ("weapons/noammo.wav"); + + gi.soundindex ("infantry/inflies1.wav"); + + sm_meat_index = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2"); + gi.modelindex ("models/objects/gibs/arm/tris.md2"); + gi.modelindex ("models/objects/gibs/bone/tris.md2"); + gi.modelindex ("models/objects/gibs/bone2/tris.md2"); + gi.modelindex ("models/objects/gibs/chest/tris.md2"); + gi.modelindex ("models/objects/gibs/skull/tris.md2"); + gi.modelindex ("models/objects/gibs/head2/tris.md2"); + +// +// Setup light animation tables. 'a' is total darkness, 'z' is doublebright. +// + + // 0 normal + gi.configstring(CS_LIGHTS+0, "m"); + + // 1 FLICKER (first variety) + gi.configstring(CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + gi.configstring(CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + gi.configstring(CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + gi.configstring(CS_LIGHTS+4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + gi.configstring(CS_LIGHTS+5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + gi.configstring(CS_LIGHTS+6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + gi.configstring(CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + gi.configstring(CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + gi.configstring(CS_LIGHTS+9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + gi.configstring(CS_LIGHTS+10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + gi.configstring(CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + gi.configstring(CS_LIGHTS+63, "a"); + +//FB 6/2/99 + if (took_damage != NULL) + gi.TagFree(took_damage); + took_damage = (int *)gi.TagMalloc(sizeof(int) * game.maxclients, TAG_GAME); +//FB 6/2/99 +} + diff --git a/g_svcmds.c b/g_svcmds.c new file mode 100644 index 0000000..ecf815d --- /dev/null +++ b/g_svcmds.c @@ -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 +removeip + +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 " 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 ; ivalue; + + 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 \n"); + return; + } + + for (i=0 ; i\n"); + return; + } + + if (!StringToFilter (gi.argv(2), &f, 0)) + return; + + for (i=0 ; istring) + 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 ; ivalue) // 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 ; iinuse) + 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 ; iclient->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 0) + { + if (!--ipfilters[i].temp_ban_games) + { + // re-pack the filters + for (j=i+1 ; jstyle); + 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"); +} diff --git a/g_trigger.c b/g_trigger.c new file mode 100644 index 0000000..2466c05 --- /dev/null +++ b/g_trigger.c @@ -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; +} + diff --git a/g_turret.c b/g_turret.c new file mode 100644 index 0000000..a6deb30 --- /dev/null +++ b/g_turret.c @@ -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); +} diff --git a/g_utils.c b/g_utils.c new file mode 100644 index 0000000..2bece12 --- /dev/null +++ b/g_utils.c @@ -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 ; iinuse && ( 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 ; iinuse) + 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 ; iinuse) + 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 +} diff --git a/g_weapon.c b/g_weapon.c new file mode 100644 index 0000000..e63bbae --- /dev/null +++ b/g_weapon.c @@ -0,0 +1,1629 @@ +#include "g_local.h" +#include "cgf_sfx_glass.h" +#include "a_game.h" //zucc for KickDoor + + +void knife_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); +void Zylon_Grenade(edict_t *ent); +void setFFState(edict_t *ent); + +//FIREBLADE +qboolean ap_already_hit[1000]; // 1000 = a number much larger than the possible max # of clients +int *took_damage; //FB 6/2/99, to keep track of shotgun damage +//FIREBLADE + +/* +================= +check_dodge + +This is a support routine used when a client is firing +a non-instant attack weapon. It checks to see if a +monster's dodge function should be called. +================= +*/ +static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed) +{ + vec3_t end; + vec3_t v; + trace_t tr; + float eta; + + // easy mode only ducks one quarter the time + if (skill->value == 0) + { + if (random() > 0.25) + return; + } + VectorMA (start, 8192, dir, end); + PRETRACE(); + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + POSTTRACE(); + if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self)) + { + VectorSubtract (tr.endpos, start, v); + eta = (VectorLength(v) - tr.ent->maxs[0]) / speed; + tr.ent->monsterinfo.dodge (tr.ent, self, eta); + } +} + + +/* zucc - bulletholes for testing spread patterns */ + +void BulletHoleThink (edict_t *self) +{ + G_FreeEdict( self ); +} + + +void SpawnHole( trace_t *tr, vec3_t dir ) +{ + + vec3_t origin; + + edict_t* lss; + lss = G_Spawn (); + lss->movetype = MOVETYPE_NOCLIP; + lss->solid = SOLID_TRIGGER; + lss->classname = "bhole"; + //lss->s.modelindex = gi.modelindex ("models/weapons/g_m4/tris.md2" ); + //lss->s.modelindex = gi.modelindex ("models/objects/holes/hole1/tris.md2" ); +// bhole model doesn't want to show up, so be it + lss->s.modelindex = gi.modelindex ("sprites/lsight.sp2"); + lss->s.renderfx = RF_GLOW; + lss->gravity = 0; + lss->think = BulletHoleThink; + lss->nextthink = level.time + 1000; + vectoangles(tr->plane.normal,lss->s.angles); + VectorNormalize(dir); + VectorMA(tr->endpos, 0,dir,origin); + VectorCopy(origin,lss->s.origin); + gi.linkentity (lss); +} + + +/* +================= +fire_hit + +Used for all impact (hit/punch/slash) attacks +================= +*/ +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t v; + vec3_t point; + float range; + vec3_t dir; + + //see if enemy is in range + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); + range = VectorLength(dir); + if (range > aim[0]) + return false; + + if (aim[1] > self->mins[0] && aim[1] < self->maxs[0]) + { + // the hit is straight on so back the range up to the edge of their bbox + range -= self->enemy->maxs[0]; + } + else + { + // this is a side hit so adjust the "right" value out to the edge of their bbox + if (aim[1] < 0) + aim[1] = self->enemy->mins[0]; + else + aim[1] = self->enemy->maxs[0]; + } + + VectorMA (self->s.origin, range, dir, point); + + PRETRACE(); + tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT); + POSTTRACE(); + if (tr.fraction < 1) + { + if (!tr.ent->takedamage) + return false; + // if it will hit any client/monster then hit the one we wanted to hit + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + tr.ent = self->enemy; + } + + AngleVectors(self->s.angles, forward, right, up); + VectorMA (self->s.origin, range, forward, point); + VectorMA (point, aim[1], right, point); + VectorMA (point, aim[2], up, point); + VectorSubtract (point, self->enemy->s.origin, dir); + + setFFState(self); + // do the damage + T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT); + + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + return false; + + // do our special form of knockback here + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v); + VectorSubtract (v, point, v); + VectorNormalize (v); + VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity); + if (self->enemy->velocity[2] > 0) + self->enemy->groundentity = NULL; + return true; +} + + +/* +================= +fire_lead + +This is an internal support routine used for bullet/pellet based weapons. +================= +*/ +static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + + PRETRACE(); + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + POSTTRACE(); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + PRETRACE(); + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + POSTTRACE(); + +// glass fx + // 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 + PRETRACE(); + tr = gi.trace (tr.endpos, NULL, NULL, end, tr.ent, content_mask); + POSTTRACE(); + } +// --- + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + PRETRACE(); + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + POSTTRACE(); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod); + } + else + { + if ( mod != MOD_M3 && mod != MOD_HC) + { + AddDecal (self, &tr); + } + //zucc remove spawnhole for real release +// SpawnHole( &tr, dir ); + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + { + PRETRACE(); + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + POSTTRACE(); + } + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } +} + + +/* +================= +fire_bullet + +Fires a single round. Used for machinegun and chaingun. Would be fine for +pistols, rifles, etc.... +================= +*/ +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod) +{ + setFFState(self); + fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod); +} + + + +// zucc fire_load_ap for rounds that pass through soft targets and keep going +static void fire_lead_ap (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + vec3_t from; + edict_t *ignore; + +//FIREBLADE + memset(ap_already_hit, 0, + game.maxclients * sizeof(qboolean)); +//FIREBLADE + + // setup + stopAP = 0; + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + VectorCopy (start, from); + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + ignore = self; + + // PRETRACE(); + // tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + // POSTTRACE(); + while (ignore) + //if (!(tr.fraction < 1.0)) + { + PRETRACE(); + //tr = gi.trace (from, NULL, NULL, end, ignore, mask); + tr = gi.trace (from, NULL, NULL, end, ignore, content_mask); + POSTTRACE(); + +// glass fx + // 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 + PRETRACE(); + tr = gi.trace (tr.endpos, NULL, NULL, end, tr.ent, content_mask); + POSTTRACE(); + } +// --- + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (from, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + VectorSubtract (end, from, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + PRETRACE(); + tr = gi.trace (water_start, NULL, NULL, end, ignore, MASK_SHOT); + POSTTRACE(); + + } + + // send gun puff / flash + + ignore = NULL; + + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + { + ignore = tr.ent; + VectorCopy (tr.endpos, from); +//FIREBLADE + // Advance the "from" point a few units + // towards "end" here + if (tr.ent->client) + { + vec3_t tempv; + vec3_t out; + if (ap_already_hit[tr.ent - g_edicts - 1]) + { + VectorSubtract(end, from, tempv); + VectorNormalize(tempv); + VectorScale(tempv, 8, out); + VectorAdd(out, from, from); + continue; + } + ap_already_hit[tr.ent - g_edicts - 1] = true; + } +//FIREBLADE + } + + if ((tr.ent != self) && (tr.ent->takedamage)) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, mod); + if ( stopAP ) // the AP round hit something that would stop it (kevlar) + ignore = NULL; + } + else if ( tr.ent != self && !water ) + { + //zucc remove spawnhole for real release + // SpawnHole( &tr, dir ); + + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + AddDecal (self, &tr); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + } + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + { + PRETRACE(); + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + POSTTRACE(); + } + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } + +} + + + +// zucc - for the M4 +void fire_bullet_sparks (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod) +{ + setFFState(self); + fire_lead_ap (self, start, aimdir, damage, kick, TE_BULLET_SPARKS, hspread, vspread, mod); +} + +// zucc - for sniper +void fire_bullet_sniper (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod) +{ + setFFState(self); + fire_lead_ap (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod); +} + + +/* + * Init and ProduceShotgunDamageReport + */ + +void InitShotgunDamageReport() +{ + memset(took_damage, 0, game.maxclients * sizeof(int)); +} + +void ProduceShotgunDamageReport(edict_t *self) +{ + int l; + int total_to_print = 0, printed = 0; + static char textbuf[1024]; + +//FB 6/2/99 - shotgun/handcannon damage notification + for (l = 1; l <= game.maxclients; l++) + { + if (took_damage[l - 1]) + total_to_print++; + } + if (total_to_print) + { + if (total_to_print > 10) + total_to_print = 10; + + strcpy(textbuf, "You hit "); + for (l = 1; l <= game.maxclients; l++) + { + if (took_damage[l - 1]) + { + if (printed == (total_to_print - 1)) + { + if (total_to_print == 2) + strcat(textbuf, " and "); + else if (total_to_print != 1) + strcat(textbuf, ", and "); + } + else if (printed) + strcat(textbuf, ", "); + strcat(textbuf, g_edicts[l].client->pers.netname); + printed++; + } + if (printed == total_to_print) + break; + } + safe_cprintf(self, PRINT_HIGH, "%s\n", textbuf); + } +//FB 6/2/99 +} + +/* +================= +fire_shotgun + +Shoots shotgun pellets. Used by shotgun and super shotgun. +================= +*/ +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod) +{ + int i; + + for (i = 0; i < count; i++) + fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod); +} + + +/* +================= +fire_blaster + +Fires a single blaster bolt. Used by the blaster and hyper blaster. +================= +*/ +void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int mod; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + if (self->spawnflags & 1) + mod = MOD_HYPERBLASTER; + else + mod = MOD_BLASTER; + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); + } + else + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + + G_FreeEdict (self); +} + +void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); +// 3.20 ANTI-LAG FIX, just in case we end up using this code for something... -FB + bolt->svflags = SVF_DEADMONSTER; +// ^^^ + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2"); + bolt->s.sound = gi.soundindex ("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster_touch; + bolt->nextthink = level.time + 2; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + if (hyper) + bolt->spawnflags = 1; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + PRETRACE(); + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + POSTTRACE(); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + +qboolean G_EntExists(edict_t *ent) +{ + return ((ent) && (ent->client));//(ent->inuse)); +} + + + +/* +================= +fire_grenade +================= +*/ +static void Grenade_Explode (edict_t *ent) +{ + vec3_t origin; + int mod; + + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + if (ent->enemy) + { + float points; + vec3_t v; + vec3_t dir; + + VectorAdd (ent->enemy->mins, ent->enemy->maxs, v); + VectorMA (ent->enemy->s.origin, 0.5, v, v); + VectorSubtract (ent->s.origin, v, v); + points = ent->dmg - 0.5 * VectorLength (v); + VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir); + if (ent->spawnflags & 1) + mod = MOD_HANDGRENADE; + else + mod = MOD_GRENADE; + T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + + if (ent->spawnflags & 2) + mod = MOD_HELD_GRENADE; + else if (ent->spawnflags & 1) + mod = MOD_HG_SPLASH; + else + mod = MOD_G_SPLASH; + T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + } + else + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + } + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +static void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { +/*FIREBLADE + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else +FIREBLADE*/ + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + // zucc not needed since grenades don't blow up on contact + //ent->enemy = other; + //Grenade_Explode (ent); +} + +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + + gi.linkentity (grenade); +} + +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + //grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "hgrenade"; + if (held) + grenade->spawnflags = 3; + else + grenade->spawnflags = 1; + //grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + if (timer <= 0.0) + Grenade_Explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} + + +/* +================= +fire_rocket +================= +*/ +void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + int n; + + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + + if (other->takedamage) + { + T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET); + } + else + { + // don't throw any debris in net games + if (!deathmatch->value && !coop->value) + { + if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING))) + { + n = rand() % 5; + while(n--) + ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin); + } + } + } + + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH); + + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *rocket; + + rocket = G_Spawn(); + VectorCopy (start, rocket->s.origin); + VectorCopy (dir, rocket->movedir); + vectoangles (dir, rocket->s.angles); + VectorScale (dir, speed, rocket->velocity); + rocket->movetype = MOVETYPE_FLYMISSILE; + rocket->clipmask = MASK_SHOT; + rocket->solid = SOLID_BBOX; + rocket->s.effects |= EF_ROCKET; + VectorClear (rocket->mins); + VectorClear (rocket->maxs); + rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2"); + rocket->owner = self; + rocket->touch = rocket_touch; + rocket->nextthink = level.time + 8000/speed; + rocket->think = G_FreeEdict; + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->classname = "rocket"; + + if (self->client) + check_dodge (self, rocket->s.origin, dir, speed); + + gi.linkentity (rocket); +} + + +/* +================= +fire_rail +================= +*/ +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick) +{ + vec3_t from; + vec3_t end; + trace_t tr; + edict_t *ignore; + int mask; + qboolean water; + + VectorMA (start, 8192, aimdir, end); + VectorCopy (start, from); + ignore = self; + water = false; + mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + while (ignore) + { + PRETRACE(); + tr = gi.trace (from, NULL, NULL, end, ignore, mask); + POSTTRACE(); + + if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA); + water = true; + } + else + { +// FROM 3.20, just in case we ever end up using this code for something... -FB + //ZOID--added so rail goes through SOLID_BBOX entities (gibs, etc) + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) || + (tr.ent->solid == SOLID_BBOX)) +// ^^^ + ignore = tr.ent; + else + ignore = NULL; + + if ((tr.ent != self) && (tr.ent->takedamage)) + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN); + } + + VectorCopy (tr.endpos, from); + } + + // send gun puff / flash + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); +// gi.multicast (start, MULTICAST_PHS); + if (water) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (tr.endpos, MULTICAST_PHS); + } + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); +} + + +/* +================= +fire_bfg +================= +*/ +void bfg_explode (edict_t *self) +{ + edict_t *ent; + float points; + vec3_t v; + float dist; + + if (self->s.frame == 0) + { + // the BFG effect + ent = NULL; + while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL) + { + if (!ent->takedamage) + continue; + if (ent == self->owner) + continue; + if (!CanDamage (ent, self)) + continue; + if (!CanDamage (ent, self->owner)) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (self->s.origin, v, v); + dist = VectorLength(v); + points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius)); + if (ent == self->owner) + points = points * 0.5; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_EXPLOSION); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT); + } + } + + self->nextthink = level.time + FRAMETIME; + self->s.frame++; + if (self->s.frame == 5) + self->think = G_FreeEdict; +} + +void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + // core explosion - prevents firing it into the wall/floor + if (other->takedamage) + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST); + T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST); + + gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0); + self->solid = SOLID_NOT; + self->touch = NULL; + VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin); + VectorClear (self->velocity); + self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2"); + self->s.frame = 0; + self->s.sound = 0; + self->s.effects &= ~EF_ANIM_ALLFAST; + self->think = bfg_explode; + self->nextthink = level.time + FRAMETIME; + self->enemy = other; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_BIGEXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + + +void bfg_think (edict_t *self) +{ + edict_t *ent; + edict_t *ignore; + vec3_t point; + vec3_t dir; + vec3_t start; + vec3_t end; + int dmg; + trace_t tr; + + if (deathmatch->value) + dmg = 5; + else + dmg = 10; + + ent = NULL; + while ((ent = findradius(ent, self->s.origin, 256)) != NULL) + { + if (ent == self) + continue; + + if (ent == self->owner) + continue; + + if (!ent->takedamage) + continue; + + if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) + continue; + + VectorMA (ent->absmin, 0.5, ent->size, point); + + VectorSubtract (point, self->s.origin, dir); + VectorNormalize (dir); + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, dir, end); + while(1) + { + 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) && (tr.ent != self->owner)) + T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER); + + // if we hit something that's not a monster or player we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (4); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (self->s.origin); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); + } + + self->nextthink = level.time + FRAMETIME; +} + + +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius) +{ + edict_t *bfg; + + bfg = G_Spawn(); + VectorCopy (start, bfg->s.origin); + VectorCopy (dir, bfg->movedir); + vectoangles (dir, bfg->s.angles); + VectorScale (dir, speed, bfg->velocity); + bfg->movetype = MOVETYPE_FLYMISSILE; + bfg->clipmask = MASK_SHOT; + bfg->solid = SOLID_BBOX; + bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST; + VectorClear (bfg->mins); + VectorClear (bfg->maxs); + bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2"); + bfg->owner = self; + bfg->touch = bfg_touch; + bfg->nextthink = level.time + 8000/speed; + bfg->think = G_FreeEdict; + bfg->radius_dmg = damage; + bfg->dmg_radius = damage_radius; + bfg->classname = "bfg blast"; + bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav"); + + bfg->think = bfg_think; + bfg->nextthink = level.time + FRAMETIME; + bfg->teammaster = bfg; + bfg->teamchain = NULL; + + if (self->client) + check_dodge (self, bfg->s.origin, dir, speed); + + gi.linkentity (bfg); +} + + +void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); + +qboolean IsFemale (edict_t *ent); + +void kick_attack (edict_t * ent ) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage = 20; + int kick = 400; + trace_t tr; + vec3_t end; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, 0, ent->client->kick_origin); + + VectorSet(offset, 0, 0, ent->viewheight-20); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorMA( start, 25, forward, end ); + + PRETRACE(); + tr = gi.trace (ent->s.origin, NULL, NULL, end, ent, MASK_SHOT); + POSTTRACE(); + + // don't need to check for water + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage || KickDoor(&tr, ent, forward)) + { + if (teamplay->value) + { + if (lights_camera_action) + return; + + if (tr.ent != ent && tr.ent->client && ent->client && + tr.ent->client->resp.team == ent->client->resp.team) + return; + } + + if ( tr.ent->health <= 0 ) + return; + + if (((tr.ent != ent) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value) && OnSameTeam (tr.ent, ent))) + return; + // zucc stop powerful upwards kicking + //forward[2] = 0; + +// glass fx + if (0 == Q_stricmp(tr.ent->classname, "func_explosive")) + { + CGF_SFX_ShootBreakableGlass(tr.ent, ent, &tr, MOD_KICK); + } + else +// --- + T_Damage (tr.ent, ent, ent, forward, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_KICK ); + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/kick.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); + ent->client->jumping = 0; // only 1 jumpkick per jump + if (tr.ent->client && ( tr.ent->client->curr_weap == M4_NUM + || tr.ent->client->curr_weap == MP5_NUM + || tr.ent->client->curr_weap == M3_NUM + || tr.ent->client->curr_weap == SNIPER_NUM + || tr.ent->client->curr_weap == HC_NUM ) + && 1 ) // crandom() > .8 ) + { + + // zucc fix this so reloading won't happen on the new gun! + tr.ent->client->reload_attempts = 0; + DropSpecialWeapon(tr.ent); + if (IsFemale(tr.ent)) + { + safe_cprintf(ent, PRINT_HIGH, "You kick %s's %s from her hands!\n", tr.ent->client->pers.netname, (tr.ent->client->pers.weapon)->pickup_name ); + } + else + { + safe_cprintf(ent, PRINT_HIGH, "You kick %s's %s from his hands!\n", tr.ent->client->pers.netname, (tr.ent->client->pers.weapon)->pickup_name ); + } + safe_cprintf(tr.ent, PRINT_HIGH, "%s kicked your weapon from your hands!\n", ent->client->pers.netname ); + } + + } + + } + } + +} + +// zucc +// return values +// 0 - missed +// 1 - hit player +// 2 - hit wall + + +int knife_attack ( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick) +{ + trace_t tr; + vec3_t end; + + + VectorMA (start, 45, aimdir, end); + + PRETRACE(); + tr = gi.trace (self->s.origin, NULL, NULL, end, self, MASK_SHOT); + POSTTRACE(); + + // don't need to check for water + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { +//glass fx + if (0 == Q_stricmp(tr.ent->classname, "func_explosive")) + { + CGF_SFX_ShootBreakableGlass(tr.ent, self, &tr, MOD_KNIFE); + } + else +// --- + if (tr.ent->takedamage) + { + setFFState(self); + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_KNIFE ); + return -2; + } + else + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPARKS); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + return -1; + } + + } + else + return 0; + } + return 0; // we hit the sky, call it a miss +} + +static int knives = 0; + +void knife_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + // int n; + + edict_t *dropped; + edict_t *knife; + // vec3_t forward, right, up; + vec3_t move_angles; + gitem_t *item; + + + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (ent->owner->client) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/clank.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + } + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + +//glass fx + if (0 == Q_stricmp(other->classname, "func_explosive")) + { + // ignore it, so it can bounce + return; + } + else +// --- + if (other->takedamage) + { + T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_KNIFE_THROWN); + } + else + { + + // code to manage excess knives in the game, guarantees that + // no more than knifelimit knives will be stuck in walls. + // if knifelimit == 0 then it won't be in effect and it can + // start removing knives even when less than the limit are + // out there. + if ( knifelimit->value != 0 ) + { + knives++; + + if (knives > knifelimit->value) + knives = 1; + + knife = FindEdictByClassnum ("weapon_Knife", knives); + + if (knife) + { + knife->nextthink = level.time + .1; + } + + } + + dropped = G_Spawn(); + item = FindItem(KNIFE_NAME); + + dropped->classname = item->classname; + dropped->item = item; + dropped->spawnflags = DROPPED_ITEM; + dropped->s.effects = item->world_model_flags; + dropped->s.renderfx = RF_GLOW; + VectorSet (dropped->mins, -15, -15, -15); + VectorSet (dropped->maxs, 15, 15, 15); + gi.setmodel (dropped, dropped->item->world_model); + dropped->solid = SOLID_TRIGGER; + dropped->movetype = MOVETYPE_TOSS; + dropped->touch = Touch_Item; + dropped->owner = ent; + dropped->gravity = 0; + dropped->classnum = knives; + + vectoangles (ent->velocity, move_angles); + //AngleVectors (ent->s.angles, forward, right, up); + VectorCopy (ent->s.origin, dropped->s.origin); + VectorCopy (move_angles, dropped->s.angles); + //VectorScale (forward, 100, dropped->velocity); + //dropped->velocity[2] = 300; + + //dropped->think = drop_make_touchable; + //dropped->nextthink = level.time + 1; + + dropped->nextthink = level.time + 120; + dropped->think = G_FreeEdict; + + gi.linkentity (dropped); + + + if ( !(ent->waterlevel) ) + { + /*gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WELDING_SPARKS); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + */ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPARKS); + gi.WritePosition (origin); + gi.WriteDir (plane->normal); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } + + } + G_FreeEdict (ent); +} + + + + +void knife_throw (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed ) +{ + edict_t *knife; +// vec3_t forward, right, up; + trace_t tr; + + knife = G_Spawn(); + + VectorNormalize (dir); + VectorCopy (start, knife->s.origin); + VectorCopy (start, knife->s.old_origin); + vectoangles (dir, knife->s.angles); + VectorScale (dir, speed, knife->velocity); + knife->movetype = MOVETYPE_TOSS; + +/* + VectorCopy (start, knife->s.origin); + VectorCopy (dir, knife->movedir); + vectoangles (dir, knife->s.angles); + VectorScale (dir, speed, knife->velocity); +*/ + + // safe_cprintf(self, PRINT_HIGH, "speed %d\n", speed); + // below add upwards trajectory, not in action though + // AngleVectors (knife->s.angles, forward, right, up); + // VectorMA (knife->velocity, 210, up, knife->velocity); + VectorSet (knife->avelocity, 1200, 0, 0); + + knife->movetype = MOVETYPE_TOSS; + knife->clipmask = MASK_SHOT; + knife->solid = SOLID_BBOX; + knife->s.effects = 0; //EF_ROTATE? + VectorClear (knife->mins); + VectorClear (knife->maxs); + knife->s.modelindex = gi.modelindex ("models/objects/knife/tris.md2"); + knife->owner = self; + knife->touch = knife_touch; + knife->nextthink = level.time + 8000/speed; + knife->think = G_FreeEdict; + knife->dmg = damage; + knife->s.sound = gi.soundindex ("misc/flyloop.wav"); + knife->classname = "thrown_knife"; + +// used by dodging monsters, skip +// if (self->client) +// check_dodge (self, rocket->s.origin, dir, speed); + + PRETRACE(); + tr = gi.trace (self->s.origin, NULL, NULL, knife->s.origin, knife, MASK_SHOT); + POSTTRACE(); + if (tr.fraction < 1.0) + { + VectorMA (knife->s.origin, -10, dir, knife->s.origin); + knife->touch (knife, tr.ent, NULL, NULL); + } + + + gi.linkentity (knife); +} + +/* +===================================================================== +setFFState: Save team wound count & warning state before an attack + +The purpose of this is so that we can increment team_wounds by 1 for +each real attack instead of just counting each bullet/pellet/shrapnel +as a wound. The ff_warning flag is so that we don't overflow the +clients from repeated FF warnings. Hopefully the overhead on this +will be low enough to not affect things. +===================================================================== +*/ +void setFFState(edict_t *ent) +{ + if (ent && ent->client) + { + ent->client->team_wounds_before = ent->client->team_wounds; + ent->client->ff_warning = 0; + } + + return; +} diff --git a/game.h b/game.h new file mode 100644 index 0000000..7c7103a --- /dev/null +++ b/game.h @@ -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 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); diff --git a/m_move.c b/m_move.c new file mode 100644 index 0000000..9bea739 --- /dev/null +++ b/m_move.c @@ -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); +} diff --git a/m_player.h b/m_player.h new file mode 100644 index 0000000..fe756ec --- /dev/null +++ b/m_player.h @@ -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 + diff --git a/p_client.c b/p_client.c new file mode 100644 index 0000000..2d37797 --- /dev/null +++ b/p_client.c @@ -0,0 +1,3303 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /LicenseToKill/src/p_client.c $ +// $Revision:: 11 $ +// $Author:: Riever $ +// $Date:: 22/10/99 20:24 $ +// +// Modifications Copyright (C) 1999 by Connor Caple +// Original code Copyright (c) 1998 ID Software +// All rights reserved. +// +//----------------------------------------------------------------------------- + +/* + * $Log: /LicenseToKill/src/p_client.c $ + * + * 11 22/10/99 20:24 Riever + * Tidying up... + * + * 10 16/10/99 10:27 Riever + * Stopped bots dropping nodes - too many bad links. + * + * 9 16/10/99 9:14 Riever + * Changed to load new config info. + * + * 8 6/10/99 20:20 Riever + * Re-enabled bots.tmp loading. + * + * 7 5/10/99 20:09 Riever + * Changes to let bots load the route file and load the bots in a + * dedicated server after the level has changed. + * + * 6 29/09/99 8:45 Riever + * lastkilledby is now used to insult the person who killed the bot. + * + * 5 27/09/99 20:45 Riever + * Allowed bots to create nodes like players do. + * + * 4 26/09/99 8:14 Riever + * Added to logging for LTK + * + */ + +#include "g_local.h" +#include "m_player.h" +#include "cgf_sfx_glass.h" + +void ClientUserinfoChanged (edict_t *ent, char *userinfo); +void ClientDisconnect(edict_t *ent); +void SP_misc_teleporter_dest (edict_t *ent); +void CopyToBodyQue (edict_t *ent); + +void Add_Frag( edict_t *ent ) +{ + ent->client->resp.kills++; + ent->client->resp.streak++; + if ( teamplay->value || (ent->client->resp.streak < 4) ) + { + ent->client->resp.score++; // just 1 normal kill + } + else if ( ent->client->resp.streak < 8 ) + { + safe_bprintf (PRINT_MEDIUM,"%s has %d kills in a row and receives %d frags for the kill!\n", + ent->client->pers.netname, ent->client->resp.streak, 2); + ent->client->resp.score += 2; + } + else if ( ent->client->resp.streak < 16 ) + { + safe_bprintf (PRINT_MEDIUM,"%s has %d kills in a row and receives %d frags for the kill!\n", + ent->client->pers.netname, ent->client->resp.streak, 4); + ent->client->resp.score += 4; + } + else if ( ent->client->resp.streak < 32 ) + { + safe_bprintf (PRINT_MEDIUM,"%s has %d kills in a row and receives %d frags for the kill!\n", + ent->client->pers.netname, ent->client->resp.streak, 8); + ent->client->resp.score += 8; + } + else + { + safe_bprintf (PRINT_MEDIUM,"%s has %d kills in a row and receives %d frags for the kill!\n", + ent->client->pers.netname, ent->client->resp.streak, 16); + ent->client->resp.score += 16; + } + +//FIREBLADE + if (!teamplay->value && ent->client->resp.streak >= 1) +//FIREBLADE + { + safe_cprintf(ent, PRINT_HIGH, "Kill count: %d\n", ent->client->resp.streak ); + } +} + +void Subtract_Frag( edict_t *ent ) +{ + ent->client->resp.score--; + ent->client->resp.streak = 0; +} + + +// FRIENDLY FIRE functions +extern qboolean Ban_TeamKiller ( edict_t *ent, int rounds ); // g_svcmds.c +extern void Kick_Client ( edict_t *ent); // g_svcmds.c + +void Add_TeamWound( edict_t *attacker, edict_t *victim, int mod) +{ + if (!teamplay->value || !attacker->client || !victim->client) + { + return; + } + + attacker->client->team_wounds++; + + // Warn both parties that they are teammates. Since shotguns are pellet based, + // make sure we don't overflow the client when using MOD_HC or MOD_SHOTGUN. The + // ff_warning flag should have been reset before each attack. + if (attacker->client->ff_warning == 0) + { + attacker->client->ff_warning++; + safe_cprintf(victim, PRINT_HIGH, "You were hit by %s, your TEAMMATE!\n", + attacker->client->pers.netname); + safe_cprintf(attacker, PRINT_HIGH, "You hit your TEAMMATE %s!\n", + victim->client->pers.netname); + } + + // We want team_wounds to increment by one for each ATTACK, not after each + // bullet or pellet does damage. With the HAND CANNON this means 2 attacks + // since it is double barreled and we don't want to go into p_weapon.c... + attacker->client->team_wounds = (attacker->client->team_wounds_before + 1); + + // If count is less than MAX_TEAMKILLS*3, return. If count is greater than + // MAX_TEAMKILLS*3 but less than MAX_TEAMKILLS*4, print off a ban warning. If + // count equal (or greater than) MAX_TEAMKILLS*4, ban and kick the client. + if ((int)maxteamkills->value < 1) //FB + return; + if (attacker->client->team_wounds < ((int)maxteamkills->value * 3)) + { + return; + } + else if (attacker->client->team_wounds < ((int)maxteamkills->value * 4)) + { + // Print a note to console, and issue a warning to the player. + safe_cprintf(NULL, PRINT_MEDIUM, + "%s is in danger of being banned for wounding teammates\n", + attacker->client->pers.netname); + safe_cprintf(attacker, PRINT_HIGH, + "WARNING: You'll be temporarily banned if you continue wounding teammates!\n"); + return; + } + else + { + if (attacker->client->ipaddr) + { + if (Ban_TeamKiller(attacker, (int)twbanrounds->value)) + { + safe_cprintf(NULL, PRINT_MEDIUM, + "Banning %s@%s for team wounding\n", + attacker->client->pers.netname, attacker->client->ipaddr); + + safe_cprintf(attacker, PRINT_HIGH, + "You've wounded teammates too many times, and are banned for %d %s.\n", + (int)twbanrounds->value, (((int)twbanrounds->value > 1) ? "games" : "game")); + } + else + { + safe_cprintf(NULL, PRINT_MEDIUM, + "Error banning %s: unable to get ipaddr\n", + attacker->client->pers.netname); + } + Kick_Client(attacker); + } + } + + return; +} + +void Add_TeamKill( edict_t *attacker ) +{ + if (!teamplay->value || !attacker->client) + { + return; + } + + attacker->client->team_kills++; + // Because the stricter team kill was incremented, lower team_wounds + // by amount inflicted in last attack (i.e., no double penalty). + if (attacker->client->team_wounds > attacker->client->team_wounds_before) + { + attacker->client->team_wounds = attacker->client->team_wounds_before; + } + + // If count is less than 1/2 MAX_TEAMKILLS, print off simple warning. If + // count is greater than 1/2 MAX_TEAMKILLS but less than MAX_TEAMKILLS, + // print off a ban warning. If count equal or greater than MAX_TEAMKILLS, + // ban and kick the client. + if (((int)maxteamkills->value < 1) || + (attacker->client->team_kills < (((int)maxteamkills->value % 2) + (int)maxteamkills->value / 2))) + { + safe_cprintf(attacker, PRINT_HIGH, "You killed your TEAMMATE!\n"); + return; + } + else if (attacker->client->team_kills < (int)maxteamkills->value) + { + // Show this on the console + safe_cprintf(NULL, PRINT_MEDIUM, "%s is in danger of being banned for killing teammates\n", attacker->client->pers.netname); + // Issue a warning to the player + safe_cprintf(attacker, PRINT_HIGH, "WARNING: You'll be temporarily banned if you continue killing teammates!\n" ); + return; + } + else + { + // They've killed too many teammates this game - kick 'em for a while + if (attacker->client->ipaddr) + { + if (Ban_TeamKiller(attacker, (int)tkbanrounds->value)) + { + safe_cprintf (NULL, PRINT_MEDIUM, + "Banning %s@%s for team killing\n", + attacker->client->pers.netname, + attacker->client->ipaddr); + safe_cprintf(attacker, PRINT_HIGH, "You've killed too many teammates, and are banned for %d %s.\n", (int)tkbanrounds->value, + (((int)tkbanrounds->value > 1) ? "games" : "game")); + } + else + { + safe_cprintf(NULL, PRINT_MEDIUM, + "Error banning %s: unable to get ipaddr\n", + attacker->client->pers.netname); + } + } + Kick_Client(attacker); + } +} +// FRIENDLY FIRE + + +// +// Gross, ugly, disgustuing hack section +// + +// this function is an ugly as hell hack to fix some map flaws +// +// the coop spawn spots on some maps are SNAFU. There are coop spots +// with the wrong targetname as well as spots with no name at all +// +// we use carnal knowledge of the maps to fix the coop spot targetnames to match +// that of the nearest named single player spot + +static void SP_FixCoopSpots (edict_t *self) +{ + edict_t *spot; + vec3_t d; + + spot = NULL; + + while(1) + { + spot = G_Find(spot, FOFS(classname), "info_player_start"); + if (!spot) + return; + if (!spot->targetname) + continue; + VectorSubtract(self->s.origin, spot->s.origin, d); + if (VectorLength(d) < 384) + { + if ((!self->targetname) || stricmp(self->targetname, spot->targetname) != 0) + { +// gi.dprintf("FixCoopSpots changed %s at %s targetname from %s to %s\n", self->classname, vtos(self->s.origin), self->targetname, spot->targetname); + self->targetname = spot->targetname; + } + return; + } + } +} + +// now if that one wasn't ugly enough for you then try this one on for size +// some maps don't have any coop spots at all, so we need to create them +// where they should have been + +static void SP_CreateCoopSpots (edict_t *self) +{ + edict_t *spot; + + if(stricmp(level.mapname, "security") == 0) + { + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 - 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 128; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + return; + } +} + + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +The normal starting point for a level. +*/ +void SP_info_player_start(edict_t *self) +{ + if (!coop->value) + return; + if(stricmp(level.mapname, "security") == 0) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_CreateCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for deathmatch games +*/ +void SP_info_player_deathmatch(edict_t *self) +{ + if (!deathmatch->value) + { + G_FreeEdict (self); + return; + } + SP_misc_teleporter_dest (self); +} + +/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for coop games +*/ + +void SP_info_player_coop(edict_t *self) +{ + if (!coop->value) + { + G_FreeEdict (self); + return; + } + + if((stricmp(level.mapname, "jail2") == 0) || + (stricmp(level.mapname, "jail4") == 0) || + (stricmp(level.mapname, "mine1") == 0) || + (stricmp(level.mapname, "mine2") == 0) || + (stricmp(level.mapname, "mine3") == 0) || + (stricmp(level.mapname, "mine4") == 0) || + (stricmp(level.mapname, "lab") == 0) || + (stricmp(level.mapname, "boss1") == 0) || + (stricmp(level.mapname, "fact3") == 0) || + (stricmp(level.mapname, "biggun") == 0) || + (stricmp(level.mapname, "space") == 0) || + (stricmp(level.mapname, "command") == 0) || + (stricmp(level.mapname, "power2") == 0) || + (stricmp(level.mapname, "strike") == 0)) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_FixCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The deathmatch intermission point will be at one of these +Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll' +*/ +void SP_info_player_intermission(void) +{ +} + + +//======================================================================= + + +void player_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + // player pain is handled at the end of the frame in P_DamageFeedback +} + + +qboolean IsFemale (edict_t *ent) +{ + char *info; + + if (!ent->client) + return false; + + // "gender" below used to be "skin", 3.20 change -FB + info = Info_ValueForKey (ent->client->pers.userinfo, "gender"); + if (info[0] == 'f' || info[0] == 'F') + return true; + return false; +} + +// FROM 3.20 -FB +qboolean IsNeutral (edict_t *ent) +{ + char *info; + + if (!ent->client) + return false; + + info = Info_ValueForKey (ent->client->pers.userinfo, "gender"); + if (info[0] != 'f' && info[0] != 'F' && info[0] != 'm' && info[0] != 'M') + return true; + return false; +} +// ^^^ + +// PrintDeathMessage: moved the actual printing of the death messages to here, to handle +// the fact that live players shouldn't receive them in teamplay. -FB +void PrintDeathMessage(char *msg, edict_t *gibee) +{ + int j; + edict_t *other; + + if (!teamplay->value) + { + safe_bprintf(PRINT_MEDIUM, msg); + return; + } + + if (dedicated->value) + safe_cprintf(NULL, PRINT_MEDIUM, "%s", msg); + + for (j = 1; j <= game.maxclients; j++) + { + other = &g_edicts[j]; + if (!other->inuse || !other->client) + continue; + if (gibee != other && team_round_going && other->solid != SOLID_NOT) + continue; + safe_cprintf(other, PRINT_MEDIUM, "%s", msg); + } +} + +void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + int mod; + int loc; + char *message; + char *message2; + char death_msg[2048]; // enough in all situations? -FB + qboolean ff; + int special = 0; + int n; + + if (coop->value && attacker->client) + meansOfDeath |= MOD_FRIENDLY_FIRE; + + if (attacker && attacker != self && attacker->client && OnSameTeam (self, attacker)) + meansOfDeath |= MOD_FRIENDLY_FIRE; + + //RiEvEr - who killed me? + if (attacker && attacker != self && attacker->client ) + self->lastkilledby = attacker; + //R + + if (deathmatch->value || coop->value) + { + ff = meansOfDeath & MOD_FRIENDLY_FIRE; + mod = meansOfDeath & ~MOD_FRIENDLY_FIRE; + loc = locOfDeath; // useful for location based hits + message = NULL; + message2 = ""; + + switch (mod) + { + case MOD_BREAKINGGLASS: + message = "ate too much glass"; + break; + case MOD_SUICIDE: + message = "suicides"; + break; + case MOD_FALLING: + // moved falling to the end + if (self->client->push_timeout) + special = 1; + //message = "hit the ground hard, real hard"; + if (IsNeutral(self)) + message = "plummets to its death"; + else if (IsFemale(self)) + message = "plummets to her death"; + else + message = "plummets to his death"; + break; + case MOD_CRUSH: + message = "was flattened"; + break; + case MOD_WATER: + message = "sank like a rock"; + break; + case MOD_SLIME: + message = "melted"; + break; + case MOD_LAVA: + message = "does a back flip into the lava"; + break; + case MOD_EXPLOSIVE: + case MOD_BARREL: + message = "blew up"; + break; + case MOD_EXIT: + message = "found a way out"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TARGET_BLASTER: + message = "got blasted"; + break; + case MOD_BOMB: + case MOD_SPLASH: + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + } + if (attacker == self) + { + switch (mod) + { + case MOD_HELD_GRENADE: + message = "tried to put the pin back in"; + break; + case MOD_HG_SPLASH: + if (IsNeutral(self)) + message = "didn't throw its grenade far enough"; + if (IsFemale(self)) + message = "didn't throw her grenade far enough"; + else + message = "didn't throw his grenade far enough"; + break; + case MOD_G_SPLASH: + if (IsNeutral(self)) + message = "tripped on its own grenade"; + else if (IsFemale(self)) + message = "tripped on her own grenade"; + else + message = "tripped on his own grenade"; + break; + case MOD_R_SPLASH: + if (IsNeutral(self)) + message = "blew itself up"; + else if (IsFemale(self)) + message = "blew herself up"; + else + message = "blew himself up"; + break; + case MOD_BFG_BLAST: + message = "should have used a smaller gun"; + break; + default: + if (IsNeutral(self)) + message = "killed itself"; + else if (IsFemale(self)) + message = "killed herself"; + else + message = "killed himself"; + break; + } + } + if (message && !special) + { + sprintf(death_msg, "%s %s\n", self->client->pers.netname, message); + PrintDeathMessage(death_msg, self); + if (deathmatch->value) + Subtract_Frag( self );//self->client->resp.score--; + self->enemy = NULL; + return; + } + else if ( special ) // handle falling with an attacker set + { + if (self->client->attacker && + self->client->attacker->client && + (self->client->attacker->client != + self->client)) + { + sprintf(death_msg, "%s was taught how to fly by %s\n", + self->client->pers.netname, self->client->attacker->client->pers.netname ); + PrintDeathMessage(death_msg, self); + +//MODIFIED FOR FF -FB + if (!((int)dmflags->value & DF_NO_FRIENDLY_FIRE) && + OnSameTeam(self, self->client->attacker) && + teamplay->value) + { + if (!teamplay->value || team_round_going) + { + Add_TeamKill(self->client->attacker); + Subtract_Frag( self->client->attacker );//attacker->client->resp.score--; + } + } + else + { + if (!teamplay->value || mod != MOD_TELEFRAG) + Add_Frag(self->client->attacker );//attacker->client->resp.score++; + } +//END FF ADD + } + else + { + if (IsNeutral(self)) + sprintf(death_msg, "%s plummets to its death\n", self->client->pers.netname); + else if (IsFemale(self)) + sprintf(death_msg, "%s plummets to her death\n", self->client->pers.netname); + else + sprintf(death_msg, "%s plummets to his death\n", self->client->pers.netname); + PrintDeathMessage(death_msg, self); + if (deathmatch->value) + Subtract_Frag( self );//self->client->resp.score--; + self->enemy = NULL; + } + return; + } + +#if 0 + // handle bleeding, not used because bleeding doesn't get set + if ( mod == MOD_BLEEDING ) + { + sprintf(death_msg, "%s bleeds to death\n", self->client->pers.netname); + PrintDeathMessage(death_msg, self); + return; + } +#endif + + self->enemy = attacker; + if (attacker && attacker->client) + { + switch (mod) + { + case MOD_MK23: // zucc + switch (loc) + { + case LOC_HDAM: + if (IsNeutral(self)) + message = " has a hole in its head from"; + else if (IsFemale(self)) + message = " has a hole in her head from"; + else + message = " has a hole in his head from"; + message2 = "'s Mark 23 pistol"; + break; + case LOC_CDAM: + message = " loses a vital chest organ thanks to"; + message2 = "'s Mark 23 pistol"; + break; + case LOC_SDAM: + if (IsNeutral(self)) + message = " loses its lunch to"; + else if (IsFemale(self)) + message = " loses her lunch to"; + else + message = " loses his lunch to"; + message2 = "'s .45 caliber pistol round"; + break; + case LOC_LDAM: + message = " is legless because of"; + message2 = "'s .45 caliber pistol round"; + break; + default: + message = " was shot by"; + message2 = "'s Mark 23 Pistol"; + } + break; + case MOD_MP5: + switch (loc) + { + case LOC_HDAM: + message = "'s brains are on the wall thanks to"; + message2 = "'s 10mm MP5/10 round"; + break; + case LOC_CDAM: + message = " feels some chest pain via"; + message2 = "'s MP5/10 Submachinegun"; + break; + case LOC_SDAM: + message = " needs some Pepto Bismol after"; + message2 = "'s 10mm MP5 round"; + break; + case LOC_LDAM: + if (IsNeutral(self)) + message = " had its legs blown off thanks to"; + else if (IsFemale(self)) + message = " had her legs blown off thanks to"; + else + message = " had his legs blown off thanks to"; + message2 = "'s MP5/10 Submachinegun"; + break; + default: + message = " was shot by"; + message2 = "'s MP5/10 Submachinegun"; + } + break; + case MOD_M4: + switch (loc) + { + case LOC_HDAM: + message = " had a makeover by"; + message2 = "'s M4 Assault Rifle"; + break; + case LOC_CDAM: + message = " feels some heart burn thanks to"; + message2 = "'s M4 Assault Rifle"; + break; + case LOC_SDAM: + message = " has an upset stomach thanks to"; + message2 = "'s M4 Assault Rifle"; + break; + case LOC_LDAM: + message = " is now shorter thanks to"; + message2 = "'s M4 Assault Rifle"; + break; + default: + message = " was shot by"; + message2 = "'s M4 Assault Rifle"; + } + break; + case MOD_M3: + n = rand() % 2 + 1; + if (n == 1) + { + message = " accepts"; + message2 = "'s M3 Super 90 Assault Shotgun in hole-y matrimony"; + } + else + { + message = " is full of buckshot from"; + message2 = "'s M3 Super 90 Assault Shotgun"; + } + break; + case MOD_HC: + n = rand() % 2 + 1; + if (n == 1) + { + message = " ate"; + message2 = "'s sawed-off 12 gauge"; + } + else + { + message = " is full of buckshot from"; + message2 = "'s sawed off shotgun"; + } + break; + case MOD_SNIPER: + switch (loc) + { + case LOC_HDAM: + if (self->client->ps.fov < 90) + { + if (IsNeutral(self)) + message = " saw the sniper bullet go through its scope thanks to"; + else if (IsFemale(self)) + message = " saw the sniper bullet go through her scope thanks to"; + else + message = " saw the sniper bullet go through his scope thanks to"; + } + else + { + message = " caught a sniper bullet between the eyes from"; + } + break; + case LOC_CDAM: + message = " was picked off by"; + break; + case LOC_SDAM: + message = " was sniped in the stomach by"; + break; + case LOC_LDAM: + message = " was shot in the legs by"; + break; + default: + message = "was sniped by"; + //message2 = "'s Sniper Rifle"; + } + break; + case MOD_DUAL: + switch (loc) + { + case LOC_HDAM: + message = " was trepanned by"; + message2 = "'s akimbo Mark 23 pistols"; + break; + case LOC_CDAM: + message = " was John Woo'd by"; + //message2 = "'s .45 caliber pistol round"; + break; + case LOC_SDAM: + message = " needs some new kidneys thanks to"; + message2 = "'s akimbo Mark 23 pistols"; + break; + case LOC_LDAM: + message = " was shot in the legs by"; + message2 = "'s akimbo Mark 23 pistols"; + break; + default: + message = " was shot by"; + message2 = "'s pair of Mark 23 Pistols"; + } + break; + case MOD_KNIFE: + switch (loc) + { + case LOC_HDAM: + if (IsNeutral(self)) + message = " had its throat slit by"; + else if (IsFemale(self)) + message = " had her throat slit by"; + else + message = " had his throat slit by"; + break; + case LOC_CDAM: + message = " had open heart surgery, compliments of"; + break; + case LOC_SDAM: + message = " was gutted by"; + break; + case LOC_LDAM: + message = " was stabbed repeatedly in the legs by"; + break; + default: + message = " was slashed apart by"; + message2 = "'s Combat Knife"; + } + break; + case MOD_KNIFE_THROWN: + switch (loc) + { + case LOC_HDAM: + message = " caught"; + if (IsNeutral(self)) + message2 = "'s flying knife with its forehead"; + else if (IsFemale(self)) + message2 = "'s flying knife with her forehead"; + else + message2 = "'s flying knife with his forehead"; + break; + case LOC_CDAM: + message = "'s ribs don't help against"; + message2 = "'s flying knife"; + break; + case LOC_SDAM: + if (IsNeutral(self)) + message = " sees the contents of its own stomach thanks to"; + else if (IsFemale(self)) + message = " sees the contents of her own stomach thanks to"; + else + message = " sees the contents of his own stomach thanks to"; + message2 = "'s flying knife"; + break; + case LOC_LDAM: + if (IsNeutral(self)) + message = " had its legs cut off thanks to"; + else if (IsFemale(self)) + message = " had her legs cut off thanks to"; + else + message = " had his legs cut off thanks to"; + message2 = "'s flying knife"; + break; + default: + message = " was hit by"; + message2 = "'s flying Combat Knife"; + } + break; + case MOD_GAS: + message = "sucks down some toxic gas thanks to"; + break; + case MOD_KICK: + n = rand() % 3 + 1; + if (n == 1) + { + if (IsNeutral(self)) + message = " got its ass kicked by"; + else if (IsFemale(self)) + message = " got her ass kicked by"; + else + message = " got his ass kicked by"; + } + else if (n == 2) + { + if (IsNeutral(self)) + { + message = " couldn't remove"; message2 = "'s boot from its ass"; + } + else if (IsFemale(self)) + { + message = " couldn't remove"; message2 = "'s boot from her ass"; + } + else + { + message = " couldn't remove"; message2 = "'s boot from his ass"; + } + } + else + { + if (IsNeutral(self)) + { + message = " had a Bruce Lee put on it by"; message2 = ", with a quickness"; + } + else if (IsFemale(self)) + { + message = " had a Bruce Lee put on her by"; message2 = ", with a quickness"; + } + else + { + message = " had a Bruce Lee put on him by"; message2 = ", with a quickness"; + } + } + break; + case MOD_BLASTER: + message = "was blasted by"; + break; + case MOD_SHOTGUN: + message = "was gunned down by"; + break; + case MOD_SSHOTGUN: + message = "was blown away by"; + message2 = "'s super shotgun"; + break; + case MOD_MACHINEGUN: + message = "was machinegunned by"; + break; + case MOD_CHAINGUN: + message = "was cut in half by"; + message2 = "'s chaingun"; + break; + case MOD_GRENADE: + message = "was popped by"; + message2 = "'s grenade"; + break; + case MOD_G_SPLASH: + message = "was shredded by"; + message2 = "'s shrapnel"; + break; + case MOD_ROCKET: + message = "ate"; + message2 = "'s rocket"; + break; + case MOD_R_SPLASH: + message = "almost dodged"; + message2 = "'s rocket"; + break; + case MOD_HYPERBLASTER: + message = "was melted by"; + message2 = "'s hyperblaster"; + break; + case MOD_RAILGUN: + message = "was railed by"; + break; + case MOD_BFG_LASER: + message = "saw the pretty lights from"; + message2 = "'s BFG"; + break; + case MOD_BFG_BLAST: + message = "was disintegrated by"; + message2 = "'s BFG blast"; + break; + case MOD_BFG_EFFECT: + message = "couldn't hide from"; + message2 = "'s BFG"; + break; + case MOD_HANDGRENADE: + message = " caught"; + message2 = "'s handgrenade"; + break; + case MOD_HG_SPLASH: + message = " didn't see"; + message2 = "'s handgrenade"; + break; + case MOD_HELD_GRENADE: + message = " feels"; + message2 = "'s pain"; + break; + case MOD_TELEFRAG: + message = " tried to invade"; + message2 = "'s personal space"; + break; + } + + if (message) + { +//FIREBLADE + sprintf(death_msg, "%s%s %s%s\n", self->client->pers.netname, message, + attacker->client->pers.netname, message2); + PrintDeathMessage(death_msg, self); +//FIREBLADE + if (deathmatch->value) + { + if (ff) + { + if (!teamplay->value || team_round_going) + { + Add_TeamKill(attacker); + Subtract_Frag( attacker );//attacker->client->resp.score--; + } + } + else + { +//FIREBLADE + if (!teamplay->value || mod != MOD_TELEFRAG) +//FIREBLADE + Add_Frag(attacker );//attacker->client->resp.score++; + } + } + return; + } + } + } + +//FIREBLADE + sprintf(death_msg, "%s died\n", self->client->pers.netname); + PrintDeathMessage(death_msg, self); +//FIREBLADE + if (deathmatch->value) + Subtract_Frag( self );//self->client->resp.score--; +} + +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +// zucc used to toss an item on death +void EjectItem( edict_t *ent, gitem_t *item ) +{ + edict_t *drop; + float spread; + if (item) + { + spread = 300.0*crandom(); + ent->client->v_angle[YAW] -= spread; + drop = Drop_Item (ent, item); + ent->client->v_angle[YAW] += spread; + drop->spawnflags = DROPPED_PLAYER_ITEM; + } + + +} + + + +// unique weapons need to be specially treated so they respawn properly +void EjectWeapon( edict_t *ent, gitem_t *item ) +{ + edict_t *drop; + float spread; + if (item) + { + spread = 300.0*crandom(); + ent->client->v_angle[YAW] -= spread; + drop = Drop_Item (ent, item); + ent->client->v_angle[YAW] += spread; + drop->spawnflags = DROPPED_PLAYER_ITEM; + drop->think = temp_think_specweap; + } + + +} + + + +//zucc toss items on death +void TossItemsOnDeath( edict_t *ent ) +{ + gitem_t *item; + + + // don't bother dropping stuff when allweapons/items is active + if (allitem->value && allweapon->value) + { + // remove the lasersight because then the observer might have it + item = FindItem(LASER_NAME); + ent->client->pers.inventory[ITEM_INDEX(item)] = 0; + return; + } + + + // don't drop weapons if allweapons is on + if (allweapon->value) + { + DeadDropSpec(ent); + return; + } + + // only drop items if allitems is not on + if(!allitem->value) + DeadDropSpec(ent); + else + { // remove the lasersight because then the observer might have it + item = FindItem(LASER_NAME); + ent->client->pers.inventory[ITEM_INDEX(item)] = 0; + SP_LaserSight(ent, item); + } + // give the player a dual pistol so they can be sure to drop one + item = FindItem(DUAL_NAME); + ent->client->pers.inventory[ITEM_INDEX(item)]++; + EjectItem( ent, item ); + + // check for every item we want to drop when a player dies + item = FindItem(MP5_NAME); + while ( ent->client->pers.inventory[ITEM_INDEX(item)] > 0 ) + { + ent->client->pers.inventory[ITEM_INDEX(item)]--; + EjectWeapon( ent, item ); + } + item = FindItem(M4_NAME); + while ( ent->client->pers.inventory[ITEM_INDEX(item)] > 0 ) + { + ent->client->pers.inventory[ITEM_INDEX(item)]--; + EjectWeapon( ent, item ); + } + item = FindItem(M3_NAME); + while ( ent->client->pers.inventory[ITEM_INDEX(item)] > 0 ) + { + ent->client->pers.inventory[ITEM_INDEX(item)]--; + EjectWeapon( ent, item ); + } + item = FindItem(HC_NAME); + while ( ent->client->pers.inventory[ITEM_INDEX(item)] > 0 ) + { + ent->client->pers.inventory[ITEM_INDEX(item)]--; + EjectWeapon( ent, item ); + } + item = FindItem(SNIPER_NAME); + while ( ent->client->pers.inventory[ITEM_INDEX(item)] > 0 ) + { + ent->client->pers.inventory[ITEM_INDEX(item)]--; + EjectWeapon( ent, item ); + } + item = FindItem(KNIFE_NAME); + if ( ent->client->pers.inventory[ITEM_INDEX(item)] > 0 ) + { + EjectItem( ent, item ); + } +// special items + +#if 0 + item = FindItem(SIL_NAME); + if ( ent->client->pers.inventory[ITEM_INDEX(item)] ) + EjectItem( ent, item ); + item = FindItem(SLIP_NAME); + if ( ent->client->pers.inventory[ITEM_INDEX(item)] ) + EjectItem( ent, item ); + item = FindItem(BAND_NAME); + if ( ent->client->pers.inventory[ITEM_INDEX(item)] ) + EjectItem( ent, item ); + item = FindItem(KEV_NAME); + if ( ent->client->pers.inventory[ITEM_INDEX(item)] ) + EjectItem( ent, item ); + item = FindItem(LASER_NAME); + if ( ent->client->pers.inventory[ITEM_INDEX(item)] ) + EjectItem( ent, item ); +#endif +} + + +void TossClientWeapon (edict_t *self) +{ + gitem_t *item; + edict_t *drop; + qboolean quad; + float spread; + + if (!deathmatch->value) + return; + + item = self->client->pers.weapon; + if (! self->client->pers.inventory[self->client->ammo_index] ) + item = NULL; + if (item && (strcmp (item->pickup_name, "Blaster") == 0)) + item = NULL; + + if (!((int)(dmflags->value) & DF_QUAD_DROP)) + quad = false; + else + quad = (self->client->quad_framenum > (level.framenum + 10)); + + if (item && quad) + spread = 22.5; + else + spread = 0.0; + + if (item) + { + self->client->v_angle[YAW] -= spread; + drop = Drop_Item (self, item); + self->client->v_angle[YAW] += spread; + drop->spawnflags = DROPPED_PLAYER_ITEM; + } + + if (quad) + { + self->client->v_angle[YAW] += spread; + drop = Drop_Item (self, FindItemByClassname ("item_quad")); + self->client->v_angle[YAW] -= spread; + drop->spawnflags |= DROPPED_PLAYER_ITEM; + + drop->touch = Touch_Item; + drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME; + drop->think = G_FreeEdict; + } +} + + +/* +================== +LookAtKiller +================== +*/ +void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + vec3_t dir; + + if (attacker && attacker != world && attacker != self) + { + VectorSubtract (attacker->s.origin, self->s.origin, dir); + } + else if (inflictor && inflictor != world && inflictor != self) + { + VectorSubtract (inflictor->s.origin, self->s.origin, dir); + } + else + { + self->client->killer_yaw = self->s.angles[YAW]; + return; + } + +// NEW FORMULA FOR THIS FROM 3.20 -FB + if (dir[0]) + self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]); + else { + self->client->killer_yaw = 0; + if (dir[1] > 0) + self->client->killer_yaw = 90; + else if (dir[1] < 0) + self->client->killer_yaw = -90; + } + if (self->client->killer_yaw < 0) + self->client->killer_yaw += 360; +// ^^^ +} + +/* +================== +player_die +================== +*/ +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ +// int n; + + VectorClear (self->avelocity); + + self->takedamage = DAMAGE_YES; + self->movetype = MOVETYPE_TOSS; + +//FIREBLADE + if (self->solid == SOLID_TRIGGER) + { + self->solid = SOLID_BBOX; + gi.linkentity(self); + RemoveFromTransparentList(self); + } +//FIREBLADE + + // zucc solves problem of people stopping doors while in their dead bodies + // + // ...only need it in DM though... + // ...for teamplay, non-solid will get set soon after in CopyToBodyQue + if (!teamplay->value) + { + self->solid = SOLID_NOT; + gi.linkentity(self); + } + + self->s.modelindex2 = 0; // remove linked weapon model + + self->s.angles[0] = 0; + self->s.angles[2] = 0; + + self->s.sound = 0; + self->client->weapon_sound = 0; + + self->client->reload_attempts = 0; // stop them from trying to reload + self->client->weapon_attempts = 0; + + self->maxs[2] = -8; + + self->svflags |= SVF_DEADMONSTER; + + if (!self->deadflag) + { + self->client->respawn_time = level.time + 1.0; + LookAtKiller (self, inflictor, attacker); + self->client->ps.pmove.pm_type = PM_DEAD; + ClientObituary (self, inflictor, attacker); + //TossClientWeapon (self); + TossItemsOnDeath(self); +//FIREBLADE + if (deathmatch->value && !teamplay->value) +//FIREBLADE + Cmd_Help_f (self); // show scores + } + + // remove powerups + self->client->quad_framenum = 0; + self->client->invincible_framenum = 0; + self->client->breather_framenum = 0; + self->client->enviro_framenum = 0; + + //zucc remove lasersight + if (self->lasersight) + SP_LaserSight(self, NULL); + +//FIREBLADE + // clean up sniper rifle stuff + self->client->no_sniper_display = 0; + self->client->resp.sniper_mode = SNIPER_1X; + self->client->desired_fov = 90; + self->client->ps.fov = 90; +//FIREBLADE + + self->client->resp.streak = 0; + Bandage(self); // clear up the leg damage when dead sound? + self->client->bandage_stopped = 0; + + // clear inventory + memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory)); + + // zucc - check if they have a primed grenade + if ( self->client->curr_weap == GRENADE_NUM && + ( + ( self->client->ps.gunframe >= GRENADE_IDLE_FIRST + && self->client->ps.gunframe <= GRENADE_IDLE_LAST ) + || ( self->client->ps.gunframe >= GRENADE_THROW_FIRST + && self->client->ps.gunframe <= GRENADE_THROW_LAST ) + ) ) + { + self->client->ps.gunframe = 0; + fire_grenade2 (self, self->s.origin, tv(0,0,0), GRENADE_DAMRAD, 0, 2, GRENADE_DAMRAD*2, false); + } + + //zucc no gibbing +/* if (self->health < -40) + { // gib + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowClientHead (self, damage); + + self->takedamage = DAMAGE_NO; + }*/ +// else + { // normal death + if (!self->deadflag) + { + static int i; + + i = (i+1)%3; + // start a death animation + self->client->anim_priority = ANIM_DEATH; + if (self->client->ps.pmove.pm_flags & PMF_DUCKED) + { + self->s.frame = FRAME_crdeath1-1; + self->client->anim_end = FRAME_crdeath5; + } + else switch (i) + { + case 0: + self->s.frame = FRAME_death101-1; + self->client->anim_end = FRAME_death106; + break; + case 1: + self->s.frame = FRAME_death201-1; + self->client->anim_end = FRAME_death206; + break; + case 2: + self->s.frame = FRAME_death301-1; + self->client->anim_end = FRAME_death308; + break; + } + if ((meansOfDeath == MOD_SNIPER) || (meansOfDeath == MOD_KNIFE) || (meansOfDeath == MOD_KNIFE_THROWN)) + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/glurp.wav"), 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0); + } + } + // zucc this will fix a jump kick death generating a weapon + self->client->curr_weap = MK23_NUM; + self->deadflag = DEAD_DEAD; + + gi.linkentity (self); +} + +//======================================================================= + +/* +============== +InitClientPersistant + +This is only called when the game first initializes in single player, +but is called after each death and level change in deathmatch +============== +*/ +void InitClientPersistant (gclient_t *client) +{ + gitem_t *item; +/* + client_persistant_t oldpers; + +//FB 6/3/99 + memcpy(oldpers, pers, sizeof(client->pers)); +//FB 6/3/99 +*/ + memset (&client->pers, 0, sizeof(client->pers)); + // changed to mk23 + item = FindItem(MK23_NAME); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + + client->pers.weapon = item; + + item = FindItem(KNIFE_NAME); + client->pers.inventory[ITEM_INDEX(item)] = 1; + + client->pers.health = 100; + client->pers.max_health = 100; + //zucc changed maximum ammo amounts + client->pers.max_bullets = 2; + client->pers.max_shells = 14; + client->pers.max_rockets = 2; + client->pers.max_grenades = 50; + client->pers.max_cells = 1; + client->pers.max_slugs = 20; + client->knife_max = 10; + client->grenade_max = 2; + + client->pers.connected = true; + //zucc + client->fired = 0; + client->burst = 0; + client->fast_reload = 0; + client->machinegun_shots = 0; + client->unique_weapon_total = 0; + client->unique_item_total = 0; + client->curr_weap = MK23_NUM; +} + + +void InitClientResp (gclient_t *client) +{ + memset (&client->resp, 0, sizeof(client->resp)); + client->resp.enterframe = level.framenum; + client->resp.coop_respawn = client->pers; + client->resp.weapon = FindItem(MP5_NAME); + client->resp.item = FindItem(KEV_NAME); + client->resp.ir = 1; +} + +/* +================== +SaveClientData + +Some information that should be persistant, like health, +is still stored in the edict structure, so it needs to +be mirrored out to the client structure before all the +edicts are wiped. +================== +*/ +void SaveClientData (void) +{ + int i; + edict_t *ent; + + for (i=0 ; iinuse) + continue; + game.clients[i].pers.health = ent->health; + game.clients[i].pers.max_health = ent->max_health; + game.clients[i].pers.powerArmorActive = (ent->flags & FL_POWER_ARMOR); + if (coop->value) + game.clients[i].pers.score = ent->client->resp.score; + } +} + +void FetchClientEntData (edict_t *ent) +{ + ent->health = ent->client->pers.health; + ent->max_health = ent->client->pers.max_health; + if (ent->client->pers.powerArmorActive) + ent->flags |= FL_POWER_ARMOR; + if (coop->value) + ent->client->resp.score = ent->client->pers.score; +} + + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +PlayersRangeFromSpot + +Returns the distance to the nearest player from the given spot +================ +*/ +float PlayersRangeFromSpot (edict_t *spot) +{ + edict_t *player; + float bestplayerdistance; + vec3_t v; + int n; + float playerdistance; + + + bestplayerdistance = 9999999; + + for (n = 1; n <= maxclients->value; n++) + { + player = &g_edicts[n]; + + if (!player->inuse) + continue; + + if (player->health <= 0) + continue; + + VectorSubtract (spot->s.origin, player->s.origin, v); + playerdistance = VectorLength (v); + + if (playerdistance < bestplayerdistance) + bestplayerdistance = playerdistance; + } + + return bestplayerdistance; +} + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point, but NOT the two points closest +to other players +================ +*/ +edict_t *SelectRandomDeathmatchSpawnPoint (void) +{ + edict_t *spot, *spot1, *spot2; + int count = 0; + int selection; + float range, range1, range2; + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + count++; + range = PlayersRangeFromSpot(spot); + if (range < range1) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if (!count) + return NULL; + + if (count <= 2) + { + spot1 = spot2 = NULL; + } + else + count -= 2; + + selection = rand() % count; + + spot = NULL; + do + { + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + if (spot == spot1 || spot == spot2) + selection++; + } while(selection--); + + return spot; +} + +/* +================ +SelectFarthestDeathmatchSpawnPoint + +================ +*/ +edict_t *SelectFarthestDeathmatchSpawnPoint (void) +{ + edict_t *bestspot; + float bestdistance, bestplayerdistance; + edict_t *spot; + + + spot = NULL; + bestspot = NULL; + bestdistance = 0; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + bestplayerdistance = PlayersRangeFromSpot (spot); + + if (bestplayerdistance > bestdistance) + { + bestspot = spot; + bestdistance = bestplayerdistance; + } + } + + if (bestspot) + { + return bestspot; + } + + // if there is a player just spawned on each and every start spot + // we have no choice to turn one into a telefrag meltdown + spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + + return spot; +} + +edict_t *SelectDeathmatchSpawnPoint (void) +{ + if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST) + return SelectFarthestDeathmatchSpawnPoint (); + else + return SelectRandomDeathmatchSpawnPoint (); +} + + +edict_t *SelectCoopSpawnPoint (edict_t *ent) +{ + int index; + edict_t *spot = NULL; + char *target; + + index = ent->client - game.clients; + + // player 0 starts in normal player spawn point + if (!index) + return NULL; + + spot = NULL; + + // assume there are four coop spots at each spawnpoint + while (1) + { + spot = G_Find (spot, FOFS(classname), "info_player_coop"); + if (!spot) + return NULL; // we didn't have enough... + + target = spot->targetname; + if (!target) + target = ""; + if ( Q_stricmp(game.spawnpoint, target) == 0 ) + { // this is a coop spawn point for one of the clients here + index--; + if (!index) + return spot; // this is it + } + } + + + return spot; +} + + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, coop start, etc +============ +*/ +void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles) +{ + edict_t *spot = NULL; + +//FIREBLADE + if (teamplay->value && ent->client->resp.team != NOTEAM) + { + spot = SelectTeamplaySpawnPoint(ent); + } + else + { +//FIREBLADE + if (deathmatch->value) + spot = SelectDeathmatchSpawnPoint (); + else if (coop->value) + spot = SelectCoopSpawnPoint (ent); + } + + // find a single player start spot + if (!spot) + { +//FIREBLADE + if (deathmatch->value) + { + gi.dprintf("Warning: failed to find deathmatch spawn point\n"); + } +//FIREBLADE + while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL) + { + if (!game.spawnpoint[0] && !spot->targetname) + break; + + if (!game.spawnpoint[0] || !spot->targetname) + continue; + + if (Q_stricmp(game.spawnpoint, spot->targetname) == 0) + break; + } + + if (!spot) + { + if (!game.spawnpoint[0]) + { // there wasn't a spawnpoint without a target, so use any + spot = G_Find (spot, FOFS(classname), "info_player_start"); + } + if (!spot) + gi.error ("Couldn't find spawn point %s\n", game.spawnpoint); + } + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); +} + +//====================================================================== + + +void InitBodyQue (void) +{ + int i; + edict_t *ent; + + level.body_que = 0; + for (i=0; iclassname = "bodyque"; + } +} + +void body_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ +/* int n;*/ + + if (self->health < -40) + { + // remove gibbing +/* gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->s.origin[2] -= 48; + ThrowClientHead (self, damage);*/ + self->takedamage = DAMAGE_NO; + } +} + +void CopyToBodyQue (edict_t *ent) +{ + edict_t *body; + + // grab a body que and cycle to the next one + body = &g_edicts[(int)maxclients->value + level.body_que + 1]; + level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE; + + // FIXME: send an effect on the removed body + + gi.unlinkentity (ent); + + gi.unlinkentity (body); + body->s = ent->s; + body->s.number = body - g_edicts; + + body->svflags = ent->svflags; + VectorCopy (ent->mins, body->mins); + VectorCopy (ent->maxs, body->maxs); + VectorCopy (ent->absmin, body->absmin); + VectorCopy (ent->absmax, body->absmax); + VectorCopy (ent->size, body->size); + // All our bodies will be non-solid -FB + body->solid = SOLID_NOT; + //body->solid = ent->solid; + body->clipmask = ent->clipmask; + body->owner = ent->owner; +//FB 5/31/99 + body->movetype = MOVETYPE_TOSS; // just in case? +// body->movetype = ent->movetype; + VectorCopy (ent->velocity, body->velocity); + body->mass = ent->mass; + body->groundentity = NULL; +//FB 5/31/99 +//FB 6/1/99 + body->s.renderfx = 0; +//FB + + body->die = body_die; + body->takedamage = DAMAGE_YES; + + gi.linkentity (body); +} + +void CleanBodies() +{ + edict_t *ptr; + int i; + ptr = g_edicts + game.maxclients + 1; + i = 0; + while (i < BODY_QUEUE_SIZE) + { + gi.unlinkentity(ptr); + ptr->solid = SOLID_NOT; + ptr->movetype = MOVETYPE_NOCLIP; + ptr->svflags |= SVF_NOCLIENT; + ptr++; + i++; + } +} + +void respawn (edict_t *self) +{ + if (deathmatch->value || coop->value) + { +// ACEBOT_ADD special respawning code + if (self->is_bot) + { + ACESP_Respawn (self); + return; + } +// ACEBOT_END +//FIREBLADE + if (self->solid != SOLID_NOT || self->deadflag == DEAD_DEAD) +//FIREBLADE + CopyToBodyQue (self); + PutClientInServer (self); + +//FIREBLADE + self->svflags &= ~SVF_NOCLIENT; +//FIREBLADE + +// Disable all this... -FB +// // add a teleportation effect +// self->s.event = EV_PLAYER_TELEPORT; +// +// // hold in place briefly +// self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; +// self->client->ps.pmove.pm_time = 14; + + self->client->respawn_time = level.time; + + return; + } + + // restart the entire server + gi.AddCommandString ("menu_loadgame\n"); +} + +//============================================================== + +void AllWeapons( edict_t *ent ) +{ + + int i; + gitem_t *it; + + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_WEAPON)) + continue; + ent->client->pers.inventory[i] = 1; + } + + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_AMMO)) + continue; + Add_Ammo (ent, it, 1000); + } + + ent->client->mk23_rds = ent->client->mk23_max; + ent->client->dual_rds = ent->client->dual_max; + ent->client->mp5_rds = ent->client->mp5_max; + ent->client->m4_rds = ent->client->m4_max; + ent->client->shot_rds = ent->client->shot_max; + ent->client->sniper_rds = ent->client->sniper_max; + ent->client->cannon_rds = ent->client->cannon_max; + + if (tgren->value <= 0) // team grenades is turned off + { + it = FindItem(GRENADE_NAME); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + } + // give them a reasonable number of knives + it = FindItem(KNIFE_NAME); + ent->client->pers.inventory[ITEM_INDEX(it)] = 10; + +} + +void AllItems( edict_t *ent ) +{ + edict_t etemp; + int i; + gitem_t *it; + + + + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_ITEM)) + continue; + + etemp.item = it; + + if ( ent->client->unique_item_total >= unique_items->value ) + ent->client->unique_item_total = unique_items->value - 1; + Pickup_Special ( &etemp, ent ); + } + +} + + + +// equips a client with item/weapon in teamplay + + +void EquipClient( edict_t *ent ) +{ + gclient_t *client; + gitem_t* item; + edict_t etemp; + int band = 0; + + client = ent->client; + + + if ( !(client->resp.item) || !(client->resp.weapon) ) + return; + + + if ( stricmp(client->resp.item->pickup_name, BAND_NAME) == 0 ) + { + band = 1; + if (tgren->value > 0) // team grenades is turned on + { + item = FindItem(GRENADE_NAME); + client->pers.inventory[ITEM_INDEX(item)] = tgren->value; + } + + } + + // set them up with initial pistol ammo + item = FindItem("Pistol Clip"); + if ( band ) + client->pers.inventory[ITEM_INDEX(item)] = 2; + else + client->pers.inventory[ITEM_INDEX(item)] = 1; + + + if ( stricmp(client->resp.weapon->pickup_name, MP5_NAME ) == 0 ) + { + item = FindItem(MP5_NAME); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + client->pers.weapon = item; + client->curr_weap = MP5_NUM; + client->unique_weapon_total = 1; + item = FindItem("Machinegun Magazine"); + if ( band ) + client->pers.inventory[ITEM_INDEX(item)] = 2; + else + client->pers.inventory[ITEM_INDEX(item)] = 1; + client->mp5_rds = client->mp5_max; + } + else if ( stricmp(client->resp.weapon->pickup_name, M4_NAME ) == 0 ) + { + item = FindItem(M4_NAME); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + client->pers.weapon = item; + client->curr_weap = M4_NUM; + client->unique_weapon_total = 1; + item = FindItem("M4 Clip"); + if ( band ) + client->pers.inventory[ITEM_INDEX(item)] = 2; + else + client->pers.inventory[ITEM_INDEX(item)] = 1; + client->m4_rds = client->m4_max; + } + else if ( stricmp(client->resp.weapon->pickup_name, M3_NAME ) == 0 ) + { + item = FindItem(M3_NAME); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + client->pers.weapon = item; + client->curr_weap = M3_NUM; + client->unique_weapon_total = 1; + item = FindItem("12 Gauge Shells"); + if ( band ) + client->pers.inventory[ITEM_INDEX(item)] = 14; + else + client->pers.inventory[ITEM_INDEX(item)] = 7; + client->shot_rds = client->shot_max; + } + else if ( stricmp(client->resp.weapon->pickup_name, HC_NAME ) == 0 ) + { + item = FindItem(HC_NAME); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + client->pers.weapon = item; + client->curr_weap = HC_NUM; + client->unique_weapon_total = 1; + item = FindItem("12 Gauge Shells"); + if ( band ) + client->pers.inventory[ITEM_INDEX(item)] = 24; + else + client->pers.inventory[ITEM_INDEX(item)] = 12; + client->cannon_rds = client->cannon_max; + } + else if ( stricmp(client->resp.weapon->pickup_name, SNIPER_NAME ) == 0 ) + { + item = FindItem(SNIPER_NAME); + client->pers.inventory[ITEM_INDEX(item)] = 1; + client->unique_weapon_total = 1; + item = FindItem("AP Sniper Ammo"); + if ( band ) + client->pers.inventory[ITEM_INDEX(item)] = 20; + else + client->pers.inventory[ITEM_INDEX(item)] = 10; + client->sniper_rds = client->sniper_max; + } + else if ( stricmp(client->resp.weapon->pickup_name, DUAL_NAME ) == 0 ) + { + item = FindItem(DUAL_NAME); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + client->pers.weapon = item; + client->curr_weap = DUAL_NUM; + item = FindItem("Pistol Clip"); + if ( band ) + client->pers.inventory[ITEM_INDEX(item)] = 4; + else + client->pers.inventory[ITEM_INDEX(item)] = 2; + client->dual_rds = client->dual_max; + } + else if ( stricmp(client->resp.weapon->pickup_name, KNIFE_NAME ) == 0 ) + { + item = FindItem(KNIFE_NAME); + client->pers.selected_item = ITEM_INDEX(item); + if ( band ) + client->pers.inventory[client->pers.selected_item] = 20; + else + client->pers.inventory[client->pers.selected_item] = 10; + client->pers.weapon = item; + client->curr_weap = KNIFE_NUM; + } + + etemp.item = client->resp.item; + Pickup_Special ( &etemp, ent ); +} + + + + + +/* +=========== +PutClientInServer + +Called when a player connects to a server or respawns in +a deathmatch. +============ +*/ + +void PutClientInServer (edict_t *ent) +{ + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int going_observer; + int i; + client_persistant_t saved; + client_respawn_t resp; + // zucc for ammo +// gitem_t *item; + +//FF + int save_team_wounds; + int save_team_kills; + char save_ipaddr[100]; +//FF + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + SelectSpawnPoint (ent, spawn_origin, spawn_angles); + + index = ent-g_edicts-1; + client = ent->client; + + // deathmatch wipes most client data every spawn + if (deathmatch->value) + { + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + InitClientPersistant (client); + ClientUserinfoChanged (ent, userinfo); + } + else if (coop->value) + { + int n; + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + // this is kind of ugly, but it's how we want to handle keys in coop + for (n = 0; n < MAX_ITEMS; n++) + { + if (itemlist[n].flags & IT_KEY) + resp.coop_respawn.inventory[n] = client->pers.inventory[n]; + } + client->pers = resp.coop_respawn; + ClientUserinfoChanged (ent, userinfo); + if (resp.score > client->pers.score) + client->pers.score = resp.score; + } + else + { + memset (&resp, 0, sizeof(resp)); + } + + // clear everything but the persistant data + saved = client->pers; +//FF + save_team_wounds = client->team_wounds; + save_team_kills = client->team_kills; + + if (client->ipaddr) + strncpy(save_ipaddr, client->ipaddr, sizeof(save_ipaddr)-1); +//FF + + memset (client, 0, sizeof(*client)); + client->pers = saved; +//FF + client->team_wounds = save_team_wounds; + client->team_kills = save_team_kills; + + if (save_ipaddr && client->ipaddr) + strncpy(client->ipaddr, save_ipaddr, sizeof(client->ipaddr)); +//FF + + if (client->pers.health <= 0) + InitClientPersistant(client); + client->resp = resp; + + // copy some data from the client to the entity + FetchClientEntData (ent); + + // clear entity values + ent->groundentity = NULL; + ent->client = &game.clients[index]; + ent->takedamage = DAMAGE_AIM; + ent->movetype = MOVETYPE_WALK; + ent->viewheight = 22; + ent->inuse = true; + ent->classname = "player"; + ent->mass = 200; + ent->solid = SOLID_BBOX; + ent->deadflag = DEAD_NO; + ent->air_finished = level.time + 12; + ent->clipmask = MASK_PLAYERSOLID; + ent->model = "players/male/tris.md2"; + ent->pain = player_pain; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags &= ~FL_NO_KNOCKBACK; + ent->svflags &= ~SVF_DEADMONSTER; +// ACEBOT_ADD + ent->is_bot = false; + ent->last_node = -1; + ent->is_jumping = false; +// ACEBOT_END +//FIREBLADE + if (!teamplay->value || ent->client->resp.team != NOTEAM) + { + ent->flags &= ~FL_GODMODE; + ent->svflags &= ~SVF_NOCLIENT; + } +//FIREBLADE + + VectorCopy (mins, ent->mins); + VectorCopy (maxs, ent->maxs); + VectorClear (ent->velocity); + + // clear playerstate values + memset (&ent->client->ps, 0, sizeof(client->ps)); + + client->ps.pmove.origin[0] = spawn_origin[0]*8; + client->ps.pmove.origin[1] = spawn_origin[1]*8; + client->ps.pmove.origin[2] = spawn_origin[2]*8; + + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + client->ps.fov = 90; + } + else + { + client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov")); + if (client->ps.fov < 1) + client->ps.fov = 90; + else if (client->ps.fov > 160) + client->ps.fov = 160; + } + + client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); + + // clear entity state values + ent->s.effects = 0; + ent->s.skinnum = ent - g_edicts - 1; + ent->s.modelindex = 255; // will use the skin specified model + + // zucc vwep + //ent->s.modelindex2 = 255; // custom gun model + ShowGun(ent); + + ent->s.frame = 0; + VectorCopy (spawn_origin, ent->s.origin); + ent->s.origin[2] += 1; // make sure off ground + VectorCopy (ent->s.origin, ent->s.old_origin); + + // set the delta angle + for (i=0 ; i<3 ; i++) + client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]); + + ent->s.angles[PITCH] = 0; + ent->s.angles[YAW] = spawn_angles[YAW]; + ent->s.angles[ROLL] = 0; + VectorCopy (ent->s.angles, client->ps.viewangles); + VectorCopy (ent->s.angles, client->v_angle); + +//FIREBLADE + if (teamplay->value) + { + going_observer = StartClient(ent); + } + else + { + going_observer = ent->client->pers.spectator; + if (going_observer) + { + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->resp.team = NOTEAM; + ent->client->ps.gunindex = 0; + } + } + +//FIREBLADE + if (!going_observer && !teamplay->value) + { // this handles telefrags... + KillBox(ent); + } +//FIREBLADE + + gi.linkentity (ent); + + //zucc give some ammo + //item = FindItem("Pistol Clip"); + // Add_Ammo(ent,item,1); + client->mk23_max = 12; + client->mp5_max = 30; + client->m4_max = 24; + client->shot_max = 7; + client->sniper_max = 6; + client->cannon_max = 2; + client->dual_max = 24; + client->mk23_rds = client->mk23_max; + client->dual_rds = client->mk23_max; + client->knife_max = 10; + client->grenade_max = 2; + + ent->lasersight = NULL; + + //other + client->resp.sniper_mode = SNIPER_1X; + client->bandaging = 0; + client->leg_damage = 0; + client->leg_noise = 0; + client->leg_dam_count = 0; + client->desired_fov = 90; + client->ps.fov = 90; + client->idle_weapon = 0; + client->drop_knife = 0; + client->no_sniper_display = 0; + client->knife_sound = 0; + client->doortoggle = 0; + client->have_laser = 0; + client->reload_attempts = 0; + client->weapon_attempts = 0; + +//FIREBLADE + if (!going_observer) + { + + // items up here so that the bandolier will change equipclient below + if ( allitem->value ) + { + AllItems( ent ); + } + + + if (teamplay->value) + EquipClient(ent); + + if (ent->client->menu) + { + PMenu_Close(ent); + return; + } +//FIREBLADE + if ( allweapon->value ) + { + AllWeapons( ent ); + } + + // force the current weapon up + client->newweapon = client->pers.weapon; + ChangeWeapon (ent); + +//FIREBLADE + if (teamplay->value) + { + ent->solid = SOLID_TRIGGER; + gi.linkentity(ent); + } +//FIREBLADE + } +} + +// ACEBOT_ADD +char current_map[55]; +// ACEBOT_END + +/* +===================== +ClientBeginDeathmatch + +A client has just connected to the server in +deathmatch mode, so clear everything out before starting them. +===================== +*/ +void ClientBeginDeathmatch (edict_t *ent) +{ +// ACEBOT_ADD +// static char current_map[55]; // moved GLOBAL +// ACEBOT_END + + G_InitEdict (ent); + + InitClientResp (ent->client); + +// ACEBOT_ADD + ACEIT_PlayerAdded(ent); +// ACEBOT_END + // locate ent at a spawn point + PutClientInServer (ent); + +// FROM 3.20 -FB + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { +// ^^^ + if (!teamplay->value) + { //FB 5/31/99 + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } + } + + safe_bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); +//FIREBLADE + if (deathmatch->value && !teamplay->value && ent->solid == SOLID_NOT) + safe_bprintf(PRINT_HIGH, "%s became a spectator\n", ent->client->pers.netname); +//FIREBLADE + +//FIREBLADE + PrintMOTD(ent); + ent->client->resp.motd_refreshes = 1; +//FIREBLADE +// ACEBOT_ADD + safe_centerprintf(ent,"\n======================================\nL.T.K. AQ2 Mod\n\n'sv addbot' to add a new bot.\n'sv removebot ' to remove bot.\n======================================\n\n"); + + // If the map changes on us, init and reload the nodes + if(strcmp(level.mapname,current_map)) + { + + ACEND_InitNodes(); + ACEND_LoadNodes(); +// ACESP_LoadBots(); + ACESP_LoadBotConfig(); + strcpy(current_map,level.mapname); + } + +// ACEBOT_END + + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the game. This will happen every level load. +============ +*/ +void ClientBegin (edict_t *ent) +{ + int i; + + ent->client = game.clients + (ent - g_edicts - 1); + + // clear modes of weapons + /* + ent->client->resp.mk23_mode = 0; + ent->client->resp.mp5_mode = 0; + ent->client->resp.m4_mode = 0; + ent->client->resp.sniper_mode = 0; + ent->client->resp.knife_mode = 0; + ent->client->resp.grenade_mode = 0; + */ + + if (deathmatch->value) + { + ClientBeginDeathmatch (ent); + return; + } + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == true) + { + // the client has cleared the client side viewangles upon + // connecting to the server, which is different than the + // state when the game is saved, so we need to compensate + // with deltaangles + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]); + } + else + { + // a spawn point will completely reinitialize the entity + // except for the persistant data that was initialized at + // ClientConnect() time + G_InitEdict (ent); + ent->classname = "player"; + InitClientResp (ent->client); + PutClientInServer (ent); + } + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { + // send effect if in a multiplayer game + if (game.maxclients > 1) + { +//FIREBLADE + if (!teamplay->value) + { +//FIREBLADE + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } + + safe_bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + } + } + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + +/* +=========== +ClientUserInfoChanged + +called whenever the player updates a userinfo variable. + +The game can override any of the settings in place +(forcing skins or names, etc) before copying it off. +============ +*/ +void ClientUserinfoChanged (edict_t *ent, char *userinfo) +{ + char *s, *r; + int playernum; + + // check for malformed or illegal info strings + if (!Info_Validate(userinfo)) + { + strcpy (userinfo, "\\name\\badinfo\\skin\\male/grunt"); + } + + // set name + s = Info_ValueForKey (userinfo, "name"); + strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1); + +//FIREBLADE + s = Info_ValueForKey(userinfo, "spectator"); + ent->client->pers.spectator = (strcmp(s, "0") != 0); + + r = Info_ValueForKey(userinfo, "rate"); + ent->client->rate = atoi(r); +//FIREBLADE + + // set skin + s = Info_ValueForKey (userinfo, "skin"); + playernum = ent-g_edicts-1; + + // combine name and skin into a configstring +//FIREBLADE + if (teamplay->value) + AssignSkin(ent, s); + else +//FIREBLADE + gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) ); + +/* Not used in Action. + // fov + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + ent->client->ps.fov = 90; + } + else + { + ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov")); + if (ent->client->ps.fov < 1) + ent->client->ps.fov = 90; + else if (ent->client->ps.fov > 160) + ent->client->ps.fov = 160; + } +*/ + ent->client->pers.firing_style = ACTION_FIRING_CENTER; + // handedness + s = Info_ValueForKey (userinfo, "hand"); + if (strlen(s)) + { + ent->client->pers.hand = atoi(s); + if (strstr(s, "classic high") != NULL) + ent->client->pers.firing_style = ACTION_FIRING_CLASSIC_HIGH; + else if (strstr(s, "classic") != NULL) + ent->client->pers.firing_style = ACTION_FIRING_CLASSIC; + } + + // save off the userinfo in case we want to check something later + strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1); + + // zucc vwep + ShowGun(ent); + +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +The game can refuse entrance to a client by returning false. +If the client is allowed, the connection process will continue +and eventually get to ClientBegin() +Changing levels will NOT cause this to be called again, but +loadgames will. +============ +*/ +qboolean ClientConnect (edict_t *ent, char *userinfo) +{ + char *value, *ipaddr; + char ipaddr_buf[100]; + + // check to see if they are on the banned IP list + ipaddr = Info_ValueForKey (userinfo, "ip"); +//FIREBLADE + if (strlen(ipaddr) > sizeof(ipaddr_buf)-1) + gi.dprintf("ipaddr_buf length exceeded\n"); + strncpy(ipaddr_buf, ipaddr, 99); + ipaddr_buf[99] = 0; +//FIREBLADE + +// FROM 3.20 -FB + if (SV_FilterPacket(ipaddr)) { + Info_SetValueForKey(userinfo, "rejmsg", "Banned."); + return false; + } + + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if (*password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect."); + return false; + } +// ^^^ + + // they can connect + ent->client = game.clients + (ent - g_edicts - 1); + +//AZEROV + ent->client->team_kills = 0; +//AZEROV + +//EEK + ent->client->team_wounds = 0; + ent->client->team_wounds_before = 0; +//EEK + + /* RiEvER ACEBOT - commented out for debugging +//FIREBLADE +// We're not going to attempt to support reconnection... + if (ent->inuse == true) + { + ClientDisconnect(ent); + ent->inuse = false; + } +//FIREBLADE +*/ + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == false) + { + // clear the respawning variables + InitClientResp (ent->client); + if (!game.autosaved || !ent->client->pers.weapon) + InitClientPersistant (ent->client); + } + + ClientUserinfoChanged (ent, userinfo); + + if (game.maxclients > 1) + gi.dprintf ("%s@%s connected\n",ent->client->pers.netname, ipaddr_buf); +//EEK + strncpy(ent->client->ipaddr, ipaddr_buf, sizeof(ent->client->ipaddr)); +//EEK + +// FROM 3.20 -FB + ent->svflags = 0; +// ^^^ + ent->client->pers.connected = true; + return true; +} + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. +============ +*/ +void ClientDisconnect (edict_t *ent) +{ + int playernum, i; + edict_t *etemp; + + if (!ent->client) + return; + + // drop items if they are alive/not observer + if ( ent->solid != SOLID_NOT ) + TossItemsOnDeath(ent); + + // zucc free the lasersight if applicable + if (ent->lasersight) + SP_LaserSight(ent, NULL); + +//FIREBLADE + if (teamplay->value && ent->solid == SOLID_TRIGGER) + RemoveFromTransparentList(ent); +//FIREBLADE + + ent->lasersight = NULL; + + safe_bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname); + +// ACEBOT_ADD + ACEIT_PlayerRemoved(ent); +// ACEBOT_END + + // go clear any clients that have this guy as their attacker + + for( i=1; i<=maxclients->value; i++ ) + { + if ( (etemp=&g_edicts[i]) && etemp->inuse ) + { + if ( etemp->client->attacker == ent ) + etemp->client->attacker = NULL; + } + } + if (!teamplay->value) + { //FB 5/31/99 + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGOUT); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } + + gi.unlinkentity (ent); + ent->s.modelindex = 0; + ent->solid = SOLID_NOT; + ent->inuse = false; + ent->classname = "disconnected"; + ent->client->pers.connected = false; + + playernum = ent-g_edicts-1; + gi.configstring (CS_PLAYERSKINS+playernum, ""); + +//FIREBLADE + if (teamplay->value) + { + CheckForUnevenTeams(); + } +//FIREBLADE +} + + +//============================================================== + + +edict_t *pm_passent; + +// pmove doesn't need to know about passent and contentmask +trace_t PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) +{ + if (pm_passent->health > 0) + return gi.trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID); + else + return gi.trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID); +} + +unsigned CheckBlock (void *b, int c) +{ + int v,i; + v = 0; + for (i=0 ; is, sizeof(pm->s)); + c2 = CheckBlock (&pm->cmd, sizeof(pm->cmd)); + Com_Printf ("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2); +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame. +============== +*/ +void ClientThink (edict_t *ent, usercmd_t *ucmd) +{ + gclient_t *client; + edict_t *other; + int i, j; + pmove_t pm; + + level.current_entity = ent; + client = ent->client; + + if (level.intermissiontime) + { + client->ps.pmove.pm_type = PM_FREEZE; + // can exit intermission after five seconds + if (level.time > level.intermissiontime + 5.0 + && (ucmd->buttons & BUTTON_ANY) ) + level.exitintermission = true; + return; + } + +//FIREBLADE + if ((int)motd_time->value > (client->resp.motd_refreshes * 2)) + { + if (client->resp.last_motd_refresh < (level.framenum - 20)) + { + client->resp.last_motd_refresh = level.framenum; + client->resp.motd_refreshes++; + PrintMOTD(ent); + } + } +//FIREBLADE + + pm_passent = ent; + +// FROM 3.20 -FB + if (ent->client->chase_mode) { + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + } else { +// ^^^ + // set up for pmove + memset (&pm, 0, sizeof(pm)); + + if (ent->movetype == MOVETYPE_NOCLIP) + client->ps.pmove.pm_type = PM_SPECTATOR; + else if (ent->s.modelindex != 255) + client->ps.pmove.pm_type = PM_GIB; + else if (ent->deadflag) + client->ps.pmove.pm_type = PM_DEAD; + else + client->ps.pmove.pm_type = PM_NORMAL; + + client->ps.pmove.gravity = sv_gravity->value; + pm.s = client->ps.pmove; + + for (i=0 ; i<3 ; i++) + { + pm.s.origin[i] = ent->s.origin[i]*8; + pm.s.velocity[i] = ent->velocity[i] * 8; + } + + if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) + { + pm.snapinitial = true; +// gi.dprintf ("pmove changed!\n"); + } + + pm.cmd = *ucmd; + + pm.trace = PM_trace; // adds default parms + pm.pointcontents = gi.pointcontents; + + // perform a pmove + gi.Pmove (&pm); + +//FB 6/3/99 - info from Mikael Lindh from AQ:G + if (pm.maxs[2] == 4) + { + ent->maxs[2] = CROUCHING_MAXS2; + pm.maxs[2] = CROUCHING_MAXS2; + ent->viewheight = CROUCHING_VIEWHEIGHT; + pm.viewheight = (float)ent->viewheight; + } +//FB 6/3/99 + + // save results of pmove + client->ps.pmove = pm.s; + client->old_pmove = pm.s; + + + // really stopping jumping with leg damage + if ( ent->client->leg_damage && ent->groundentity && pm.s.velocity[2] > 10 ) + { + pm.s.velocity[2] = 0.0; + } + + for (i=0 ; i<3 ; i++) + { + ent->s.origin[i] = pm.s.origin[i]*0.125; + ent->velocity[i] = pm.s.velocity[i]*0.125; + } + + + // zucc stumbling associated with leg damage + if (level.framenum % 6 <= 2) + { + + //Slow down code FOO/zucc + for (i=0 ; i<3 ; i++) + { + if ( ent->client->leg_damage && ((i < 2) || (ent->velocity[2] > 0)) && (ent->groundentity && pm.groundentity) ) + ent->velocity[i] /= 4*ent->client->leghits; //FOO + } + if (level.framenum % (6*12) == 0 && ent->client->leg_damage > 1) + gi.sound (ent, CHAN_BODY, gi.soundindex(va("*pain100_1.wav")), 1, ATTN_NORM, 0); + ent->velocity[0] = (float)((int)(ent->velocity[0]*8))/8; + ent->velocity[1] = (float)((int)(ent->velocity[1]*8))/8; + ent->velocity[2] = (float)((int)(ent->velocity[2]*8))/8; + } + + /* + if ( ent->client->leg_damage ) + { + ent->client->leg_dam_count++; + ent->client->leg_noise++; + if ( ent->groundentity && pm.s.velocity[2] > 10 ) + { + // safe_cprintf(ent, PRINT_HIGH, "Cutting velocity\n"); + pm.s.velocity[2] = 0.0; + } + } + + if ( ent->client->leg_noise == 250 && ent->health > 0 )// && ent->groundentity && crandom() > 0.0 ) + { +// safe_cprintf(ent, PRINT_HIGH, "Playing sound?\n"); + ent->client->leg_noise = 0; + if (IsFemale(ent)) + gi.sound(ent, CHAN_VOICE, gi.soundindex("player/female/pain100_1.wav"), 1, ATTN_IDLE, 0); + else + gi.sound(ent, CHAN_VOICE, gi.soundindex("player/male/pain100_1.wav"), 1, ATTN_IDLE, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); + } + + if ( !ent->groundentity ) + { + for (i=0 ; i<3 ; i++) + { + ent->s.origin[i] = pm.s.origin[i]*0.125; + ent->velocity[i] = pm.s.velocity[i]*0.125; + } + } + else if ( ent->client->leg_dam_count < 10 ) + { + + for (i=0 ; i<3 ; i++) + { + ent->s.origin[i] = pm.s.origin[i]*0.125; + ent->velocity[i] = pm.s.velocity[i]*0.125; + } + } + else + { + for (i=0 ; i<3 ; i++) + { + ent->velocity[i] = 0;//pm.s.velocity[i]*0.125; + } + if ( ent->client->leg_dam_count > 11 ) + ent->client->leg_dam_count = 0; + } +*/ + VectorCopy (pm.mins, ent->mins); + VectorCopy (pm.maxs, ent->maxs); + + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + + // don't play sounds if they have leg damage, they can't jump anyway + if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0) + && !ent->client->leg_damage ) + { + /* don't play jumps period. + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); + */ + ent->client->jumping = 1; + } + + ent->viewheight = pm.viewheight; + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + ent->groundentity = pm.groundentity; + if (pm.groundentity) + ent->groundentity_linkcount = pm.groundentity->linkcount; + + if (ent->deadflag) + { + client->ps.viewangles[ROLL] = 40; + client->ps.viewangles[PITCH] = -15; + client->ps.viewangles[YAW] = client->killer_yaw; + } + else + { + VectorCopy (pm.viewangles, client->v_angle); + VectorCopy (pm.viewangles, client->ps.viewangles); + } + + gi.linkentity (ent); + + if (ent->movetype != MOVETYPE_NOCLIP) + G_TouchTriggers (ent); + + // stop manipulating doors + client->doortoggle = 0; + + if ( ent->client->jumping && +//FIREBLADE + ent->solid != SOLID_NOT ) +//FIREBLADE + kick_attack( ent ); + + // touch other objects + for (i=0 ; itouch) + continue; + other->touch (other, ent, NULL, NULL); + } + } + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + // save light level the player is standing on for + // monster sighting AI + ent->light_level = ucmd->lightlevel; + + // fire weapon from final position if needed + if ((client->latched_buttons & BUTTON_ATTACK) + //Black Cross - Begin + || (((limchasecam->value && !client->chase_mode) || + (limchasecam->value == 2 && client->chase_mode == 1)) && + team_round_going && (client->resp.team != NOTEAM) && + !(limchasecam->value == 2 && client->chase_mode == 2)) + //Black Cross - End + ) + { + if (ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) + { + client->latched_buttons = 0; + if (client->chase_mode) + { + if (client->chase_mode == 1) + { + client->desired_fov = 90; + client->ps.fov = 90; + client->chase_mode++; + } + else + { + client->chase_mode = 0; + client->chase_target = NULL; + client->desired_fov = 90; + client->ps.fov = 90; + client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + } + } + else + { + client->chase_target = NULL; + GetChaseTarget(ent); + if (client->chase_target != NULL) + { + client->chase_mode = 1; + UpdateChaseCam(ent); + } + } + } + else if (!client->weapon_thunk) + { + client->weapon_thunk = true; + Think_Weapon (ent); + } + } + +// ACEBOT_ADD + if (!ent->is_bot && !ent->deadflag && !(ent->solid == SOLID_NOT) ) + ACEND_PathMap(ent); +// ACEBOT_END + + + if (client->chase_mode) + { + if (ucmd->upmove >= 10) + { + if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD)) + { + client->ps.pmove.pm_flags |= PMF_JUMP_HELD; + if (client->chase_target) + { + ChaseNext(ent); + } + else + { + GetChaseTarget(ent); + UpdateChaseCam(ent); + } + } + } + else + client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD; + +//FIREBLADE + ChaseTargetGone(ent); // run a check...result not important. +//FIREBLADE + } + +// FROM 3.20 -FB + // update chase cam if being followed + for (i = 1; i <= maxclients->value; i++) { + other = g_edicts + i; + if (other->inuse && other->client->chase_mode && other->client->chase_target == ent) + UpdateChaseCam(other); + } +// ^^^ +} + + +/* +============== +ClientBeginServerFrame + +This will be called once for each server frame, before running +any other entities in the world. +============== +*/ +void ClientBeginServerFrame (edict_t *ent) +{ + gclient_t *client; + int buttonMask; + + if (level.intermissiontime) + return; + + client = ent->client; + +//FIREBLADE + if (deathmatch->value && !teamplay->value && + ((ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) != ent->client->pers.spectator)) + { + if (ent->solid != SOLID_NOT || ent->deadflag == DEAD_DEAD) + { + if (ent->deadflag != DEAD_DEAD) + { + ent->flags &= ~FL_GODMODE; + ent->health = 0; + meansOfDeath = MOD_SUICIDE; + player_die (ent, ent, ent, 100000, vec3_origin); + // don't even bother waiting for death frames + ent->deadflag = DEAD_DEAD; + + // This will make ClientBeginServerFrame crank us into observer mode + // as soon as our death frames are done... -FB + ent->solid = SOLID_NOT; + // Also set this so we can have a way to know we've already done this... + ent->movetype = MOVETYPE_NOCLIP; + + gi.linkentity(ent); + + safe_bprintf(PRINT_HIGH, "%s became a spectator\n", ent->client->pers.netname); + } + else // immediately become observer... + { + if (ent->movetype != MOVETYPE_NOCLIP) // have we already done this? see above... + { + CopyToBodyQue(ent); + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->movetype = MOVETYPE_NOCLIP; + ent->client->pers.health = 100; + ent->health = 100; + ent->deadflag = DEAD_NO; + gi.linkentity(ent); + safe_bprintf(PRINT_HIGH, "%s became a spectator\n", ent->client->pers.netname); + } + } + } + else + { + ent->client->chase_mode = 0; + ent->client->chase_target = NULL; + ent->client->desired_fov = 90; + ent->client->ps.fov = 90; // FB 5/31/99 added + ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + ent->solid = SOLID_BBOX; + gi.linkentity(ent); + safe_bprintf(PRINT_HIGH, "%s rejoined the game\n", ent->client->pers.netname); + respawn(ent); + } + } + +//FIREBLADE + + // run weapon animations if it hasn't been done by a ucmd_t + if (!client->weapon_thunk) + Think_Weapon (ent); + else + client->weapon_thunk = false; + + if (ent->deadflag) + { + // wait for any button just going down + if (level.time > client->respawn_time) + { +//FIREBLADE + if (teamplay->value || + (!teamplay->value && ent->client->pers.spectator && ent->solid == SOLID_NOT && + ent->deadflag == DEAD_DEAD)) + { + CopyToBodyQue(ent); + + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->movetype = MOVETYPE_NOCLIP; + ent->client->pers.health = 100; + ent->health = 100; + ent->deadflag = DEAD_NO; + + client->ps.pmove.delta_angles[PITCH] = ANGLE2SHORT(0 - client->resp.cmd_angles[PITCH]); + client->ps.pmove.delta_angles[YAW] = ANGLE2SHORT(client->killer_yaw - client->resp.cmd_angles[YAW]); + client->ps.pmove.delta_angles[ROLL] = ANGLE2SHORT(0 - client->resp.cmd_angles[ROLL]); + ent->s.angles[PITCH] = 0; + ent->s.angles[YAW] = client->killer_yaw; + ent->s.angles[ROLL] = 0; + VectorCopy (ent->s.angles, client->ps.viewangles); + VectorCopy (ent->s.angles, client->v_angle); + + gi.linkentity(ent); + } +//FIREBLADE + else + { + // in deathmatch, only wait for attack button + if (deathmatch->value) + buttonMask = BUTTON_ATTACK; + else + buttonMask = -1; + + if ( ( client->latched_buttons & buttonMask ) || + (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN) ) ) + + { + respawn(ent); + client->latched_buttons = 0; + } + } + } + return; + } + + // add player trail so monsters can follow + if (!deathmatch->value) + if (!visible (ent, PlayerTrail_LastSpot() ) ) + PlayerTrail_Add (ent->s.old_origin); + + client->latched_buttons = 0; +} diff --git a/p_hud.c b/p_hud.c new file mode 100644 index 0000000..da6fd96 --- /dev/null +++ b/p_hud.c @@ -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 ; ivalue ; 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 ; ivalue ; 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 ; ivalue ; 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 ; iinuse) + continue; + score = game.clients[i].resp.score; + for (j=0 ; j 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=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 +} diff --git a/p_trail.c b/p_trail.c new file mode 100644 index 0000000..814c010 --- /dev/null +++ b/p_trail.c @@ -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)]; +} diff --git a/p_view.c b/p_view.c new file mode 100644 index 0000000..4f830ea --- /dev/null +++ b/p_view.c @@ -0,0 +1,1351 @@ +#include "g_local.h" +#include "m_player.h" + + + +static edict_t *current_player; +static gclient_t *current_client; + +static vec3_t forward, right, up; +float xyspeed; + +float bobmove; +int bobcycle; // odd cycles are right foot going forward +float bobfracsin; // sin(bobfrac*M_PI) + +/* +=============== +SV_CalcRoll + +=============== +*/ +float SV_CalcRoll (vec3_t angles, vec3_t velocity) +{ + float sign; + float side; + float value; + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs(side); + + value = sv_rollangle->value; + + if (side < sv_rollspeed->value) + side = side * value / sv_rollspeed->value; + else + side = value; + + return side*sign; + +} + + +/* +=============== +P_DamageFeedback + +Handles color blends and view kicks +=============== +*/ +void P_DamageFeedback (edict_t *player) +{ + gclient_t *client; + float side; + float realcount, count, kick; + vec3_t v; + int r, l; + static vec3_t power_color = {0.0, 1.0, 0.0}; + static vec3_t acolor = {1.0, 1.0, 1.0}; + static vec3_t bcolor = {1.0, 0.0, 0.0}; + + client = player->client; + + // flash the backgrounds behind the status numbers + client->ps.stats[STAT_FLASHES] = 0; + if (client->damage_blood) + client->ps.stats[STAT_FLASHES] |= 1; + if (client->damage_armor && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + client->ps.stats[STAT_FLASHES] |= 2; + + // total points of damage shot at the player this frame + count = (client->damage_blood + client->damage_armor + client->damage_parmor); + if (count == 0) + return; // didn't take any damage + + // start a pain animation if still in the player model + if (client->anim_priority < ANIM_PAIN && player->s.modelindex == 255) + { + static int i; + + client->anim_priority = ANIM_PAIN; + if (client->ps.pmove.pm_flags & PMF_DUCKED) + { + player->s.frame = FRAME_crpain1-1; + client->anim_end = FRAME_crpain4; + } + else + { + i = (i+1)%3; + switch (i) + { + case 0: + player->s.frame = FRAME_pain101-1; + client->anim_end = FRAME_pain104; + break; + case 1: + player->s.frame = FRAME_pain201-1; + client->anim_end = FRAME_pain204; + break; + case 2: + player->s.frame = FRAME_pain301-1; + client->anim_end = FRAME_pain304; + break; + } + } + } + + realcount = count; + if (count < 10) + count = 10; // always make a visible effect + + // play an apropriate pain sound + if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + { + r = 1 + (rand()&1); + player->pain_debounce_time = level.time + 0.7; + if (player->health < 25) + l = 25; + else if (player->health < 50) + l = 50; + else if (player->health < 75) + l = 75; + else + l = 100; + gi.sound (player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0); + } + + // the total alpha of the blend is always proportional to count + if (client->damage_alpha < 0) + client->damage_alpha = 0; + client->damage_alpha += count*0.01; + if (client->damage_alpha < 0.2) + client->damage_alpha = 0.2; + if (client->damage_alpha > 0.6) + client->damage_alpha = 0.6; // don't go too saturated + + // the color of the blend will vary based on how much was absorbed + // by different armors + VectorClear (v); + if (client->damage_parmor) + VectorMA (v, (float)client->damage_parmor/realcount, power_color, v); + if (client->damage_armor) + VectorMA (v, (float)client->damage_armor/realcount, acolor, v); + if (client->damage_blood) + VectorMA (v, (float)client->damage_blood/realcount, bcolor, v); + VectorCopy (v, client->damage_blend); + + + // + // calculate view angle kicks + // + kick = abs(client->damage_knockback); + if (kick && player->health > 0) // kick of 0 means no view adjust at all + { + kick = kick * 100 / player->health; + + if (kick < count*0.5) + kick = count*0.5; + if (kick > 50) + kick = 50; + + VectorSubtract (client->damage_from, player->s.origin, v); + VectorNormalize (v); + + side = DotProduct (v, right); + client->v_dmg_roll = kick*side*0.3; + + side = -DotProduct (v, forward); + client->v_dmg_pitch = kick*side*0.3; + + client->v_dmg_time = level.time + DAMAGE_TIME; + } + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_parmor = 0; + client->damage_knockback = 0; +} + + + + +/* +=============== +SV_CalcViewOffset + +Auto pitching on slopes? + + fall from 128: 400 = 160000 + fall from 256: 580 = 336400 + fall from 384: 720 = 518400 + fall from 512: 800 = 640000 + fall from 640: 960 = + + damage = deltavelocity*deltavelocity * 0.0001 + +=============== +*/ +void SV_CalcViewOffset (edict_t *ent) +{ + float *angles; + float bob; + float ratio; + float delta; + vec3_t v; + + + //=================================== + + // base angles + angles = ent->client->ps.kick_angles; + + // if dead, fix the angle and don't add any kick + if (ent->deadflag) + { + VectorClear (angles); + + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + ent->client->ps.viewangles[YAW] = ent->client->killer_yaw; + } + else + { + // add angles based on weapon kick + + VectorCopy (ent->client->kick_angles, angles); + + // add angles based on damage kick + + ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME; + if (ratio < 0) + { + ratio = 0; + ent->client->v_dmg_pitch = 0; + ent->client->v_dmg_roll = 0; + } + angles[PITCH] += ratio * ent->client->v_dmg_pitch; + angles[ROLL] += ratio * ent->client->v_dmg_roll; + + // add pitch based on fall kick + + ratio = (ent->client->fall_time - level.time) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * ent->client->fall_value; + + // add angles based on velocity + + delta = DotProduct (ent->velocity, forward); + angles[PITCH] += delta*run_pitch->value; + + delta = DotProduct (ent->velocity, right); + angles[ROLL] += delta*run_roll->value; + + // add angles based on bob + + delta = bobfracsin * bob_pitch->value * xyspeed; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + delta *= 6; // crouching + angles[PITCH] += delta; + delta = bobfracsin * bob_roll->value * xyspeed; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + delta *= 6; // crouching + if (bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + } + + //=================================== + + // base origin + + VectorClear (v); + + // add view height + + v[2] += ent->viewheight; + + // add fall height + + ratio = (ent->client->fall_time - level.time) / FALL_TIME; + if (ratio < 0) + ratio = 0; + v[2] -= ratio * ent->client->fall_value * 0.4; + + // add bob height + + bob = bobfracsin * xyspeed * bob_up->value; + if (bob > 6) + bob = 6; + //gi.DebugGraph (bob *2, 255); + v[2] += bob; + + // add kick offset + + VectorAdd (v, ent->client->kick_origin, v); + + // absolutely bound offsets + // so the view can never be outside the player box + + if (v[0] < -14) + v[0] = -14; + else if (v[0] > 14) + v[0] = 14; + if (v[1] < -14) + v[1] = -14; + else if (v[1] > 14) + v[1] = 14; + if (v[2] < -22) + v[2] = -22; + else if (v[2] > 30) + v[2] = 30; + + VectorCopy (v, ent->client->ps.viewoffset); +} + +/* +============== +SV_CalcGunOffset +============== +*/ +void SV_CalcGunOffset (edict_t *ent) +{ + int i; + float delta; + + // gun angles from bobbing + ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005; + ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01; + if (bobcycle & 1) + { + ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL]; + ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW]; + } + + ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005; + + // gun angles from delta movement + for (i=0 ; i<3 ; i++) + { + delta = ent->client->oldviewangles[i] - ent->client->ps.viewangles[i]; + if (delta > 180) + delta -= 360; + if (delta < -180) + delta += 360; + if (delta > 45) + delta = 45; + if (delta < -45) + delta = -45; + if (i == YAW) + ent->client->ps.gunangles[ROLL] += 0.1*delta; + ent->client->ps.gunangles[i] += 0.2 * delta; + } + + // gun height + VectorClear (ent->client->ps.gunoffset); + // ent->ps->gunorigin[2] += bob; + + // gun_x / gun_y / gun_z are development tools + for (i=0 ; i<3 ; i++) + { + ent->client->ps.gunoffset[i] += forward[i]*(gun_y->value); + ent->client->ps.gunoffset[i] += right[i]*gun_x->value; + ent->client->ps.gunoffset[i] += up[i]* (-gun_z->value); + } +} + + +/* +============= +SV_AddBlend +============= +*/ +void SV_AddBlend (float r, float g, float b, float a, float *v_blend) +{ + float a2, a3; + + if (a <= 0) + return; + a2 = v_blend[3] + (1-v_blend[3])*a; // new total alpha + a3 = v_blend[3]/a2; // fraction of color from old + + v_blend[0] = v_blend[0]*a3 + r*(1-a3); + v_blend[1] = v_blend[1]*a3 + g*(1-a3); + v_blend[2] = v_blend[2]*a3 + b*(1-a3); + v_blend[3] = a2; +} + + +/* +============= +SV_CalcBlend +============= +*/ +void SV_CalcBlend (edict_t *ent) +{ + int contents; + vec3_t vieworg; + int remaining; + + + // enable ir vision if appropriate + if ( ir->value ) + { + if ((ent->client->pers.inventory[ITEM_INDEX(FindItem(BAND_NAME))] && + ent->client->resp.ir == 0) || + (ent->client->chase_target != NULL && + ent->client->chase_target->client != NULL && + ent->client->chase_mode == 2 && + ent->client->chase_target->client->resp.ir == 0 && + ent->client->chase_target->client->pers.inventory[ITEM_INDEX(FindItem(BAND_NAME))])) + + { + ent->client->ps.rdflags |= RDF_IRGOGGLES; + } + else + { + ent->client->ps.rdflags &= ~RDF_IRGOGGLES; + } + + } + + + + ent->client->ps.blend[0] = ent->client->ps.blend[1] = + ent->client->ps.blend[2] = ent->client->ps.blend[3] = 0; + + // add for contents + VectorAdd (ent->s.origin, ent->client->ps.viewoffset, vieworg); + contents = gi.pointcontents (vieworg); + if (contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER) ) + ent->client->ps.rdflags |= RDF_UNDERWATER; + else + ent->client->ps.rdflags &= ~RDF_UNDERWATER; + + if (contents & (CONTENTS_SOLID|CONTENTS_LAVA)) + SV_AddBlend (1.0, 0.3, 0.0, 0.6, ent->client->ps.blend); + else if (contents & CONTENTS_SLIME) + SV_AddBlend (0.0, 0.1, 0.05, 0.6, ent->client->ps.blend); + else if (contents & CONTENTS_WATER) + SV_AddBlend (0.5, 0.3, 0.2, 0.4, ent->client->ps.blend); + + // add for powerups + if (ent->client->quad_framenum > level.framenum) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0, 0, 1, 0.08, ent->client->ps.blend); + } + else if (ent->client->invincible_framenum > level.framenum) + { + remaining = ent->client->invincible_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (1, 1, 0, 0.08, ent->client->ps.blend); + } + else if (ent->client->enviro_framenum > level.framenum) + { + remaining = ent->client->enviro_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0, 1, 0, 0.08, ent->client->ps.blend); + } + else if (ent->client->breather_framenum > level.framenum) + { + remaining = ent->client->breather_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0.4, 1, 0.4, 0.04, ent->client->ps.blend); + } + + // add for damage + if (ent->client->damage_alpha > 0) + SV_AddBlend (ent->client->damage_blend[0],ent->client->damage_blend[1] + ,ent->client->damage_blend[2], ent->client->damage_alpha, ent->client->ps.blend); + + if (ent->client->bonus_alpha > 0) + SV_AddBlend (0.85, 0.7, 0.3, ent->client->bonus_alpha, ent->client->ps.blend); + + // drop the damage value + ent->client->damage_alpha -= 0.06; + if (ent->client->damage_alpha < 0) + ent->client->damage_alpha = 0; + + // drop the bonus value + ent->client->bonus_alpha -= 0.1; + if (ent->client->bonus_alpha < 0) + ent->client->bonus_alpha = 0; +} + + +/* +================= +P_FallingDamage +================= +*/ +void P_FallingDamage (edict_t *ent) +{ + float delta; + int damage; + vec3_t dir; + gitem_t* item; + + if (teamplay->value) + { + if (lights_camera_action) + { + // ent->s.event = EV_FOOTSTEP; + return; + } + } + + // zucc look for slippers to avoid noise + item = FindItem(SLIP_NAME); + + if (ent->s.modelindex != 255) + return; // not in the player model + + if (ent->movetype == MOVETYPE_NOCLIP) + return; + + if ((ent->client->oldvelocity[2] < 0) && (ent->velocity[2] > ent->client->oldvelocity[2]) && (!ent->groundentity)) + { + delta = ent->client->oldvelocity[2]; + } + else + { + if (!ent->groundentity) + return; + delta = ent->velocity[2] - ent->client->oldvelocity[2]; + ent->client->jumping = 0; + } + delta = delta*delta * 0.0001; + + // never take falling damage if completely underwater + if (ent->waterlevel == 3) + return; + if (ent->waterlevel == 2) + delta *= 0.25; + if (ent->waterlevel == 1) + delta *= 0.5; + + if (delta < 1) + return; + + //zucc add check for slippers + if (delta < 15 && !(ent->client->pers.inventory[ITEM_INDEX(item)]) ) + { + ent->s.event = EV_FOOTSTEP; + return; + } + + ent->client->fall_value = delta*0.5; + if (ent->client->fall_value > 40) + ent->client->fall_value = 40; + ent->client->fall_time = level.time + FALL_TIME; + + if (delta > 30) + { + if (ent->health > 0) + { + if (delta >= 55) + ent->s.event = EV_FALLFAR; + else // all falls are far + ent->s.event = EV_FALLFAR; + } + ent->pain_debounce_time = level.time; // no normal pain sound + + damage = (int)(((delta-30)/2)); + if (damage < 1) + damage = 1; + // zucc scale this up + damage *= 10; + VectorSet (dir, 0, 0, 1); + + if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING) ) + { + T_Damage (ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, 0, MOD_FALLING); + } + } + //zucc added check for slippers, this is just another noise + else if ( !(ent->client->pers.inventory[ITEM_INDEX(item)]) ) + { + ent->s.event = EV_FALLSHORT; + return; + } +} + + + +/* +============= +P_WorldEffects +============= +*/ +void P_WorldEffects (void) +{ + qboolean breather; + qboolean envirosuit; + int waterlevel, old_waterlevel; + + if (current_player->movetype == MOVETYPE_NOCLIP) + { + current_player->air_finished = level.time + 12; // don't need air + return; + } + + waterlevel = current_player->waterlevel; + old_waterlevel = current_client->old_waterlevel; + current_client->old_waterlevel = waterlevel; + + breather = current_client->breather_framenum > level.framenum; + envirosuit = current_client->enviro_framenum > level.framenum; + + // + // if just entered a water volume, play a sound + // + if (!old_waterlevel && waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + if (current_player->watertype & CONTENTS_LAVA) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_SLIME) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_WATER) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + current_player->flags |= FL_INWATER; + + // clear damage_debounce, so the pain sound will play immediately + current_player->damage_debounce_time = level.time - 1; + } + + // + // if just completely exited a water volume, play a sound + // + if (old_waterlevel && ! waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + current_player->flags &= ~FL_INWATER; + } + + // + // check for head just going under water + // + if (old_waterlevel != 3 && waterlevel == 3) + { + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0); + } + + // + // check for head just coming out of water + // + if (old_waterlevel == 3 && waterlevel != 3) + { + if (current_player->air_finished < level.time) + { // gasp for air + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + } + else if (current_player->air_finished < level.time + 11) + { // just break surface + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0); + } + } + + // + // check for drowning + // + if (waterlevel == 3) + { + // breather or envirosuit give air + if (breather || envirosuit) + { + current_player->air_finished = level.time + 10; + + if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0) + { + if (!current_client->breather_sound) + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0); + current_client->breather_sound ^= 1; + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + //FIXME: release a bubble? + } + } + + // if out of air, start drowning + if (current_player->air_finished < level.time) + { // drown! + if (current_player->client->next_drown_time < level.time + && current_player->health > 0) + { + current_player->client->next_drown_time = level.time + 1; + + // take more damage the longer underwater + current_player->dmg += 2; + if (current_player->dmg > 15) + current_player->dmg = 15; + + // play a gurp sound instead of a normal pain sound + if (current_player->health <= current_player->dmg) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0); + else if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0); + + current_player->pain_debounce_time = level.time; + + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + } + else + { + current_player->air_finished = level.time + 12; + current_player->dmg = 2; + } + + // + // check for sizzle damage + // + if (waterlevel && (current_player->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + { + if (current_player->watertype & CONTENTS_LAVA) + { + if (current_player->health > 0 + && current_player->pain_debounce_time <= level.time + && current_client->invincible_framenum < level.framenum) + { + if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0); + current_player->pain_debounce_time = level.time + 1; + } + + if (envirosuit) // take 1/3 damage with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_LAVA); + else + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 3*waterlevel, 0, 0, MOD_LAVA); + } + + if (current_player->watertype & CONTENTS_SLIME) + { + if (!envirosuit) + { // no damage from slime with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_SLIME); + } + } + } +} + + +/* +=============== +G_SetClientEffects +=============== +*/ +void G_SetClientEffects (edict_t *ent) +{ + int pa_type; + int remaining; + + ent->s.effects = 0; + // zucc added RF_IR_VISIBLE + //FB 6/1/99 - only for live players + if (ent->deadflag != DEAD_DEAD) + ent->s.renderfx = RF_IR_VISIBLE; + else + ent->s.renderfx = 0; + + if (ent->health <= 0 || level.intermissiontime) + return; + + if (ent->powerarmor_time > level.time) + { + pa_type = PowerArmorType (ent); + if (pa_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (pa_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } + + if (ent->client->quad_framenum > level.framenum) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_QUAD; + } + + if (ent->client->invincible_framenum > level.framenum) + { + remaining = ent->client->invincible_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_PENT; + } + + // show cheaters!!! + if (ent->flags & FL_GODMODE) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + } +} + + +/* +=============== +G_SetClientEvent +=============== +*/ +void G_SetClientEvent (edict_t *ent) +{ + gitem_t* item; + + item = FindItem(SLIP_NAME); + + if (ent->s.event) + return; + + if ( ent->groundentity && xyspeed > 225) + { + //zucc added item check to see if they have slippers + if ( (int)(current_client->bobtime+bobmove) != bobcycle + && !(ent->client->pers.inventory[ITEM_INDEX(item)]) ) + ent->s.event = EV_FOOTSTEP; + } +} + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound (edict_t *ent) +{ + char *weap; + + if (ent->client->resp.game_helpchanged != game.helpchanged) + { + ent->client->resp.game_helpchanged = game.helpchanged; + ent->client->resp.helpchanged = 1; + } + + // help beep (no more than three times) + if (ent->client->resp.helpchanged && ent->client->resp.helpchanged <= 3 && !(level.framenum&63) ) + { + ent->client->resp.helpchanged++; + gi.sound (ent, CHAN_VOICE, gi.soundindex ("misc/pc_up.wav"), 1, ATTN_STATIC, 0); + } + + + if (ent->client->pers.weapon) + weap = ent->client->pers.weapon->classname; + else + weap = ""; + + if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + ent->s.sound = snd_fry; + else if (strcmp(weap, "weapon_railgun") == 0) + ent->s.sound = gi.soundindex("weapons/rg_hum.wav"); + else if (strcmp(weap, "weapon_bfg") == 0) + ent->s.sound = gi.soundindex("weapons/bfg_hum.wav"); + else if (ent->client->weapon_sound) + ent->s.sound = ent->client->weapon_sound; + else + ent->s.sound = 0; +} + +/* +=============== +G_SetClientFrame +=============== +*/ +void G_SetClientFrame (edict_t *ent) +{ + gclient_t *client; + qboolean duck, run; + + if (ent->s.modelindex != 255) + return; // not in the player model + + client = ent->client; + + if (client->ps.pmove.pm_flags & PMF_DUCKED) + duck = true; + else + duck = false; + if (xyspeed) + run = true; + else + run = false; + + // check for stand/duck and stop/go transitions + if (duck != client->anim_duck && client->anim_priority < ANIM_DEATH) + goto newanim; + if (run != client->anim_run && client->anim_priority == ANIM_BASIC) + goto newanim; + if (!ent->groundentity && client->anim_priority <= ANIM_WAVE) + goto newanim; + + + // zucc vwep + if(client->anim_priority == ANIM_REVERSE) + { + if(ent->s.frame > client->anim_end) + { + ent->s.frame--; + return; + } + } + else if (ent->s.frame < client->anim_end) + { // continue an animation + ent->s.frame++; + return; + } + + if (client->anim_priority == ANIM_DEATH) + return; // stay there + if (client->anim_priority == ANIM_JUMP) + { + if (!ent->groundentity) + return; // stay there + ent->client->anim_priority = ANIM_WAVE; + ent->s.frame = FRAME_jump3; + ent->client->anim_end = FRAME_jump6; + return; + } + + newanim: + // return to either a running or standing frame + client->anim_priority = ANIM_BASIC; + client->anim_duck = duck; + client->anim_run = run; + + if (!ent->groundentity) + { + client->anim_priority = ANIM_JUMP; + if (ent->s.frame != FRAME_jump2) + ent->s.frame = FRAME_jump1; + client->anim_end = FRAME_jump2; + } + else if (run) + { // running + if (duck) + { + ent->s.frame = FRAME_crwalk1; + client->anim_end = FRAME_crwalk6; + } + else + { + ent->s.frame = FRAME_run1; + client->anim_end = FRAME_run6; + } + } + else + { // standing + if (duck) + { + ent->s.frame = FRAME_crstnd01; + client->anim_end = FRAME_crstnd19; + } + else + { + ent->s.frame = FRAME_stand01; + client->anim_end = FRAME_stand40; + } + } +} + + + + + +void Do_Bleeding( edict_t *ent ) +{ + int damage; + int temp; + vec3_t norm; + VectorSet(norm, 0.0, 0.0, 0.0 ); + if ( !(ent->client->bleeding) || (ent->health <= 0) ) + { + return; + } + + temp = (int)(ent->client->bleeding * .2); + ent->client->bleeding -= temp; + if ( temp <= 0 ) + temp = 1; + ent->client->bleed_remain += temp; + damage = (int)(ent->client->bleed_remain/BLEED_TIME); + if ( ent->client->bleed_remain >= BLEED_TIME ) + { + ent->health -= damage; + if ( damage > 1 ) + { + // action doens't do this + //ent->client->damage_blood += damage; // for feedback + } + if ( ent->health <= 0 ) + { + meansOfDeath = ent->client->attacker_mod; + locOfDeath = ent->client->attacker_loc; + Killed(ent, ent->client->attacker, ent->client->attacker, damage, ent->s.origin); + } + else + { + ent->client->bleed_remain %= BLEED_TIME; + } + if (ent->client->bleeddelay <= level.time) + { + vec3_t pos; + ent->client->bleeddelay = level.time + 2; // 2 seconds + VectorAdd(ent->client->bleedloc_offset, ent->absmax, pos); + //safe_cprintf(ent, PRINT_HIGH, "Bleeding now.\n"); + EjectBlooder(ent, pos, pos); + + // do bleeding + + } + + } + + +} + + +int canFire( edict_t* ent ) +{ + int result = 0; + + + switch ( ent->client->curr_weap ) + { + case MK23_NUM: + { + + if ( ent->client->mk23_rds > 0 ) + { + result = 1; + } + break; + + } + case MP5_NUM: + { + if ( ent->client->mp5_rds > 0 ) + { + result = 1; + } + break; + } + case M4_NUM: + { + if ( ent->client->m4_rds > 0 ) + { + result = 1; + } + break; + } + case M3_NUM: + { + if ( ent->client->shot_rds > 0 ) + { + result = 1; + } + break; + } + case HC_NUM: + { + if ( ent->client->cannon_rds == 2 ) + { + result = 1; + } + break; + } + case SNIPER_NUM: + { + if ( ent->client->sniper_rds > 0 ) + { + result = 1; + } + break; + } + case DUAL_NUM: + { + if ( ent->client->dual_rds > 0 ) + { + result = 1; + } + break; + } + + default: + result = 0; + } + return result; +} +/* +================= +ClientEndServerFrame + +Called for each player at the end of the server frame +and right after spawning +================= +*/ +void ClientEndServerFrame (edict_t *ent) +{ + float bobtime; + int i; + // int damage; // zucc for bleeding + + current_player = ent; + current_client = ent->client; + + //FIREBLADE - Unstick avoidance stuff. + if (ent->solid == SOLID_TRIGGER && !lights_camera_action) + { + edict_t *overlap; + if ((overlap = FindOverlap(ent, NULL)) == NULL) + { + ent->solid = SOLID_BBOX; + gi.linkentity(ent); + RemoveFromTransparentList(ent); + } + else + { + do + { + if (overlap->solid == SOLID_BBOX) + { + overlap->solid = SOLID_TRIGGER; + gi.linkentity(overlap); + AddToTransparentList(overlap); + } + overlap = FindOverlap(ent, overlap); + } while (overlap != NULL); + } + } + //FIREBLADE + + // + // If the origin or velocity have changed since ClientThink(), + // update the pmove values. This will happen when the client + // is pushed by a bmodel or kicked by an explosion. + // + // If it wasn't updated here, the view position would lag a frame + // behind the body position when pushed -- "sinking into plats" + // + for (i=0 ; i<3 ; i++) + { + current_client->ps.pmove.origin[i] = ent->s.origin[i]*8.0; + current_client->ps.pmove.velocity[i] = ent->velocity[i]*8.0; + } + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if (level.intermissiontime) + { + // FIXME: add view drifting here? + current_client->ps.blend[3] = 0; + current_client->ps.fov = 90; + G_SetStats (ent); + return; + } + + AngleVectors (ent->client->v_angle, forward, right, up); + + // burn from lava, etc + P_WorldEffects (); + + // + // set model angles from view angles so other things in + // the world can tell which direction you are looking + // + if (ent->client->v_angle[PITCH] > 180) + ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH])/3; + else + ent->s.angles[PITCH] = ent->client->v_angle[PITCH]/3; + ent->s.angles[YAW] = ent->client->v_angle[YAW]; + ent->s.angles[ROLL] = 0; + ent->s.angles[ROLL] = SV_CalcRoll (ent->s.angles, ent->velocity)*4; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]); + + if (xyspeed < 5 || + //FIREBLADE + ent->solid == SOLID_NOT) + //FIREBLADE + { + bobmove = 0; + current_client->bobtime = 0; // start at beginning of cycle again + } + else if (ent->groundentity) + { // so bobbing only cycles when on ground + if (xyspeed > 210) + bobmove = 0.25; + else if (xyspeed > 100) + bobmove = 0.125; + else + bobmove = 0.0625; + } + + bobtime = (current_client->bobtime += bobmove); + + if (current_client->ps.pmove.pm_flags & PMF_DUCKED) + bobtime *= 4; + + bobcycle = (int)bobtime; + bobfracsin = fabs(sin(bobtime*M_PI)); + + // detect hitting the floor + P_FallingDamage (ent); + + // zucc handle any bleeding damage here + Do_Bleeding( ent ); + + // apply all the damage taken this frame + P_DamageFeedback (ent); + + // determine the view offsets + SV_CalcViewOffset (ent); + + // determine the gun offsets + SV_CalcGunOffset (ent); + + // determine the full screen color blend + // must be after viewoffset, so eye contents can be + // accurately determined + // FIXME: with client prediction, the contents + // should be determined by the client + SV_CalcBlend (ent); + + G_SetStats (ent); + + //FIREBLADE + for (i = 1; i <= maxclients->value; i++) { + int stats_copy; + edict_t *e = g_edicts + i; + if (!ent->inuse || + e->client->chase_mode == 0 || + e->client->chase_target != ent) + continue; + for (stats_copy = 0; stats_copy < MAX_STATS; stats_copy++) + { + if (stats_copy >= STAT_TEAM_HEADER && stats_copy <= STAT_TEAM2_SCORE) + continue; // protect these + if (stats_copy == STAT_LAYOUTS || stats_copy == STAT_ID_VIEW) + continue; // protect these + if (stats_copy == STAT_SNIPER_ICON && + e->client->chase_mode != 2) + continue; // only show sniper lens when in chase mode 2 + if (stats_copy == STAT_FRAGS) + continue; + e->client->ps.stats[stats_copy] = + ent->client->ps.stats[stats_copy]; + } + + //FB e->client->ps.stats[STAT_LAYOUTS] = 1; + //FB break; + } + //FIREBLADE + + G_SetClientEvent (ent); + + G_SetClientEffects (ent); + + G_SetClientSound (ent); + + G_SetClientFrame (ent); + + VectorCopy (ent->velocity, ent->client->oldvelocity); + VectorCopy (ent->client->ps.viewangles, ent->client->oldviewangles); + + // clear weapon kicks + VectorClear (ent->client->kick_origin); + VectorClear (ent->client->kick_angles); + + // zucc - clear the open door command + ent->client->doortoggle = 0; + + if ( ent->client->push_timeout > 0 ) + ent->client->push_timeout--; + /* else + { + ent->client->attacker = NULL; + ent->client->attacker_mod = MOD_BLEEDING; + } + */ + if ( ent->client->reload_attempts > 0 ) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) && canFire(ent) ) + { + ent->client->reload_attempts = 0; + } + else + { + Cmd_Reload_f(ent); + } + } + if ( ent->client->weapon_attempts > 0 ) + Cmd_Weapon_f(ent); + + // if the scoreboard is up, update it + if (ent->client->showscores && !(level.framenum & 31) ) + { + //FIREBLADE + if (ent->client->menu && (Q_stricmp(ent->classname, "bot") )) + { + PMenu_Update(ent); + gi.unicast (ent, false); + } else + //FIREBLADE + { + //RiEvEr - no messages to bots! + if( (Q_stricmp(ent->classname, "bot") ) ) + //R + { + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, false); + } + } + } + + //FIREBLADE + RadioThink(ent); + //FIREBLADE +} + diff --git a/p_weapon.c b/p_weapon.c new file mode 100644 index 0000000..ac82451 --- /dev/null +++ b/p_weapon.c @@ -0,0 +1,4640 @@ +// g_weapon.c + +#include "g_local.h" +#include "m_player.h" + + +static qboolean is_quad; +static byte is_silenced; + +void setFFState(edict_t *ent); +void weapon_grenade_fire (edict_t *ent, qboolean held); + +void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + vec3_t _distance; + + VectorCopy (distance, _distance); + + if (client->pers.firing_style == ACTION_FIRING_CLASSIC || + client->pers.firing_style == ACTION_FIRING_CLASSIC_HIGH) + { + if (client->pers.hand == LEFT_HANDED) + _distance[1] *= -1; + else if (client->pers.hand == CENTER_HANDED) + _distance[1] = 0; + } + else + { + _distance[1] = 0; // fire from center always + } + + G_ProjectSource (point, _distance, forward, right, result); +} + +// used for setting up the positions of the guns in shell ejection +void Old_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + vec3_t _distance; + + VectorCopy (distance, _distance); + if (client->pers.hand == LEFT_HANDED) + _distance[1] = -1; // changed from = to *= + // Fireblade 2/28/99 + // zucc reversed, this is only used for setting up shell ejection and + // since those look good this shouldn't be changed + else if (client->pers.hand == CENTER_HANDED) + _distance[1] = 0; + G_ProjectSource (point, _distance, forward, right, result); +} + +// this one is the real old project source +void Knife_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + vec3_t _distance; + + VectorCopy (distance, _distance); + if (client->pers.hand == LEFT_HANDED) + _distance[1] *= -1; // changed from = to *= + else if (client->pers.hand == CENTER_HANDED) + _distance[1] = 0; + G_ProjectSource (point, _distance, forward, right, result); +} + + + +/* +=============== +PlayerNoise + + Each player can have two noise objects associated with it: + a personal noise (jumping, pain, weapon firing), and a weapon + target noise (bullet wall impacts) + + Monsters that don't directly see the player can move + to a noise in hopes of seeing the player from there. + =============== +*/ +void PlayerNoise(edict_t *who, vec3_t where, int type) +{ + edict_t *noise; + + if (type == PNOISE_WEAPON) + { + if (who->client->silencer_shots) + { + who->client->silencer_shots--; + return; + } + } + + if (deathmatch->value) + return; + + if (who->flags & FL_NOTARGET) + return; + + + if (!who->mynoise) + { + noise = G_Spawn(); + noise->classname = "player_noise"; + VectorSet (noise->mins, -8, -8, -8); + VectorSet (noise->maxs, 8, 8, 8); + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise = noise; + + noise = G_Spawn(); + noise->classname = "player_noise"; + VectorSet (noise->mins, -8, -8, -8); + VectorSet (noise->maxs, 8, 8, 8); + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise2 = noise; + } + + if (type == PNOISE_SELF || type == PNOISE_WEAPON) + { + noise = who->mynoise; + level.sound_entity = noise; + level.sound_entity_framenum = level.framenum; + } + else // type == PNOISE_IMPACT + { + noise = who->mynoise2; + level.sound2_entity = noise; + level.sound2_entity_framenum = level.framenum; + } + + VectorCopy (where, noise->s.origin); + VectorSubtract (where, noise->maxs, noise->absmin); + VectorAdd (where, noise->maxs, noise->absmax); + noise->teleport_time = level.time; + gi.linkentity (noise); +} + + +void PlaceHolder( edict_t *ent ) +{ + ent->nextthink = level.time + 1000; +} + +// keep the entity around so we can find it later if we need to respawn the weapon there +void SetSpecWeapHolder (edict_t *ent ) +{ + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->think = PlaceHolder; + gi.linkentity (ent); +} + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other) +{ + int index, index2; + gitem_t *ammo; + gitem_t *item; + + int special = 0; + int band = 0; + + index = ITEM_INDEX(ent->item); + + if ( ( ((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) + && other->client->pers.inventory[index]) + { + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ) ) + return false; // leave the weapon for others to pickup + } + + // find out if they have a bandolier + item = FindItem(BAND_NAME); + if ( other->client->pers.inventory[ITEM_INDEX(item)] ) + band = 1; + else + band = 0; + + + + // zucc special cases for picking up weapons + // the mk23 should never be dropped, probably + + if ( stricmp(ent->item->pickup_name, MK23_NAME) == 0 ) + { + if ( other->client->pers.inventory[index] ) // already has one + { + if ( !(ent->spawnflags & DROPPED_ITEM) ) + { + ammo = FindItem (ent->item->ammo); + return ( Add_Ammo(other, ammo, ammo->quantity) ); + } + } + other->client->pers.inventory[index]++; + if ( !(ent->spawnflags & DROPPED_ITEM) ) + other->client->mk23_rds = other->client->mk23_max; + } + else if ( stricmp(ent->item->pickup_name, MP5_NAME) == 0 ) + { + if ( other->client->unique_weapon_total < unique_weapons->value + band ) + { + other->client->pers.inventory[index]++; + other->client->unique_weapon_total++; + if ( !(ent->spawnflags & DROPPED_ITEM) ) + other->client->mp5_rds = other->client->mp5_max; + special = 1; + safe_cprintf(other, PRINT_HIGH, "%s - Unique Weapon\n", ent->item->pickup_name); + } + else + return false; // we can't get it + } + else if ( stricmp(ent->item->pickup_name, M4_NAME) == 0 ) + { + if ( other->client->unique_weapon_total < unique_weapons->value + band ) + { + other->client->pers.inventory[index]++; + other->client->unique_weapon_total++; + if ( !(ent->spawnflags & DROPPED_ITEM) ) + other->client->m4_rds = other->client->m4_max; + special = 1; + safe_cprintf(other, PRINT_HIGH, "%s - Unique Weapon\n", ent->item->pickup_name); + } + else + return false; // we can't get it + + } + else if ( stricmp(ent->item->pickup_name, M3_NAME) == 0 ) + { + if ( other->client->unique_weapon_total < unique_weapons->value + band ) + { + other->client->pers.inventory[index]++; + other->client->unique_weapon_total++; + if ( !(ent->spawnflags & DROPPED_ITEM) ) + { + // any weapon that doesn't completely fill up each reload can + //end up in a state where it has a full weapon and pending reload(s) + if ( other->client->weaponstate == WEAPON_RELOADING ) + { + if ( other->client->fast_reload ) + { + other->client->shot_rds = other->client->shot_max - 2; + } + else + other->client->shot_rds = other->client->shot_max - 1; + } + else + { + other->client->shot_rds = other->client->shot_max; + } + } + special = 1; + safe_cprintf(other, PRINT_HIGH, "%s - Unique Weapon\n", ent->item->pickup_name); + } + else + return false; // we can't get it + + } + else if ( stricmp(ent->item->pickup_name, HC_NAME) == 0 ) + { + if ( other->client->unique_weapon_total < unique_weapons->value + band ) + { + other->client->pers.inventory[index]++; + other->client->unique_weapon_total++; + if ( !(ent->spawnflags & DROPPED_ITEM) ) + { + other->client->cannon_rds = other->client->cannon_max; + index2 = ITEM_INDEX(FindItem(ent->item->ammo)); + if ( other->client->pers.inventory[index2] + 5 > other->client->pers.max_shells ) + other->client->pers.inventory[index2] = other->client->pers.max_shells; + else + other->client->pers.inventory[index2] += 5; + + } + safe_cprintf(other, PRINT_HIGH, "%s - Unique Weapon\n", ent->item->pickup_name); + special = 1; + } + else + return false; // we can't get it + + } + else if ( stricmp(ent->item->pickup_name, SNIPER_NAME) == 0 ) + { + if ( other->client->unique_weapon_total < unique_weapons->value + band ) + { + other->client->pers.inventory[index]++; + other->client->unique_weapon_total++; + if ( !(ent->spawnflags & DROPPED_ITEM) ) + { + if ( other->client->weaponstate == WEAPON_RELOADING ) + { + if ( other->client->fast_reload ) + { + other->client->sniper_rds = other->client->sniper_max - 2; + } + else + other->client->sniper_rds = other->client->sniper_max - 1; + } + else + { + other->client->sniper_rds = other->client->sniper_max; + } + } + special = 1; + safe_cprintf(other, PRINT_HIGH, "%s - Unique Weapon\n", ent->item->pickup_name); + } + else + return false; // we can't get it + + } + else if ( stricmp(ent->item->pickup_name, DUAL_NAME) == 0 ) + { + + if ( other->client->pers.inventory[index] ) // already has one + { + if ( !(ent->spawnflags & DROPPED_ITEM) ) + { + ammo = FindItem (ent->item->ammo); + return (Add_Ammo(other, ammo, ammo->quantity) ); + } + } + other->client->pers.inventory[index]++; + if ( !(ent->spawnflags & DROPPED_ITEM) ) + { + other->client->dual_rds += other->client->mk23_max; + // assume the player uses the new (full) pistol + other->client->mk23_rds = other->client->mk23_max; + } + } + else if ( stricmp(ent->item->pickup_name, KNIFE_NAME) == 0 ) + { + + if ( other->client->pers.inventory[index] < other->client->knife_max ) + { + other->client->pers.inventory[index]++; + return true; + } + else + { + return false; + } + } + else if ( stricmp(ent->item->pickup_name, GRENADE_NAME) == 0 ) + { + + if ( other->client->pers.inventory[index] < other->client->grenade_max ) + { + other->client->pers.inventory[index]++; + if (! (ent->spawnflags & DROPPED_PLAYER_ITEM) && !(ent->spawnflags & DROPPED_ITEM) ) + { + if (deathmatch->value) + { + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + ent->flags |= FL_RESPAWN; + else + SetRespawn (ent, 30); + } + if (coop->value) + ent->flags |= FL_RESPAWN; + } + + return true; + } + else + { + return false; + } + } + else + { + + other->client->pers.inventory[index]++; + + + if (!(ent->spawnflags & DROPPED_ITEM) ) + { + // give them some ammo with it + ammo = FindItem (ent->item->ammo); + + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + Add_Ammo (other, ammo, 1000); + else + Add_Ammo (other, ammo, ammo->quantity); + + if (! (ent->spawnflags & DROPPED_PLAYER_ITEM) ) + { + if (deathmatch->value) + { + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + ent->flags |= FL_RESPAWN; + else + SetRespawn (ent, 30); + } + if (coop->value) + ent->flags |= FL_RESPAWN; + } + } + } + /* zucc - don't want auto switching + if (other->client->pers.weapon != ent->item && + (other->client->pers.inventory[index] == 1) && + ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) ) + other->client->newweapon = ent->item; + */ + if ( !(ent->spawnflags & DROPPED_ITEM) + && !(ent->spawnflags & DROPPED_PLAYER_ITEM) + && (SPEC_WEAPON_RESPAWN) ) + { + SetSpecWeapHolder( ent ); + } + + + return true; +} + + +// zucc vwep 3.17(?) vwep support +void ShowGun(edict_t *ent) +{ + + int nIndex; + char *pszIcon; + + // No weapon? + if ( !ent->client->pers.weapon) + { + ent->s.modelindex2 = 0; + return; + } + + // Determine the weapon's precache index. + + nIndex = 0; + pszIcon = ent->client->pers.weapon->icon; + + if ( strcmp( pszIcon, "w_mk23") == 0) + nIndex = 1; + else if ( strcmp( pszIcon, "w_mp5") == 0) + nIndex = 2; + else if ( strcmp( pszIcon, "w_m4") == 0) + nIndex = 3; + else if ( strcmp( pszIcon, "w_cannon") == 0) + nIndex = 4; + else if ( strcmp( pszIcon, "w_super90") == 0) + nIndex = 5; + else if ( strcmp( pszIcon, "w_sniper") == 0) + nIndex = 6; + else if ( strcmp( pszIcon, "w_akimbo") == 0) + nIndex = 7; + else if ( strcmp( pszIcon, "w_knife") == 0) + nIndex = 8; + else if ( strcmp( pszIcon, "a_m61frag") == 0) + nIndex = 9; + + // Clear previous weapon model. + ent->s.skinnum &= 255; + + // Set new weapon model. + ent->s.skinnum |= (nIndex << 8); + ent->s.modelindex2 = 255; + /* + char heldmodel[128]; + int len; + + if(!ent->client->pers.weapon) + { + ent->s.modelindex2 = 0; + return; + } + + strcpy(heldmodel, "players/"); + strcat(heldmodel, Info_ValueForKey (ent->client->pers.userinfo, "skin")); + for(len = 8; heldmodel[len]; len++) + { + if(heldmodel[len] == '/') + heldmodel[++len] = '\0'; + } + strcat(heldmodel, ent->client->pers.weapon->icon); + strcat(heldmodel, ".md2"); + //gi.dprintf ("%s\n", heldmodel); + ent->s.modelindex2 = gi.modelindex(heldmodel); +*/ +} + + + + + + + + + +//#define GRENADE_IDLE_FIRST 40 +//#define GRENADE_IDLE_LAST 69 + + +/* +=============== +ChangeWeapon + +The old weapon has been dropped all the way, so make the new one +current +=============== +*/ +void ChangeWeapon (edict_t *ent) +{ + gitem_t *item; + if (ent->client->grenade_time) + { + ent->client->grenade_time = level.time; + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, false); + ent->client->grenade_time = 0; + } + + // zucc - prevent reloading queue for previous weapon from doing anything + ent->client->reload_attempts = 0; + + ent->client->pers.lastweapon = ent->client->pers.weapon; + ent->client->pers.weapon = ent->client->newweapon; + ent->client->newweapon = NULL; + ent->client->machinegun_shots = 0; + + if (ent->client->pers.weapon && ent->client->pers.weapon->ammo) + ent->client->ammo_index = ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo)); + else + ent->client->ammo_index = 0; + + if (!ent->client->pers.weapon || ent->s.modelindex != 255) // zucc vwep + { // dead + ent->client->ps.gunindex = 0; + return; + } + + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->ps.gunframe = 0; + +//FIREBLADE + if (ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) + return; +//FIREBLADE + + ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); + + // zucc hentai's animation for vwep + ent->client->anim_priority = ANIM_PAIN; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain1; + ent->client->anim_end = FRAME_crpain4; + } + else + { + ent->s.frame = FRAME_pain301; + ent->client->anim_end = FRAME_pain304; + + } + + ShowGun(ent); + // zucc done + + + + + if(stricmp(ent->client->pers.weapon->pickup_name, MK23_NAME) == 0) + { + ent->client->curr_weap = MK23_NUM; + } + else if(stricmp(ent->client->pers.weapon->pickup_name, MP5_NAME) == 0) + { + ent->client->curr_weap = MP5_NUM; + } + else if(stricmp(ent->client->pers.weapon->pickup_name, M4_NAME) == 0) + { + ent->client->curr_weap = M4_NUM; + } + else if(stricmp(ent->client->pers.weapon->pickup_name, M3_NAME) == 0) + { + ent->client->curr_weap = M3_NUM; + } + else if(stricmp(ent->client->pers.weapon->pickup_name, HC_NAME) == 0) + { + ent->client->curr_weap = HC_NUM; + } + + else if(stricmp(ent->client->pers.weapon->pickup_name, SNIPER_NAME) == 0) + { + ent->client->curr_weap = SNIPER_NUM; + } + else if(stricmp(ent->client->pers.weapon->pickup_name, DUAL_NAME) == 0) + { + ent->client->curr_weap = DUAL_NUM; + } + else if(stricmp(ent->client->pers.weapon->pickup_name, KNIFE_NAME) == 0) + { + ent->client->curr_weap = KNIFE_NUM; + } + else if(stricmp(ent->client->pers.weapon->pickup_name, GRENADE_NAME) == 0) + { + ent->client->curr_weap = GRENADE_NUM; + } + + item = FindItem(LASER_NAME); + if ( ent->client->pers.inventory[ITEM_INDEX(item)] ) + SP_LaserSight(ent, item);//item->use(ent, item); +} + +/* +================= +NoAmmoWeaponChange +================= +*/ +void NoAmmoWeaponChange (edict_t *ent) +{ + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("railgun"))] ) + { + ent->client->newweapon = FindItem ("railgun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))] ) + { + ent->client->newweapon = FindItem ("hyperblaster"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("chaingun"))] ) + { + ent->client->newweapon = FindItem ("chaingun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("machinegun"))] ) + { + ent->client->newweapon = FindItem ("machinegun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] > 1 + && ent->client->pers.inventory[ITEM_INDEX(FindItem("super shotgun"))] ) + { + ent->client->newweapon = FindItem ("super shotgun"); + return; + } + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] + && ent->client->pers.inventory[ITEM_INDEX(FindItem("shotgun"))] ) + { + ent->client->newweapon = FindItem ("shotgun"); + return; + } + ent->client->newweapon = FindItem ("blaster"); +} + +/* +================= +Think_Weapon + +Called by ClientBeginServerFrame and ClientThink +================= +*/ +void Think_Weapon (edict_t *ent) +{ + // if just died, put the weapon away + if (ent->health < 1) + { + ent->client->newweapon = NULL; + ChangeWeapon (ent); + } + + // call active weapon think routine + if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink) + { + is_quad = (ent->client->quad_framenum > level.framenum); + if (ent->client->silencer_shots) + is_silenced = MZ_SILENCED; + else + is_silenced = 0; + ent->client->pers.weapon->weaponthink (ent); + } +} + + +/* +================ +Use_Weapon + +Make the weapon ready if there is ammo +================ +*/ +void Use_Weapon (edict_t *ent, gitem_t *item) +{ + //int ammo_index; + //gitem_t *ammo_item; + + +// if(ent->client->weaponstate == WEAPON_BANDAGING || ent->client->bandaging == 1 ) + // return; + + + + + // see if we're already using it + if (item == ent->client->pers.weapon) + return; + + // zucc - let them change if they want + /*if (item->ammo && !g_select_empty->value && !(item->flags & IT_AMMO)) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + + if (!ent->client->pers.inventory[ammo_index]) + { + safe_cprintf (ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + + if (ent->client->pers.inventory[ammo_index] < item->quantity) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + }*/ + + // change to this weapon when down + ent->client->newweapon = item; +} + + + + +edict_t *FindSpecWeapSpawn( edict_t* ent ) +{ + edict_t *spot = NULL; + + //safe_bprintf (PRINT_HIGH, "Calling the FindSpecWeapSpawn\n"); + spot = G_Find (spot, FOFS(classname), ent->classname); + //safe_bprintf (PRINT_HIGH, "spot = %p and spot->think = %p and playerholder = %p, spot, (spot ? spot->think : 0), PlaceHolder\n"); + while ( spot && spot->think != PlaceHolder )//(spot->spawnflags & DROPPED_ITEM ) && spot->think != PlaceHolder )//spot->solid == SOLID_NOT ) + { + // safe_bprintf (PRINT_HIGH, "Calling inside the loop FindSpecWeapSpawn\n"); + spot = G_Find (spot, FOFS(classname), ent->classname); + } +/* if (!spot) + { + safe_bprintf(PRINT_HIGH, "Failed to find a spawn spot for %s\n", ent->classname); + } + else + safe_bprintf(PRINT_HIGH, "Found a spawn spot for %s\n", ent->classname); +*/ + return spot; +} + +void ThinkSpecWeap( edict_t* ent ); + + +static void SpawnSpecWeap(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_PLAYER_ITEM;//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); + 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->think = NULL; + + gi.linkentity (ent); + */ + SetRespawn(spot, 1); + gi.linkentity(spot); +} + +void temp_think_specweap( edict_t* ent ) +{ + ent->touch = Touch_Item; + if (deathmatch->value && !teamplay->value && !allweapon->value ) + { + ent->nextthink = level.time + 74; + ent->think = ThinkSpecWeap; + } + else if ( teamplay->value && !allweapon->value) + { + ent->nextthink = level.time + 1000; + ent->think = PlaceHolder; + } + else // allweapon set + { + ent->nextthink = level.time + 1; + ent->think = G_FreeEdict; + } +} + + + +// zucc make dropped weapons respawn elsewhere +void ThinkSpecWeap( edict_t* ent ) +{ + edict_t *spot; + + if ((spot = FindSpecWeapSpawn(ent)) != NULL) { + SpawnSpecWeap(ent->item, spot); + G_FreeEdict(ent); + } else { + ent->nextthink = level.time + 1; + ent->think = G_FreeEdict; + } +} + + + + + +/* +================ +Drop_Weapon +================ +*/ +void Drop_Weapon (edict_t *ent, gitem_t *item) +{ + int index; + gitem_t *replacement; + edict_t *temp = NULL; + + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + return; + + + if ( ent->client->weaponstate == WEAPON_BANDAGING || + ent->client->bandaging == 1 || ent->client->bandage_stopped == 1 ) + { + safe_cprintf(ent, PRINT_HIGH, "You are too busy bandaging right now...\n"); + return; + } + // don't let them drop this, causes duplication + if ( ent->client->newweapon == item ) + { + return; + } + + + index = ITEM_INDEX(item); + // see if we're already using it + //zucc special cases for dropping + if ( stricmp(item->pickup_name, MK23_NAME) == 0 ) + { + safe_cprintf(ent, PRINT_HIGH, "Can't drop the %s.\n", MK23_NAME); + return; + } + else if ( stricmp(item->pickup_name, MP5_NAME) == 0 ) + { + + if ( ent->client->pers.weapon == item && (ent->client->pers.inventory[index] == 1) ) + { + replacement = FindItem (MK23_NAME); // back to the pistol then + ent->client->newweapon = replacement; + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 51; + + //ChangeWeapon( ent ); + } + ent->client->unique_weapon_total--; // dropping 1 unique weapon + temp = Drop_Item (ent, item); + temp->think = temp_think_specweap; + ent->client->pers.inventory[index]--; + } + else if ( stricmp(item->pickup_name, M4_NAME) == 0 ) + { + + + if ( ent->client->pers.weapon == item && (ent->client->pers.inventory[index] == 1) ) + { + replacement = FindItem (MK23_NAME); // back to the pistol then + ent->client->newweapon = replacement; + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 44; + + //ChangeWeapon( ent ); + } + ent->client->unique_weapon_total--; // dropping 1 unique weapon + temp = Drop_Item (ent, item); + temp->think = temp_think_specweap; + ent->client->pers.inventory[index]--; + } + else if ( stricmp(item->pickup_name, M3_NAME) == 0 ) + { + + if ( ent->client->pers.weapon == item && (ent->client->pers.inventory[index] == 1) ) + { + replacement = FindItem (MK23_NAME); // back to the pistol then + ent->client->newweapon = replacement; + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 41; + //ChangeWeapon( ent ); + } + ent->client->unique_weapon_total--; // dropping 1 unique weapon + temp = Drop_Item (ent, item); + temp->think = temp_think_specweap; + ent->client->pers.inventory[index]--; + } + else if ( stricmp(item->pickup_name, HC_NAME) == 0 ) + { + if ( ent->client->pers.weapon == item && (ent->client->pers.inventory[index] == 1) ) + { + replacement = FindItem (MK23_NAME); // back to the pistol then + ent->client->newweapon = replacement; + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 61; + //ChangeWeapon( ent ); + } + ent->client->unique_weapon_total--; // dropping 1 unique weapon + temp = Drop_Item (ent, item); + temp->think = temp_think_specweap; + ent->client->pers.inventory[index]--; + } + else if ( stricmp(item->pickup_name, SNIPER_NAME) == 0 ) + { + if ( ent->client->pers.weapon == item && (ent->client->pers.inventory[index] == 1) ) + { + // in case they are zoomed in + ent->client->ps.fov = 90; + ent->client->desired_fov = 90; + replacement = FindItem (MK23_NAME); // back to the pistol then + ent->client->newweapon = replacement; + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 50; + //ChangeWeapon( ent ); + } + ent->client->unique_weapon_total--; // dropping 1 unique weapon + temp = Drop_Item (ent, item); + temp->think = temp_think_specweap; + ent->client->pers.inventory[index]--; + } + else if ( stricmp(item->pickup_name, DUAL_NAME) == 0 ) + { + if ( ent->client->pers.weapon == item ) + { + replacement = FindItem (MK23_NAME); // back to the pistol then + ent->client->newweapon = replacement; + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 40; + //ChangeWeapon( ent ); + } + ent->client->dual_rds = ent->client->mk23_rds; + } + else if ( stricmp(item->pickup_name, KNIFE_NAME) == 0 ) + { + //safe_cprintf(ent, PRINT_HIGH, "Before checking knife inven frames = %d\n", ent->client->ps.gunframe); + + if ( ((ent->client->pers.weapon == item) ) && (ent->client->pers.inventory[index] == 1) ) + { + replacement = FindItem (MK23_NAME); // back to the pistol then + ent->client->newweapon = replacement; + if ( ent->client->resp.knife_mode ) // hack to avoid an error + { + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 111; + } + else + ChangeWeapon( ent ); + // safe_cprintf(ent, PRINT_HIGH, "After change weap from knife drop frames = %d\n", ent->client->ps.gunframe); + } + } + else if ( stricmp(item->pickup_name, GRENADE_NAME) == 0 ) + { + if ( (ent->client->pers.weapon == item ) && (ent->client->pers.inventory[index] == 1) ) + { + if ( (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)]--; + ent->client->newweapon = FindItem(MK23_NAME); + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 0; + return; + + } + + + replacement = FindItem (MK23_NAME); // back to the pistol then + ent->client->newweapon = replacement; + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 0; + //ChangeWeapon( ent ); + } + } + + + else if ( ((item == ent->client->pers.weapon) || (item == ent->client->newweapon))&& (ent->client->pers.inventory[index] == 1) ) + { + safe_cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n"); + return; + } + + if ( !temp ) + { + temp = Drop_Item (ent, item); + ent->client->pers.inventory[index]--; + } + +} + + + + + +//zucc drop special weapon (only 1 of them) +void DropSpecialWeapon ( edict_t* ent ) +{ + + // first check if their current weapon is a special weapon, if so, drop it. + if ( (ent->client->pers.weapon == FindItem(MP5_NAME)) + || (ent->client->pers.weapon == FindItem(M4_NAME)) + || (ent->client->pers.weapon == FindItem(M3_NAME)) + || (ent->client->pers.weapon == FindItem(HC_NAME)) + || (ent->client->pers.weapon == FindItem(SNIPER_NAME)) ) + Drop_Weapon (ent, ent->client->pers.weapon); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SNIPER_NAME))] > 0 ) + Drop_Weapon (ent, FindItem(SNIPER_NAME)); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(HC_NAME))] > 0 ) + Drop_Weapon (ent, FindItem(HC_NAME)); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(M3_NAME))] > 0 ) + Drop_Weapon (ent, FindItem(M3_NAME)); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(MP5_NAME))] > 0 ) + Drop_Weapon (ent, FindItem(MP5_NAME)); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(M4_NAME))] > 0 ) + Drop_Weapon (ent, FindItem(M4_NAME)); + // special case, aq does this, guess I can support it + else if (ent->client->pers.weapon == FindItem(DUAL_NAME)) + ent->client->newweapon = FindItem(MK23_NAME); + +} + +// used for when we want to force a player to drop an extra special weapon +// for when they drop the bandolier and they are over the weapon limit +void DropExtraSpecial( edict_t* ent ) +{ + gitem_t *item; + + + if ( (ent->client->pers.weapon == FindItem(MP5_NAME)) + || (ent->client->pers.weapon == FindItem(M4_NAME)) + || (ent->client->pers.weapon == FindItem(M3_NAME)) + || (ent->client->pers.weapon == FindItem(HC_NAME)) + || (ent->client->pers.weapon == FindItem(SNIPER_NAME)) ) + { + item = ent->client->pers.weapon; + // if they have more than 1 then they are willing to drop one + if ( ent->client->pers.inventory[ITEM_INDEX(item)] > 1 ) + { + Drop_Weapon(ent, ent->client->pers.weapon ); + return; + } + } + // otherwise drop some weapon they aren't using + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SNIPER_NAME))] > 0 + && FindItem(SNIPER_NAME) != ent->client->pers.weapon ) + Drop_Weapon (ent, FindItem(SNIPER_NAME)); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(HC_NAME))] > 0 + && FindItem(HC_NAME) != ent->client->pers.weapon ) + Drop_Weapon (ent, FindItem(HC_NAME)); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(M3_NAME))] > 0 + && FindItem(M3_NAME) != ent->client->pers.weapon ) + Drop_Weapon (ent, FindItem(M3_NAME)); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(MP5_NAME))] > 0 + && FindItem(MP5_NAME) != ent->client->pers.weapon ) + Drop_Weapon (ent, FindItem(MP5_NAME)); + else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(M4_NAME))] > 0 + && FindItem(M4_NAME) != ent->client->pers.weapon ) + Drop_Weapon (ent, FindItem(M4_NAME)); + else + gi.dprintf("Couldn't find the appropriate weapon to drop.\n"); + + +} + +//zucc ready special weapon +void ReadySpecialWeapon ( edict_t* ent ) +{ + int weapons[5] = {MP5_NUM, M4_NUM, M3_NUM, HC_NUM, SNIPER_NUM}; + char *strings[5] = {MP5_NAME, M4_NAME, M3_NAME, HC_NAME, SNIPER_NAME}; + int curr, i; + int last; + + + if(ent->client->weaponstate == WEAPON_BANDAGING || ent->client->bandaging == 1 ) + return; + + + for ( curr = 0; curr < 5; curr++ ) + { + if ( ent->client->curr_weap == weapons[curr] ) + break; + } + if ( curr >= 5 ) + { + curr = -1; + last = 5; + } + else + { + last = curr + 5; + } + + for ( i = (curr + 1); i != last; i = (i + 1)) + { + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(strings[i % 5]))] ) + { + ent->client->newweapon = FindItem(strings[i % 5]); + return; + } + } + + +} + +/* +================ +Weapon_Generic + +A generic function to handle the basics of weapon thinking +================ +*/ + +//zucc - copied in BD's code, modified for use with other weapons + +#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1) +#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1) +#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1) + +#define FRAME_RELOAD_FIRST (FRAME_DEACTIVATE_LAST +1) +#define FRAME_LASTRD_FIRST (FRAME_RELOAD_LAST +1) + +#define MK23MAG 12 +#define MP5MAG 30 +#define M4MAG 24 +#define DUALMAG 24 + +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, + int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, + int FRAME_RELOAD_LAST, int FRAME_LASTRD_LAST, + int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) +{ + int n; + int bFire = 0; + int bOut = 0; +/* int bBursting = 0;*/ + + + // zucc vwep + if(ent->s.modelindex != 255) + return; // not on client, so VWep animations could do wacky things + +//FIREBLADE + if (ent->client->weaponstate == WEAPON_FIRING && + ((ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) || + lights_camera_action)) + { + ent->client->weaponstate = WEAPON_READY; + } +//FIREBLADE + + //+BD - Added Reloading weapon, done manually via a cmd + if( ent->client->weaponstate == WEAPON_RELOADING) + { + if(ent->client->ps.gunframe < FRAME_RELOAD_FIRST || ent->client->ps.gunframe > FRAME_RELOAD_LAST) + ent->client->ps.gunframe = FRAME_RELOAD_FIRST; + else if(ent->client->ps.gunframe < FRAME_RELOAD_LAST) + { + ent->client->ps.gunframe++; + switch ( ent->client->curr_weap ) + { + //+BD - Check weapon to find out when to play reload sounds + case MK23_NUM: + { + if(ent->client->ps.gunframe == 46) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23out.wav"), 1, ATTN_NORM, 0); + else if(ent->client->ps.gunframe == 53) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23in.wav"), 1, ATTN_NORM, 0); + else if(ent->client->ps.gunframe == 59) // 3 + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23slap.wav"), 1, ATTN_NORM, 0); + break; + } + case MP5_NUM: + { + if(ent->client->ps.gunframe == 55) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mp5out.wav"), 1, ATTN_NORM, 0); + else if(ent->client->ps.gunframe == 59) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mp5in.wav"), 1, ATTN_NORM, 0); + else if(ent->client->ps.gunframe == 63) //61 + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mp5slap.wav"), 1, ATTN_NORM, 0); + break; + } + case M4_NUM: + { + if(ent->client->ps.gunframe == 52) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m4a1out.wav"), 1, ATTN_NORM, 0); + else if(ent->client->ps.gunframe == 58) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m4a1in.wav"), 1, ATTN_NORM, 0); + break; + } + case M3_NUM: + { + if ( ent->client->shot_rds >= ent->client->shot_max) + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + ent->client->weaponstate = WEAPON_READY; + return; + } + + if(ent->client->ps.gunframe == 48) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m3in.wav"), 1, ATTN_NORM, 0); + } + if(ent->client->ps.gunframe == 49 ) + { + if ( ent->client->fast_reload == 1 ) + { + ent->client->fast_reload = 0; + ent->client->shot_rds++; + ent->client->ps.gunframe = 44; + } + } + break; + } + case HC_NUM: + { + if(ent->client->ps.gunframe == 64) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/copen.wav"), 1, ATTN_NORM, 0); + else if(ent->client->ps.gunframe == 67) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/cout.wav"), 1, ATTN_NORM, 0); + else if(ent->client->ps.gunframe == 76) //61 + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/cin.wav"), 1, ATTN_NORM, 0); + else if (ent->client->ps.gunframe == 80) // 3 + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/cclose.wav"), 1, ATTN_NORM, 0); + break; + } + case SNIPER_NUM: + { + + if ( ent->client->sniper_rds >= ent->client->sniper_max) + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + ent->client->weaponstate = WEAPON_READY; + return; + } + + if(ent->client->ps.gunframe == 59) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/ssgbolt.wav"), 1, ATTN_NORM, 0); + } + else if(ent->client->ps.gunframe == 71) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/ssgin.wav"), 1, ATTN_NORM, 0); + } + else if(ent->client->ps.gunframe == 73 ) + { + if ( ent->client->fast_reload == 1 ) + { + ent->client->fast_reload = 0; + ent->client->sniper_rds++; + ent->client->ps.gunframe = 67; + } + } + else if(ent->client->ps.gunframe == 76) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/ssgbolt.wav"), 1, ATTN_NORM, 0); + } + break; + } + case DUAL_NUM: + { + if(ent->client->ps.gunframe == 45) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23out.wav"), 1, ATTN_NORM, 0); + else if(ent->client->ps.gunframe == 53) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23slap.wav"), 1, ATTN_NORM, 0); + else if(ent->client->ps.gunframe == 60) // 3 + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23slap.wav"), 1, ATTN_NORM, 0); + break; + } + default: + gi.dprintf("No weapon choice for reloading (sounds).\n"); + break; + + } + } + else + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + ent->client->weaponstate = WEAPON_READY; + switch ( ent->client->curr_weap ) + { + case MK23_NUM: + { + ent->client->dual_rds -= ent->client->mk23_rds; + ent->client->mk23_rds = ent->client->mk23_max; + ent->client->dual_rds += ent->client->mk23_max; + (ent->client->pers.inventory[ent->client->ammo_index])--; + if ( ent->client->pers.inventory[ent->client->ammo_index] < 0) + { + ent->client->pers.inventory[ent->client->ammo_index] = 0; + } + ent->client->fired = 0; // reset any firing delays + break; + //else + // ent->client->mk23_rds = ent->client->pers.inventory[ent->client->ammo_index]; + } + case MP5_NUM: + { + + ent->client->mp5_rds = ent->client->mp5_max; + (ent->client->pers.inventory[ent->client->ammo_index])--; + if ( ent->client->pers.inventory[ent->client->ammo_index] < 0) + { + ent->client->pers.inventory[ent->client->ammo_index] = 0; + } + ent->client->fired = 0; // reset any firing delays + ent->client->burst = 0; // reset any bursting + break; + } + case M4_NUM: + { + + ent->client->m4_rds = ent->client->m4_max; + (ent->client->pers.inventory[ent->client->ammo_index])--; + if ( ent->client->pers.inventory[ent->client->ammo_index] < 0) + { + ent->client->pers.inventory[ent->client->ammo_index] = 0; + } + ent->client->fired = 0; // reset any firing delays + ent->client->burst = 0; // reset any bursting + ent->client->machinegun_shots = 0; + break; + } + case M3_NUM: + { + ent->client->shot_rds++; + (ent->client->pers.inventory[ent->client->ammo_index])--; + if ( ent->client->pers.inventory[ent->client->ammo_index] < 0) + { + ent->client->pers.inventory[ent->client->ammo_index] = 0; + } + break; + } + case HC_NUM: + { + ent->client->cannon_rds = ent->client->cannon_max; + (ent->client->pers.inventory[ent->client->ammo_index]) -= 2; + if ( ent->client->pers.inventory[ent->client->ammo_index] < 0) + { + ent->client->pers.inventory[ent->client->ammo_index] = 0; + } + break; + } + case SNIPER_NUM: + { + ent->client->sniper_rds++; + (ent->client->pers.inventory[ent->client->ammo_index])--; + if ( ent->client->pers.inventory[ent->client->ammo_index] < 0) + { + ent->client->pers.inventory[ent->client->ammo_index] = 0; + } + return; + } + case DUAL_NUM: + { + ent->client->dual_rds = ent->client->dual_max; + ent->client->mk23_rds = ent->client->mk23_max; + (ent->client->pers.inventory[ent->client->ammo_index]) -= 2; + if ( ent->client->pers.inventory[ent->client->ammo_index] < 0) + { + ent->client->pers.inventory[ent->client->ammo_index] = 0; + } + break; + } + default: + gi.dprintf("No weapon choice for reloading.\n"); + break; + } + + + } + } + + if( ent->client->weaponstate == WEAPON_END_MAG) + { + if(ent->client->ps.gunframe < FRAME_LASTRD_LAST) + ent->client->ps.gunframe++; + else + ent->client->ps.gunframe = FRAME_LASTRD_LAST; + // see if our weapon has ammo (from something other than reloading) + if ( (( ent->client->curr_weap == MK23_NUM ) && ( ent->client->mk23_rds > 0 )) + || (( ent->client->curr_weap == MP5_NUM ) && ( ent->client->mp5_rds > 0 )) + || (( ent->client->curr_weap == M4_NUM ) && ( ent->client->m4_rds > 0 )) + || (( ent->client->curr_weap == M3_NUM ) && ( ent->client->shot_rds > 0 )) + || (( ent->client->curr_weap == HC_NUM ) && ( ent->client->cannon_rds > 0 )) + || (( ent->client->curr_weap == SNIPER_NUM ) && ( ent->client->sniper_rds > 0 )) + || (( ent->client->curr_weap == DUAL_NUM ) && ( ent->client->dual_rds > 0 )) ) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + } + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) + //FIREBLADE + && (ent->solid != SOLID_NOT || ent->deadflag == DEAD_DEAD) && !lights_camera_action) + //FIREBLADE + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + + + } + + } + } + + if (ent->client->weaponstate == WEAPON_DROPPING) + { + if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST) + { + + ChangeWeapon (ent); + return; + } + // zucc for vwep + else if((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + + + ent->client->ps.gunframe++; + return; + } + + if ( ent->client->weaponstate == WEAPON_BANDAGING ) + { + if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST ) + { + ent->client->weaponstate = WEAPON_BUSY; + ent->client->idle_weapon = BANDAGE_TIME; + return; + } + ent->client->ps.gunframe++; + return; + } + + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + // sounds for activation? + switch( ent->client->curr_weap ) + { + case MK23_NUM: + { + if (ent->client->dual_rds >= ent->client->mk23_max ) + ent->client->mk23_rds = ent->client->mk23_max; + else + ent->client->mk23_rds = ent->client->dual_rds; + if(ent->client->ps.gunframe == 3) // 3 + { + if ( ent->client->mk23_rds > 0 ) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23slide.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + //mk23slap + ent->client->ps.gunframe = 62; + ent->client->weaponstate = WEAPON_END_MAG; + } + } + ent->client->fired = 0; //reset any firing delays + break; + } + case MP5_NUM: + { + if(ent->client->ps.gunframe == 3) // 3 + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mp5slide.wav"), 1, ATTN_NORM, 0); + ent->client->fired = 0; //reset any firing delays + ent->client->burst = 0; + break; + } + case M4_NUM: + { + if(ent->client->ps.gunframe == 3) // 3 + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m4a1slide.wav"), 1, ATTN_NORM, 0); + ent->client->fired = 0; //reset any firing delays + ent->client->burst = 0; + ent->client->machinegun_shots = 0; + break; + } + case M3_NUM: + { + ent->client->fired = 0; //reset any firing delays + ent->client->burst = 0; + ent->client->fast_reload = 0; + break; + } + case HC_NUM: + { + ent->client->fired = 0; //reset any firing delays + ent->client->burst = 0; + break; + } + case SNIPER_NUM: + { + ent->client->fired = 0; //reset any firing delays + ent->client->burst = 0; + ent->client->fast_reload = 0; + break; + } + case DUAL_NUM: + { + if ( ent->client->dual_rds <= 0 && ent->client->ps.gunframe == 3) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + if ( ent->client->dual_rds <= 0 && ent->client->ps.gunframe == 4) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->client->ps.gunframe = 68; + ent->client->weaponstate = WEAPON_END_MAG; + ent->client->resp.sniper_mode = 0; + + ent->client->desired_fov = 90; + ent->client->ps.fov = 90; + ent->client->fired = 0; //reset any firing delays + ent->client->burst = 0; + + return; + } + ent->client->fired = 0; //reset any firing delays + ent->client->burst = 0; + break; + } + + default: + gi.dprintf("Activated unknown weapon.\n"); + break; + } + + ent->client->resp.sniper_mode = 0; + // has to be here for dropping the sniper rifle, in the drop command didn't work... + ent->client->desired_fov = 90; + ent->client->ps.fov = 90; + ent->client->ps.gunframe++; + return; + } + + if (ent->client->weaponstate == WEAPON_BUSY) + { + if ( ent->client->bandaging == 1 ) + { + if ( !(ent->client->idle_weapon) ) + { + Bandage( ent ); + return; + } + else + { + (ent->client->idle_weapon)--; + return; + } + } + + // for after bandaging delay + if ( !(ent->client->idle_weapon) && ent->client->bandage_stopped ) + { + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->ps.gunframe = 0; + ent->client->bandage_stopped = 0; + return; + } + else if ( ent->client->bandage_stopped ) + { + (ent->client->idle_weapon)--; + return; + } + + if( ent->client->curr_weap == SNIPER_NUM ) + { + if ( ent->client->desired_fov == 90 ) + { + ent->client->ps.fov = 90; + ent->client->weaponstate = WEAPON_READY; + ent->client->idle_weapon = 0; + } + if ( !(ent->client->idle_weapon) && ent->client->desired_fov != 90 ) + { + ent->client->ps.fov = ent->client->desired_fov; + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunindex = 0; + return; + } + else + (ent->client->idle_weapon)--; + + } + } + + + if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING) + && (ent->client->weaponstate != WEAPON_BURSTING ) + && (ent->client->bandage_stopped == 0) ) + { + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + ent->client->desired_fov = 90; + ent->client->ps.fov = 90; + ent->client->resp.sniper_mode = 0; + if ( ent->client->pers.weapon ) + ent->client->ps.gunindex = gi.modelindex( ent->client->pers.weapon->view_model ); + + // zucc more vwep stuff + if((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + return; + } + + // bandaging case + if ( (ent->client->bandaging) + && (ent->client->weaponstate != WEAPON_FIRING) + && (ent->client->weaponstate != WEAPON_BURSTING) + && (ent->client->weaponstate != WEAPON_BUSY) + && (ent->client->weaponstate != WEAPON_BANDAGING) ) + { + ent->client->weaponstate = WEAPON_BANDAGING; + ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) +//FIREBLADE + && (ent->solid != SOLID_NOT || ent->deadflag == DEAD_DEAD) && !lights_camera_action) +//FIREBLADE + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + switch ( ent->client->curr_weap ) + { + case MK23_NUM: + { + // safe_cprintf (ent, PRINT_HIGH, "Calling ammo check %d\n", ent->client->mk23_rds); + if ( ent->client->mk23_rds > 0 ) + { + // safe_cprintf(ent, PRINT_HIGH, "Entered fire selection\n"); + if ( ent->client->resp.mk23_mode != 0 && ent->client->fired == 0 ) + { + ent->client->fired = 1; + bFire = 1; + } + else if ( ent->client->resp.mk23_mode == 0 ) + { + bFire = 1; + } + } + else + bOut = 1; + break; + + } + case MP5_NUM: + { + if ( ent->client->mp5_rds > 0 ) + { + if ( ent->client->resp.mp5_mode != 0 && ent->client->fired == 0 && ent->client->burst == 0 ) + { + ent->client->fired = 1; + ent->client->ps.gunframe = 70; + ent->client->burst = 1; + ent->client->weaponstate = WEAPON_BURSTING; + + // start the animation + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1-1; + ent->client->anim_end = FRAME_attack8; + } + } + else if ( ent->client->resp.mp5_mode == 0 && ent->client->fired == 0 ) + { + bFire = 1; + } + } + else + bOut = 1; + break; + } + case M4_NUM: + { + if ( ent->client->m4_rds > 0 ) + { + if ( ent->client->resp.m4_mode != 0 && ent->client->fired == 0 && ent->client->burst == 0 ) + { + ent->client->fired = 1; + ent->client->ps.gunframe = 64; + ent->client->burst = 1; + ent->client->weaponstate = WEAPON_BURSTING; + + // start the animation + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1-1; + ent->client->anim_end = FRAME_attack8; + } + } + else if ( ent->client->resp.m4_mode == 0 && ent->client->fired == 0 ) + { + bFire = 1; + } + } + else + bOut = 1; + break; + } + case M3_NUM: + { + if ( ent->client->shot_rds > 0 ) + { + bFire = 1; + } + else + bOut = 1; + break; + } + case HC_NUM: + { + if ( ent->client->cannon_rds == 2 ) + { + bFire = 1; + } + else + bOut = 1; + break; + } + case SNIPER_NUM: + { + if ( ent->client->ps.fov != ent->client->desired_fov) + ent->client->ps.fov = ent->client->desired_fov; + // if they aren't at 90 then they must be zoomed, so remove their weapon from view + if ( ent->client->ps.fov != 90 ) + { + ent->client->ps.gunindex = 0; + ent->client->no_sniper_display = 0; + } + + if ( ent->client->sniper_rds > 0 ) + { + bFire = 1; + } + else + bOut = 1; + break; + } + case DUAL_NUM: + { + if ( ent->client->dual_rds > 0 ) + { + bFire = 1; + } + else + bOut = 1; + break; + } + + + default: + { + safe_cprintf (ent, PRINT_HIGH, "Calling non specific ammo code\n"); + if ((!ent->client->ammo_index) || ( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity)) + { + bFire = 1; + } + else + { + bFire = 0; + bOut = 1; + } + } + + } + if ( bFire ) + { + ent->client->ps.gunframe = FRAME_FIRE_FIRST; + ent->client->weaponstate = WEAPON_FIRING; + + // start the animation + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1-1; + ent->client->anim_end = FRAME_attack8; + } + } + else if ( bOut ) // out of ammo + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + //+BD - Disabled for manual weapon change + //NoAmmoWeaponChange (ent); + } + } + else + { + + if ( ent->client->ps.fov != ent->client->desired_fov) + ent->client->ps.fov = ent->client->desired_fov; + // if they aren't at 90 then they must be zoomed, so remove their weapon from view + if ( ent->client->ps.fov != 90 ) + { + ent->client->ps.gunindex = 0; + ent->client->no_sniper_display = 0; + } + + if (ent->client->ps.gunframe == FRAME_IDLE_LAST) + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + /* if (pause_frames) + { + for (n = 0; pause_frames[n]; n++) + { + if (ent->client->ps.gunframe == pause_frames[n]) + { + if (rand()&15) + return; + } + } + } + */ + ent->client->ps.gunframe++; + ent->client->fired = 0; // weapon ready and button not down, now they can fire again + ent->client->burst = 0; + ent->client->machinegun_shots = 0; + return; + } + } + + if (ent->client->weaponstate == WEAPON_FIRING ) + { + for (n = 0; fire_frames[n]; n++) + { + if (ent->client->ps.gunframe == fire_frames[n]) + { + if (ent->client->quad_framenum > level.framenum) gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + fire (ent); + break; + } + } + + if (!fire_frames[n]) + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1) + ent->client->weaponstate = WEAPON_READY; + } + // player switched into + if ( ent->client->weaponstate == WEAPON_BURSTING ) + { + for (n = 0; fire_frames[n]; n++) + { + if (ent->client->ps.gunframe == fire_frames[n]) + { + if (ent->client->quad_framenum > level.framenum) gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + { + // safe_cprintf (ent, PRINT_HIGH, "Calling fire code, frame = %d.\n", ent->client->ps.gunframe); + fire (ent); + } + break; + } + } + + if (!fire_frames[n]) + ent->client->ps.gunframe++; + + //safe_cprintf (ent, PRINT_HIGH, "Calling stricmp, frame = %d.\n", ent->client->ps.gunframe); + + + if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1) + ent->client->weaponstate = WEAPON_READY; + + if ( ent->client->curr_weap == MP5_NUM ) + { + if (ent->client->ps.gunframe >= 76) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST+1; + } + // safe_cprintf (ent, PRINT_HIGH, "Succes stricmp now: frame = %d.\n", ent->client->ps.gunframe); + + } + if ( ent->client->curr_weap == M4_NUM ) + { + if (ent->client->ps.gunframe >= 69) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST+1; + } + // safe_cprintf (ent, PRINT_HIGH, "Succes stricmp now: frame = %d.\n", ent->client->ps.gunframe); + + } + + } +} + + + + +/* +====================================================================== + +GRENADE + +====================================================================== +*/ + +#define GRENADE_TIMER 3.0 +#define GRENADE_MINSPEED 400 +#define GRENADE_MAXSPEED 800 + +void weapon_grenade_fire (edict_t *ent, qboolean held) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 125; + float timer; + int speed; + float radius; + + radius = damage+40; + if (is_quad) + damage *= 4; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + timer = ent->client->grenade_time - level.time; + speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER); + fire_grenade2 (ent, start, forward, GRENADE_DAMRAD, speed, timer, GRENADE_DAMRAD*2, held); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->grenade_time = level.time + 1.0; +} + +void Weapon_Grenade (edict_t *ent) +{ + if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY)) + { + ChangeWeapon (ent); + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if (ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 1; + ent->client->weaponstate = WEAPON_FIRING; + ent->client->grenade_time = 0; + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + return; + } + + if ((ent->client->ps.gunframe == 29) || (ent->client->ps.gunframe == 34) || (ent->client->ps.gunframe == 39) || (ent->client->ps.gunframe == 48)) + { + if (rand()&15) + return; + } + + if (++ent->client->ps.gunframe > 48) + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0); + + if (ent->client->ps.gunframe == 11) + { + if (!ent->client->grenade_time) + { + ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2; + ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav"); + //ent->client->weapon_sound = gi.soundindex("weapons/grenlb1b.wav"); + } + + // they waited too long, detonate it in their hand + if (!ent->client->grenade_blew_up && level.time >= ent->client->grenade_time) + { + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, true); + ent->client->grenade_blew_up = true; + } + + if (ent->client->buttons & BUTTON_ATTACK) + return; + + if (ent->client->grenade_blew_up) + { + if (level.time >= ent->client->grenade_time) + { + ent->client->ps.gunframe = 15; + ent->client->grenade_blew_up = false; + } + else + { + return; + } + } + } + + if (ent->client->ps.gunframe == 12) + { + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, false); + } + + if ((ent->client->ps.gunframe == 15) && (level.time < ent->client->grenade_time)) + return; + + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == 16) + { + ent->client->grenade_time = 0; + ent->client->weaponstate = WEAPON_READY; + } + } +} + + + +/* +====================================================================== + +GRENADE LAUNCHER + +====================================================================== +*/ + +void weapon_grenadelauncher_fire (edict_t *ent) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 120; + float radius; + + radius = damage+40; + if (is_quad) + damage *= 4; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + fire_grenade (ent, start, forward, damage, 600, 2.5, radius); + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_GRENADE | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_GrenadeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, 0, 0, pause_frames, fire_frames, weapon_grenadelauncher_fire); +} + +/* +====================================================================== + +ROCKET + +====================================================================== +*/ + +void Weapon_RocketLauncher_Fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius; + int radius_damage; + + damage = 100 + (int)(random() * 20.0); + radius_damage = 120; + damage_radius = 120; + if (is_quad) + { + damage *= 4; + radius_damage *= 4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_ROCKET | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_RocketLauncher (edict_t *ent) +{ + static int pause_frames[] = {25, 33, 42, 50, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 12, 50, 54, 0,0, pause_frames, fire_frames, Weapon_RocketLauncher_Fire); +} + + +/* +====================================================================== + +BLASTER / HYPERBLASTER + +====================================================================== +*/ + +void Blaster_Fire (edict_t *ent, vec3_t g_offset, int damage, qboolean hyper, int effect) +{ + vec3_t forward, right; + vec3_t start; + vec3_t offset; + + if (is_quad) + damage *= 4; + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 8, ent->viewheight-8); + VectorAdd (offset, g_offset, offset); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + fire_blaster (ent, start, forward, damage, 1000, effect, hyper); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + if (hyper) + gi.WriteByte (MZ_HYPERBLASTER | is_silenced); + else + gi.WriteByte (MZ_BLASTER | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); +} + + +void Weapon_Blaster_Fire (edict_t *ent) +{ + int damage; + + if (deathmatch->value) + damage = 15; + else + damage = 10; + Blaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER); + ent->client->ps.gunframe++; +} + +void Weapon_Blaster (edict_t *ent) +{ + static int pause_frames[] = {19, 32, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 8, 52, 55, 0, 0, pause_frames, fire_frames, Weapon_Blaster_Fire); +} + + +void Weapon_HyperBlaster_Fire (edict_t *ent) +{ + float rotation; + vec3_t offset; + int effect; + int damage; + + ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav"); + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe++; + } + else + { + if (! ent->client->pers.inventory[ent->client->ammo_index] ) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + else + { + rotation = (ent->client->ps.gunframe - 5) * 2*M_PI/6; + offset[0] = -4 * sin(rotation); + offset[1] = 0; + offset[2] = 4 * cos(rotation); + + if ((ent->client->ps.gunframe == 6) || (ent->client->ps.gunframe == 9)) + effect = EF_HYPERBLASTER; + else + effect = 0; + if (deathmatch->value) + damage = 15; + else + damage = 20; + Blaster_Fire (ent, offset, damage, true, effect); + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + } + + ent->client->ps.gunframe++; + if (ent->client->ps.gunframe == 12 && ent->client->pers.inventory[ent->client->ammo_index]) + ent->client->ps.gunframe = 6; + } + + if (ent->client->ps.gunframe == 12) + { + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + ent->client->weapon_sound = 0; + } + +} + +void Weapon_HyperBlaster (edict_t *ent) +{ + static int pause_frames[] = {0}; + static int fire_frames[] = {6, 7, 8, 9, 10, 11, 0}; + + Weapon_Generic (ent, 5, 20, 49, 53, 0, 0, pause_frames, fire_frames, Weapon_HyperBlaster_Fire); +} + +/* +====================================================================== + +MACHINEGUN / CHAINGUN + +====================================================================== +*/ + +void Machinegun_Fire (edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = 8; + int kick = 2; + vec3_t offset; + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->machinegun_shots = 0; + ent->client->ps.gunframe++; + return; + } + + if (ent->client->ps.gunframe == 5) + ent->client->ps.gunframe = 4; + else + ent->client->ps.gunframe = 5; + + if (ent->client->pers.inventory[ent->client->ammo_index] < 1) + { + ent->client->ps.gunframe = 6; + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + for (i=1 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + ent->client->kick_origin[0] = crandom() * 0.35; + ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5; + + // raise the gun as it is firing + if (!deathmatch->value) + { + ent->client->machinegun_shots++; + if (ent->client->machinegun_shots > 9) + ent->client->machinegun_shots = 9; + } + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); + AngleVectors (angles, forward, right, NULL); + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_MACHINEGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_Machinegun (edict_t *ent) +{ + static int pause_frames[] = {23, 45, 0}; + static int fire_frames[] = {4, 5, 0}; + + Weapon_Generic (ent, 3, 5, 45, 49, 0, 0, pause_frames, fire_frames, Machinegun_Fire); +} + +void Chaingun_Fire (edict_t *ent) +{ + int i; + int shots; + vec3_t start; + vec3_t forward, right, up; + float r, u; + vec3_t offset; + int damage; + int kick = 2; + + if (deathmatch->value) + damage = 6; + else + damage = 8; + + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0); + + if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe = 32; + ent->client->weapon_sound = 0; + return; + } + else if ((ent->client->ps.gunframe == 21) && (ent->client->buttons & BUTTON_ATTACK) + && ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 15; + } + else + { + ent->client->ps.gunframe++; + } + + if (ent->client->ps.gunframe == 22) + { + ent->client->weapon_sound = 0; + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0); + } + else + { + ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav"); + } + + if (ent->client->ps.gunframe <= 9) + shots = 1; + else if (ent->client->ps.gunframe <= 14) + { + if (ent->client->buttons & BUTTON_ATTACK) + shots = 2; + else + shots = 1; + } + else + shots = 3; + + if (ent->client->pers.inventory[ent->client->ammo_index] < shots) + shots = ent->client->pers.inventory[ent->client->ammo_index]; + + if (!shots) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + for (i=0 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + + for (i=0 ; iclient->v_angle, forward, right, up); + r = 7 + crandom()*4; + u = crandom()*4; + VectorSet(offset, 0, r, u + ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN); + } + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte ((MZ_CHAINGUN1 + shots - 1) | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= shots; +} + + +void Weapon_Chaingun (edict_t *ent) +{ + static int pause_frames[] = {38, 43, 51, 61, 0}; + static int fire_frames[] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0}; + + Weapon_Generic (ent, 4, 31, 61, 64, 0, 0, pause_frames, fire_frames, Chaingun_Fire); +} + + +/* +====================================================================== + +SHOTGUN / SUPERSHOTGUN + +====================================================================== +*/ + +void weapon_shotgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage = 4; + int kick = 8; + + if (ent->client->ps.gunframe == 9) + { + ent->client->ps.gunframe++; + return; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + if (deathmatch->value) + fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN); + else + fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_Shotgun (edict_t *ent) +{ + static int pause_frames[] = {22, 28, 34, 0}; + static int fire_frames[] = {8, 9, 0}; + + Weapon_Generic (ent, 7, 18, 36, 39, 0, 0, pause_frames, fire_frames, weapon_shotgun_fire); +} + + +void weapon_supershotgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + vec3_t v; + int damage = 6; + int kick = 12; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] - 5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN); + v[YAW] = ent->client->v_angle[YAW] + 5; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SSHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 2; +} + +void Weapon_SuperShotgun (edict_t *ent) +{ + static int pause_frames[] = {29, 42, 57, 0}; + static int fire_frames[] = {7, 0}; + + Weapon_Generic (ent, 6, 17, 57, 61, 0, 0, pause_frames, fire_frames, weapon_supershotgun_fire); +} + + + +/* +====================================================================== + +RAILGUN + +====================================================================== +*/ + +void weapon_railgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage; + int kick; + + if (deathmatch->value) + { // normal damage is too extreme in dm + damage = 100; + kick = 200; + } + else + { + damage = 150; + kick = 250; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -3, ent->client->kick_origin); + ent->client->kick_angles[0] = -3; + + VectorSet(offset, 0, 7, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_rail (ent, start, forward, damage, kick); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_RAILGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + + +void Weapon_Railgun (edict_t *ent) +{ + static int pause_frames[] = {56, 0}; + static int fire_frames[] = {4, 0}; + + Weapon_Generic (ent, 3, 18, 56, 61, 0, 0, pause_frames, fire_frames, weapon_railgun_fire); +} + + +/* +====================================================================== + +BFG10K + +====================================================================== +*/ + +void weapon_bfg_fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius = 1000; + + if (deathmatch->value) + damage = 200; + else + damage = 500; + + if (ent->client->ps.gunframe == 9) + { + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_BFG | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + return; + } + + // cells can go down during windup (from power armor hits), so + // check again and abort firing if we don't have enough now + if (ent->client->pers.inventory[ent->client->ammo_index] < 50) + { + ent->client->ps.gunframe++; + return; + } + + if (is_quad) + damage *= 4; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + + // make a big pitch kick with an inverse fall + ent->client->v_dmg_pitch = -40; + ent->client->v_dmg_roll = crandom()*8; + ent->client->v_dmg_time = level.time + DAMAGE_TIME; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_bfg (ent, start, forward, damage, 400, damage_radius); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 50; +} + +void Weapon_BFG (edict_t *ent) +{ + static int pause_frames[] = {39, 45, 50, 55, 0}; + static int fire_frames[] = {9, 17, 0}; + + Weapon_Generic (ent, 8, 32, 55, 58, 0, 0, pause_frames, fire_frames, weapon_bfg_fire); +} + + + + +#define MK23_SPREAD 140 +#define MP5_SPREAD 250 +#define M4_SPREAD 300 +#define SNIPER_SPREAD 425 +#define DUAL_SPREAD 300 + +int AdjustSpread( edict_t * ent, int spread ) +{ + + int running = 225; // minimum speed for running + int walking = 10; // minimum speed for walking + int laser = 0; + float factor[] = {.7, 1, 2, 6}; + int stage = 0; + + // 225 is running + // < 10 will be standing + float xyspeed = (ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]); + + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) // crouching + return( spread * .65); + + if ( (ent->client->pers.inventory[ITEM_INDEX(FindItem(LASER_NAME))]) + && (ent->client->curr_weap == MK23_NUM + || ent->client->curr_weap == MP5_NUM + || ent->client->curr_weap == M4_NUM ) ) + laser = 1; + + + // running + if ( xyspeed > running*running ) + stage = 3; + // walking + else if ( xyspeed >= walking*walking ) + stage = 2; + // standing + else + stage = 1; + + // laser advantage + if (laser) + { + if (stage == 1) + stage = 0; + else + stage = 1; + } + + return (int)(spread * factor[stage]); +} + + + +//====================================================================== + + +//====================================================================== +// mk23 derived from tutorial by GreyBear + +void Pistol_Fire(edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = 90; + int kick = 150; + vec3_t offset; + int spread = MK23_SPREAD; + int height; + + + + + if (ent->client->pers.firing_style == ACTION_FIRING_CLASSIC) + { + height = 8; + } + else + height = 0; + + //If the user isn't pressing the attack button, advance the frame and go away.... + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe++; + return; + } + ent->client->ps.gunframe++; + + //Oops! Out of ammo! + if (ent->client->mk23_rds < 1) + { + ent->client->ps.gunframe = 13; + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"),1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + //NoAmmoWeaponChange (ent); + return; + } + + + //Calculate the kick angles + for (i=1 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + ent->client->kick_origin[0] = crandom() * 0.35; + ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5; + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); + AngleVectors (angles, forward, right, NULL); + VectorSet(offset, 0, 8, ent->viewheight-height); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + if (!sv_shelloff->value) + { + vec3_t result; + Old_ProjectSource (ent->client, ent->s.origin, offset, forward, right, result); + EjectShell(ent, result, 0); + } + + spread = AdjustSpread( ent, spread ); + + if ( ent->client->resp.mk23_mode ) + spread *= .7; + +// safe_cprintf(ent, PRINT_HIGH, "Spread is %d\n", spread); + + if ((ent->client->mk23_rds == 1)) + { + //Hard coded for reload only. + ent->client->ps.gunframe=62; + ent->client->weaponstate = WEAPON_END_MAG; + fire_bullet (ent, start, forward, damage, kick, spread, spread,MOD_MK23); + ent->client->mk23_rds--; + ent->client->dual_rds--; + } + else + + { + //If no reload, fire normally. + fire_bullet (ent, start, forward, damage, kick, spread, spread,MOD_MK23); + ent->client->mk23_rds--; + ent->client->dual_rds--; + } + + // silencer suppresses both sound and muzzle flash + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SIL_NAME))] ) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("misc/silencer.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23fire.wav"), 1, ATTN_NORM, 0); + + + //Display the yellow muzzleflash light effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + //If not silenced, play a shot sound for everyone else + gi.WriteByte (MZ_MACHINEGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + PlayerNoise(ent, start, PNOISE_WEAPON); + } +} + +void Weapon_MK23 (edict_t *ent) +{ + //Idle animation entry points - These make the fidgeting look more random + static int pause_frames[] = {13, 22, 40}; + //The frames at which the weapon will fire + static int fire_frames[] = {10, 0}; + + //The call is made... + Weapon_Generic (ent, 9, 12, 37, 40, 61, 65, pause_frames, fire_frames, Pistol_Fire); +} + + +void MP5_Fire(edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = 55; + int kick = 90; + vec3_t offset; + int spread = MP5_SPREAD; + int height; + + if (ent->client->pers.firing_style == ACTION_FIRING_CLASSIC) + height = 8; + else + height = 0; + + + //If the user isn't pressing the attack button, advance the frame and go away.... + if (!(ent->client->buttons & BUTTON_ATTACK) && !(ent->client->burst) ) + { + ent->client->ps.gunframe++; + return; + } + + if ( ent->client->burst == 0 && !(ent->client->resp.mp5_mode) ) + { + if (ent->client->ps.gunframe == 12) + ent->client->ps.gunframe = 11; + else + ent->client->ps.gunframe = 12; + } + //burst mode + else if ( ent->client->burst == 0 && ent->client->resp.mp5_mode ) + { + ent->client->ps.gunframe = 72; + ent->client->weaponstate = WEAPON_BURSTING; + ent->client->burst = 1; + ent->client->fired = 1; + } + + else if ( ent->client->ps.gunframe >= 70 && ent->client->ps.gunframe <= 75 ) + { + ent->client->ps.gunframe++; + + } + + + //Oops! Out of ammo! + if (ent->client->mp5_rds < 1) + { + ent->client->ps.gunframe = 13; + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"),1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + //NoAmmoWeaponChange (ent); + return; + } + + + spread = AdjustSpread( ent, spread ); + if ( ent->client->burst ) + spread *= .7; + + + //Calculate the kick angles + for (i=1 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.25; + ent->client->kick_angles[i] = crandom() * 0.5; + } + ent->client->kick_origin[0] = crandom() * 0.35; + ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5; + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); + AngleVectors (angles, forward, right, NULL); + VectorSet(offset, 0, 8, ent->viewheight-height); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_bullet (ent, start, forward, damage, kick, spread, spread,MOD_MP5); + ent->client->mp5_rds--; + + if (!sv_shelloff->value) + { + vec3_t result; + Old_ProjectSource (ent->client, ent->s.origin, offset, forward, right, result); + EjectShell(ent, result, 0); + } + + + + // zucc vwep + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_attack8; + } + + // zucc vwep done + + // silencer suppresses both sound and muzzle flash + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SIL_NAME))] ) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("misc/silencer.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mp5fire1.wav"), 1, ATTN_NORM, 0); + + + //Display the yellow muzzleflash light effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + //If not silenced, play a shot sound for everyone else + gi.WriteByte (MZ_MACHINEGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + PlayerNoise(ent, start, PNOISE_WEAPON); + } +} + +void Weapon_MP5 (edict_t *ent) +{ + //Idle animation entry points - These make the fidgeting look more random + static int pause_frames[] = {13, 30, 47}; + //The frames at which the weapon will fire + static int fire_frames[] = {11, 12, 71, 72, 73, 0}; + + //The call is made... + Weapon_Generic (ent, 10, 12, 47, 51, 69, 77, pause_frames, fire_frames, MP5_Fire); +} + +void M4_Fire(edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = 90; + int kick = 90; + vec3_t offset; + int spread = M4_SPREAD; + int height; + + if (ent->client->pers.firing_style == ACTION_FIRING_CLASSIC) + height = 8; + else + height = 0; + + //If the user isn't pressing the attack button, advance the frame and go away.... + if (!(ent->client->buttons & BUTTON_ATTACK) && !(ent->client->burst) ) + { + ent->client->ps.gunframe++; + ent->client->machinegun_shots = 0; + return; + } + + if ( ent->client->burst == 0 && !(ent->client->resp.m4_mode) ) + { + if (ent->client->ps.gunframe == 12) + ent->client->ps.gunframe = 11; + else + ent->client->ps.gunframe = 12; + } + //burst mode + else if ( ent->client->burst == 0 && ent->client->resp.m4_mode ) + { + ent->client->ps.gunframe = 66; + ent->client->weaponstate = WEAPON_BURSTING; + ent->client->burst = 1; + ent->client->fired = 1; + } + + else if ( ent->client->ps.gunframe >= 64 && ent->client->ps.gunframe <= 69 ) + { + ent->client->ps.gunframe++; + + } + + + //Oops! Out of ammo! + if (ent->client->m4_rds < 1) + { + ent->client->ps.gunframe = 13; + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"),1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + //NoAmmoWeaponChange (ent); + return; + } + + // causes the ride up + if ( ent->client->weaponstate != WEAPON_BURSTING ) + { + ent->client->machinegun_shots++; + if (ent->client->machinegun_shots > 23) + ent->client->machinegun_shots = 23; + } + else // no kick when in burst mode + { + ent->client->machinegun_shots = 0; + } + + + spread = AdjustSpread( ent, spread ); + if ( ent->client->burst ) + spread *= .7; + + + // safe_cprintf(ent, PRINT_HIGH, "Spread is %d\n", spread); + + + //Calculate the kick angles + for (i=1 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.25; + ent->client->kick_angles[i] = crandom() * 0.5; + } + ent->client->kick_origin[0] = crandom() * 0.35; + ent->client->kick_angles[0] = ent->client->machinegun_shots * -.7; + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); + AngleVectors (angles, forward, right, NULL); + VectorSet(offset, 0, 8, ent->viewheight-height); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_bullet_sparks (ent, start, forward, damage, kick, spread, spread,MOD_M4); + ent->client->m4_rds--; + + if (!sv_shelloff->value) + { + vec3_t result; + Old_ProjectSource (ent->client, ent->s.origin, offset, forward, right, result); + EjectShell(ent, result, 0); + } + + + // zucc vwep + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_attack8; + } + + // zucc vwep done + + // silencer suppresses both sound and muzzle flash + /* if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SIL_NAME))] ) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("misc/silencer.wav"), 1, ATTN_NORM, 0); + } + else + */ + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m4a1fire.wav"), 1, ATTN_NORM, 0); + + + //Display the yellow muzzleflash light effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + //If not silenced, play a shot sound for everyone else + gi.WriteByte (MZ_MACHINEGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + PlayerNoise(ent, start, PNOISE_WEAPON); + } +} + +void Weapon_M4 (edict_t *ent) +{ + //Idle animation entry points - These make the fidgeting look more random + static int pause_frames[] = {13, 24, 39}; + //The frames at which the weapon will fire + static int fire_frames[] = {11, 12, 65, 66, 67, 0}; + + //The call is made... + Weapon_Generic (ent, 10, 12, 39, 44, 63, 71, pause_frames, fire_frames, M4_Fire); +} + + +void InitShotgunDamageReport(); +void ProduceShotgunDamageReport(edict_t *); + +void M3_Fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage = 17; //actionquake is 15 standard + int kick = 20; + int height; + + if (ent->client->pers.firing_style == ACTION_FIRING_CLASSIC) + height = 8; + else + height = 0; + + if (ent->client->ps.gunframe == 9) + { + ent->client->ps.gunframe++; + return; + } + + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-height); + + + + if ( ent->client->ps.gunframe == 14 ) + { + if (!sv_shelloff->value) + { + vec3_t result; + Old_ProjectSource (ent->client, ent->s.origin, offset, forward, right, result); + EjectShell(ent, result, 0); + } + ent->client->ps.gunframe++; + return; + } + + + + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + +/* if (is_quad) + { + damage *= 4; + kick *= 4; + }*/ + + + setFFState(ent); + InitShotgunDamageReport(); //FB 6/3/99 + if (deathmatch->value) + fire_shotgun (ent, start, forward, damage, kick, 800, 800, 12/*DEFAULT_DEATHMATCH_SHOTGUN_COUNT*/, MOD_M3); + else + fire_shotgun (ent, start, forward, damage, kick, 800, 800, 12/*DEFAULT_SHOTGUN_COUNT*/, MOD_M3); + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/shotgf1b.wav"), 1, ATTN_NORM, 0); + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ProduceShotgunDamageReport(ent); //FB 6/3/99 + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + //if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + // ent->client->pers.inventory[ent->client->ammo_index]--; + ent->client->shot_rds--; + +} + +void Weapon_M3 (edict_t *ent) +{ + static int pause_frames[] = {22, 28, 0}; + static int fire_frames[] = {8, 9, 14, 0}; + + Weapon_Generic (ent, 7, 15, 35, 41, 52, 60, pause_frames, fire_frames, M3_Fire); +} + + +void HC_Fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + vec3_t v; + int damage = 20; + int kick = 40; + int height; + + if (ent->client->pers.firing_style == ACTION_FIRING_CLASSIC) + height = 8; + else + height = 0; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-height); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + /*if (is_quad) + { + damage *= 4; + kick *= 4; + }*/ + + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] - 5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, NULL, NULL); + // default hspread is 1k and default vspread is 500 + setFFState(ent); + InitShotgunDamageReport(); //FB 6/3/99 + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD*4, DEFAULT_SHOTGUN_VSPREAD*4, 34/2, MOD_HC); + v[YAW] = ent->client->v_angle[YAW] + 5; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD*4, DEFAULT_SHOTGUN_VSPREAD*5, 34/2, MOD_HC); + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/cannon_fire.wav"), 1, ATTN_NORM, 0); + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SSHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + ProduceShotgunDamageReport(ent); //FB 6/3/99 + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + +// if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) +// ent->client->pers.inventory[ent->client->ammo_index] -= 2; + + ent->client->cannon_rds -= 2; +} + +void Weapon_HC (edict_t *ent) +{ + static int pause_frames[] = {29, 42, 57, 0}; + static int fire_frames[] = {7, 0}; + + Weapon_Generic (ent, 6, 17, 57, 61, 82, 83, pause_frames, fire_frames, HC_Fire); +} + +void Sniper_Fire(edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = 250; + int kick = 200; + vec3_t offset; + int spread = SNIPER_SPREAD; + + + + + if ( ent->client->ps.gunframe == 13 ) + { + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/ssgbolt.wav"), 1, ATTN_NORM, 0); + ent->client->ps.gunframe++; + + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 0, 0, ent->viewheight-0); + + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + EjectShell(ent, start, 0); + return; + } + + if ( ent->client->ps.gunframe == 21 ) + { + if ( ent->client->ps.fov != ent->client->desired_fov) + ent->client->ps.fov = ent->client->desired_fov; + // if they aren't at 90 then they must be zoomed, so remove their weapon from view + if ( ent->client->ps.fov != 90 ) + { + ent->client->ps.gunindex = 0; + ent->client->no_sniper_display = 0; + } + ent->client->ps.gunframe++; + + return; + } + + + //If the user isn't pressing the attack button, advance the frame and go away.... + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe++; + return; + } + ent->client->ps.gunframe++; + + //Oops! Out of ammo! + if (ent->client->sniper_rds < 1) + { + ent->client->ps.gunframe = 22; + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"),1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + //NoAmmoWeaponChange (ent); + return; + } + + spread = AdjustSpread( ent, spread ); + if ( ent->client->resp.sniper_mode == SNIPER_2X ) + spread = 0; + else if ( ent->client->resp.sniper_mode == SNIPER_4X ) + spread = 0; + else if ( ent->client->resp.sniper_mode == SNIPER_6X ) + spread = 0; + + + + //Calculate the kick angles + for (i=1 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0; + ent->client->kick_angles[i] = crandom() * 0; + } + ent->client->kick_origin[0] = crandom() * 0; + ent->client->kick_angles[0] = ent->client->machinegun_shots * 0; + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 0, 0, ent->viewheight-0); + + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + + { + //If no reload, fire normally. + fire_bullet_sniper (ent, start, forward, damage, kick, spread, spread,MOD_SNIPER); + ent->client->sniper_rds--; + ent->client->ps.fov = 90; // so we can watch the next round get chambered + ent->client->ps.gunindex = gi.modelindex( ent->client->pers.weapon->view_model ); + ent->client->no_sniper_display = 1; + } + + + + // silencer suppresses both sound and muzzle flash + if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SIL_NAME))] ) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("misc/silencer.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/ssgfire.wav"), 1, ATTN_NORM, 0); + //Display the yellow muzzleflash light effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + //If not silenced, play a shot sound for everyone else + gi.WriteByte (MZ_MACHINEGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + PlayerNoise(ent, start, PNOISE_WEAPON); + } +} + +void Weapon_Sniper (edict_t *ent) +{ + //Idle animation entry points - These make the fidgeting look more random + static int pause_frames[] = {21, 40}; + //The frames at which the weapon will fire + static int fire_frames[] = {9, 13, 21, 0}; + + //The call is made... + Weapon_Generic (ent, 8, 21, 41, 50, 81, 95, pause_frames, fire_frames, Sniper_Fire); +} + +void Dual_Fire(edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = 90; + int kick = 90; + vec3_t offset; + int spread = DUAL_SPREAD; + int height; + + if (ent->client->pers.firing_style == ACTION_FIRING_CLASSIC) + height = 8; + else + height = 0; + + + spread = AdjustSpread( ent, spread ); + + if (ent->client->dual_rds < 1) + { + ent->client->ps.gunframe = 68; + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"),1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + //NoAmmoWeaponChange (ent); + return; + } + + + + //If the user isn't pressing the attack button, advance the frame and go away.... + if ( ent->client->ps.gunframe == 8 ) + { + //gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23fire.wav"), 1, ATTN_NORM, 0); + ent->client->ps.gunframe++; + + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); + AngleVectors (angles, forward, right, NULL); + + VectorSet(offset, 0, 8, ent->viewheight-height); + P_ProjectSource(ent->client, ent->s.origin, offset, forward, right, start); + if ( ent->client->dual_rds > 1 ) + { + + fire_bullet (ent, start, forward, damage, kick, spread, spread,MOD_DUAL); + + if (!sv_shelloff->value) + { + vec3_t result; + Old_ProjectSource (ent->client, ent->s.origin, offset, forward, right, result); + EjectShell(ent, result, 2); + } + + if ( ent->client->dual_rds > ent->client->mk23_max + 1 ) + { + ent->client->dual_rds -= 2; + } + else if ( ent->client->dual_rds > ent->client->mk23_max ) // 13 rounds left + { + ent->client->dual_rds -= 2; + ent->client->mk23_rds--; + } + else + { + ent->client->dual_rds -= 2; + ent->client->mk23_rds -= 2; + } + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23fire.wav"), 1, ATTN_NORM, 0); + + if (ent->client->dual_rds == 0) + { + ent->client->ps.gunframe=68; + ent->client->weaponstate = WEAPON_END_MAG; + + } + + + } + else + { + ent->client->dual_rds = 0; + ent->client->mk23_rds = 0; + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/noammo.wav"),1, ATTN_NORM, 0); + //ent->pain_debounce_time = level.time + 1; + ent->client->ps.gunframe=68; + ent->client->weaponstate = WEAPON_END_MAG; + + } + return; + } + + if ( ent->client->ps.gunframe == 9 ) + { + ent->client->ps.gunframe += 2; + + return; + } + + + /*if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe++; + return; +}*/ + ent->client->ps.gunframe++; + + //Oops! Out of ammo! + if (ent->client->dual_rds < 1) + { + ent->client->ps.gunframe = 12; + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"),1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + //NoAmmoWeaponChange (ent); + return; + } + + + //Calculate the kick angles + for (i=1 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.25; + ent->client->kick_angles[i] = crandom() * 0.5; + } + ent->client->kick_origin[0] = crandom() * 0.35; + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); + AngleVectors (angles, forward, right, NULL); + // first set up for left firing + VectorSet(offset, 0, -20, ent->viewheight-height); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (!sv_shelloff->value) + { + vec3_t result; + Old_ProjectSource (ent->client, ent->s.origin, offset, forward, right, result); + EjectShell(ent, result, 1); + } + + + + //If no reload, fire normally. + fire_bullet (ent, start, forward, damage, kick, spread, spread,MOD_DUAL); + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23fire.wav"), 1, ATTN_NORM, 0); + + + + //gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23fire.wav"), 1, ATTN_NORM, 0); + //Display the yellow muzzleflash light effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + //If not silenced, play a shot sound for everyone else + gi.WriteByte (MZ_MACHINEGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + PlayerNoise(ent, start, PNOISE_WEAPON); + + +} + +void Weapon_Dual (edict_t *ent) +{ + //Idle animation entry points - These make the fidgeting look more random + static int pause_frames[] = {13, 22, 32}; + //The frames at which the weapon will fire + static int fire_frames[] = {7,8,9, 0}; + + //The call is made... + Weapon_Generic (ent, 6, 10, 32, 40, 65, 68, pause_frames, fire_frames, Dual_Fire); +} + + + +//zucc + +#define FRAME_PREPARETHROW_FIRST (FRAME_DEACTIVATE_LAST +1) +#define FRAME_IDLE2_FIRST (FRAME_PREPARETHROW_LAST +1) +#define FRAME_THROW_FIRST (FRAME_IDLE2_LAST +1) +#define FRAME_STOPTHROW_FIRST (FRAME_THROW_LAST +1) +#define FRAME_NEWKNIFE_FIRST (FRAME_STOPTHROW_LAST +1) + +void Weapon_Generic_Knife(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, + int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, + int FRAME_PREPARETHROW_LAST, int FRAME_IDLE2_LAST, + int FRAME_THROW_LAST, int FRAME_STOPTHROW_LAST, + int FRAME_NEWKNIFE_LAST, + int *pause_frames, int *fire_frames, int (*fire)(edict_t *ent)) +{ + + if(ent->s.modelindex != 255) // zucc vwep + return; // not on client, so VWep animations could do wacky things + +//FIREBLADE + if (ent->client->weaponstate == WEAPON_FIRING && + ((ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) || lights_camera_action)) + { + ent->client->weaponstate = WEAPON_READY; + } +//FIREBLADE + + if( ent->client->weaponstate == WEAPON_RELOADING) + { + if(ent->client->ps.gunframe < FRAME_NEWKNIFE_LAST) + { + ent->client->ps.gunframe++; + } + else + { + ent->client->ps.gunframe = FRAME_IDLE2_FIRST; + ent->client->weaponstate = WEAPON_READY; + } + } + + if (ent->client->weaponstate == WEAPON_DROPPING) + { + if (ent->client->resp.knife_mode == 1) + { + if (ent->client->ps.gunframe == FRAME_NEWKNIFE_FIRST) + { + ChangeWeapon (ent); + return; + } + else + { + // zucc going to have to do this a bit different because + // of the way I roll gunframes backwards for the thrownknife position + if((ent->client->ps.gunframe - FRAME_NEWKNIFE_FIRST) == 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + ent->client->ps.gunframe--; + } + + } + else + { + if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST) + { + ChangeWeapon (ent); + return; + } + else if((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + + + + ent->client->ps.gunframe++; + } + return; + } + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + + if ( ent->client->resp.knife_mode == 1 && ent->client->ps.gunframe == 0 ) + { +// safe_cprintf(ent, PRINT_HIGH, "NewKnifeFirst\n"); + ent->client->ps.gunframe = FRAME_PREPARETHROW_FIRST; + return; + } + if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST + || ent->client->ps.gunframe == FRAME_STOPTHROW_LAST ) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + if (ent->client->ps.gunframe == FRAME_PREPARETHROW_LAST) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE2_FIRST; + return; + } + + ent->client->resp.sniper_mode = 0; + // has to be here for dropping the sniper rifle, in the drop command didn't work... + ent->client->desired_fov = 90; + ent->client->ps.fov = 90; + + ent->client->ps.gunframe++; +// safe_cprintf(ent, PRINT_HIGH, "After increment frames = %d\n", ent->client->ps.gunframe); + return; + } + + + + // bandaging case + if ( (ent->client->bandaging) + && (ent->client->weaponstate != WEAPON_FIRING) + && (ent->client->weaponstate != WEAPON_BURSTING) + && (ent->client->weaponstate != WEAPON_BUSY) + && (ent->client->weaponstate != WEAPON_BANDAGING) ) + { + ent->client->weaponstate = WEAPON_BANDAGING; + ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + return; + } + + + + + + if ( ent->client->weaponstate == WEAPON_BUSY ) + { + + + if ( ent->client->bandaging == 1 ) + { + if ( !(ent->client->idle_weapon) ) + { + Bandage( ent ); + //ent->client->weaponstate = WEAPON_ACTIVATING; + // ent->client->ps.gunframe = 0; + } + else + (ent->client->idle_weapon)--; + return; + } + + // for after bandaging delay + if ( !(ent->client->idle_weapon) && ent->client->bandage_stopped ) + { + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->ps.gunframe = 0; + ent->client->bandage_stopped = 0; + return; + } + else if ( ent->client->bandage_stopped == 1 ) + { + (ent->client->idle_weapon)--; + return; + } + + + if (ent->client->ps.gunframe == 98 ) + { + ent->client->weaponstate = WEAPON_READY; + return; + } + else + ent->client->ps.gunframe++; + } + + if ( ent->client->weaponstate == WEAPON_BANDAGING ) + { + if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST ) + { + ent->client->weaponstate = WEAPON_BUSY; + ent->client->idle_weapon = BANDAGE_TIME; + return; + } + ent->client->ps.gunframe++; + return; + } + + + if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING) + && (ent->client->weaponstate != WEAPON_BUSY ) ) + { + + if ( ent->client->resp.knife_mode == 1 ) // throwing mode + { + ent->client->ps.gunframe = FRAME_NEWKNIFE_LAST; + // zucc more vwep stuff + if((FRAME_NEWKNIFE_LAST - FRAME_NEWKNIFE_FIRST) < 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + } + else // not in throwing mode + { + ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + // zucc more vwep stuff + if((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + } + ent->client->weaponstate = WEAPON_DROPPING; + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) +//FIREBLADE + && (ent->solid != SOLID_NOT || ent->deadflag == DEAD_DEAD) && !lights_camera_action) +//FIREBLADE + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + + + if ( ent->client->resp.knife_mode == 1 ) + { + ent->client->ps.gunframe = FRAME_THROW_FIRST; + } + else + { + ent->client->ps.gunframe = FRAME_FIRE_FIRST; + } + ent->client->weaponstate = WEAPON_FIRING; + + // start the animation + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1-1; + ent->client->anim_end = FRAME_attack8; + } + return; + } + if (ent->client->ps.gunframe == FRAME_IDLE_LAST) + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + if ( ent->client->ps.gunframe == FRAME_IDLE2_LAST) + { + ent->client->ps.gunframe = FRAME_IDLE2_FIRST; + return; + } +/* // zucc this causes you to not be able to throw a knife while it is flipping + if ( ent->client->ps.gunframe == 93 ) + { + ent->client->weaponstate = WEAPON_BUSY; + return; + } + */ + //safe_cprintf(ent, PRINT_HIGH, "Before a gunframe additon frames = %d\n", ent->client->ps.gunframe); + ent->client->ps.gunframe++; + return; + } + if (ent->client->weaponstate == WEAPON_FIRING ) + { + int n; + for (n = 0; fire_frames[n]; n++) + { + if (ent->client->ps.gunframe == fire_frames[n]) + { + if (ent->client->quad_framenum > level.framenum) gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + if ( fire(ent) ) + break; + else // we ran out of knives and switched weapons + return; + } + } + + + + if (!fire_frames[n]) + ent->client->ps.gunframe++; + + /*if ( ent->client->ps.gunframe == FRAME_STOPTHROW_FIRST + 1 ) + { + ent->client->ps.gunframe = FRAME_NEWKNIFE_FIRST; + ent->client->weaponstate = WEAPON_RELOADING; + }*/ + + if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1 || + ent->client->ps.gunframe == FRAME_IDLE2_FIRST+1 ) + ent->client->weaponstate = WEAPON_READY; + } + +} + + +int Knife_Fire (edict_t *ent) +{ + vec3_t start, v; + vec3_t forward, right; + + vec3_t offset; + int damage = 200; + int throwndamage = 250; + int kick = 50; // doesn't throw them back much.. + int knife_return = 3; + gitem_t* item; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + + + v[PITCH] = ent->client->v_angle[PITCH]; + v[ROLL] = ent->client->v_angle[ROLL]; + + // zucc updated these to not have offsets anymore for 1.51, + // this should fix the complaints about the knife not + // doing enough damage + + if ( ent->client->ps.gunframe == 6 ) + { + v[YAW] = ent->client->v_angle[YAW];// + 5; + AngleVectors (v, forward, NULL, NULL); + } + else if ( ent->client->ps.gunframe == 7 ) + { + v[YAW] = ent->client->v_angle[YAW];// + 2; + AngleVectors (v, forward, NULL, NULL); + } + else if ( ent->client->ps.gunframe == 8 ) + { + v[YAW] = ent->client->v_angle[YAW] ; + AngleVectors (v, forward, NULL, NULL); + } + else if ( ent->client->ps.gunframe == 9 ) + { + v[YAW] = ent->client->v_angle[YAW];// - 2; + AngleVectors (v, forward, NULL, NULL); + } + else if ( ent->client->ps.gunframe == 10 ) + { + v[YAW] = ent->client->v_angle[YAW]; //-5; + AngleVectors (v, forward, NULL, NULL); + } + + + if ( ent->client->resp.knife_mode == 0 ) + { + knife_return = knife_attack (ent, start, forward, damage, kick ); + + if (knife_return < ent->client->knife_sound) + ent->client->knife_sound = knife_return; + + if ( ent->client->ps.gunframe == 8) // last slicing frame, time for some sound + { + if ( ent->client->knife_sound == -2 ) + { + // we hit a player + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/stab.wav"), 1, ATTN_NORM, 0); + } + else if ( ent->client->knife_sound == -1 ) + { + // we hit a wall + + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/clank.wav"), 1, ATTN_NORM, 0); + } + else // we missed + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/swish.wav"), 1, ATTN_NORM, 0); + } + ent->client->knife_sound = 0; + } + } + + else + { + // do throwing stuff here + + damage = throwndamage; + + // throwing sound + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/throw.wav"), 1, ATTN_NORM, 0); + + + // below is exact code from action for how it sets the knife up + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 8, ent->viewheight-8); + VectorAdd (offset, vec3_origin, offset); + forward[2] += .17; + + // zucc using old style because the knife coming straight out looks + // pretty stupid + + + Knife_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + item = FindItem(KNIFE_NAME); + ent->client->pers.inventory[ITEM_INDEX(item)]--; + if ( ent->client->pers.inventory[ITEM_INDEX(item)] <= 0 ) + { + ent->client->newweapon = FindItem( MK23_NAME ); + ChangeWeapon( ent ); + // zucc was at 1250, dropping speed to 1200 + knife_throw (ent, start, forward, damage, 1200 ); + return 0; + } + else + { + ent->client->weaponstate = WEAPON_RELOADING; + ent->client->ps.gunframe = 111; + } + + + + /*AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + */ + // fire_rocket (ent, start, forward, damage, 650, 200, 200); + + knife_throw (ent, start, forward, damage, 1200 ); + + + // + } + + ent->client->ps.gunframe++; + if ( ent->client->ps.gunframe == 106 ) // over the throwing frame limit + ent->client->ps.gunframe = 64; // the idle animation frame for throwing + // not sure why the frames weren't ordered + // with throwing first, unwise. + PlayerNoise(ent, start, PNOISE_WEAPON); + return 1; + +} + +void Weapon_Knife (edict_t *ent) +{ + static int pause_frames[] = {22, 28, 0}; + static int fire_frames[] = {6, 7, 8, 9, 10, 105, 0}; + // I think we need a special version of the generic function for this... + Weapon_Generic_Knife (ent, 5, 12, 52, 59, 63, 102, 105, 110, 117, pause_frames, fire_frames, Knife_Fire); +} + + +// zucc - I thought about doing a gas grenade and even experimented with it some. It +// didn't work out that great though, so I just changed this code to be the standard +// action grenade. So the function name is just misleading... + +void gas_fire (edict_t *ent ) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = GRENADE_DAMRAD; + float timer; + int speed; + float radius; +/* int held = false;*/ + gitem_t* item; + + radius = damage + 40; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + timer = 2.0; + + if ( ent->client->resp.grenade_mode == 0 ) + speed = 400; + else if ( ent->client->resp.grenade_mode == 1 ) + speed = 720; + else + speed = 920; + + fire_grenade2 (ent, start, forward, GRENADE_DAMRAD, speed, timer, 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 ); + ChangeWeapon( ent ); + return; + } + else + { + ent->client->weaponstate = WEAPON_RELOADING; + ent->client->ps.gunframe = 0; + } + + +// ent->client->grenade_time = level.time + 1.0; + ent->client->ps.gunframe++; +} + + +#define GRENADE_ACTIVATE_LAST 3 +#define GRENADE_PULL_FIRST 72 +#define GRENADE_PULL_LAST 79 +//#define GRENADE_THROW_FIRST 4 +//#define GRENADE_THROW_LAST 9 // throw it on frame 8? +#define GRENADE_PINIDLE_FIRST 10 +#define GRENADE_PINIDLE_LAST 39 +// moved these up in the file (actually now in g_local.h) +//#define GRENADE_IDLE_FIRST 40 +//#define GRENADE_IDLE_LAST 69 + +// the 2 deactivation frames are a joke, they look like shit, probably best to roll +// the activation frames backwards. Aren't really enough of them either. + +void Weapon_Gas (edict_t *ent) +{ + gitem_t* item; + if(ent->s.modelindex != 255) // zucc vwep + return; // not on client, so VWep animations could do wacky things + + //FIREBLADE + if (ent->client->weaponstate == WEAPON_FIRING && + ((ent->solid == SOLID_NOT && ent->deadflag != DEAD_DEAD) || + lights_camera_action)) + { + ent->client->weaponstate = WEAPON_READY; + } + //FIREBLADE + + if( ent->client->weaponstate == WEAPON_RELOADING) + { + if(ent->client->ps.gunframe < GRENADE_ACTIVATE_LAST) + { + ent->client->ps.gunframe++; + } + else + { + ent->client->ps.gunframe = GRENADE_PINIDLE_FIRST; + ent->client->weaponstate = WEAPON_READY; + } + } + + if (ent->client->weaponstate == WEAPON_DROPPING) + { + if (ent->client->ps.gunframe == 0) + { + ChangeWeapon (ent); + return; + } + else + { + // zucc going to have to do this a bit different because + // of the way I roll gunframes backwards for the thrownknife position + if((ent->client->ps.gunframe) == 3) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + ent->client->ps.gunframe--; + return; + } + + } + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + + if (ent->client->ps.gunframe == GRENADE_ACTIVATE_LAST ) + { + ent->client->ps.gunframe = GRENADE_PINIDLE_FIRST; + ent->client->weaponstate = WEAPON_READY; + return; + } + + if ( ent->client->ps.gunframe == GRENADE_PULL_LAST ) + { + ent->client->ps.gunframe = GRENADE_IDLE_FIRST; + ent->client->weaponstate = WEAPON_READY; + safe_cprintf(ent, PRINT_HIGH, "Pin pulled, ready for %s range throw\n", ent->client->resp.grenade_mode == 0 ? "short" : (ent->client->resp.grenade_mode == 1 ? "medium" : "long" ) ); + return; + } + + if ( ent->client->ps.gunframe == 75 ) + { + gi.sound(ent, CHAN_WEAPON, gi.soundindex("misc/grenade.wav"), 1, ATTN_NORM, 0); + } + + ent->client->resp.sniper_mode = 0; + // has to be here for dropping the sniper rifle, in the drop command didn't work... + ent->client->desired_fov = 90; + ent->client->ps.fov = 90; + + ent->client->ps.gunframe++; + // safe_cprintf(ent, PRINT_HIGH, "After increment frames = %d\n", ent->client->ps.gunframe); + return; + } + + + // bandaging case + if ( (ent->client->bandaging) + && (ent->client->weaponstate != WEAPON_FIRING) + && (ent->client->weaponstate != WEAPON_BURSTING) + && (ent->client->weaponstate != WEAPON_BUSY) + && (ent->client->weaponstate != WEAPON_BANDAGING) ) + { + ent->client->weaponstate = WEAPON_BANDAGING; + ent->client->ps.gunframe = GRENADE_ACTIVATE_LAST; + return; + } + + + if ( ent->client->weaponstate == WEAPON_BUSY ) + { + + + if ( ent->client->bandaging == 1 ) + { + if ( !(ent->client->idle_weapon) ) + { + Bandage( ent ); + } + else + (ent->client->idle_weapon)--; + return; + } + // for after bandaging delay + if ( !(ent->client->idle_weapon) && ent->client->bandage_stopped ) + { + gitem_t *item; + item = FindItem(GRENADE_NAME); + if ( ent->client->pers.inventory[ITEM_INDEX(item)] <= 0 ) + { + ent->client->newweapon = FindItem( MK23_NAME ); + ent->client->bandage_stopped = 0; + ChangeWeapon( ent ); + return; + } + + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->ps.gunframe = 0; + ent->client->bandage_stopped = 0; + } + else if ( ent->client->bandage_stopped ) + (ent->client->idle_weapon)--; + + + } + + if ( ent->client->weaponstate == WEAPON_BANDAGING ) + { + if (ent->client->ps.gunframe == 0 ) + { + ent->client->weaponstate = WEAPON_BUSY; + ent->client->idle_weapon = BANDAGE_TIME; + return; + } + ent->client->ps.gunframe--; + return; + } + + + if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING) + && (ent->client->weaponstate != WEAPON_BUSY ) ) + { + + // 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 ) ) ) + { + 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 ); + ChangeWeapon( ent ); + return; + } + + } + + + ent->client->ps.gunframe = GRENADE_ACTIVATE_LAST; + // zucc more vwep stuff + if((GRENADE_ACTIVATE_LAST) < 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + + ent->client->weaponstate = WEAPON_DROPPING; + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) + //FIREBLADE + && (ent->solid != SOLID_NOT || ent->deadflag == DEAD_DEAD) && + !lights_camera_action) + //FIREBLADE + + { + + + if ( ent->client->ps.gunframe <= GRENADE_PINIDLE_LAST && + ent->client->ps.gunframe >= GRENADE_PINIDLE_FIRST ) + { + ent->client->ps.gunframe = GRENADE_PULL_FIRST; + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->latched_buttons &= ~BUTTON_ATTACK; + } + else + { + if (ent->client->ps.gunframe == GRENADE_IDLE_LAST) + { + ent->client->ps.gunframe = GRENADE_IDLE_FIRST; + return; + } + ent->client->ps.gunframe++; + return; + } + } + + if ( ent->client->ps.gunframe >= GRENADE_IDLE_FIRST && + ent->client->ps.gunframe <= GRENADE_IDLE_LAST ) + { + ent->client->ps.gunframe = GRENADE_THROW_FIRST; + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1-1; + ent->client->anim_end = FRAME_attack8; + } + ent->client->weaponstate = WEAPON_FIRING; + return; + + + + } + + if ( ent->client->ps.gunframe == GRENADE_PINIDLE_LAST) + { + ent->client->ps.gunframe = GRENADE_PINIDLE_FIRST; + return; + } + + ent->client->ps.gunframe++; + return; + } + if (ent->client->weaponstate == WEAPON_FIRING ) + { + + + if ( ent->client->ps.gunframe == 8 ) + { + gas_fire(ent); + return; + } + + + + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == GRENADE_IDLE_FIRST+1 || + ent->client->ps.gunframe == GRENADE_PINIDLE_FIRST+1 ) + ent->client->weaponstate = WEAPON_READY; + } +} diff --git a/q_shared.c b/q_shared.c new file mode 100644 index 0000000..b6f2c67 --- /dev/null +++ b/q_shared.c @@ -0,0 +1,1401 @@ +#include "q_shared.h" + +#define DEG2RAD( a ) ( a * M_PI ) / 180.0F + +vec3_t vec3_origin = {0,0,0}; + +//============================================================================ + +#ifdef _WIN32 +#pragma optimize( "", off ) +#endif + +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ) +{ + float m[3][3]; + float im[3][3]; + float zrot[3][3]; + float tmpmat[3][3]; + float rot[3][3]; + int i; + vec3_t vr, vup, vf; + + vf[0] = dir[0]; + vf[1] = dir[1]; + vf[2] = dir[2]; + + PerpendicularVector( vr, dir ); + CrossProduct( vr, vf, vup ); + + m[0][0] = vr[0]; + m[1][0] = vr[1]; + m[2][0] = vr[2]; + + m[0][1] = vup[0]; + m[1][1] = vup[1]; + m[2][1] = vup[2]; + + m[0][2] = vf[0]; + m[1][2] = vf[1]; + m[2][2] = vf[2]; + + memcpy( im, m, sizeof( im ) ); + + im[0][1] = m[1][0]; + im[0][2] = m[2][0]; + im[1][0] = m[0][1]; + im[1][2] = m[2][1]; + im[2][0] = m[0][2]; + im[2][1] = m[1][2]; + + memset( zrot, 0, sizeof( zrot ) ); + zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F; + + zrot[0][0] = cos( DEG2RAD( degrees ) ); + zrot[0][1] = sin( DEG2RAD( degrees ) ); + zrot[1][0] = -sin( DEG2RAD( degrees ) ); + zrot[1][1] = cos( DEG2RAD( degrees ) ); + + R_ConcatRotations( m, zrot, tmpmat ); + R_ConcatRotations( tmpmat, im, rot ); + + for ( i = 0; i < 3; i++ ) + { + dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2]; + } +} + +#ifdef _WIN32 +#pragma optimize( "", on ) +#endif + + + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} + + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) +{ + float d; + vec3_t n; + float inv_denom; + + inv_denom = 1.0F / DotProduct( normal, normal ); + + d = DotProduct( normal, p ) * inv_denom; + + n[0] = normal[0] * inv_denom; + n[1] = normal[1] * inv_denom; + n[2] = normal[2] * inv_denom; + + dst[0] = p[0] - d * n[0]; + dst[1] = p[1] - d * n[1]; + dst[2] = p[2] - d * n[2]; +} + +/* +** assumes "src" is normalized +*/ +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + int pos; + int i; + float minelem = 1.0F; + vec3_t tempvec; + + /* + ** find the smallest magnitude axially aligned vector + */ + for ( pos = 0, i = 0; i < 3; i++ ) + { + if ( fabs( src[i] ) < minelem ) + { + pos = i; + minelem = fabs( src[i] ); + } + } + tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; + tempvec[pos] = 1.0F; + + /* + ** project the point onto the plane defined by src + */ + ProjectPointOnPlane( dst, tempvec, src ); + + /* + ** normalize the result + */ + VectorNormalize( dst ); +} + + + +/* +================ +R_ConcatRotations +================ +*/ +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + + +/* +================ +R_ConcatTransforms +================ +*/ +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + + +//============================================================================ + + +float Q_fabs (float f) +{ +#if 0 + if (f >= 0) + return f; + return -f; +#else + int tmp = * ( int * ) &f; + tmp &= 0x7FFFFFFF; + return * ( float * ) &tmp; +#endif +} + +#if defined _M_IX86 && !defined C_ONLY +#pragma warning (disable:4035) +__declspec( naked ) long Q_ftol( float f ) +{ + static int tmp; + __asm fld dword ptr [esp+4] + __asm fistp tmp + __asm mov eax, tmp + __asm ret +} +#pragma warning (default:4035) +#endif + +/* +=============== +LerpAngle + +=============== +*/ +float LerpAngle (float a2, float a1, float frac) +{ + if (a1 - a2 > 180) + a1 -= 360; + if (a1 - a2 < -180) + a1 += 360; + return a2 + frac * (a1 - a2); +} + + +float anglemod(float a) +{ +#if 0 + if (a >= 0) + a -= 360*(int)(a/360); + else + a += 360*( 1 + (int)(-a/360) ); +#endif + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + + int i; + vec3_t corners[2]; + + +// this is the slow, general version +int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + int i; + float dist1, dist2; + int sides; + vec3_t corners[2]; + + for (i=0 ; i<3 ; i++) + { + if (p->normal[i] < 0) + { + corners[0][i] = emins[i]; + corners[1][i] = emaxs[i]; + } + else + { + corners[1][i] = emins[i]; + corners[0][i] = emaxs[i]; + } + } + dist1 = DotProduct (p->normal, corners[0]) - p->dist; + dist2 = DotProduct (p->normal, corners[1]) - p->dist; + sides = 0; + if (dist1 >= 0) + sides = 1; + if (dist2 < 0) + sides |= 2; + + return sides; +} + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 +================== +*/ +// __linux__ define check on next line from 3.20 source -FB +#if !id386 || defined __linux__ +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + float dist1, dist2; + int sides; + +// fast axial cases + if (p->type < 3) + { + if (p->dist <= emins[p->type]) + return 1; + if (p->dist >= emaxs[p->type]) + return 2; + return 3; + } + +// general case + switch (p->signbits) + { + case 0: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 1: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 2: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 3: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 4: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 5: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 6: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + case 7: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + default: + dist1 = dist2 = 0; // shut up compiler + assert( 0 ); + break; + } + + sides = 0; + if (dist1 >= p->dist) + sides = 1; + if (dist2 < p->dist) + sides |= 2; + + assert( sides != 0 ); + + return sides; +} +#else +#pragma warning( disable: 4035 ) + +__declspec( naked ) int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + static int bops_initialized; + static int Ljmptab[8]; + + __asm { + + push ebx + + cmp bops_initialized, 1 + je initialized + mov bops_initialized, 1 + + mov Ljmptab[0*4], offset Lcase0 + mov Ljmptab[1*4], offset Lcase1 + mov Ljmptab[2*4], offset Lcase2 + mov Ljmptab[3*4], offset Lcase3 + mov Ljmptab[4*4], offset Lcase4 + mov Ljmptab[5*4], offset Lcase5 + mov Ljmptab[6*4], offset Lcase6 + mov Ljmptab[7*4], offset Lcase7 + +initialized: + + mov edx,ds:dword ptr[4+12+esp] + mov ecx,ds:dword ptr[4+4+esp] + xor eax,eax + mov ebx,ds:dword ptr[4+8+esp] + mov al,ds:byte ptr[17+edx] + cmp al,8 + jge Lerror + fld ds:dword ptr[0+edx] + fld st(0) + jmp dword ptr[Ljmptab+eax*4] +Lcase0: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase1: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase2: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase3: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase4: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase5: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase6: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase7: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) +LSetSides: + faddp st(2),st(0) + fcomp ds:dword ptr[12+edx] + xor ecx,ecx + fnstsw ax + fcomp ds:dword ptr[12+edx] + and ah,1 + xor ah,1 + add cl,ah + fnstsw ax + and ah,1 + add ah,ah + add cl,ah + pop ebx + mov eax,ecx + ret +Lerror: + int 3 + } +} +#pragma warning( default: 4035 ) +#endif + +void ClearBounds (vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + + +int VectorCompare (vec3_t v1, vec3_t v2) +{ + if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) + return 0; + + return 1; +} + + +vec_t VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +vec_t VectorNormalize2 (vec3_t v, vec3_t out) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + out[0] = v[0]*ilength; + out[1] = v[1]*ilength; + out[2] = v[2]*ilength; + } + + return length; + +} + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +double sqrt(double x); + +vec_t VectorLength(vec3_t v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (vec3_t in, vec_t scale, vec3_t out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + + + +//==================================================================================== + +/* +============ +COM_SkipPath +============ +*/ +char *COM_SkipPath (char *pathname) +{ + char *last; + + last = pathname; + while (*pathname) + { + if (*pathname=='/') + last = pathname+1; + pathname++; + } + return last; +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension (char *in, char *out) +{ + while (*in && *in != '.') + *out++ = *in++; + *out = 0; +} + +/* +============ +COM_FileExtension +============ +*/ +char *COM_FileExtension (char *in) +{ + static char exten[8]; + int i; + + while (*in && *in != '.') + in++; + if (!*in) + return ""; + in++; + for (i=0 ; i<7 && *in ; i++,in++) + exten[i] = *in; + exten[i] = 0; + return exten; +} + +/* +============ +COM_FileBase +============ +*/ +void COM_FileBase (char *in, char *out) +{ + char *s, *s2; + + s = in + strlen(in) - 1; + + while (s != in && *s != '.') + s--; + + for (s2 = s ; s2 != in && *s2 != '/' ; s2--) + ; + + if (s-s2 < 2) + out[0] = 0; + else + { + s--; + strncpy (out,s2+1, s-s2); + out[s-s2] = 0; + } +} + +/* +============ +COM_FilePath + +Returns the path up to, but not including the last / +============ +*/ +void COM_FilePath (char *in, char *out) +{ + char *s; + + s = in + strlen(in) - 1; + + while (s != in && *s != '/') + s--; + + strncpy (out,in, s-in); + out[s-in] = 0; +} + + +/* +================== +COM_DefaultExtension +================== +*/ +void COM_DefaultExtension (char *path, char *extension) +{ + char *src; +// +// if path doesn't have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen(path) - 1; + + while (*src != '/' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strcat (path, extension); +} + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +qboolean bigendien; + +// can't just use function pointers, or dll linkage can +// mess up when qcommon is included in multiple places +short (*_BigShort) (short l); +short (*_LittleShort) (short l); +int (*_BigLong) (int l); +int (*_LittleLong) (int l); +float (*_BigFloat) (float l); +float (*_LittleFloat) (float l); + +short BigShort(short l){return _BigShort(l);} +short LittleShort(short l) {return _LittleShort(l);} +int BigLong (int l) {return _BigLong(l);} +int LittleLong (int l) {return _LittleLong(l);} +float BigFloat (float l) {return _BigFloat(l);} +float LittleFloat (float l) {return _LittleFloat(l);} + +short ShortSwap (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short ShortNoSwap (short l) +{ + return l; +} + +int LongSwap (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LongNoSwap (int l) +{ + return l; +} + +float FloatSwap (float f) +{ + union + { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +float FloatNoSwap (float f) +{ + return f; +} + +/* +================ +Swap_Init +================ +*/ +void Swap_Init (void) +{ + byte swaptest[2] = {1,0}; + +// set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1) + { + bigendien = false; + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } + else + { + bigendien = true; + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } + +} + + + +/* +============ +va + +does a varargs printf into a temp buffer, so I don't need to have +varargs versions of all text functions. +FIXME: make this buffer size safe someday +============ +*/ +char *va(char *format, ...) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + + +char com_token[MAX_TOKEN_CHARS]; + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char **data_p) +{ + int c; + int len; + char *data; + + data = *data_p; + len = 0; + com_token[0] = 0; + + if (!data) + { + *data_p = NULL; + return ""; + } + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + { + *data_p = NULL; + return ""; + } + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = data; + return com_token; + } + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + } + } + +// parse a regular word + do + { + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + data++; + c = *data; + } while (c>32); + + if (len == MAX_TOKEN_CHARS) + { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = data; + return com_token; +} + + +/* +=============== +Com_PageInMemory + +=============== +*/ +int paged_total; + +void Com_PageInMemory (byte *buffer, int size) +{ + int i; + + for (i=size-1 ; i>0 ; i-=4096) + paged_total += buffer[i]; +} + + + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +// FIXME: replace all Q_stricmp with Q_strcasecmp +int Q_stricmp (char *s1, char *s2) +{ +#if defined(WIN32) + return _stricmp (s1, s2); +#else + return strcasecmp (s1, s2); +#endif +} + + +int Q_strncasecmp (char *s1, char *s2, int n) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return -1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +int Q_strcasecmp (char *s1, char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + + + +void Com_sprintf (char *dest, int size, char *fmt, ...) +{ + int len; + va_list argptr; + char bigbuffer[0x10000]; + + va_start (argptr,fmt); + len = vsprintf (bigbuffer,fmt,argptr); + va_end (argptr); + if (len >= size) + Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size); + strncpy (dest, bigbuffer, size-1); +} + +/* +===================================================================== + + INFO STRINGS + +===================================================================== +*/ + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +=============== +*/ +char *Info_ValueForKey (char *s, char *key) +{ + char pkey[512]; + static char value[2][512]; // use two buffers so compares + // work without stomping on each other + static int valueindex; + char *o; + + valueindex ^= 1; + if (*s == '\\') + s++; + while (1) + { + o = pkey; + while (*s != '\\') + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + s++; + + o = value[valueindex]; + + while (*s != '\\' && *s) + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + return value[valueindex]; + + if (!*s) + return ""; + s++; + } +} + +void Info_RemoveKey (char *s, char *key) +{ + char *start; + char pkey[512]; + char value[512]; + char *o; + + if (strstr (key, "\\")) + { +// Com_Printf ("Can't use a key with a \\\n"); + return; + } + + while (1) + { + start = s; + if (*s == '\\') + s++; + o = pkey; + while (*s != '\\') + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + { + strcpy (start, s); // remove this part + return; + } + + if (!*s) + return; + } + +} + + +/* +================== +Info_Validate + +Some characters are illegal in info strings because they +can mess up the server's parsing +================== +*/ +qboolean Info_Validate (char *s) +{ + if (strstr (s, "\"")) + return false; + if (strstr (s, ";")) + return false; + return true; +} + +void Info_SetValueForKey (char *s, char *key, char *value) +{ + char newi[MAX_INFO_STRING], *v; + int c; + int maxsize = MAX_INFO_STRING; + + if (strstr (key, "\\") || strstr (value, "\\") ) + { + Com_Printf ("Can't use keys or values with a \\\n"); + return; + } + + if (strstr (key, ";") ) + { + Com_Printf ("Can't use keys or values with a semicolon\n"); + return; + } + + if (strstr (key, "\"") || strstr (value, "\"") ) + { + Com_Printf ("Can't use keys or values with a \"\n"); + return; + } + + if (strlen(key) > MAX_INFO_KEY-1 || strlen(value) > MAX_INFO_KEY-1) + { + Com_Printf ("Keys and values must be < 64 characters.\n"); + return; + } + Info_RemoveKey (s, key); + if (!value || !strlen(value)) + return; + + Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); + + if (strlen(newi) + strlen(s) > maxsize) + { + Com_Printf ("Info string length exceeded\n"); + return; + } + + // only copy ascii values + s += strlen(s); + v = newi; + while (*v) + { + c = *v++; + c &= 127; // strip high bits + if (c >= 32 && c < 127) + *s++ = c; + } + *s = 0; +} + +//==================================================================== + + diff --git a/q_shared.h b/q_shared.h new file mode 100644 index 0000000..fa1f49f --- /dev/null +++ b/q_shared.h @@ -0,0 +1,1281 @@ + +// q_shared.h -- included first by ALL program modules + +#ifdef _WIN32 +// unknown pragmas are SUPPOSED to be ignored, but.... +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncation from const double to float + +#endif + +#include +#include +#include +#include +#include +#include +#include +//FIREBLADE +#include +//FIREBLADE + +// New define for this came from 3.20 -FB +#if (defined _M_IX86 || defined __i386__) && !defined C_ONLY && !defined __sun__ +#define id386 1 +#else +#define id386 0 +#endif + +#if defined _M_ALPHA && !defined C_ONLY +#define idaxp 1 +#else +#define idaxp 0 +#endif + +typedef unsigned char byte; +#if ( defined __cplusplus ) +typedef int qboolean; +#else +typedef enum {false, true} qboolean; +#endif + + + +#ifndef NULL +#define NULL ((void *)0) +#endif + + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 80 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 128 // max length of an individual token + +#define MAX_QPATH 64 // max length of a quake game pathname +#define MAX_OSPATH 128 // max length of a filesystem pathname + +// +// per-level limits +// +#define MAX_CLIENTS 256 // absolute limit +#define MAX_EDICTS 1024 // must change protocol to increase more +#define MAX_LIGHTSTYLES 256 +#define MAX_MODELS 256 // these are sent over the net as bytes +#define MAX_SOUNDS 256 // so they cannot be blindly increased +#define MAX_IMAGES 256 +#define MAX_ITEMS 256 +#define MAX_GENERAL (MAX_CLIENTS*2) // general config strings (from 3.20 -FB) + + +// game print flags +#define PRINT_LOW 0 // pickup messages +#define PRINT_MEDIUM 1 // death messages +#define PRINT_HIGH 2 // critical messages +//#define PRINT_HIGH 0 // critical messages +#define PRINT_CHAT 3 // chat messages + + + +#define ERR_FATAL 0 // exit the entire game with a popup window +#define ERR_DROP 1 // print to console and disconnect from game +#define ERR_DISCONNECT 2 // don't kill server + +#define PRINT_ALL 0 +#define PRINT_DEVELOPER 1 // only print when "developer 1" +#define PRINT_ALERT 2 + + +// destination class for gi.multicast() +typedef enum +{ +MULTICAST_ALL, +MULTICAST_PHS, +MULTICAST_PVS, +MULTICAST_ALL_R, +MULTICAST_PHS_R, +MULTICAST_PVS_R +} multicast_t; + + +/* +============================================================== + +MATHLIB + +============================================================== +*/ + +typedef float vec_t; +typedef vec_t vec3_t[3]; +typedef vec_t vec5_t[5]; + +typedef int fixed4_t; +typedef int fixed8_t; +typedef int fixed16_t; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +struct cplane_s; + +extern vec3_t vec3_origin; + +#define nanmask (255<<23) + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +// microsoft's fabs seems to be ungodly slow... +//float Q_fabs (float f); +//#define fabs(f) Q_fabs(f) +// next define line was changed by 3.20 -FB +#if !defined C_ONLY && !defined __linux__ && !defined __sgi +extern long Q_ftol( float f ); +#else +#define Q_ftol( f ) ( long ) (f) +#endif + +#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +#define VectorSubtract(a,b,c) (c[0]=a[0]-b[0],c[1]=a[1]-b[1],c[2]=a[2]-b[2]) +#define VectorAdd(a,b,c) (c[0]=a[0]+b[0],c[1]=a[1]+b[1],c[2]=a[2]+b[2]) +#define VectorCopy(a,b) (b[0]=a[0],b[1]=a[1],b[2]=a[2]) +#define VectorClear(a) (a[0]=a[1]=a[2]=0) +#define VectorNegate(a,b) (b[0]=-a[0],b[1]=-a[1],b[2]=-a[2]) +#define VectorSet(v, x, y, z) (v[0]=(x), v[1]=(y), v[2]=(z)) + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc); + +// just in case you do't want to use the macros +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); +int VectorCompare (vec3_t v1, vec3_t v2); +vec_t VectorLength (vec3_t v); +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (vec3_t v); // returns vector length +vec_t VectorNormalize2 (vec3_t v, vec3_t out); +void VectorInverse (vec3_t v); +void VectorScale (vec3_t in, vec_t scale, vec3_t out); +int Q_log2(int val); + +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane); +float anglemod(float a); +float LerpAngle (float a1, float a2, float frac); + +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ + (((p)->type < 3)? \ + ( \ + ((p)->dist <= (emins)[(p)->type])? \ + 1 \ + : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type])?\ + 2 \ + : \ + 3 \ + ) \ + ) \ + : \ + BoxOnPlaneSide( (emins), (emaxs), (p))) + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ); +void PerpendicularVector( vec3_t dst, const vec3_t src ); +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ); + + +//============================================= + +char *COM_SkipPath (char *pathname); +void COM_StripExtension (char *in, char *out); +void COM_FileBase (char *in, char *out); +void COM_FilePath (char *in, char *out); +void COM_DefaultExtension (char *path, char *extension); + +char *COM_Parse (char **data_p); +// data is an in/out parm, returns a parsed out token + +void Com_sprintf (char *dest, int size, char *fmt, ...); + +void Com_PageInMemory (byte *buffer, int size); + +//============================================= + +// portable case insensitive compare +int Q_stricmp (char *s1, char *s2); +int Q_strcasecmp (char *s1, char *s2); +int Q_strncasecmp (char *s1, char *s2, int n); + +//============================================= + +short BigShort(short l); +short LittleShort(short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); + +void Swap_Init (void); +char *va(char *format, ...); + +//============================================= + +// +// key / value info strings +// +#define MAX_INFO_KEY 64 +#define MAX_INFO_VALUE 64 +#define MAX_INFO_STRING 512 + +char *Info_ValueForKey (char *s, char *key); +void Info_RemoveKey (char *s, char *key); +void Info_SetValueForKey (char *s, char *key, char *value); +qboolean Info_Validate (char *s); + +/* +============================================================== + +SYSTEM SPECIFIC + +============================================================== +*/ + +extern int curtime; // time returned by last Sys_Milliseconds + +int Sys_Milliseconds (void); +void Sys_Mkdir (char *path); + +// large block stack allocation routines +void *Hunk_Begin (int maxsize); +void *Hunk_Alloc (int size); +void Hunk_Free (void *buf); +int Hunk_End (void); + +// directory searching +#define SFF_ARCH 0x01 +#define SFF_HIDDEN 0x02 +#define SFF_RDONLY 0x04 +#define SFF_SUBDIR 0x08 +#define SFF_SYSTEM 0x10 + +/* +** pass in an attribute mask of things you wish to REJECT +*/ +char *Sys_FindFirst (char *path, unsigned musthave, unsigned canthave ); +char *Sys_FindNext ( unsigned musthave, unsigned canthave ); +void Sys_FindClose (void); + + +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...); +void Com_Printf (char *msg, ...); + + +/* +========================================================== + +CVARS (console variables) + +========================================================== +*/ + +#ifndef CVAR +#define CVAR + +#define CVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc +#define CVAR_USERINFO 2 // added to userinfo when changed +#define CVAR_SERVERINFO 4 // added to serverinfo when changed +#define CVAR_NOSET 8 // don't allow change from console at all, + // but can be set from the command line +#define CVAR_LATCH 16 // save changes until server restart + +// nothing outside the Cvar_*() functions should modify these fields! +typedef struct cvar_s +{ + char *name; + char *string; + char *latched_string; // for CVAR_LATCH vars + int flags; + qboolean modified; // set each time the cvar is changed + float value; + struct cvar_s *next; +} cvar_t; + +#endif // CVAR + +/* +============================================================== + +COLLISION DETECTION + +============================================================== +*/ + +// lower bits are stronger, and will eat weaker brushes completely +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_WINDOW 2 // translucent, but not watery +#define CONTENTS_AUX 4 +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_MIST 64 +#define LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x40000 +#define CONTENTS_CURRENT_90 0x80000 +#define CONTENTS_CURRENT_180 0x100000 +#define CONTENTS_CURRENT_270 0x200000 +#define CONTENTS_CURRENT_UP 0x400000 +#define CONTENTS_CURRENT_DOWN 0x800000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +#define CONTENTS_TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 + + + +#define SURF_LIGHT 0x1 // value will hold the light strength + +#define SURF_SLICK 0x2 // effects game physics + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp +#define SURF_TRANS33 0x10 +#define SURF_TRANS66 0x20 +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + + + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID|CONTENTS_WINDOW) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW) +#define MASK_MONSTERSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) +//#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEADMONSTER) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_WINDOW) +#define MASK_CURRENT (CONTENTS_CURRENT_0|CONTENTS_CURRENT_90|CONTENTS_CURRENT_180|CONTENTS_CURRENT_270|CONTENTS_CURRENT_UP|CONTENTS_CURRENT_DOWN) + + +// gi.BoxEdicts() can return a list of either solid or trigger entities +// FIXME: eliminate AREA_ distinction? +#define AREA_SOLID 1 +#define AREA_TRIGGERS 2 + + +// plane_t structure +// !!! if this is changed, it must be changed in asm code too !!! +typedef struct cplane_s +{ + vec3_t normal; + float dist; + byte type; // for fast side tests + byte signbits; // signx + (signy<<1) + (signz<<1) + byte pad[2]; +} cplane_t; + +// structure offset for asm code +#define CPLANE_NORMAL_X 0 +#define CPLANE_NORMAL_Y 4 +#define CPLANE_NORMAL_Z 8 +#define CPLANE_DIST 12 +#define CPLANE_TYPE 16 +#define CPLANE_SIGNBITS 17 +#define CPLANE_PAD0 18 +#define CPLANE_PAD1 19 + +typedef struct cmodel_s +{ + vec3_t mins, maxs; + vec3_t origin; // for sounds or lights + int headnode; +} cmodel_t; + +typedef struct csurface_s +{ + char name[16]; + int flags; + int value; +} csurface_t; + +// FROM 3.20 -FB +typedef struct mapsurface_s // used internally due to name len probs //ZOID +{ + csurface_t c; + char rname[32]; +} mapsurface_t; +// ^^^ + +// a trace is returned when a box is swept through the world +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + csurface_t *surface; // surface hit + int contents; // contents on other side of surface hit + struct edict_s *ent; // not set by CM_*() functions +} trace_t; + + + +// pmove_state_t is the information necessary for client side movement +// prediction +typedef enum +{ + // can accelerate and turn + PM_NORMAL, + PM_SPECTATOR, + // no acceleration or turning + PM_DEAD, + PM_GIB, // different bounding box + PM_FREEZE +} pmtype_t; + +// pmove->pm_flags +#define PMF_DUCKED 1 +#define PMF_JUMP_HELD 2 +#define PMF_ON_GROUND 4 +#define PMF_TIME_WATERJUMP 8 // pm_time is waterjump +#define PMF_TIME_LAND 16 // pm_time is time before rejump +#define PMF_TIME_TELEPORT 32 // pm_time is non-moving time +#define PMF_NO_PREDICTION 64 // temporarily disables prediction (used for grappling hook) + +// this structure needs to be communicated bit-accurate +// from the server to the client to guarantee that +// prediction stays in sync, so no floats are used. +// if any part of the game code modifies this struct, it +// will result in a prediction error of some degree. +typedef struct +{ + pmtype_t pm_type; + + short origin[3]; // 12.3 + short velocity[3]; // 12.3 + byte pm_flags; // ducked, jump_held, etc + byte pm_time; // each unit = 8 ms + short gravity; + short delta_angles[3]; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters +} pmove_state_t; + + +// +// button bits +// +#define BUTTON_ATTACK 1 +#define BUTTON_USE 2 +#define BUTTON_ANY 128 // any key whatsoever + + +// usercmd_t is sent to the server each client frame +typedef struct usercmd_s +{ + byte msec; + byte buttons; + short angles[3]; + short forwardmove, sidemove, upmove; + byte impulse; // remove? + byte lightlevel; // light level the player is standing on +} usercmd_t; + + +#define MAXTOUCH 32 +typedef struct +{ + // state (in / out) + pmove_state_t s; + + // command (in) + usercmd_t cmd; + qboolean snapinitial; // if s has been changed outside pmove + + // results (out) + int numtouch; + struct edict_s *touchents[MAXTOUCH]; + + vec3_t viewangles; // clamped + float viewheight; + + vec3_t mins, maxs; // bounding box size + + struct edict_s *groundentity; + int watertype; + int waterlevel; + + // callbacks to test the world + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + int (*pointcontents) (vec3_t point); +} pmove_t; + + +// entity_state_t->effects +// Effects are things handled on the client side (lights, particles, frame animations) +// that happen constantly on the given entity. +// An entity that has effects will be sent to the client +// even if it has a zero index model. +#define EF_ROTATE 0x00000001 // rotate (bonus items) +#define EF_GIB 0x00000002 // leave a trail +#define EF_BLASTER 0x00000008 // redlight + trail +#define EF_ROCKET 0x00000010 // redlight + trail +#define EF_GRENADE 0x00000020 +#define EF_HYPERBLASTER 0x00000040 +#define EF_BFG 0x00000080 +#define EF_COLOR_SHELL 0x00000100 +#define EF_POWERSCREEN 0x00000200 +#define EF_ANIM01 0x00000400 // automatically cycle between frames 0 and 1 at 2 hz +#define EF_ANIM23 0x00000800 // automatically cycle between frames 2 and 3 at 2 hz +#define EF_ANIM_ALL 0x00001000 // automatically cycle through all frames at 2hz +#define EF_ANIM_ALLFAST 0x00002000 // automatically cycle through all frames at 10hz +#define EF_FLIES 0x00004000 +#define EF_QUAD 0x00008000 +#define EF_PENT 0x00010000 +#define EF_TELEPORTER 0x00020000 // particle fountain +#define EF_FLAG1 0x00040000 +#define EF_FLAG2 0x00080000 + +// FOLLOWING FROM 3.20 CODE -FB +// RAFAEL +#define EF_IONRIPPER 0x00100000 +#define EF_GREENGIB 0x00200000 +#define EF_BLUEHYPERBLASTER 0x00400000 +#define EF_SPINNINGLIGHTS 0x00800000 +#define EF_PLASMA 0x01000000 +#define EF_TRAP 0x02000000 + +//ROGUE +#define EF_TRACKER 0x04000000 +#define EF_DOUBLE 0x08000000 +#define EF_SPHERETRANS 0x10000000 +#define EF_TAGTRAIL 0x20000000 +#define EF_HALF_DAMAGE 0x40000000 +#define EF_TRACKERTRAIL 0x80000000 +//ROGUE +// ^^^ + + +// RAFAEL +#define EF_IONRIPPER 0x00100000 +#define EF_GREENGIB 0x00200000 +#define EF_BLUEHYPERBLASTER 0x00400000 +#define EF_SPINNINGLIGHTS 0x00800000 +#define EF_PLASMA 0x01000000 +#define EF_TRAP 0x02000000 + +//ROGUE +#define EF_TRACKER 0x04000000 +#define EF_DOUBLE 0x08000000 +#define EF_SPHERETRANS 0x10000000 +#define EF_TAGTRAIL 0x20000000 +#define EF_HALF_DAMAGE 0x40000000 +#define EF_TRACKERTRAIL 0x80000000 +//ROGUE + + + + + + +// zucc some I got from quake devels + + +#define EF_BLUE 0x00400000 //a blue light + +#define EF_ROTATEREDSPOT 0x00800000 //fast rotate with a red spot of light at the front + +#define EF_TRANSLIGHT 0x01000000 //transparant with some lighting + +#define EF_PFOUNT 0x02000000 // particle foundtain + +#define EF_DYNDARK 0x04000000 //DYNAMIC darkness the one i was looking for! + +#define EF_YELLOWSHELL 0x08000000 //a yellow shell similar to those found with EF_COLOR_SHELL IIRC + +#define EF_TRANS 0x10000000 //translucency + +#define EF_YELLOWDOT 0x20000000 //yellow lighting with yellow dots under the model + +#define EF_WHITESHELL 0x40000000 //a yellow shell around the model (like EF_YELLOWSHELL) + + + + + +#define EF_FLIES2 0x80000000 //Flies go a buzzin' and the sky grows dim + +#define EF_EDARK 0x84000000 // Extreme Darkness, you won't believe! + +#define EF_BLUE_CRUST 0x08208000 // An odd blue "crust" around the model + +#define EF_QBF 0x90408000 // The best of three worlds, blue shell like a quad, dark, and covered with flies + +#define EF_REDC 0x30050001 // This one is nice, a red light eminating of a red Quad crust + +#define EF_GREENRC 0x35152841 // It's Christmas time! Red shell and geen light! + +#define EF_REDG 0x22010107 // RedCrust with gib effect trail + +#define EF_PUP 0x86080100 // 'ere's an odd one, a straight, upwards line of particles sprays above the modle + +#define EF_FLYG 0x60507800 // A few flies with a light, greenish yello crust + +#define EF_YELLOW_CRUST 0x10300070 // Yellow crust with a of smoke & yellow particles + +#define EF_BACKRED 0x90900900 // The Usual is black fly maham, but with a red light peeking through. + + + + + +// entity_state_t->renderfx flags +#define RF_MINLIGHT 1 // allways have some light (viewmodel) +#define RF_VIEWERMODEL 2 // don't draw through eyes, only mirrors +#define RF_WEAPONMODEL 4 // only draw through eyes +#define RF_FULLBRIGHT 8 // allways draw full intensity +#define RF_DEPTHHACK 16 // for view weapon Z crunching +#define RF_TRANSLUCENT 32 +#define RF_FRAMELERP 64 +#define RF_BEAM 128 +#define RF_CUSTOMSKIN 256 // skin is an index in image_precache +#define RF_GLOW 512 // pulse lighting for bonus items +#define RF_SHELL_RED 1024 +#define RF_SHELL_GREEN 2048 +#define RF_SHELL_BLUE 4096 + +//ROGUE (FROM 3.20) +#define RF_IR_VISIBLE 0x00008000 // 32768 +#define RF_SHELL_DOUBLE 0x00010000 // 65536 +#define RF_SHELL_HALF_DAM 0x00020000 +#define RF_USE_DISGUISE 0x00040000 +//ROGUE + +// player_state_t->refdef flags +#define RDF_UNDERWATER 1 // warp the screen as apropriate +#define RDF_NOWORLDMODEL 2 // used for player configuration screen + +//ROGUE +#define RDF_IRGOGGLES 4 +#define RDF_UVGOGGLES 8 +//ROGUE + + + + +// player_state_t->refdef flags +#define RDF_UNDERWATER 1 // warp the screen as apropriate +#define RDF_NOWORLDMODEL 2 // used for player configuration screen + +// +// muzzle flashes / player effects +// +#define MZ_BLASTER 0 +#define MZ_MACHINEGUN 1 +#define MZ_SHOTGUN 2 +#define MZ_CHAINGUN1 3 +#define MZ_CHAINGUN2 4 +#define MZ_CHAINGUN3 5 +#define MZ_RAILGUN 6 +#define MZ_ROCKET 7 +#define MZ_GRENADE 8 +#define MZ_LOGIN 9 +#define MZ_LOGOUT 10 +#define MZ_RESPAWN 11 +#define MZ_BFG 12 +#define MZ_SSHOTGUN 13 +#define MZ_HYPERBLASTER 14 +#define MZ_ITEMRESPAWN 15 +// FROM 3.20 -FB +// RAFAEL +#define MZ_IONRIPPER 16 +#define MZ_BLUEHYPERBLASTER 17 +#define MZ_PHALANX 18 +// ^^^ +#define MZ_SILENCED 128 // bit flag ORed with one of the above numbers + +//ROGUE (3.20) +#define MZ_ETF_RIFLE 30 +#define MZ_UNUSED 31 +#define MZ_SHOTGUN2 32 +#define MZ_HEATBEAM 33 +#define MZ_BLASTER2 34 +#define MZ_TRACKER 35 +#define MZ_NUKE1 36 +#define MZ_NUKE2 37 +#define MZ_NUKE4 38 +#define MZ_NUKE8 39 +//ROGUE + +// +// monster muzzle flashes +// +#define MZ2_TANK_BLASTER_1 1 +#define MZ2_TANK_BLASTER_2 2 +#define MZ2_TANK_BLASTER_3 3 +#define MZ2_TANK_MACHINEGUN_1 4 +#define MZ2_TANK_MACHINEGUN_2 5 +#define MZ2_TANK_MACHINEGUN_3 6 +#define MZ2_TANK_MACHINEGUN_4 7 +#define MZ2_TANK_MACHINEGUN_5 8 +#define MZ2_TANK_MACHINEGUN_6 9 +#define MZ2_TANK_MACHINEGUN_7 10 +#define MZ2_TANK_MACHINEGUN_8 11 +#define MZ2_TANK_MACHINEGUN_9 12 +#define MZ2_TANK_MACHINEGUN_10 13 +#define MZ2_TANK_MACHINEGUN_11 14 +#define MZ2_TANK_MACHINEGUN_12 15 +#define MZ2_TANK_MACHINEGUN_13 16 +#define MZ2_TANK_MACHINEGUN_14 17 +#define MZ2_TANK_MACHINEGUN_15 18 +#define MZ2_TANK_MACHINEGUN_16 19 +#define MZ2_TANK_MACHINEGUN_17 20 +#define MZ2_TANK_MACHINEGUN_18 21 +#define MZ2_TANK_MACHINEGUN_19 22 +#define MZ2_TANK_ROCKET_1 23 +#define MZ2_TANK_ROCKET_2 24 +#define MZ2_TANK_ROCKET_3 25 + +#define MZ2_INFANTRY_MACHINEGUN_1 26 +#define MZ2_INFANTRY_MACHINEGUN_2 27 +#define MZ2_INFANTRY_MACHINEGUN_3 28 +#define MZ2_INFANTRY_MACHINEGUN_4 29 +#define MZ2_INFANTRY_MACHINEGUN_5 30 +#define MZ2_INFANTRY_MACHINEGUN_6 31 +#define MZ2_INFANTRY_MACHINEGUN_7 32 +#define MZ2_INFANTRY_MACHINEGUN_8 33 +#define MZ2_INFANTRY_MACHINEGUN_9 34 +#define MZ2_INFANTRY_MACHINEGUN_10 35 +#define MZ2_INFANTRY_MACHINEGUN_11 36 +#define MZ2_INFANTRY_MACHINEGUN_12 37 +#define MZ2_INFANTRY_MACHINEGUN_13 38 + +#define MZ2_SOLDIER_BLASTER_1 39 +#define MZ2_SOLDIER_BLASTER_2 40 +#define MZ2_SOLDIER_SHOTGUN_1 41 +#define MZ2_SOLDIER_SHOTGUN_2 42 +#define MZ2_SOLDIER_MACHINEGUN_1 43 +#define MZ2_SOLDIER_MACHINEGUN_2 44 + +#define MZ2_GUNNER_MACHINEGUN_1 45 +#define MZ2_GUNNER_MACHINEGUN_2 46 +#define MZ2_GUNNER_MACHINEGUN_3 47 +#define MZ2_GUNNER_MACHINEGUN_4 48 +#define MZ2_GUNNER_MACHINEGUN_5 49 +#define MZ2_GUNNER_MACHINEGUN_6 50 +#define MZ2_GUNNER_MACHINEGUN_7 51 +#define MZ2_GUNNER_MACHINEGUN_8 52 +#define MZ2_GUNNER_GRENADE_1 53 +#define MZ2_GUNNER_GRENADE_2 54 +#define MZ2_GUNNER_GRENADE_3 55 +#define MZ2_GUNNER_GRENADE_4 56 + +#define MZ2_CHICK_ROCKET_1 57 + +#define MZ2_FLYER_BLASTER_1 58 +#define MZ2_FLYER_BLASTER_2 59 + +#define MZ2_MEDIC_BLASTER_1 60 + +#define MZ2_GLADIATOR_RAILGUN_1 61 + +#define MZ2_HOVER_BLASTER_1 62 + +#define MZ2_ACTOR_MACHINEGUN_1 63 + +#define MZ2_SUPERTANK_MACHINEGUN_1 64 +#define MZ2_SUPERTANK_MACHINEGUN_2 65 +#define MZ2_SUPERTANK_MACHINEGUN_3 66 +#define MZ2_SUPERTANK_MACHINEGUN_4 67 +#define MZ2_SUPERTANK_MACHINEGUN_5 68 +#define MZ2_SUPERTANK_MACHINEGUN_6 69 +#define MZ2_SUPERTANK_ROCKET_1 70 +#define MZ2_SUPERTANK_ROCKET_2 71 +#define MZ2_SUPERTANK_ROCKET_3 72 + +#define MZ2_BOSS2_MACHINEGUN_L1 73 +#define MZ2_BOSS2_MACHINEGUN_L2 74 +#define MZ2_BOSS2_MACHINEGUN_L3 75 +#define MZ2_BOSS2_MACHINEGUN_L4 76 +#define MZ2_BOSS2_MACHINEGUN_L5 77 +#define MZ2_BOSS2_ROCKET_1 78 +#define MZ2_BOSS2_ROCKET_2 79 +#define MZ2_BOSS2_ROCKET_3 80 +#define MZ2_BOSS2_ROCKET_4 81 + +#define MZ2_FLOAT_BLASTER_1 82 + +#define MZ2_SOLDIER_BLASTER_3 83 +#define MZ2_SOLDIER_SHOTGUN_3 84 +#define MZ2_SOLDIER_MACHINEGUN_3 85 +#define MZ2_SOLDIER_BLASTER_4 86 +#define MZ2_SOLDIER_SHOTGUN_4 87 +#define MZ2_SOLDIER_MACHINEGUN_4 88 +#define MZ2_SOLDIER_BLASTER_5 89 +#define MZ2_SOLDIER_SHOTGUN_5 90 +#define MZ2_SOLDIER_MACHINEGUN_5 91 +#define MZ2_SOLDIER_BLASTER_6 92 +#define MZ2_SOLDIER_SHOTGUN_6 93 +#define MZ2_SOLDIER_MACHINEGUN_6 94 +#define MZ2_SOLDIER_BLASTER_7 95 +#define MZ2_SOLDIER_SHOTGUN_7 96 +#define MZ2_SOLDIER_MACHINEGUN_7 97 +#define MZ2_SOLDIER_BLASTER_8 98 +#define MZ2_SOLDIER_SHOTGUN_8 99 +#define MZ2_SOLDIER_MACHINEGUN_8 100 + +// --- Xian shit below --- +#define MZ2_MAKRON_BFG 101 +#define MZ2_MAKRON_BLASTER_1 102 +#define MZ2_MAKRON_BLASTER_2 103 +#define MZ2_MAKRON_BLASTER_3 104 +#define MZ2_MAKRON_BLASTER_4 105 +#define MZ2_MAKRON_BLASTER_5 106 +#define MZ2_MAKRON_BLASTER_6 107 +#define MZ2_MAKRON_BLASTER_7 108 +#define MZ2_MAKRON_BLASTER_8 109 +#define MZ2_MAKRON_BLASTER_9 110 +#define MZ2_MAKRON_BLASTER_10 111 +#define MZ2_MAKRON_BLASTER_11 112 +#define MZ2_MAKRON_BLASTER_12 113 +#define MZ2_MAKRON_BLASTER_13 114 +#define MZ2_MAKRON_BLASTER_14 115 +#define MZ2_MAKRON_BLASTER_15 116 +#define MZ2_MAKRON_BLASTER_16 117 +#define MZ2_MAKRON_BLASTER_17 118 +#define MZ2_MAKRON_RAILGUN_1 119 +#define MZ2_JORG_MACHINEGUN_L1 120 +#define MZ2_JORG_MACHINEGUN_L2 121 +#define MZ2_JORG_MACHINEGUN_L3 122 +#define MZ2_JORG_MACHINEGUN_L4 123 +#define MZ2_JORG_MACHINEGUN_L5 124 +#define MZ2_JORG_MACHINEGUN_L6 125 +#define MZ2_JORG_MACHINEGUN_R1 126 +#define MZ2_JORG_MACHINEGUN_R2 127 +#define MZ2_JORG_MACHINEGUN_R3 128 +#define MZ2_JORG_MACHINEGUN_R4 129 +#define MZ2_JORG_MACHINEGUN_R5 130 +#define MZ2_JORG_MACHINEGUN_R6 131 +#define MZ2_JORG_BFG_1 132 +#define MZ2_BOSS2_MACHINEGUN_R1 133 +#define MZ2_BOSS2_MACHINEGUN_R2 134 +#define MZ2_BOSS2_MACHINEGUN_R3 135 +#define MZ2_BOSS2_MACHINEGUN_R4 136 +#define MZ2_BOSS2_MACHINEGUN_R5 137 + +//ROGUE (3.20 -FB) +#define MZ2_CARRIER_MACHINEGUN_L1 138 +#define MZ2_CARRIER_MACHINEGUN_R1 139 +#define MZ2_CARRIER_GRENADE 140 +#define MZ2_TURRET_MACHINEGUN 141 +#define MZ2_TURRET_ROCKET 142 +#define MZ2_TURRET_BLASTER 143 +#define MZ2_STALKER_BLASTER 144 +#define MZ2_DAEDALUS_BLASTER 145 +#define MZ2_MEDIC_BLASTER_2 146 +#define MZ2_CARRIER_RAILGUN 147 +#define MZ2_WIDOW_DISRUPTOR 148 +#define MZ2_WIDOW_BLASTER 149 +#define MZ2_WIDOW_RAIL 150 +#define MZ2_WIDOW_PLASMABEAM 151 // PMM - not used +#define MZ2_CARRIER_MACHINEGUN_L2 152 +#define MZ2_CARRIER_MACHINEGUN_R2 153 +#define MZ2_WIDOW_RAIL_LEFT 154 +#define MZ2_WIDOW_RAIL_RIGHT 155 +#define MZ2_WIDOW_BLASTER_SWEEP1 156 +#define MZ2_WIDOW_BLASTER_SWEEP2 157 +#define MZ2_WIDOW_BLASTER_SWEEP3 158 +#define MZ2_WIDOW_BLASTER_SWEEP4 159 +#define MZ2_WIDOW_BLASTER_SWEEP5 160 +#define MZ2_WIDOW_BLASTER_SWEEP6 161 +#define MZ2_WIDOW_BLASTER_SWEEP7 162 +#define MZ2_WIDOW_BLASTER_SWEEP8 163 +#define MZ2_WIDOW_BLASTER_SWEEP9 164 +#define MZ2_WIDOW_BLASTER_100 165 +#define MZ2_WIDOW_BLASTER_90 166 +#define MZ2_WIDOW_BLASTER_80 167 +#define MZ2_WIDOW_BLASTER_70 168 +#define MZ2_WIDOW_BLASTER_60 169 +#define MZ2_WIDOW_BLASTER_50 170 +#define MZ2_WIDOW_BLASTER_40 171 +#define MZ2_WIDOW_BLASTER_30 172 +#define MZ2_WIDOW_BLASTER_20 173 +#define MZ2_WIDOW_BLASTER_10 174 +#define MZ2_WIDOW_BLASTER_0 175 +#define MZ2_WIDOW_BLASTER_10L 176 +#define MZ2_WIDOW_BLASTER_20L 177 +#define MZ2_WIDOW_BLASTER_30L 178 +#define MZ2_WIDOW_BLASTER_40L 179 +#define MZ2_WIDOW_BLASTER_50L 180 +#define MZ2_WIDOW_BLASTER_60L 181 +#define MZ2_WIDOW_BLASTER_70L 182 +#define MZ2_WIDOW_RUN_1 183 +#define MZ2_WIDOW_RUN_2 184 +#define MZ2_WIDOW_RUN_3 185 +#define MZ2_WIDOW_RUN_4 186 +#define MZ2_WIDOW_RUN_5 187 +#define MZ2_WIDOW_RUN_6 188 +#define MZ2_WIDOW_RUN_7 189 +#define MZ2_WIDOW_RUN_8 190 +#define MZ2_CARRIER_ROCKET_1 191 +#define MZ2_CARRIER_ROCKET_2 192 +#define MZ2_CARRIER_ROCKET_3 193 +#define MZ2_CARRIER_ROCKET_4 194 +#define MZ2_WIDOW2_BEAMER_1 195 +#define MZ2_WIDOW2_BEAMER_2 196 +#define MZ2_WIDOW2_BEAMER_3 197 +#define MZ2_WIDOW2_BEAMER_4 198 +#define MZ2_WIDOW2_BEAMER_5 199 +#define MZ2_WIDOW2_BEAM_SWEEP_1 200 +#define MZ2_WIDOW2_BEAM_SWEEP_2 201 +#define MZ2_WIDOW2_BEAM_SWEEP_3 202 +#define MZ2_WIDOW2_BEAM_SWEEP_4 203 +#define MZ2_WIDOW2_BEAM_SWEEP_5 204 +#define MZ2_WIDOW2_BEAM_SWEEP_6 205 +#define MZ2_WIDOW2_BEAM_SWEEP_7 206 +#define MZ2_WIDOW2_BEAM_SWEEP_8 207 +#define MZ2_WIDOW2_BEAM_SWEEP_9 208 +#define MZ2_WIDOW2_BEAM_SWEEP_10 209 +#define MZ2_WIDOW2_BEAM_SWEEP_11 210 +// ROGUE + +extern vec3_t monster_flash_offset []; + + +// temp entity events +// +// Temp entity events are for things that happen +// at a location seperate from any existing entity. +// Temporary entity messages are explicitly constructed +// and broadcast. +typedef enum +{ + TE_GUNSHOT, + TE_BLOOD, + TE_BLASTER, + TE_RAILTRAIL, + TE_SHOTGUN, + TE_EXPLOSION1, + TE_EXPLOSION2, + TE_ROCKET_EXPLOSION, + TE_GRENADE_EXPLOSION, + TE_SPARKS, + TE_SPLASH, + TE_BUBBLETRAIL, + TE_SCREEN_SPARKS, + TE_SHIELD_SPARKS, + TE_BULLET_SPARKS, + TE_LASER_SPARKS, + TE_PARASITE_ATTACK, + TE_ROCKET_EXPLOSION_WATER, + TE_GRENADE_EXPLOSION_WATER, + TE_MEDIC_CABLE_ATTACK, + TE_BFG_EXPLOSION, + TE_BFG_BIGEXPLOSION, + TE_BOSSTPORT, // used as '22' in a map, so DON'T RENUMBER!!! + TE_BFG_LASER, + TE_GRAPPLE_CABLE, + TE_WELDING_SPARKS, +// FROM 3.20 -FB + TE_GREENBLOOD, + TE_BLUEHYPERBLASTER, + TE_PLASMA_EXPLOSION, + TE_TUNNEL_SPARKS, +//ROGUE + TE_BLASTER2, + TE_RAILTRAIL2, + TE_FLAME, + TE_LIGHTNING, + TE_DEBUGTRAIL, + TE_PLAIN_EXPLOSION, + TE_FLASHLIGHT, + TE_FORCEWALL, + TE_HEATBEAM, + TE_MONSTER_HEATBEAM, + TE_STEAM, + TE_BUBBLETRAIL2, + TE_MOREBLOOD, + TE_HEATBEAM_SPARKS, + TE_HEATBEAM_STEAM, + TE_CHAINFIST_SMOKE, + TE_ELECTRIC_SPARKS, + TE_TRACKER_EXPLOSION, + TE_TELEPORT_EFFECT, + TE_DBALL_GOAL, + TE_WIDOWBEAMOUT, + TE_NUKEBLAST, + TE_WIDOWSPLASH, + TE_EXPLOSION1_BIG, + TE_EXPLOSION1_NP, + TE_FLECHETTE +//ROGUE +// ^^^ +} temp_event_t; + +#define SPLASH_UNKNOWN 0 +#define SPLASH_SPARKS 1 +#define SPLASH_BLUE_WATER 2 +#define SPLASH_BROWN_WATER 3 +#define SPLASH_SLIME 4 +#define SPLASH_LAVA 5 +#define SPLASH_BLOOD 6 + + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) allways override a playing sound on that channel +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +// modifier flags +#define CHAN_NO_PHS_ADD 8 // send to all clients, not just ones in PHS (ATTN 0 will also do this) +#define CHAN_RELIABLE 16 // send by reliable message, not datagram + + +// sound attenuation values +#define ATTN_NONE 0 // full volume the entire level +#define ATTN_NORM 1 +#define ATTN_IDLE 2 +#define ATTN_STATIC 3 // diminish very rapidly with distance + + +// player_state->stats[] indexes +#define STAT_HEALTH_ICON 0 +#define STAT_HEALTH 1 +#define STAT_AMMO_ICON 2 +#define STAT_AMMO 3 +#define STAT_ARMOR_ICON 4 +#define STAT_ARMOR 5 +#define STAT_SELECTED_ICON 6 +#define STAT_PICKUP_ICON 7 +#define STAT_PICKUP_STRING 8 +#define STAT_TIMER_ICON 9 +#define STAT_TIMER 10 +#define STAT_HELPICON 11 +#define STAT_SELECTED_ITEM 12 +#define STAT_LAYOUTS 13 +#define STAT_FRAGS 14 +#define STAT_FLASHES 15 // cleared each frame, 1 = health, 2 = armor + +//zucc need some new ones +#define STAT_CLIP_ICON 16 +#define STAT_CLIP 17 +#define STAT_SNIPER_ICON 18 +#define STAT_ITEMS_ICON 19 +#define STAT_WEAPONS_ICON 20 +#define STAT_ID_VIEW 21 + +//FIREBLADE +#define STAT_TEAM_HEADER 22 +// 23 available +#define STAT_TEAM1_PIC 24 +#define STAT_TEAM2_PIC 25 +#define STAT_TEAM1_SCORE 26 +#define STAT_TEAM2_SCORE 27 +//FIREBLADE + +//zucc more for me +#define STAT_GRENADE_ICON 28 +#define STAT_GRENADES 29 + +#define MAX_STATS 32 + + +// dmflags->value flags +#define DF_NO_HEALTH 1 +#define DF_NO_ITEMS 2 +#define DF_WEAPONS_STAY 4 +#define DF_NO_FALLING 8 +#define DF_INSTANT_ITEMS 16 +#define DF_SAME_LEVEL 32 +#define DF_SKINTEAMS 64 +#define DF_MODELTEAMS 128 +#define DF_NO_FRIENDLY_FIRE 256 +#define DF_SPAWN_FARTHEST 512 +#define DF_FORCE_RESPAWN 1024 +#define DF_NO_ARMOR 2048 +#define DF_ALLOW_EXIT 4096 +#define DF_INFINITE_AMMO 8192 +#define DF_QUAD_DROP 16384 +#define DF_FIXED_FOV 32768 + +// FROM 3.20 -FB +// RAFAEL +#define DF_QUADFIRE_DROP 0x00010000 // 65536 + +//ROGUE +#define DF_NO_MINES 0x00020000 +#define DF_NO_STACK_DOUBLE 0x00040000 +#define DF_NO_NUKES 0x00080000 +#define DF_NO_SPHERES 0x00100000 +//ROGUE +// ^^^ + + +/* +========================================================== + + ELEMENTS COMMUNICATED ACROSS THE NET + +========================================================== +*/ + +#define ANGLE2SHORT(x) ((int)((x)*65536/360) & 65535) +#define SHORT2ANGLE(x) ((x)*(360.0/65536)) + + +// +// config strings are a general means of communication from +// the server to all connected clients. +// Each config string can be at most MAX_QPATH characters. +// +#define CS_NAME 0 +#define CS_CDTRACK 1 +#define CS_SKY 2 +#define CS_SKYAXIS 3 // %f %f %f format +#define CS_SKYROTATE 4 +#define CS_STATUSBAR 5 // display program string + +// FROM 3.20 -FB +#define CS_AIRACCEL 29 // air acceleration control +// ^^^ +#define CS_MAXCLIENTS 30 +#define CS_MAPCHECKSUM 31 // for catching cheater maps + +#define CS_MODELS 32 +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) +#define CS_IMAGES (CS_SOUNDS+MAX_SOUNDS) +#define CS_LIGHTS (CS_IMAGES+MAX_IMAGES) +#define CS_ITEMS (CS_LIGHTS+MAX_LIGHTSTYLES) +#define CS_PLAYERSKINS (CS_ITEMS+MAX_ITEMS) +// FROM 3.20 -FB +#define CS_GENERAL (CS_PLAYERSKINS+MAX_CLIENTS) +#define MAX_CONFIGSTRINGS (CS_GENERAL+MAX_GENERAL) +// ^^^ + + +//============================================== + + +// entity_state_t->event values +// ertity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. +// All muzzle flashes really should be converted to events... +typedef enum +{ + EV_NONE, + EV_ITEM_RESPAWN, + EV_FOOTSTEP, + EV_FALLSHORT, + EV_FALL, + EV_FALLFAR, + EV_PLAYER_TELEPORT, +// FROM 3.20 -FB + EV_OTHER_TELEPORT +// ^^^ +} entity_event_t; + + +// entity_state_t is the information conveyed from the server +// in an update message about entities that the client will +// need to render in some way +typedef struct entity_state_s +{ + int number; // edict index + + vec3_t origin; + vec3_t angles; + vec3_t old_origin; // for lerping + int modelindex; + int modelindex2, modelindex3, modelindex4; // weapons, CTF flags, etc + int frame; + int skinnum; +// FROM 3.20 -FB + unsigned int effects; +// ^^^ + int renderfx; + int solid; // for client side prediction, 8*(bits 0-4) is x/y radius + // 8*(bits 5-9) is z down distance, 8(bits10-15) is z up + // gi.linkentity sets this properly + int sound; // for looping sounds, to guarantee shutoff + int event; // impulse events -- muzzle flashes, footsteps, etc + // events only go out for a single frame, they + // are automatically cleared each frame +} entity_state_t; + +//============================================== + + +// player_state_t is the information needed in addition to pmove_state_t +// to rendered a view. There will only be 10 player_state_t sent each second, +// but the number of pmove_state_t changes will be reletive to client +// frame rates +typedef struct +{ + pmove_state_t pmove; // for prediction + + // these fields do not need to be communicated bit-precise + + vec3_t viewangles; // for fixed views + vec3_t viewoffset; // add to pmovestate->origin + vec3_t kick_angles; // add to view direction to get render angles + // set by weapon kicks, pain effects, etc + + vec3_t gunangles; + vec3_t gunoffset; + int gunindex; + int gunframe; + + float blend[4]; // rgba full screen effect + + float fov; // horizontal field of view + + int rdflags; // refdef flags + + short stats[MAX_STATS]; // fast status bar updates +} player_state_t; + + +// FROM 3.20 -FB +// ================== +// PGM +#define VIDREF_GL 1 +#define VIDREF_SOFT 2 +#define VIDREF_OTHER 3 + +extern int vidref_val; +// PGM +// ================== +// ^^^