commit c480e687aa03c1e8847ed3a06d7132ff43dbf669 Author: mv Date: Mon Sep 2 14:03:07 2024 +0300 Extract original source code from wfsource421.zip, dated 1999 diff --git a/FAVORITE.txt b/FAVORITE.txt new file mode 100644 index 0000000..0d0c361 --- /dev/null +++ b/FAVORITE.txt @@ -0,0 +1,53 @@ +ref number for editing the bots.cfg +makes bots pick thier favorite weapon the most +make sure you edit in the right place... + + # WF3.4 + 2 :Shotgun :All + 3 :Super Shotgun :Recon + 4 :MachineGun :Cyborg + 5 :ChainGun :Gunner + 6 :GrenadeLauncher :none class10 file? + 7 :RocketLauncher :Marine + 8 :RailGun :Recon + 9 :HyperBlaster :Marine + 0 :BFG10k :None + 10 :SniperRifle :Sniper /not done yet + 11 :LightningGun :Cyborg + 12 :InfectedDartLauncher :Nurse + 13 :PulseCannon :Gunner + 14 :TeslaCoil :Engineer + 15 :FlameThrower :Arsonist + 16 :PelletRocketLauncher :Marine + 17 :NapalmRocketLauncher :Arsonist + 18 :ClusterRocketlauncher :Cyborg + 19 :Needler :Nurse + 20 :SHC :Arsonist + 21 :HandGrenades /Normal :All + 22 :ArmorDartLauncher :WF Engineer + 23 :AK47 :Merc + 24 :Pistol :Merc + xx :FlareGun :Arsonist /not done yet + xx :MegaChainGun :Gunner /not done yet + xx :TranquilizerDart :Spy /not done yet + xx :Knife :Spy/Merc /not done yet + xx :Stinger :Merc /not done yet +////////////////////////////////////////////////////////////////////////// +To add items to maps + ie type ctf_item item_armor_combat and where you are standing combat armor +will be there next time you load the map,unsure if you need to have bot_calc_nodes 1,but it does save this to the rtz file so you might,to remove all items type clear_items.if the level is already routed and you have to +turn on bot_calc_nodes to drop these make sure you turn it back off after you +drop something otherwise when you move you'll be adding extra routing info, +if this is the inital routing of a map dont worry about it. + +partial list of dropable items: +item_armor_combat +item_armor_body +ammo_slugs +ammo_rockets +ammo_grenades +ammo_cells +ammo_shells +ammo_bullets + +weapons will not work they are removed automatically from maps..... \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0feaa3e --- /dev/null +++ b/Makefile @@ -0,0 +1,247 @@ +###################################################################### +# Makefile for WF for Quake2 +# +# Note: If you developed your source files on a Win32 machine, type +# "make stripcr" to get rid of any stray carriage returns that +# may be lurking in your files or make sure that you use ASCII +# mode to transfer via FTP. +# +###################################################################### + + +###################################################################### +# C_OBJS (Custom Objects): You should use this group below to +# specify any custom files required by you that are not +# included in id's sources. e.g. if your mod requires the addition +# of custom files "foo.c" and "bar.c", you'd add: +# +# C_OBJS = foo.o bar.o +# +###################################################################### +C_OBJS = alarm.o \ +b_biosentry.o \ +b_healingdepot.o \ +b_missile.o \ +b_supplydepot.o \ +b_turret.o \ +bot_ai.o \ +bot_die.o \ +bot_items.o \ +bot_misc.o \ +bot_nav.o \ +bot_spawn.o \ +bot_wpns.o \ +camclient.o \ +debug.o \ +dwm.o \ +g_ai.o \ +g_chase.o \ +g_cmds.o \ +g_combat.o \ +g_ctf.o \ +g_ent.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_utils.o \ +g_weapon.o \ +grapple.o \ +j_diesease.o \ +j_fire.o \ +j_kamikaze.o \ +m_flash.o \ +m_move.o \ +p_client.o \ +p_hud.o \ +p_menu.o \ +p_trail.o \ +p_view.o \ +p_weapon.o \ +q_devels.o \ +q_shared.o \ +r_weap.o \ +remotecam.o \ +stdlog.o \ +throwup.o \ +w_ak47.o \ +w_armordart.o \ +w_boltedblaster.o \ +w_cgprojectilelauncher.o \ +w_clustermissiles.o \ +w_concussion.o \ +w_flamethrower.o \ +w_flare.o \ +w_flaregun.o \ +w_gasgrenade.o \ +w_infectdartlauncher.o \ +w_lasercutter.o \ +w_lasersight.o \ +w_laserweapons.o \ +w_lightninggun.o \ +w_magnotron.o \ +w_mbpc.o \ +w_megachaingun.o \ +w_nag.o \ +w_nailgun.o \ +w_napalm.o \ +w_napalmmissiles.o \ +w_needler.o \ +w_other.o \ +w_pelletmissile.o \ +w_pistol.o \ +w_plague.o \ +w_plaguetime.o \ +w_plasmabomb.o \ +w_poisondart.o \ +w_sentrykiller.o \ +w_shc.o \ +w_shock.o \ +w_shrapnal.o \ +w_slowgrenade.o \ +w_sniperrifle.o \ +w_stinger.o \ +w_telsacoil.o \ +w_tranquilizer.o \ +wf_classmgr.o \ +wf_cluster.o \ +wf_config.o \ +wf_decoy.o \ +wf_earthquake.o \ +wf_feign.o \ +wf_fileio.o \ +wf_flagcap.o \ +wf_flame.o \ +wf_flash.o \ +wf_freezer.o \ +wf_friends.o \ +wf_goodyear.o \ +wf_ipban.o \ +wf_jet.o \ +wf_knife.o \ +wf_laserball.o \ +wf_maplist.o \ +wf_misc.o \ +wf_napalm.o \ +wf_pipebomb.o \ +wf_proximity.o \ +wf_quake.o \ +wf_referee.o \ +wf_scanner.o \ +wf_tempents.o \ +wf_turret.o \ +x_weap.o + +# g_unzip.o +# g_zip.o + +# Game-related objects +G_OBJS = + +# Monster-related objects +M_OBJS = + +# Player-related objects +P_OBJS = + +# Quake2-related objects +Q_OBJS = + +# This will compile the original C_OBJS along with any extra files that +# you defined above +# +OBJS = $(C_OBJS) $(G_OBJS) $(M_OBJS) $(P_OBJS) $(Q_OBJS) + +###################################################################### +# UNCOMMENT BELOW LINES FOR WF DEPENDING ON OS +###################################################################### + +# Uncomment for Linux +TARGET = gamei386.so + +# Uncomment for Solaris +#TARGET = gamesparc.so + +CC = gcc +# CC = cc + +SHELL = /bin/sh + +# This #define saves us from having to change stricmp() to +# strcasecmp() in the actual sources, which means we can leave +# the source code as portable as possible (Win32 doesn't like +# strcasecmp() any more than Linux likes stricmp()). By doing +# it this way the same source code can be built under both +# platforms unchanged. +# +BASE_CFLAGS = -Dstricmp=strcasecmp + +# These build objects optimized for speed rather than size on Linux. +#CFLAGS = $(BASE_CFLAGS) -O2 -DC_ONLY -ffast-math -funroll-loops \ +# -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ +# -malign-jumps=2 -malign-functions=2 -fno-strength-reduce -Wall +#CFLAGS = $(BASE_CFLAGS) -O3 -DC_ONLY -ffast-math \ +# -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ +# -malign-jumps=2 -malign-functions=2 -fno-strength-reduce -Wall +# -fverbose-asm -S -g + +# This builds optimized for speed under Solaris +#CFLAGS = $(BASE_CFLAGS) -O2 -DC_ONLY -ffast-math -funroll-loops \ +# -fomit-frame-pointer -fexpensive-optimizations \ +# -fno-strength-reduce -Wall + +# Use this for testing/debugging under Linux or Solaris +CFLAGS = $(BASE_CFLAGS) -O2 -g -DC_ONLY + +# Linker flags for building a shared library (*.so). +# +# Redhat Linux users don't need -ldl or -lm... +LDFLAGS = + +# but Slackware people do +#LDFLAGS = -ldl -lm + +SHLIBCFLAGS = -fPIC +SHLIBLDFLAGS = -shared + +###################################################################### +# Targets +###################################################################### + +all: $(TARGET) + +.c.o: + $(CC) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< + +$(TARGET): $(OBJS) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(OBJS) $(LDFLAGS) +# strip $(TARGET) + +dep: + @echo "Updating dependencies..." + @$(CC) -MM $(OBJS:.o=.c) > .depend + +stripcr: . + @echo "Stripping carriage returns from source files..." + @for f in *.[ch]; do \ + cat $$f | tr -d '\015' > .stripcr; \ + mv .stripcr $$f; \ + done; \ + rm -f .stripcr + +clean: + @echo "Deleting WF temporary and compiled files..." + @rm -f $(OBJS) *.orig ~* core *.so + @echo "Restoring p_trail.o..." + @cp p_trail.o.sav p_trail.o + +distclean: clean + @echo "Deleting everything that can be rebuilt..." + @rm -f $(TARGET) .depend diff --git a/alarm.c b/alarm.c new file mode 100644 index 0000000..547bfce --- /dev/null +++ b/alarm.c @@ -0,0 +1,290 @@ +#include "g_local.h" +//void Cmd_WFPlayTeam (edict_t *self, char *wavename); + +void alarm_remove(edict_t *self) +{ + if (self == NULL) + { + return; + } + if (self->owner && self->owner->client) + { +// --self->owner->client->pers.active_special[ITEM_SPECIAL_ALARMS]; + self->owner->alarm1 = NULL; + } + G_FreeEdict (self); +} + +void alarm_die (edict_t *self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point) +{ + self->takedamage=DAMAGE_NO; + //T_RadiusDamage (self, self->owner, 20, NULL, 10,0); + // BANG ! + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition(self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + G_FreeEdict (self); +// --self->owner->client->pers.active_special[ITEM_SPECIAL_ALARMS]; +} + +void alarmthink(edict_t *ent) +{ + edict_t *blip; + + if (level.time > ent->delay) //don't exploce on contact + { + alarm_remove (ent); + return; + } + + blip = NULL; + while ((blip = findradius (blip, ent->s.origin, 256)) != NULL) + { + if (!blip->client) + continue; + // Don't sound alarm for team mates + if (blip->wf_team == ent->wf_team) + continue; + // Don't sound alarm for dead enemies + if (blip->health <= 0) + continue; + // Don't sound alarm for folks we can't see + if (!visible(ent, blip)) + continue; + // Don't sound alarm for spies 95% of the time + if ((blip->disguised) && (random() < 0.95)) + continue; + // Sound alarm for everyone else + break; + + } + + if (blip != NULL) + { + if ( ent->wf_team == CTF_TEAM1) + { + // play Team 1's sound + gi.sound (ent, CHAN_WEAPON, gi.soundindex ("alarm.wav"), 1, ATTN_NORM, 0); + } + else + { + // play Team 2's sound + gi.sound (ent, CHAN_WEAPON, gi.soundindex ("ctf/tech1.wav"), 1, ATTN_NORM, 0); + } + // let the player know their alarm was triggered + safe_cprintf (ent->owner, PRINT_HIGH, "Alarm triggered by %s\n", blip->client->pers.netname); + // don't check for another second because we are playing the sound for one second + ent->nextthink = level.time + 1.0; + //If this is an enemy spy, tell the team! + // if (ent->owner->client->player_special & SPECIAL_DISGUISE) + // { + // Cmd_WFPlayTeam(ent->owner, "radio/d_spy.wav"); + // } + + } + else + { + // check next frmae because we are idle + ent->nextthink = level.time + 0.1; + } +} + +void place_alarm (int number,edict_t *ent) +{ + + vec3_t forward, + wallp; + + trace_t tr; + + // valid ent ? + if ((!ent->client) || (ent->health<=0)) + return; + + // cells for laser ? + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < 25) + { + safe_cprintf(ent, PRINT_HIGH, "Not enough cells for an Alarm.\n"); + return; + } + + //Are there too many alarms now? +/* if (ent->client->pers.active_special[ITEM_SPECIAL_ALARMS] >= MAX_SPECIAL_ALARMS) + { + safe_cprintf(ent, PRINT_HIGH, "You can only have %d active Alarms.\n",MAX_SPECIAL_ALARMS ); + return; + } +*/ + + // Setup "little look" to close wall + VectorCopy(ent->s.origin,wallp); + + // Cast along view angle + AngleVectors (ent->client->v_angle, forward, NULL, NULL); + + // Setup end point + wallp[0]=ent->s.origin[0]+forward[0]*50; + wallp[1]=ent->s.origin[1]+forward[1]*50; + wallp[2]=ent->s.origin[2]+forward[2]*50; + + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, wallp, ent, MASK_SOLID); + + // Line complete ? (ie. no collision) + if (tr.fraction == 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Too far from wall.\n"); + return; + } + + // Hit sky ? + if (tr.surface) + if (tr.surface->flags & SURF_SKY) + return; + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= 25; + +// ++ent->client->pers.active_special[ITEM_SPECIAL_ALARMS]; + + + if (number == 1) + { + if (ent->alarm1) + { + alarm_remove (ent->alarm1); + safe_cprintf(ent, PRINT_HIGH, "Alarm off.\n"); + return; + } + safe_cprintf(ent, PRINT_HIGH, "Alarm on.\n"); + ent->alarm1 = G_Spawn(); + VectorClear (ent->alarm1->mins); + VectorClear (ent->alarm1->maxs); + VectorCopy (tr.endpos, ent->alarm1->s.origin); + vectoangles(tr.plane.normal,ent->alarm1 -> s.angles); + ent->alarm1 -> movetype = MOVETYPE_NONE; + ent->alarm1 -> clipmask = MASK_SHOT; + //grenade -> solid = SOLID_NOT; + ent->alarm1->solid = SOLID_BBOX; + VectorSet(ent->alarm1->mins, -3, -3, 0); + VectorSet(ent->alarm1->maxs, 3, 3, 6); +// ent->alarm1->takedamage=DAMAGE_YES; + ent->alarm1->takedamage=DAMAGE_NO; + ent->alarm1->mass = 2; + ent->alarm1 -> s.modelindex = gi.modelindex (GRNORMAL_MODEL); + ent->alarm1 -> owner = ent; +// ent->alarm1->die = alarm_die; + ent->alarm1 -> nextthink = level.time + 0.2; + ent->alarm1 -> think = alarmthink; + ent->alarm1->classname = "Alarm"; + ent->alarm1->health= 60; + ent->alarm1->max_health =60; + ent->alarm1->wf_team = ent->wf_team; + ent->alarm1->delay = level.time + 120.0; + gi.linkentity (ent->alarm1); + } + else if (number == 2) + { + if (ent->alarm2) + { + G_FreeEdict(ent->alarm2); + ent->alarm2 = NULL; + gi.bprintf (PRINT_HIGH, "Alarm 2 off.\n"); + return; + } + gi.bprintf (PRINT_HIGH, "Alarm 2 on.\n"); + ent->alarm2 = G_Spawn(); + VectorClear (ent->alarm2->mins); + VectorClear (ent->alarm2->maxs); + VectorCopy (tr.endpos, ent->alarm2->s.origin); + vectoangles(tr.plane.normal,ent->alarm2 -> s.angles); + ent->alarm2 -> movetype = MOVETYPE_NONE; + ent->alarm2 -> clipmask = MASK_SHOT; + //grenade -> solid = SOLID_NOT; + ent->alarm2->solid = SOLID_BBOX; + VectorSet(ent->alarm2->mins, -3, -3, 0); + VectorSet(ent->alarm2->maxs, 3, 3, 6); + ent->alarm2->takedamage=DAMAGE_YES; + ent->alarm2->mass = 2; + ent->alarm2 -> s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2"); + ent->alarm2 -> owner = ent; + ent->alarm2->die = alarm_die; + ent->alarm2 -> nextthink = level.time + 0.1; + ent->alarm2 -> think = alarmthink; + ent->alarm2->health= 60; + ent->alarm2->max_health =60; + ent->alarm2->classname = "Alarm"; + ent->alarm2->wf_team = ent->wf_team; + gi.linkentity (ent->alarm2); + } + else if (number == 3) + { + if (ent->alarm3) + { + G_FreeEdict(ent->alarm3); + ent->alarm3 = NULL; + gi.bprintf (PRINT_HIGH, "Alarm 3 off.\n"); + return; + } + gi.bprintf (PRINT_HIGH, "Alarm 3 on.\n"); + ent->alarm1 = G_Spawn(); + VectorClear (ent->alarm3->mins); + VectorClear (ent->alarm3->maxs); + VectorCopy (tr.endpos, ent->alarm3->s.origin); + vectoangles(tr.plane.normal,ent->alarm3 -> s.angles); + ent->alarm3 -> movetype = MOVETYPE_NONE; + ent->alarm3 -> clipmask = MASK_SHOT; + //grenade -> solid = SOLID_NOT; + ent->alarm3->solid = SOLID_BBOX; + VectorSet(ent->alarm3->mins, -3, -3, 0); + VectorSet(ent->alarm3->maxs, 3, 3, 6); + ent->alarm3->takedamage=DAMAGE_YES; + ent->alarm3->mass = 2; + ent->alarm3->die = alarm_die; + ent->alarm3 -> s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2"); + ent->alarm3 -> owner = ent; + ent->alarm3 -> nextthink = level.time + 0.1; + ent->alarm3 -> think = alarmthink; + ent->alarm3->health= 60; + ent->alarm3->max_health =60; + ent->alarm3->classname = "Alarm"; + ent->alarm3->wf_team = ent->wf_team; + gi.linkentity (ent->alarm3); + } +} +void cmd_Alarm(edict_t *ent) +{ + char *string; + + string = gi.args(); + + if (!ent->client) return; + + //argument = "on", "off" + if (Q_stricmp ( string, "on") == 0) + { + if (!ent->alarm1) + { + alarm_remove(ent->alarm1); + } + } + else if (Q_stricmp ( string, "off") == 0) + { + if (ent->alarm1) + { + alarm_remove(ent->alarm1); + } + + } + else + { + if (ent->alarm1) + { + alarm_remove(ent->alarm1); + } + else + { + place_alarm(1, ent); + } + + } +} diff --git a/b_antiweapon.h b/b_antiweapon.h new file mode 100644 index 0000000..f3c06fb --- /dev/null +++ b/b_antiweapon.h @@ -0,0 +1,47 @@ +if (ent->anti) +{ + blip = NULL; + while ((blip = findradius(blip, ent->s.origin, 500)) != NULL) + { + + //The only non-client to track is sentry guns + if ((strcmp(blip->classname, "rocket") != 0) && (strcmp(blip->classname, "proximity") != 0)&&(strcmp(blip->classname, "turret") != 0)&&(strcmp(blip->classname, "napalm grenade") != 0)&&(strcmp(blip->classname, "goodyear") != 0)&&(strcmp(blip->classname, "grenade") != 0)) + continue; + if(ent == blip->owner) + continue; + tr = gi.trace (ent->s.origin, NULL, NULL, blip->s.origin, ent, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + ent->enemy = blip; + } + if(ent->enemy) + { + + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))]>0) + { + ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))]--; + + start[0] = ent->s.origin[0]; + start[1] = ent->s.origin[1]; + start[2] = ent->s.origin[2]; + + // calc direction to where we targetd + VectorMA (ent->enemy->s.origin, -0.05, ent->enemy->velocity, target); + + + VectorSubtract (target, start, dir); + VectorNormalize (dir); + + //fire bullet + fire_bullet (ent, start, dir, 8, 0, 125, 125, MOD_SENTRY); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent - g_edicts); + gi.WriteByte (MZ_SHOTGUN ); + gi.multicast (start, MULTICAST_PVS); + gi.sound(ent, CHAN_VOICE, gi.soundindex("boss3\xfire.wav"), 1, ATTN_NORM, 0); + } + ent->enemy=NULL; + } +} \ No newline at end of file diff --git a/b_biosentry.c b/b_biosentry.c new file mode 100644 index 0000000..8541a2f --- /dev/null +++ b/b_biosentry.c @@ -0,0 +1,795 @@ +#include "g_local.h" +#include "m_player.h" +#define newBio self->Bio1 +#define newBio2 self->Bio1->Bio2 +#define IdleStart 0 +#define IdleEnd 2 +#define AttackStart 3 +#define AttackEnd 7 +#define AttackFire 7 +#define StatusIdle 0 +#define StatusAttack 1 + +void BecomeExplosion1 (edict_t *self); +void SP_Biosentry (edict_t *self); +//void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread); +void BiosentryFire1(edict_t *self); +void Biosentry_self_remove(edict_t *ent); +void Biosentry_remove(edict_t *ent); +void BiosentryReload(edict_t *ent, pmenu_t *p); +void BiosentryRepair(edict_t *ent, pmenu_t *p); +void fire_infecteddart (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int mod); + + +void BiosentryAnimate(edict_t *ent) +{ + if(ent->delay==StatusIdle) + { + if (ent->s.frame >= IdleEnd) + { + ent->s.frame = IdleStart; + return; + } + } + else if (ent->delay==StatusAttack) + { + if(ent->s.frame < AttackStart) + { + ent->s.frame = AttackStart; + return; + } + if(ent->s.frame >= AttackEnd) + { + ent->s.frame = IdleStart; + ent->delay = StatusIdle; + ent->enemy = NULL; + return; + } + } + ent->s.frame++; + return; +} + +//Add or subtract ammo from biosentry +void UpdateBiosentryAmmo(edict_t *ent, int change_amt) +{ + ent->light_level += change_amt; + if (ent->light_level == 25) + { + safe_cprintf(ent->creator, PRINT_HIGH, "Warning: Your biosentry is low on ammo.\n"); + } + +} + +void FireBiosentry(edict_t *ent) +{ + vec3_t forward, right, start, target, dir; + vec3_t temp_vel; + + temp_vel[0] = 0; + temp_vel[1] = 0; + temp_vel[2] = 0; + + if(ent->light_level<=0) + { + Biosentry_self_remove(ent); + return; + } + + UpdateBiosentryAmmo(ent, -1); + + AngleVectors (ent->s.angles, forward, right, NULL); + + start[0] = ent->s.origin[0] + forward[0] * 3 ; + start[1] = ent->s.origin[1] + forward[1] * 3 ; + start[2] = ent->s.origin[2] + forward[2] * 3+4; + + // calc direction to where we targetd +// VectorMA (ent->enemy->s.origin, -0.05, ent->enemy->velocity, target); + VectorMA (ent->enemy->s.origin, -0.05, temp_vel, target); + + //Adjust for height +// target[2] += ent->enemy->viewheight/1.5; + + VectorSubtract (target, start, dir); + VectorNormalize (dir); + + //fire dart + fire_infecteddart (ent, start, dir, 20, 500, EF_GIB, MOD_BIOSENTRY); + + // send muzzle flash + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent - g_edicts); + gi.WriteByte (MZ_SHOTGUN ); + gi.multicast (start, MULTICAST_PVS); +// gi.sound(ent, CHAN_VOICE, gi.soundindex("boss3/xfire.wav"), 1, ATTN_NORM, 0); +} + +void Biosentry_Think(edict_t *self) +{ + static char Bio[32]; + static char Armor[32]; + static char Ammo[32]; + static char Level[32]; + int contents; + edict_t *blip; + float dist; + vec3_t v; + trace_t tr; +// gitem_t *ammo; +// int max; + float checkyaw; + blip = NULL; + + //gi.error("john"); + + //If there is no client attached to this Bio any more, it should be removed + //from the game + if (!self->creator) //There is no creator + { + Biosentry_self_remove(self); + } + + if (!self->creator->client) //Creator isn't a client + { + Biosentry_self_remove(self); + } + + //Make sure that the creator of this biosentry also has an + //entry that points back to this Bio + if (!self->creator->sentry) //creator doesn't know about this Bio + { + Biosentry_self_remove(self); + } + + if (self->creator->sentry != self) //creator has a different Bio + { + Biosentry_self_remove(self); + } + + contents = (int)gi.pointcontents(self->s.origin); + + if (contents & CONTENTS_SOLID) + { + safe_cprintf(self->creator, PRINT_HIGH, "Your biosentry was in a bad map position, so it was removed.\n");//5/99 + Biosentry_self_remove(self); + return; + } + + + if(self->enemy) + { + if(self->enemy->health<=0) + { + self->enemy= NULL; + } + } + + //Play sound if we are ready +// --self->turretsoundcountdown; + + if(!self->enemy) + { + self->delay=StatusIdle; + //Reduce range + while (blip = findradius (blip, self->s.origin, 500)) + { + if (!blip->client) + continue; //not a player + if (blip->wf_team != self->wf_team) //shoot only at team mates + continue; + if (blip->solid == SOLID_NOT) + continue; //don't see observers + if (blip->disguised) + continue; + if (blip->health <= 0 || blip->health >= 100) + continue; + tr = gi.trace (self->s.origin, NULL, NULL, blip->s.origin, self, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + VectorSubtract (self->s.origin, blip->s.origin, v); + + dist = VectorLength(v); + + if (!visible(self, blip) && dist > 300) + continue; + + self->enemy = blip; + self->delay = StatusAttack; + } + } + else + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + + self->ideal_yaw = vectoyaw(v); + + M_ChangeYaw(self); + + checkyaw = anglemod(self->s.angles[YAW])-self->ideal_yaw; + + if (checkyaw>-25 && checkyaw<25) + { + if (self->light_level>0) + { + if (visible(self, self->enemy)) + { + if (self->s.frame == AttackFire) + { + FireBiosentry(self); + } + } + else + { + self->delay=StatusIdle; + self->enemy=NULL; + } + } + } + else + { + self->delay=StatusIdle; + self->enemy=NULL; + } + } + + BiosentryAnimate(self); + self->nextthink = level.time + 0.1; + + if(self->delay==StatusIdle) + { + if(self->sentrydelayPlasmaDelay) + self->PlasmaDelay=0; + else + self->PlasmaDelay=1; + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/comp_up.wav"), 0.1, ATTN_NORM, 0); + self->sentrydelay=level.time + 2; + } + if(self->PlasmaDelay) + self->s.angles[YAW]+=3.5; + else + self->s.angles[YAW]-=3.5; + if(self->s.angles[YAW]<0) + self->s.angles[YAW]+=360; + else if(self->s.angles[YAW]>360) + self->s.angles[YAW]-=360; + } + //if(self->healthmax_health) + // self->health++; +} + + +//Ent = Bio entity +void Biosentry_self_remove(edict_t *ent) +{ + + //Clear client's pointer to Biosentry + if (ent->creator) + ent->creator->sentry = NULL; + + + //Then free the biosentry + BecomeExplosion1 (ent); +// G_FreeEdict(ent); +} + +//Ent = Player entity +void Biosentry_remove(edict_t *ent) +{ + if (ent->sentry) + { //42 ebc + safe_cprintf(ent, PRINT_HIGH, "biosentry off.\n"); + + + //free the Biosentry + BecomeExplosion1 (ent->sentry); +// G_FreeEdict(ent->sentry); + + ent->sentry = NULL; + + if (ent->client->oldplayer) + G_FreeEdict(ent->client->oldplayer); + return; + } +} + +void Biosentry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + + edict_t *blip = NULL; + int i; + vec3_t origin; + +//42 bot sentry clear itemnode owner +if (self->creator->bot_client) + for (i=1, blip=g_edicts+i ; i < globals.num_edicts ; i++,blip++) + { + if (blip->owner == self->creator) + { + if (!strcmp(blip->classname, "item_sentryspot") ) + { + blip->owner = NULL; + } + } + } +//42 end clear item owner + +//42 scbc + safe_cprintf(self->creator, PRINT_HIGH, "biosentry Destroyed.\n"); + + //Give a frag to the attacker + if (attacker->client && attacker->wf_team != self->wf_team) + { + attacker->client->resp.score++; + } + + VectorCopy (self->s.origin,origin); + origin[2]+= 0.5; + self->takedamage = DAMAGE_NO; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (origin); + gi.multicast (origin, MULTICAST_PVS); + + Biosentry_self_remove(self); + +} + +void BiosentryReload(edict_t *ent, pmenu_t *p) +{ + int max, armorfill; + float dist; + vec3_t distance; + if (!ent->selectedsentry) + { + safe_cprintf(ent, PRINT_HIGH, "No bio-sentry selected\n"); + } + distance[0]=ent->s.origin[0] - ent->selectedsentry->s.origin[0]; + distance[1]=ent->s.origin[1] - ent->selectedsentry->s.origin[1]; + distance[2]=ent->s.origin[2] - ent->selectedsentry->s.origin[2]; + dist=VectorLength(distance); + if(dist>100) + { + safe_cprintf(ent, PRINT_HIGH, "Biosentry too far away.\n"); + PMenu_Close(ent); + return; + } + if (ent->selectedsentry->light_level!=ent->selectedsentry->gib_health) + { + max = ent->selectedsentry->gib_health; + armorfill=ent->selectedsentry->gib_health-ent->selectedsentry->light_level; + if(armorfill>75) + armorfill=75; +// if(armorfill>25) +// armorfill=25; + //42 ebc + if (!ent->bot_client && ent->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))] < 25) + { + safe_cprintf(ent, PRINT_HIGH, "You need 25 Bullets to reload the biosentry.\n"); + if (ent->selectedsentry) gi.sound(ent->selectedsentry, CHAN_VOICE, gi.soundindex("misc/keytry.wav"), 1, ATTN_NORM, 0); + PMenu_Close(ent); + return; + } + + //25 player bullets will give 75 bullets to biosentry (Gregg) +// if(armorfill > ent->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))]) +// armorfill = ent->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))]; + + UpdateBiosentryAmmo(ent->selectedsentry, armorfill); +// ent->selectedsentry->light_level += armorfill; + ent->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))] -= 25; + if (ent->selectedsentry->light_level > max) + ent->selectedsentry->light_level = max; + gi.sound(ent->selectedsentry, CHAN_ITEM, gi.soundindex("misc/w_pkup.wav"), 1, ATTN_NORM, 0); + } + PMenu_Close(ent); +} + +void AddArmorToBiosentry(edict_t *ent, int amt) +{ + int max, armor; + + if (ent->health < ent->max_health) + { + max = ent->max_health; + + //How much armor is needed? + armor = ent->max_health - ent->health; + + if (amt < armor) + armor = amt; + else + armor = 0; + + ent->health+= armor; + if (ent->health > max) + ent->health = max; + gi.sound(ent->selectedsentry, CHAN_ITEM, gi.soundindex("misc/w_pkup.wav"), 1, ATTN_NORM, 0); + } +} + +void BiosentryRepair(edict_t *ent, pmenu_t *p) +{ + int max, armor; + int cells, maxcells, currcells; + float dist; + vec3_t distance; + if (!ent->selectedsentry) + {//42 ebc + safe_cprintf(ent, PRINT_HIGH, "No bio-sentry selected\n"); + } + distance[0]=ent->s.origin[0] - ent->selectedsentry->s.origin[0]; + distance[1]=ent->s.origin[1] - ent->selectedsentry->s.origin[1]; + distance[2]=ent->s.origin[2] - ent->selectedsentry->s.origin[2]; + dist=VectorLength(distance); + if(dist>100) + { + safe_cprintf(ent, PRINT_HIGH, "Biosentry too far away.\n"); + PMenu_Close(ent); + return; + } + if (ent->selectedsentry->healthselectedsentry->max_health) + { + max = ent->selectedsentry->max_health; + + //How much armor is needed? + armor = ent->selectedsentry->max_health - ent->selectedsentry->health; + + //Can't give more than 100 points armor in one shot + if(armor > 100) armor=100; + + //Each cell gives 4 points armor + currcells = ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))]; + maxcells = armor / 4; + + if (currcells ==0) + {//42 ebc + safe_cprintf(ent, PRINT_HIGH, "You need Cells to repair the biosentry.\n"); + if (ent->selectedsentry) gi.sound(ent->selectedsentry, CHAN_VOICE, gi.soundindex("misc/keytry.wav"), 1, ATTN_NORM, 0); + + PMenu_Close(ent); + return; + } + + //If we don't have enough cells, adjust the amount of armor to give + if(currcells < maxcells) + { + cells = currcells; + armor = cells * 4; + } + else + { + cells = maxcells; + } + + ent->selectedsentry->health+= armor; + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= cells; + if (ent->selectedsentry->health > max) + ent->selectedsentry->health = max; + gi.sound(ent->selectedsentry, CHAN_ITEM, gi.soundindex("misc/w_pkup.wav"), 1, ATTN_NORM, 0); + } + PMenu_Close(ent); +} + + +void Biosentry_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->bot_client) + return;//42 + + if (other->client) other->selectedsentry = ent; + + //Skip if they already have a menu up + if ((other->client) && (other->client->menu)) + return; + + if(other->sentrydelay>level.time) + return; + + //Only show menu a max of every 1 second + + //Only allow team mates access to menu +//if(!other->bot_client) + if (other->client && (other->wf_team == ent->wf_team) && (other->client->player_special & SPECIAL_BIOSENTRY)) + { + PMenu_Close(other); + + sprintf(other->client->wfsentrystr[0], "*%s's Bio", ent->creator->client->pers.netname); + sprintf(other->client->wfsentrystr[1], " Armor:%d/%d", ent->health, ent->max_health); + sprintf(other->client->wfsentrystr[2], " Ammo:%d/%d", ent->light_level,ent->gib_health); + sprintf(other->client->wfsentrystr[3], " Level:%d", ent->count); + other->client->sentrymenu[0].text = other->client->wfsentrystr[0]; + other->client->sentrymenu[0].SelectFunc = NULL; + other->client->sentrymenu[0].align = PMENU_ALIGN_CENTER; + other->client->sentrymenu[0].arg = 0; + other->client->sentrymenu[1].text = other->client->wfsentrystr[1]; + other->client->sentrymenu[1].SelectFunc = NULL; + other->client->sentrymenu[1].align = PMENU_ALIGN_LEFT; + other->client->sentrymenu[1].arg = 0; + other->client->sentrymenu[2].text = other->client->wfsentrystr[2]; + other->client->sentrymenu[2].SelectFunc = NULL; + other->client->sentrymenu[2].align = PMENU_ALIGN_LEFT; + other->client->sentrymenu[2].arg = 0; + other->client->sentrymenu[3].text = other->client->wfsentrystr[3]; + other->client->sentrymenu[3].SelectFunc = NULL; + other->client->sentrymenu[3].align = PMENU_ALIGN_LEFT; + other->client->sentrymenu[3].arg = 0; + other->client->sentrymenu[5].text = "1. Repair"; + other->client->sentrymenu[5].SelectFunc = BiosentryRepair; + other->client->sentrymenu[5].align = PMENU_ALIGN_LEFT; + other->client->sentrymenu[5].arg = 0; + other->client->sentrymenu[6].text = "2. Reload"; + other->client->sentrymenu[6].SelectFunc = BiosentryReload; + other->client->sentrymenu[6].align = PMENU_ALIGN_LEFT; + other->client->sentrymenu[6].arg = 0; + +// other->selectedsentry = ent->creator->sentry; + other->selectedsentry = ent; + + PMenu_Open(other, other->client->sentrymenu, -1, sizeof(other->client->sentrymenu) / sizeof(pmenu_t), true, false); + other->sentrydelay = level.time + 3; + + //Set timeout for menu (4 seconds) + if (other->client->menu) other->client->menu->MenuTimeout = level.time + 4; + } +} + + +//Build the Biosentry +void PlaceBiosentry (edict_t *ent) +{ + vec3_t forward,up,right,wallp, pos,try1,try2,try3,try4; + + trace_t tr; + + // valid ent ? + if ((!ent->client) || (ent->health<=0)) + return; + + /**** DEBUGGING ***/ + //safe_cprintf(ent, PRINT_HIGH, "Sorry - biosentry disabled while we are testing.\n"); + //return; + /**** DEBUGGING ***/ + + if (ent->sentry) + { + Biosentry_remove(ent); + + if (ent->client->oldplayer) + G_FreeEdict(ent->client->oldplayer); + return; + } + + // cells for biosentry ? + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < 50) + {//42 ebc + safe_cprintf(ent, PRINT_HIGH, "You need 50 cells to create biosentry.\n"); + return; + } + + // Setup "little look" to close wall + VectorCopy(ent->s.origin,wallp); + + // Cast along view angle + AngleVectors (ent->client->v_angle, forward, right, up); + + // Setup end point + pos[0]=ent->s.origin[0]+forward[0]*75; + pos[1]=ent->s.origin[1]+forward[1]*75; + pos[2]=ent->s.origin[2]+forward[2]*75+30; + wallp[0]=ent->s.origin[0]+forward[0]*150; + wallp[1]=ent->s.origin[1]+forward[1]*150; + wallp[2]=ent->s.origin[2]+forward[2]*150+30; + try1[0]=ent->s.origin[0]+forward[0]*150+right[0]*20; + try1[1]=ent->s.origin[1]+forward[1]*150+right[1]*20; + try1[2]=ent->s.origin[2]+forward[2]*150+30+right[2]*20; + try2[0]=ent->s.origin[0]+forward[0]*150+right[0]*-20; + try2[1]=ent->s.origin[1]+forward[1]*150+right[1]*-20; + try2[2]=ent->s.origin[2]+forward[2]*150+30+right[2]*-20; + try3[0]=ent->s.origin[0]+forward[0]*75+right[0]*20; + try3[1]=ent->s.origin[1]+forward[1]*75+right[1]*20; + try3[2]=ent->s.origin[2]+forward[2]*75+30+right[2]*20; + try4[0]=ent->s.origin[0]+forward[0]*75+right[0]*-20; + try4[1]=ent->s.origin[1]+forward[1]*75+right[1]*-20; + try4[2]=ent->s.origin[2]+forward[2]*75+30+right[2]*-20; + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, wallp, ent, MASK_SOLID); + + + // Line complete ? (ie. no collision) + if (tr.fraction != 1.0) + {//42 ebc + safe_cprintf (ent, PRINT_HIGH, "Not enough room.\n"); + return; + } + wallp[2]+=22; + tr = gi.trace (pos, NULL, NULL, wallp, ent, MASK_SOLID); + // Line complete ? (ie. no collision) + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room. Try aiming lower\n"); + return; + } + wallp[2]-=40; + tr = gi.trace (pos, NULL, NULL, wallp, ent, MASK_SOLID); + // Line complete ? (ie. no collision) + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room. Try aiming higher\n"); + return; + } + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, try1, ent, MASK_SOLID); + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room.\n"); + return; + } + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, try2, ent, MASK_SOLID); + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room.\n"); + return; + } + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, try3, ent, MASK_SOLID); + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room.\n"); + return; + } + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, try4, ent, MASK_SOLID); + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room.\n"); + return; + } + + // Hit sky ? + if (tr.surface) + if (tr.surface->flags & SURF_SKY) + return; + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= 50; + + + safe_cprintf(ent, PRINT_HIGH, "biosentry on.\n"); + ent->sentry = G_Spawn(); + +// ent->sentry->turretsoundcountdown = 0; + + VectorClear (ent->sentry->mins); + VectorClear (ent->sentry->maxs); + VectorCopy (pos, ent->sentry->s.origin); + ent->sentry ->s.angles[0]=ent->s.angles[0]; + ent->sentry -> movetype = MOVETYPE_STEP; + ent->sentry -> clipmask = MASK_PLAYERSOLID; + ent->sentry->mass = 400; + ent->s.renderfx=RF_FRAMELERP|RF_TRANSLUCENT|RF_GLOW; + //grenade -> solid = SOLID_NOT; + ent->sentry->solid = SOLID_BBOX; + + VectorSet(ent->sentry->mins, -45,-15,-35); + VectorSet(ent->sentry->maxs, 25, 18, 18); + + ent->sentry->takedamage=DAMAGE_YES; + ent->sentry -> s.modelindex = gi.modelindex ("models/biosentry/tris.md2"); + ent->sentry -> creator = ent; + ent->sentry ->sentrydelay =level.time+1; + ent->sentry->creator=ent; + ent->sentry->think = Biosentry_Think; + ent->sentry->nextthink = level.time + 0.1; +// ent->sentry->touch = Biosentry_Touch; + ent->sentry->die = Biosentry_die; + ent->sentry->health= 80; + ent->sentry->max_health = 100; + ent->sentry->count = 1; + ent->sentry->s.sound = gi.soundindex ("weapons/biosentry/bioidle.wav"); + ent->sentry->attenuation =1; + ent->sentry->volume = 0.5; + ent->sentry->classname = "biosentry"; + ent->sentry->wf_team = ent->wf_team; + ent->sentry->noteamdamage = true; //Don't let teammates damage it + ent->sentry->yaw_speed = 35; + ent->sentry->gib_health = 75;//Max Ammo + ent->sentry->light_level = 75;//Ammo Total + + ent->sentry->delay=StatusIdle; + gi.linkentity (ent->sentry); +} + + +//test_Bio() - see if there is a Bio close enough to work on + +//Returns 0 if Bio does not exist or is too far away +//Returns 1 if Bio exists and is close enough +int test_Biosentry (edict_t *ent) +{ + float dist; + float tdist; + edict_t *Bio; + vec3_t distance; + edict_t *blip; + if (!ent->sentry) + { + safe_cprintf(ent, PRINT_HIGH, "Biosentry not found.\n"); + return 0; + } + + blip = NULL; + dist = 0; + Bio = NULL; + while (blip = findradius (blip, ent->s.origin, 100)) + { + if (strcmp(blip->classname, "biosentry")) + continue; //Not a biosentry + + distance[0]=ent->s.origin[0] - blip->s.origin[0]; + distance[1]=ent->s.origin[1] - blip->s.origin[1]; + distance[2]=ent->s.origin[2] - blip->s.origin[2]; + tdist=VectorLength(distance); + if ((dist == 0) || (tdist < dist)) + { + dist = tdist; + Bio = blip; + } + } + if(dist>100 || Bio == NULL) + { + safe_cprintf(ent, PRINT_HIGH, "Bio too far away.\n"); + return 0; + } + else + { + ent->selectedsentry = Bio; + return 1; + } + +} + +void cmd_Biosentry (edict_t *ent) +{ + char *string; + + string=gi.args(); + + if (!ent->client) return; + + //argument = "build", "remove", "repair" and "reload" + if (Q_stricmp ( string, "build") == 0) + { + if (!ent->sentry) + PlaceBiosentry(ent); + else + safe_cprintf(ent, PRINT_HIGH, "Bio already exists.\n"); + } + else if (Q_stricmp ( string, "remove") == 0) + { + if (ent->sentry) + Biosentry_remove(ent); + else + safe_cprintf(ent, PRINT_HIGH, "Bio not found.\n"); + } + else if (Q_stricmp ( string, "repair") == 0) + { + if (test_Biosentry(ent)) BiosentryRepair(ent, NULL); + } + else if (Q_stricmp ( string, "reload") == 0) + { + if (test_Biosentry(ent)) BiosentryReload(ent, NULL); + } + + //Otherwise toggle on/off + else if (Q_stricmp ( string, "") == 0) + { + PlaceBiosentry(ent); + } + else + safe_cprintf(ent, PRINT_HIGH, "Invalid Bio command.\n"); + +} diff --git a/b_healingdepot.c b/b_healingdepot.c new file mode 100644 index 0000000..80b4915 --- /dev/null +++ b/b_healingdepot.c @@ -0,0 +1,192 @@ +#include "g_local.h" + +#define newHealingDepot self->supply +void SP_HealingDepot(edict_t *self); +void Remove_Player_Flames (edict_t *ent); +void healingdepot_explode (edict_t *self); +/* +================= +Healing Depot +================= +*/ + +void HealPlayer(edict_t *ent) +{ + ent->disease= 0; + ent->lame = 0; + ent->superslow=0; + ent->Slower=0; + ent->DrunkTime=level.time - 1; + ent->client->blindBase = 0; + ent->client->blindTime = 0; + ent->cantmove = 0; + + Remove_Player_Flames (ent); + +} + +void HealingDepotThink (edict_t *self) +{ + edict_t *other; + int contents; + other = NULL; + + contents = (int)gi.pointcontents(self->s.origin); + + if (contents & CONTENTS_SOLID) + { + safe_cprintf(self->owner, PRINT_HIGH, "Your healing depot was in a bad map position, so it was removed.\n");//5/99 + healingdepot_explode (self); + return; + } + + while ((other = findradius(other, self->s.origin, 16)) != NULL) + { + if (other->client) + { + HealPlayer(other); + + //Give some health + if (other->health < 100) + { + other->health += 5; + if (other->health > 100) other->health = 100; + } + + //destroy depot for bots if enemy is using it + if ((self->wf_team != other->wf_team) && (self->owner->bot_client)) + { healingdepot_explode (self); + return; + } + if ((self->wf_team != other->wf_team) && (self->owner)) + safe_cprintf(self->owner, PRINT_HIGH, "Enemies are using your healing depot!\n"); + gi.sound(self, CHAN_ITEM, gi.soundindex("ctf/tech4.wav"), 1, ATTN_NORM, 0); + } + } + + self->nextthink = level.time + 0.35; +} + +void healingdepot_explode (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + if (self->waterlevel) + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + make_debris(self); + + self->takedamage = DAMAGE_NO; + + if (self->owner) safe_cprintf(self->owner, PRINT_HIGH, "Healing Depot Off.\n"); + + if ((self->owner) && (self->owner->supply)) + self->owner->supply = NULL; + + T_RadiusDamage(self, self->owner, self->dmg, NULL, self->dmg_radius, MOD_HEALINGDEPOT); + G_FreeEdict(self); + +} + +void healingdepot_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *blip = NULL; + int i; + +//42 bot supply clear itemnode owner +if (self->owner->bot_client) + for (i=1, blip=g_edicts+i ; i < globals.num_edicts ; i++,blip++) + { + if (blip->owner == self->owner) + { + if (!strcmp(blip->classname, "item_depotspot") ) + { + blip->owner = NULL; + } + } + } +//42 end clear item owner + + gi.WriteByte (svc_temp_entity); + if (self->waterlevel) + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + make_debris(self); + + self->takedamage = DAMAGE_NO; + + if (self->owner) safe_cprintf(self->owner, PRINT_HIGH, "Healing Depot Destroyed.\n"); + + if ((self->owner) && (self->owner->supply)) + self->owner->supply = NULL; + + T_RadiusDamage(self, self->owner, self->dmg, NULL, self->dmg_radius, MOD_HEALINGDEPOT); + G_FreeEdict(self); + +} + +void SP_HealingDepot(edict_t *self) +{ + /**** DEBUGGING ***/ + //safe_cprintf(self, PRINT_HIGH, "Sorry - healing depot disabled while we are testing.\n"); + //return; + /**** DEBUGGING ***/ + + if ( newHealingDepot ) + { + healingdepot_explode(newHealingDepot); + return; + } + + // cells for laser ? + if (self->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < 10) + { + safe_cprintf(self, PRINT_HIGH, "Need 10 cells for a Healing Depot.\n"); + return; + } + safe_cprintf(self, PRINT_HIGH, "Healing Depot on.\n"); + newHealingDepot = G_Spawn (); + VectorCopy(self->s.origin,newHealingDepot->s.origin); + newHealingDepot->s.origin[2] += 40; + newHealingDepot->classname="healingdepot"; + newHealingDepot->takedamage=DAMAGE_AIM; + newHealingDepot->movetype= MOVETYPE_TOSS; + newHealingDepot->mass = 200; + newHealingDepot->solid = SOLID_BBOX; + newHealingDepot->clipmask=MASK_ALL; + newHealingDepot->deadflag =DEAD_NO; + newHealingDepot->clipmask = MASK_SHOT; + newHealingDepot->model = self->model; + newHealingDepot->s.modelindex = gi.modelindex ("models/objects/dmspot/tris.md2"); + newHealingDepot->s.skinnum = 2; + newHealingDepot->solid = SOLID_BBOX; + newHealingDepot->noteamdamage = true; //Don't let teammates damage it + newHealingDepot->wf_team = self->wf_team; + + VectorSet (newHealingDepot->mins, -32, -32, -24); + VectorSet (newHealingDepot->maxs, 32, 32, -16); + newHealingDepot->s.frame =0; + newHealingDepot->waterlevel = 0; + newHealingDepot->watertype=0; + newHealingDepot->health= 100; + newHealingDepot->max_health =100; + newHealingDepot->gib_health = -80; + newHealingDepot->die = healingdepot_die; + newHealingDepot->owner = self; + newHealingDepot->dmg = 150; + newHealingDepot->dmg_radius = 160; +// newHealingDepot->touch = HealingTouch; + newHealingDepot->think =HealingDepotThink; + newHealingDepot->nextthink = level.time + 1; + VectorClear (newHealingDepot->velocity); + gi.linkentity (newHealingDepot); + + self->client->pers.inventory[ITEM_INDEX(FindItem("cells"))]-= 10; + + if (self->client && !self->bot_client ) gi.centerprintf (self,"Healing Depot set!\n"); +} diff --git a/b_missile.c b/b_missile.c new file mode 100644 index 0000000..7c79d49 --- /dev/null +++ b/b_missile.c @@ -0,0 +1,646 @@ +#include "g_local.h" +#include "m_player.h" +#define newTurret self->turret1 +#define newTurret2 self->turret1->turret2 + + +void BecomeExplosion1 (edict_t *self); + +// WF & CCH: New think function for homing missiles + +/* +================= +fire_rocket +================= +*/ +void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +void mhoming_think (edict_t *ent) +{ + edict_t *target = NULL; + edict_t *blip = NULL; + vec3_t targetdir, blipdir; + vec_t speed; + + while ((blip = findradius(blip, ent->s.origin, 1000)) != NULL) + { + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + { + //The only non-client to track is turret grenades and sentry guns + if ( (strcmp(blip->classname, "turret") != 0) && + (strcmp(blip->classname, "SentryGun") != 0)&& + (strcmp(blip->classname, "MissileTurret") != 0)) + { + continue; + } + } + if (strcmp(blip->classname, "hook") == 0 ) + continue; //not a grapple 5/99 + if (blip == ent->creator) + continue; + + if (blip->disguised) + continue; + //dont aim at same team unless friendly fire is on + if ((blip->wf_team == ent->wf_team) && (((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE)==0)) + continue; + if (!blip->takedamage) + continue; + if (blip->health <= 0) + continue; + if (!visible(ent, blip)) + continue; + if (!infront(ent, blip)) + continue; + VectorSubtract(blip->s.origin, ent->s.origin, blipdir); + blipdir[2] += 16; + if ((target == NULL) || (VectorLength(blipdir) < VectorLength(targetdir))) + { + target = blip; + VectorCopy(blipdir, targetdir); + } + } + + if (target != NULL) + { + // target acquired, nudge our direction toward it + VectorNormalize(targetdir); + VectorScale(targetdir, 0.28, targetdir); + VectorAdd(targetdir, ent->movedir, targetdir); + VectorNormalize(targetdir); + VectorCopy(targetdir, ent->movedir); + vectoangles(targetdir, ent->s.angles); + speed = VectorLength(ent->velocity); + VectorScale(targetdir, speed, ent->velocity); + + //is this the first time we locked in? sound warning for the target + if (ent->homing_lock == 0) + { + gi.sound (target, CHAN_AUTO, gi.soundindex ("homelock.wav"), 1, ATTN_NORM, 0); +// gi.sound (target, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0); + ent->homing_lock = 1; + } + } + + //ent->nextthink = level.time + .1; +} + +void fire_turretrocket (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->creator = self; + rocket->owner = self; + rocket->touch = rocket_touch; + +//WF & CCH - Add homing lock status +// rocket->nextthink = level.time + 8000/speed; +// rocket->think = G_FreeEdict; + + //Set the team of the rocket + rocket->wf_team = self->wf_team; + + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + + rocket->nextthink = level.time + .1; + rocket->think = mhoming_think; + + //reduce damage of homing rockets to 1/2 of normal rocket + rocket->dmg = damage / 2; +// rocket->radius_dmg = radius_damage / 2; + + +//WF + + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->classname = "rocket"; + + gi.linkentity (rocket); +} + + +void MTurret_Think(edict_t *self) +{ + edict_t *blip; + float dist; + vec3_t v; + vec3_t start,forward; + trace_t tr; + int max; + blip = NULL; + + //gi.error("john"); + + if(self->enemy) + { + if(self->enemy->health<=0) + self->enemy= NULL; + } + if (self->light_level!=self->gib_health) + { + if (self->PlasmaDelaygib_health; + + if (self->count==3) + self->PlasmaDelay = level.time +1; + else if (self->count==2) + self->PlasmaDelay = level.time +2; + else + self->PlasmaDelay = level.time +3; + self->light_level++; + if (self->light_level > max) + self->light_level = max; + gi.sound(self, CHAN_ITEM, gi.soundindex("misc/w_pkup.wav"), 1, ATTN_NORM, 0); + } + } + + if (self->count == 1) + { + if(self->delaylight_level) + self->s.frame=1; + else + self->s.frame=0; + } + else + self->s.frame=0; + if(!self->enemy) + { + while (blip = findradius (blip, self->s.origin, 1024)) + { + + //shoot monsters, decoys or players + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + { + //allow it to shoot decoys + if (strcmp(blip->classname, "decoy") ) + continue; //not a decoy + } + if (strcmp(blip->classname, "hook") == 0 ) + continue; //not a grapple 5/99 + if (blip->health <= 0) + continue; + if (blip == self->creator) + continue; + if (blip->disguised) + continue; + if ((blip->wf_team == self->wf_team) && (((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE)==0)) + continue; + tr = gi.trace (self->s.origin, NULL, NULL, blip->s.origin, self, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + VectorSubtract (self->s.origin, blip->s.origin, v); + + dist = VectorLength(v); + + if (!visible(self, blip) && dist > 400) + continue; + + self->enemy = blip; + } + } + else + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + + self->ideal_yaw = vectoyaw(v); + + + M_ChangeYaw(self); + + if (self->light_level>0) + { + if ((visible(self, self->enemy))&& (self->delays.angles, forward, NULL, NULL); + start[0] = self->s.origin[0] + forward[0] * 25; + start[1] = self->s.origin[1] + forward[1] * 25; + start[2] = self->s.origin[2] + forward[2] * 25+10; + + +// fire_turretrocket (self, start, forward, 30, 700, 150, 50); + fire_rocket (self, start, forward, 30, 750, 150, 50, MOD_MISSILE); + self->light_level-=1; + self->delay = level.time + 1.0; + } + else + { + self->enemy=NULL; + } + } + else + { + self->enemy=NULL; + } + } + } + + else if (self->count == 2) + { + + self->s.frame = self->light_level; + if(!self->enemy) + { + while (blip = findradius (blip, self->s.origin, 2048)) + { + + //shoot monsters, decoys or players + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + { + //allow it to shoot decoys + if (strcmp(blip->classname, "decoy") ) + continue; //not a decoy + } + if (strcmp(blip->classname, "hook") == 0 ) + continue; //not a grapple 5/99 + if (blip->health <= 0) + continue; + if (blip == self->creator) + continue; + if (blip->disguised) + continue; + if ((blip->wf_team == self->wf_team) && (((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE)==0)) + continue; + tr = gi.trace (self->s.origin, NULL, NULL, blip->s.origin, self, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + VectorSubtract (self->s.origin, blip->s.origin, v); + + dist = VectorLength(v); + + if (!visible(self, blip) && dist > 800) + continue; + + self->enemy = blip; + } + } + else + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + + self->ideal_yaw = vectoyaw(v); + + + M_ChangeYaw(self); + + if (self->light_level>0) + { + if ((visible(self, self->enemy))&&(self->delays.angles, forward, NULL, NULL); + start[0] = self->s.origin[0] + forward[0] * 25; + start[1] = self->s.origin[1] + forward[1] * 25; + start[2] = self->s.origin[2] + forward[2] * 25+10; + + +// fire_turretrocket (self, start, forward, 30, 700, 150, 50); + fire_rocket (self, start, forward, 30, 700, 150, 50, MOD_ROCKET); + self->light_level-=1; + self->delay = level.time + 0.3; + + + } + else + { + self->enemy=NULL; + } + } + else + { + self->enemy=NULL; + } + } + } + else if (self->count == 3) + { + self->s.frame =self->light_level; + if(!self->enemy) + { + + while (blip = findradius (blip, self->s.origin, 2048)) + { + + //shoot monsters, decoys or players + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + { + //allow it to shoot decoys + if (strcmp(blip->classname, "decoy") ) + continue; //not a decoy + } + if (strcmp(blip->classname, "hook") == 0 ) + continue; //not a grapple 5/99 + if (blip->health <= 0) + continue; + if (blip == self->creator) + continue; + if (blip->disguised) + continue; + if ((blip->wf_team == self->wf_team) && (((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE)==0)) + continue; + tr = gi.trace (self->s.origin, NULL, NULL, blip->s.origin, self, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + VectorSubtract (self->s.origin, blip->s.origin, v); + + dist = VectorLength(v); + + if (!visible(self, blip) && dist > 1400) + continue; + + self->enemy = blip; + } + } + else + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + + self->ideal_yaw = vectoyaw(v); + + + M_ChangeYaw(self); + + if (self->light_level>0) + { + if ((visible(self, self->enemy))&&(self->delays.angles, forward, NULL, NULL); + start[0] = self->s.origin[0] + forward[0] * 25; + start[1] = self->s.origin[1] + forward[1] * 25; + start[2] = self->s.origin[2] + forward[2] * 25+10; + + +// fire_turretrocket (self, start, forward, 30, 700, 150, 50); + fire_rocket (self, start, forward, 30, 700, 150, 50, MOD_ROCKET); + self->light_level-=1; + self->delay = level.time + 0.1; + } + + else + { + + self->enemy=NULL; + } + } + else + { + + self->enemy = NULL; + } + } + } + VectorCopy(self->s.origin,self->missile->s.origin); + self->nextthink = level.time + 0.1; + if(self->healthmax_health) + self->health++; +} + +//self = mturret entity +void mturret_remove(edict_t *self) +{ + if (self->missile) + { + G_FreeEdict(self->missile); + } + + //Clear client's pointer to missile gun + if (self->creator) + self->creator->missile = NULL; + + G_FreeEdict (self); +} + +void mturret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + vec3_t origin; + VectorCopy (self->s.origin,origin); + origin[2]+= 0.5; + self->takedamage = DAMAGE_NO; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (origin); + gi.multicast (origin, MULTICAST_PVS); + //G_FreeEdict(self->turret2); + + //Remove stand (which is the missile of the missile gun) + mturret_remove(self); + gi.bprintf (PRINT_HIGH, "Missile Gun Destroyed.\n"); +} + +void place_missile (edict_t *ent) +{ + + vec3_t forward,up,right,wallp, pos; + + trace_t tr; + edict_t *missilestand; + + // valid ent ? + if ((!ent->client) || (ent->health<=0)) + return; + + if (ent->missile) + { + //First free the stand + if (ent->missile->missile) + { + G_FreeEdict(ent->missile->missile); + ent->missile->missile = NULL; + } + + //Then free the missile gun + G_FreeEdict(ent->missile); + ent->missile = NULL; + gi.bprintf (PRINT_HIGH, "Missile Turret off.\n"); + if (ent->client->oldplayer) + G_FreeEdict(ent->client->oldplayer); + return; + } + + // cells for missile gun ? + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < 25) + { + safe_cprintf(ent, PRINT_HIGH, "You need 25 cells to create Missile Turret\n"); + return; + } + + // Setup "little look" to close wall + VectorCopy(ent->s.origin,wallp); + + // Cast along view angle + AngleVectors (ent->client->v_angle, forward, right, up); + + // Setup end point + pos[0]=ent->s.origin[0]+forward[0]*75; + pos[1]=ent->s.origin[1]+forward[1]*75; + pos[2]=ent->s.origin[2]+forward[2]*75+30; + wallp[0]=ent->s.origin[0]+forward[0]*150; + wallp[1]=ent->s.origin[1]+forward[1]*150; + wallp[2]=ent->s.origin[2]+forward[2]*150+30; + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, wallp, ent, MASK_SOLID); + + + // Line complete ? (ie. no collision) + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room.\n"); + return; + } + wallp[2]+=22; + tr = gi.trace (pos, NULL, NULL, wallp, ent, MASK_SOLID); + // Line complete ? (ie. no collision) + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room. Try aiming lower\n"); + return; + } + wallp[2]-=40; + tr = gi.trace (pos, NULL, NULL, wallp, ent, MASK_SOLID); + // Line complete ? (ie. no collision) + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room. Try aiming higher\n"); + return; + } + + // Hit sky ? + if (tr.surface) + if (tr.surface->flags & SURF_SKY) + return; + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= 25; + + +// if (ent->missile) +// { +// G_FreeEdict(ent->missile); +// ent->missile = NULL; +// gi.bprintf (PRINT_HIGH, "missile Gun off.\n"); +// return; +// } + gi.bprintf (PRINT_HIGH, "Missile Turret on.\n"); + ent->missile = G_Spawn(); + VectorClear (ent->missile->mins); + VectorClear (ent->missile->maxs); + VectorCopy (pos, ent->missile->s.origin); + ent->missile ->s.angles[0]=ent->s.angles[0]; +// ent->missile -> movetype = MOVETYPE_STEP; +// ent->missile -> clipmask = MASK_SHOT; + ent->missile -> movetype = MOVETYPE_STEP; + ent->missile -> clipmask = MASK_PLAYERSOLID; + ent->missile->mass = 400; + //grenade -> solid = SOLID_NOT; + ent->missile->solid = SOLID_BBOX; +//GR - reduce size of bounding box since I reduced size of model +// VectorSet(ent->missile->mins, -95,-44,-80); +// VectorSet(ent->missile->maxs, 57, 41, 24); + VectorSet(ent->missile->mins, -50,-20,-40); + VectorSet(ent->missile->maxs, 30, 21, 22); + ent->missile->takedamage=DAMAGE_YES; + ent->missile -> s.modelindex = gi.modelindex ("models/missileb/missile1/tris.md2"); + ent->missile -> creator = ent; + ent->missile->think = MTurret_Think; + ent->missile->nextthink = level.time + 0.1; + ent->missile->die = mturret_die; + ent->missile->health= 100; + ent->missile->max_health =100; + ent->missile->count = 1; + ent->missile->classname = "MissileTurret"; + ent->missile->wf_team = ent->wf_team; + ent->missile->noteamdamage = true; //Don't let teammates damage it + ent->missile->yaw_speed = 10; + ent->missile->gib_health = 3;//Max Ammo + ent->missile->light_level = 3;//Ammo Total + + gi.linkentity (ent->missile); + missilestand = G_Spawn(); + VectorClear (missilestand->mins); + VectorClear (missilestand->maxs); + VectorCopy (pos,missilestand->s.origin); + missilestand->s.angles[0]=ent -> s.angles[0]; + missilestand->movetype = MOVETYPE_NONE; + missilestand->mass = 400; + //grenade -> solid = SOLID_NOT; + missilestand->solid = SOLID_BBOX; + VectorSet(missilestand->mins, -45,-15,-35); + VectorSet(missilestand->maxs, 25, 18, 18); + missilestand->takedamage=DAMAGE_NO; + missilestand-> s.modelindex = gi.modelindex ("models/stand/tris.md2"); + missilestand-> creator = ent->missile; + missilestand->wf_team = ent->wf_team; + missilestand->noteamdamage = true; //Don't let teammates damage it + + gi.linkentity (missilestand); + ent->missile->missile = missilestand; +} + +void UpgradeMissileTurret(edict_t *self) +{ + edict_t *blip; + trace_t tr; + blip = NULL; + while (blip = findradius (blip, self->s.origin, 2048)) + { + + if (self->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < 60) + { + safe_cprintf(self, PRINT_HIGH, "You need 60 cells to upgrade\n"); + return; + } + + if (Q_stricmp("MissileTurret", blip->classname)) + continue; + + if (Q_stricmp("MissileTurret", blip->classname)) + continue; + + tr = gi.trace (self->s.origin, NULL, NULL, blip->s.origin, self, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + if (blip->creator != self) + continue; + if (blip->count == 3) + safe_cprintf(self, PRINT_HIGH, "missile gun already at level 3\n"); + + if (blip->count < 3) + blip->count++; + self->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= 60; + if (blip->count == 2) + { + blip->health= 150; + blip->max_health =150; + blip -> s.modelindex = gi.modelindex ("models/missileb/missile2/tris.md2"); + //blip->delay = StatusIdle; + blip->s.frame = 0; + blip->yaw_speed = 15; + blip->gib_health = 4;//Max Ammo + } + if (blip->count == 3) + { + blip->health= 200; + blip->max_health =200; + blip -> s.modelindex = gi.modelindex ("models/missileb/missile3/tris.md2"); + //blip->delay = StatusIdle; + blip->s.frame = 0; + blip->yaw_speed = 20; + blip->gib_health = 12;//Max Ammo + } + } +} \ No newline at end of file diff --git a/b_supplydepot.c b/b_supplydepot.c new file mode 100644 index 0000000..bfc5fd6 --- /dev/null +++ b/b_supplydepot.c @@ -0,0 +1,266 @@ +#include "g_local.h" + +#define newSupplyDepot self->supply +void SP_SupplyDepot(edict_t *self); +void supplydepot_explode (edict_t *self); +/* +================= +Supply Depot +================= +*/ + +void SupplyThink (edict_t *self) +{ + edict_t *other; + int x; + int contents; + other = NULL; + x = 0; + + contents = (int)gi.pointcontents(self->s.origin); + + if (contents & CONTENTS_SOLID) + { + safe_cprintf(self->owner, PRINT_HIGH, "Your supply depot was in a bad map position, so it was removed.\n");//5/99 + supplydepot_explode (self); + return; + } + + + while ((other = findradius(other, self->s.origin, 16)) != NULL) + { + if (other->client) + { + x = 0; +// other->DrunkTime=0; +// other->disease= 0; +// other->lame = 0; + + //destroy depot for bots if enemy is using it + if ((self->wf_team != other->wf_team) && (self->owner->bot_client)) + { supplydepot_explode (self); + return; + } + if ((self->wf_team != other->wf_team) && (self->owner)) + safe_cprintf(self->owner, PRINT_HIGH, "Enemies are using your depot!\n"); + + + if (other->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))]client->pers.max_bullets) + { + x =1; + other->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))]+= 1; + } + if (other->client->pers.inventory[ITEM_INDEX(FindItem("shells"))]client->pers.max_shells) + { + x=1; + other->client->pers.inventory[ITEM_INDEX(FindItem("shells"))]+= 1; + } + if (other->client->pers.inventory[ITEM_INDEX(FindItem("grenades"))]client->pers.max_grenades) + { + x=1; + other->client->pers.inventory[ITEM_INDEX(FindItem("grenades"))]+= 1; + } + if (other->client->pers.inventory[ITEM_INDEX(FindItem("rockets"))]client->pers.max_rockets) + { + x=1; + other->client->pers.inventory[ITEM_INDEX(FindItem("rockets"))]+= 1; + } + if (other->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))]client->pers.max_slugs) + { + x=1; + other->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))]+= 1; + } + if (other->client->pers.inventory[ITEM_INDEX(FindItem("cells"))]client->pers.max_cells) + { + x=1; + other->client->pers.inventory[ITEM_INDEX(FindItem("cells"))]+= 1; + } + } + } + + if (x ==1) + gi.sound(self, CHAN_ITEM, gi.soundindex("misc/w_pkup.wav"), 1, ATTN_NORM, 0); + + self->nextthink = level.time + 0.2; +} + +// Creator destroyed it +void supplydepot_explode (edict_t *self) +{ + edict_t *blip = NULL; + int i; +//42 bot supply clear itemnode owner +if (self->owner->bot_client) + for (i=1, blip=g_edicts+i ; i < globals.num_edicts ; i++,blip++) + { + if (blip->owner == self->owner) + { + if (!strcmp(blip->classname, "item_depotspot") ) + { + blip->owner = NULL; + } + } + } +//42 end clear item owner + + gi.WriteByte (svc_temp_entity); + if (self->waterlevel) + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + make_debris(self); + + self->takedamage = DAMAGE_NO; + + if (self->owner) safe_cprintf(self->owner, PRINT_HIGH, "Supply Depot Off.\n"); + + if ((self->owner) && (self->owner->supply)) + self->owner->supply = NULL; + + T_RadiusDamage(self, self->owner, self->dmg, NULL, self->dmg_radius, MOD_DEPOT); + G_FreeEdict(self); + +} + +// some other player blew it up +void supplydepot_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *blip = NULL; + int i; + +//42 bot supply clear itemnode owner +if (self->owner->bot_client) + for (i=1, blip=g_edicts+i ; i < globals.num_edicts ; i++,blip++) + { + if (blip->owner == self->owner) + { + if (!strcmp(blip->classname, "item_depotspot") ) + { + blip->owner = NULL; + } + } + } +//42 end clear item owner + + gi.WriteByte (svc_temp_entity); + if (self->waterlevel) + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + make_debris(self); + + self->takedamage = DAMAGE_NO; + + if (self->owner) + { + safe_cprintf(self->owner, PRINT_HIGH, "Supply Depot Destroyed.\n"); + if (self->owner->supply) + self->owner->supply = NULL; + } + +// T_RadiusDamage(self, self->owner, self->dmg, NULL, self->dmg_radius, MOD_DEPOT); + T_RadiusDamage(self, attacker, self->dmg, NULL, self->dmg_radius, MOD_DEPOT_EXPLODE); + G_FreeEdict(self); + +} + +void SP_SupplyDepot(edict_t *self) +{ + if ( newSupplyDepot ) + { + supplydepot_explode(newSupplyDepot); + return; + } + + // cells for laser ? +if (!self->bot_client) +{ + if (self->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < 10) + { + safe_cprintf(self, PRINT_HIGH, "Need 10 cells for a Supply Depot.\n"); + return; + } + if (self->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))] < 10) + { + safe_cprintf(self, PRINT_HIGH, "Need 10 bullets for a Supply Depot.\n"); + return; + } + if (self->client->pers.inventory[ITEM_INDEX(FindItem("Shells"))] < 10) + { + safe_cprintf(self, PRINT_HIGH, "Need 10 shells for a Supply Depot.\n"); + return; + } + if (self->client->pers.inventory[ITEM_INDEX(FindItem("Rockets"))] < 10) + { + safe_cprintf(self, PRINT_HIGH, "Need 10 rockets for a Supply Depot.\n"); + return; + } + if (self->client->pers.inventory[ITEM_INDEX(FindItem("Grenades"))] < 10) + { + safe_cprintf(self, PRINT_HIGH, "Need 10 grenades for a Supply Depot.\n"); +gi.dprintf("you only have %d grenades.\n",self->client->pers.inventory[ITEM_INDEX(FindItem("Grenades"))] ); + return; + } + if (self->client->pers.inventory[ITEM_INDEX(FindItem("Slugs"))] < 10) + { + safe_cprintf(self, PRINT_HIGH, "Need 10 slugs for a Supply Depot.\n"); + return; + } + safe_cprintf(self, PRINT_HIGH, "Supply Depot on.\n"); +} + newSupplyDepot = G_Spawn (); + VectorCopy(self->s.origin,newSupplyDepot->s.origin); + newSupplyDepot->s.origin[2] += 40; + newSupplyDepot->classname="depot"; + newSupplyDepot->takedamage=DAMAGE_AIM; + newSupplyDepot->movetype= MOVETYPE_TOSS; + newSupplyDepot->mass = 200; + newSupplyDepot->solid = SOLID_BBOX; + //newSupplyDepot->clipmask=MASK_ALL; + newSupplyDepot->deadflag =DEAD_NO; + newSupplyDepot->clipmask = MASK_SHOT; + newSupplyDepot->model = self->model; + newSupplyDepot->s.modelindex = gi.modelindex ("models/objects/dmspot/tris.md2"); + newSupplyDepot->s.skinnum = 1; + newSupplyDepot->solid = SOLID_BBOX; + newSupplyDepot->noteamdamage = true; //Don't let teammates damage it + newSupplyDepot->wf_team = self->wf_team; + + VectorSet (newSupplyDepot->mins, -32, -32, -24); + VectorSet (newSupplyDepot->maxs, 32, 32, -16); + newSupplyDepot->s.frame =0; + newSupplyDepot->waterlevel = 0; + newSupplyDepot->watertype=0; + newSupplyDepot->health= 100; + newSupplyDepot->max_health =100; + newSupplyDepot->gib_health = -80; + newSupplyDepot->die = supplydepot_die; + newSupplyDepot->owner = self; + newSupplyDepot->dmg = 150; + newSupplyDepot->dmg_radius = 160; +// newSupplyDepot->touch = SupplyTouch; + newSupplyDepot->think =SupplyThink; + newSupplyDepot->nextthink = level.time +1; + VectorClear (newSupplyDepot->velocity); + gi.linkentity (newSupplyDepot); +if (!self->bot_client) +{ + self->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))]-= 10; + + self->client->pers.inventory[ITEM_INDEX(FindItem("shells"))]-= 10; + + self->client->pers.inventory[ITEM_INDEX(FindItem("grenades"))]-= 10; + + self->client->pers.inventory[ITEM_INDEX(FindItem("rockets"))]-= 10; + + self->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))]-= 10; + + self->client->pers.inventory[ITEM_INDEX(FindItem("cells"))]-= 10; + + if (self->client) gi.centerprintf (self,"Supply Depot set!\nNote: 10 of each ammo has been\ndrained into the Supply Depot to be\nreplicated\n"); +} +} \ No newline at end of file diff --git a/b_turret.c b/b_turret.c new file mode 100644 index 0000000..97cdefa --- /dev/null +++ b/b_turret.c @@ -0,0 +1,1455 @@ +#include "g_local.h" +#include "m_player.h" +#include "bot_procs.h" + +#define newTurret self->turret1 +#define newTurret2 self->turret1->turret2 +#define Level1Idle 0 +#define Level1StartFirst 1 +#define Level1StartEnd 2 +#define Level1AttackFirst 3 +#define Level1AttackEnd 6 +#define Level23Idle 0 +#define Level23AttackFirst 1 +#define Level23AttackEnd 2 +#define StatusIdle 0 +#define StatusStart 1 +#define StatusAttack 2 + +#define DIRECTION_LEFT 1 +#define DIRECTION_CENTER 2 +#define DIRECTION_RIGHT 3 + +#define LEVEL1_DAMAGE 6 +#define LEVEL2_DAMAGE 8 +#define LEVEL3_DAMAGE 12 + +#define max(a,b) (((a) > (b)) ? (a) : (b)) + +void BecomeExplosion1 (edict_t *self); +void SP_Turret (edict_t *self); +//void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread); +void TurretFire1(edict_t *self); +void turret_self_remove(edict_t *ent); +void turret_remove(edict_t *ent); +void SentryReload(edict_t *ent, pmenu_t *p); +void SentryUpgrade(edict_t *ent, pmenu_t *p); +void SentryRepair(edict_t *ent, pmenu_t *p); + +int PlayerChangeScore(edict_t *self, int points); + + +//Add or subtract ammo from sentry gun +void UpdateSentryAmmo(edict_t *ent, int change_amt) +{ + ent->light_level += change_amt; + if (ent->light_level == 25) + { + safe_cprintf(ent->creator, PRINT_HIGH, "Warning: Your sentry gun is low on ammo.\n"); + } + +} + +void turret_fire_rocket (edict_t *ent, int direction) +{ + vec3_t forward, right, start, target, dir; + + if(ent->light_level<1) + return; + + UpdateSentryAmmo(ent, -1); + + AngleVectors (ent->s.angles, forward, right, NULL); + if (direction == DIRECTION_LEFT) + { + start[0] = ent->s.origin[0] + forward[0] * 3 + right[0] * -0.5; + start[1] = ent->s.origin[1] + forward[1] * 3 + right[1] * -0.5; + start[2] = ent->s.origin[2] + forward[2] * 3 + right[2] * -0.5 +4; + } + else if (direction == DIRECTION_CENTER) + { + start[0] = ent->s.origin[0] + forward[0] * 3 ; + start[1] = ent->s.origin[1] + forward[1] * 3 ; + start[2] = ent->s.origin[2] + forward[2] * 3+4; + } + else // Right + { + start[0] = ent->s.origin[0] + forward[0] * 3 + right[0] * 0.5; + start[1] = ent->s.origin[1] + forward[1] * 3 + right[1] * 0.5; + start[2] = ent->s.origin[2] + forward[2] * 3 + right[2] * 0.5+4; + } + + // calc direction to where we targetd + VectorMA (ent->enemy->s.origin, -0.05, ent->enemy->velocity, target); + VectorSubtract (target, start, dir); + VectorNormalize (dir); + + fire_rocket (ent, start, dir, 25, 650, 25, 25, MOD_SENTRY_ROCKET); + + // send muzzle flash + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent - g_edicts); + gi.WriteByte (MZ_SHOTGUN ); + gi.multicast (start, MULTICAST_PVS); +// gi.sound(ent, CHAN_VOICE, gi.soundindex("boss3/xfire.wav"), 1, ATTN_NORM, 0); + +} + + + +//New turret bullet firing code. Replaces fire_bullet and fire_rail +//Fires at ent->enemy +//direction = 1 (left), 2 (center), 3 (right) +void turret_fire_bullet (edict_t *ent, int direction) +{ + + vec3_t forward, right, start, target, dir; + vec3_t from; + vec3_t end; + trace_t tr; + edict_t *ignore; + int mask; + qboolean water; + int kick = 0; + int mod = MOD_SENTRY; + int damage; + float chance_of_hit; + float maxvelocity; + float r; + +if (wfdebug) gi.dprintf("turret_fire_bullet\n"); + + if(ent->light_level<1) //do we have ammo? + return; + + if (!ent->enemy) + return; + + UpdateSentryAmmo(ent, -1); + + //Calculate starting position + AngleVectors (ent->s.angles, forward, right, NULL); + + if (direction == DIRECTION_LEFT) + { + start[0] = ent->s.origin[0] + forward[0] * 3 + right[0] * -0.5; + start[1] = ent->s.origin[1] + forward[1] * 3 + right[1] * -0.5; + start[2] = ent->s.origin[2] + forward[2] * 3 + right[2] * -0.5 +4; + } + else if (direction == DIRECTION_CENTER) + { + start[0] = ent->s.origin[0] + forward[0] * 3 ; + start[1] = ent->s.origin[1] + forward[1] * 3 ; + start[2] = ent->s.origin[2] + forward[2] * 3+4; + } + else // Right + { + start[0] = ent->s.origin[0] + forward[0] * 3 + right[0] * 0.5; + start[1] = ent->s.origin[1] + forward[1] * 3 + right[1] * 0.5; + start[2] = ent->s.origin[2] + forward[2] * 3 + right[2] * 0.5+4; + } + + // calc direction to where we targetd + VectorMA (ent->enemy->s.origin, -0.05, ent->enemy->velocity, target); + + //Adjust for height +// target[2] += ent->enemy->viewheight/1.5; + + VectorSubtract (target, start, dir); + VectorNormalize (dir); + + VectorMA (start, 8192, dir, end); + VectorCopy (start, from); + ignore = ent; + water = false; + mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + + tr = gi.trace (from, NULL, NULL, end, ignore, mask); + + if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA); + water = true; + } + + //Do damage + + //Damage based on sentry level + chance_of_hit = 1; + if (ent->count == 1) + { + damage = LEVEL1_DAMAGE; + chance_of_hit = .85; + } + else if (ent->count == 2) + { + damage = LEVEL2_DAMAGE; + chance_of_hit = .90; + } + else if (ent->count == 3) + { + damage = LEVEL3_DAMAGE; + chance_of_hit = .95; + } + else + { + damage = 1; //don't know what level it is + chance_of_hit = 1.0; //always hit + } + + //Moving reduces chance of being hit + maxvelocity = max( abs (ent->enemy->velocity[0]), abs (ent->enemy->velocity[1]) ); + + if (maxvelocity >= 320) //wierd setting for grappling + { + chance_of_hit = chance_of_hit * .65; + } + else if (maxvelocity >= 250) //running + { + chance_of_hit = chance_of_hit * .74; + } + else if (maxvelocity >= 150) //walking + { + chance_of_hit = chance_of_hit * .90; + } + r = random(); + +if (wfdebug) gi.dprintf("chance =%f, rnd = %f, takedamage? %d\n", chance_of_hit, r, ent->enemy->takedamage); + + if ((ent->enemy->takedamage) && (r <= chance_of_hit)) +// T_Damage (ent->enemy, ent, ent->creator, dir, tr.endpos, tr.plane.normal, damage, 0, 0, MOD_SENTRY); + T_Damage (ent->enemy, ent, ent, dir, tr.endpos, tr.plane.normal, damage, 0, 0, MOD_SENTRY); + + //Do gunshot effect + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_GUNSHOT); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (ent->client) + PlayerNoise(ent, tr.endpos, PNOISE_IMPACT); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent - g_edicts); + gi.WriteByte (MZ_SHOTGUN ); + gi.multicast (start, MULTICAST_PVS); +// gi.sound(ent, CHAN_VOICE, gi.soundindex("boss3/xfire.wav"), 1, ATTN_NORM, 0); +} + + + + +void TurretAnimate(edict_t *ent) +{ + if(ent->count==1) + { + if(ent->delay==StatusIdle) + { + ent->s.frame = Level1Idle; + return; + } + else if(ent->delay==StatusStart) + { + if(ent->s.frame == Level1StartEnd) + { + ent->delay=StatusAttack; + ent->s.frame=Level1AttackFirst; + return; + } + else if(ent->s.frame==Level1Idle) + { + ent->s.frame = Level1StartFirst; + return; + } + else if(ent->s.frame==Level1StartFirst) + { + ent->s.frame++; + return; + } + else + { + ent->s.frame = Level1Idle; + return; + } + } + else + { + if(ent->delay==StatusAttack) + { + if(ent->s.frame==Level1AttackEnd) + { + ent->s.frame--; + return; + } + else + { + ent->s.frame++; + return; + } + } + } + } + else + { + if(ent->delay ==StatusIdle) + { + ent->s.frame = Level23Idle; + return; + } + else + { + if(ent->s.frame==Level23AttackEnd) + { + ent->s.frame = Level23AttackFirst; + return; + } + ent->s.frame++; + return; + } + } +} + + +void FireTurretLeft(edict_t *ent) +{ + float f; + +if (wfdebug) gi.dprintf("Sentry Fire Left\n"); + + //Fire bullet. For level 3 sentry's, fire rocket every 10th round + f = ent->light_level / 10; + if (((f * 10) == ent->light_level) && (ent->count == 3)) + turret_fire_rocket (ent, DIRECTION_LEFT); + else + turret_fire_bullet (ent, DIRECTION_LEFT); +} + + +void FireTurretCenter(edict_t *ent) +{ + float f; + +if (wfdebug) gi.dprintf("Sentry Fire Center\n"); + + //Fire bullet. For level 3 sentry's, fire rocket every 10th round + f = ent->light_level / 10; + if (((f * 10) == ent->light_level) && (ent->count == 3)) + turret_fire_rocket (ent, DIRECTION_CENTER); + else + turret_fire_bullet (ent, DIRECTION_CENTER); +} + +void FireTurretRight(edict_t *ent) +{ + float f; + +if (wfdebug) gi.dprintf("Sentry Fire Right\n"); + + //Fire bullet. For level 3 sentry's, fire rocket every 10th round + f = ent->light_level / 10; + if (((f * 10) == ent->light_level) && (ent->count == 3)) + turret_fire_rocket (ent, DIRECTION_RIGHT); + else + turret_fire_bullet (ent, DIRECTION_RIGHT); +} + + +void Turret_Think(edict_t *self) +{ + static char Sentry[32]; + static char Armor[32]; + static char Ammo[32]; + static char Level[32]; + int range; + int light_level; + int max_dist; + edict_t *blip; + float dist; + int contents; + int ideal_yaw = 0; + vec3_t v; + trace_t tr; +// gitem_t *ammo; +// int max; + float checkyaw; + blip = NULL; + + + //If there is no client attached to this turret any more, it should be removed + //from the game + if (!self->creator) //There is no creator + { + turret_self_remove(self); + } + + if (!self->creator->client) //Creator isn't a client + { + turret_self_remove(self); + } + + //Make sure that the creator of this sentry gun also has an + //entry that points back to this sentry + if (!self->creator->sentry) //creator doesn't know about this sentry + { + turret_self_remove(self); + } + + if (self->creator->sentry != self) //creator has a different sentry + { + turret_self_remove(self); + } + + //If sentry or stand is in something solid, remove it + contents = (int)gi.pointcontents(self->s.origin); + + if (contents & CONTENTS_SOLID) + { + safe_cprintf(self->creator, PRINT_HIGH, "Your sentry was in a bad map position, so it was removed.\n"); + turret_self_remove(self); + return; + } + + if (self->sentry) //stand + { + contents = (int)gi.pointcontents(self->sentry->s.origin); + + if (contents & CONTENTS_SOLID) + { + safe_cprintf(self->creator, PRINT_HIGH, "Your sentry stand in a bad map position so it was removed.\n"); + turret_self_remove(self); + return; + } + } + + + /*if (self->light_level!=self->gib_health) + { + if (self->PlasmaDelaygib_health; + + if (self->count==3) + self->PlasmaDelay = level.time +0.1; + else if (self->count==2) + self->PlasmaDelay = level.time +0.2; + else + self->PlasmaDelay = level.time +0.4; + self->light_level++; + if (self->light_level > max) + self->light_level = max; +//GREGG gi.sound(self, CHAN_ITEM, gi.soundindex("misc/w_pkup.wav"), 1, ATTN_NORM, 0); + } + }*/ + + //Play sound if we are ready +/* + --self->turretsoundcountdown; + if (self->turretsoundcountdown<=0) + { + //Play a sound + if (self->count == 1) + gi.sound(self, CHAN_VOICE, gi.soundindex("medic/medsrch1.wav"), 1, ATTN_NORM, 0); + else if (self->count == 2) + gi.sound(self, CHAN_VOICE, gi.soundindex("floater/fltatck2.wav"), 1, ATTN_NORM, 0); + else if (self->count == 3) + gi.sound(self, CHAN_VOICE, gi.soundindex("tank/tnkatck4.wav"), 1, ATTN_NORM, 0); + self->turretsoundcountdown = 110; //same as 11 seconds + + } +*/ + + // if (self->turretsoundcountdown == 40)//back to ambient sound +// self->s.sound = gi.soundindex ("world/amb3.wav"); + + if (self->count == 1) + { + range = 500; + light_level = 10; + max_dist = 300; + } + else if (self->count == 2) + { + range = 800; + light_level = 7; + max_dist = 500; + } + + else if (self->count == 3) + { + range = 900; + light_level = 4; + max_dist = 700; + } + + // Don't shoot frozen, dead, or non visible enemies + if ((self->enemy) && + ((self->enemy->health <= 0) || (!visible(self, self->enemy)) || (self->enemy->frozen))) + { + self->enemy = NULL; + } + + if (!self->enemy) + { + // If we are going back to idle from some other status, + // copy the angles back to original +/* + if (self->delay != StatusIdle) + { + VectorCopy (self->orig_angles , self->s.angles); + self->sentrydelay =level.time+1; + } +*/ + self->delay=StatusIdle; + + while (blip = findradius (blip, self->s.origin, range)) + { + + if (!blip->inuse) + continue; + + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + { + //allow it to shoot decoys + if (strcmp(blip->classname, "decoy") ) + continue; //not a decoy + } + if (strcmp(blip->classname, "hook") == 0 ) + continue; //not a grapple + if (blip->solid == SOLID_NOT) + continue; //don't see observers + if (blip->health <= 0) + continue; + if (blip->frozen) + continue; +// if (blip == self->creator) //wont work for anarchy mode +// continue; + if (blip->disguised) + continue; + if ((blip->wf_team == self->wf_team) && (((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE)==0)) +// if (blip->wf_team == self->wf_team) + continue; +// if (blip->light_level < light_level) +// continue; + tr = gi.trace (self->s.origin, NULL, NULL, blip->s.origin, self, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + + //Check the angle. +/* VectorSubtract (blip->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + checkyaw = anglemod(self->s.angles[YAW]) - self->ideal_yaw; + if (checkyaw < -25 || checkyaw > 25) + continue; + + VectorSubtract (self->s.origin, blip->s.origin, v); + +*/ + dist = VectorLength(v); + + + if (!visible(self, blip) && dist > max_dist) + continue; + + self->enemy = blip; + } + } + else + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + + self->ideal_yaw = vectoyaw(v); + + M_ChangeYaw(self); + + checkyaw = anglemod(self->s.angles[YAW])-self->ideal_yaw; + + if (checkyaw>-25 && checkyaw<25) + { + if (self->light_level>0) + { + if (visible(self, self->enemy)) + { + if (self->s.frame != 1) + { + if (self->delay==StatusIdle) + self->delay = StatusStart; + + if (self->count == 1) + { + if(self->s.frame == 3) + FireTurretRight(self); + + if( (self->s.frame == 1) || (self->s.frame ==5) ) + FireTurretLeft(self); + } + else if (self->count == 2) + { + FireTurretCenter(self); + } + + else if (self->count == 3) + + { + //Maybe this will reduce lag +// FireTurretRight(self); +// FireTurretLeft(self); + FireTurretCenter(self); + } + + } + } + else + { + self->delay=StatusIdle; + self->enemy=NULL; + } + } + } + else + { + self->delay=StatusIdle; + self->enemy=NULL; + } + } + + + //Move the stand? (GREGG) + if (self->sentry) + VectorCopy(self->s.origin,self->sentry->s.origin); + + // Move the sentry to stay with stand (TeT) +// if (self->sentry) +// VectorCopy(self->sentry->s.origin, self->s.origin); + + TurretAnimate(self); + self->nextthink = level.time + 0.1; + + if(self->delay==StatusIdle) + { + if(self->sentrydelayPlasmaDelay) + self->PlasmaDelay=0; + else + self->PlasmaDelay=1; + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/comp_up.wav"), 0.1, ATTN_NORM, 0); + self->sentrydelay=level.time + 2; + } + if(self->PlasmaDelay) + self->s.angles[YAW]+=3.5; + else + self->s.angles[YAW]-=3.5; + if(self->s.angles[YAW]<0) + self->s.angles[YAW]+=360; + else if(self->s.angles[YAW]>360) + self->s.angles[YAW]-=360; + } + //if(self->healthmax_health) + // self->health++; +} + + +//Ent = turret entity +void turret_self_remove(edict_t *ent) +{ + + //Clear client's pointer to sentry gun + if (ent->creator) + ent->creator->sentry = NULL; + + //first free stand + if (ent->sentry) G_FreeEdict(ent->sentry); + + //Then free the sentry gun + BecomeExplosion1 (ent); +// G_FreeEdict(ent); + + +} + +//Ent = Player entity +void turret_remove(edict_t *ent) +{ + if (ent->sentry) + { //42 ebc + safe_cprintf(ent, PRINT_HIGH, "Sentry Gun off.\n"); + + //First free the stand + if (ent->sentry->sentry) + { + G_FreeEdict(ent->sentry->sentry); + ent->sentry->sentry = NULL; + } + + + //Then free the sentry gun + BecomeExplosion1 (ent->sentry); +// G_FreeEdict(ent->sentry); + + ent->sentry = NULL; + + if (ent->client->oldplayer) + G_FreeEdict(ent->client->oldplayer); + return; + } +} + +void turret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + + vec3_t origin; + char *name1; + char *name2; +//42 sebc + + edict_t *blip = NULL; + int i; + +//42 bot sentry clear itemnode owner +if (self->creator->bot_client) + for (i=1, blip=g_edicts+i ; i < globals.num_edicts ; i++,blip++) + { + if (blip->owner == self->creator) + { + if (!strcmp(blip->classname, "item_sentryspot") ) + { + blip->owner = NULL; + } + } + } +//42 end clear item owner + + + + safe_cprintf(self->creator, PRINT_HIGH, "Sentry Gun Destroyed.\n"); + + //Give a frag to the attacker + if (attacker->client && attacker->wf_team != self->wf_team) + { + PlayerChangeScore(attacker,CTF_SENTRY_POINTS); + name1 = attacker->client->pers.netname; + if (self->creator->client) + name2 = self->creator->client->pers.netname; + else + name2 = "unknown player"; + my_bprintf(PRINT_HIGH, "%s destroyed %s's sentry gun.\n", name1, name2); + + } + + VectorCopy (self->s.origin,origin); + origin[2]+= 0.5; + self->takedamage = DAMAGE_NO; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (origin); + gi.multicast (origin, MULTICAST_PVS); + + turret_self_remove(self); + +} + +void SentryReload(edict_t *ent, pmenu_t *p) +{ + int max, armorfill; + float dist; + vec3_t distance; + + if (!ent->selectedsentry) + return; + + distance[0]=ent->s.origin[0] - ent->selectedsentry->s.origin[0]; + distance[1]=ent->s.origin[1] - ent->selectedsentry->s.origin[1]; + distance[2]=ent->s.origin[2] - ent->selectedsentry->s.origin[2]; + dist=VectorLength(distance); + if(dist>100) + { + safe_cprintf(ent, PRINT_HIGH, "Sentry too far away.\n"); + PMenu_Close(ent); + return; + } + if (ent->selectedsentry->light_level!=ent->selectedsentry->gib_health) + { + max = ent->selectedsentry->gib_health; + armorfill=ent->selectedsentry->gib_health-ent->selectedsentry->light_level; + if(armorfill>75) + armorfill=75; +// if(armorfill>25) +// armorfill=25; + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))] < 25) + { + safe_cprintf(ent, PRINT_HIGH, "You need 25 Bullets to reload the sentry gun.\n"); + if (ent->selectedsentry) gi.sound(ent->selectedsentry, CHAN_VOICE, gi.soundindex("misc/keytry.wav"), 1, ATTN_NORM, 0); + PMenu_Close(ent); + return; + } + + //25 player bullets will give 75 bullets to sentry gun (Gregg) +// if(armorfill > ent->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))]) +// armorfill = ent->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))]; + + UpdateSentryAmmo(ent->selectedsentry, armorfill); +// ent->selectedsentry->light_level += armorfill; + ent->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))] -= 25; + if (ent->selectedsentry->light_level > max) + ent->selectedsentry->light_level = max; + gi.sound(ent->selectedsentry, CHAN_ITEM, gi.soundindex("misc/w_pkup.wav"), 1, ATTN_NORM, 0); + } + PMenu_Close(ent); +} + +void AddArmorToSentry(edict_t *ent, int amt) +{ + int max, armor; + + if (ent->health < ent->max_health) + { + max = ent->max_health; + + //How much armor is needed? + armor = ent->max_health - ent->health; + + if (amt < armor) + armor = amt; + else + armor = 0; + + ent->health+= armor; + if (ent->health > max) + ent->health = max; + + if (ent->selectedsentry) + gi.sound(ent->selectedsentry, CHAN_ITEM, gi.soundindex("misc/w_pkup.wav"), 1, ATTN_NORM, 0); + } +} + +void SentryRepair(edict_t *ent, pmenu_t *p) +{ + int max, armor; + int cells, maxcells, currcells; + float dist; + vec3_t distance; + + if (!ent->selectedsentry) + return; + + distance[0]=ent->s.origin[0] - ent->selectedsentry->s.origin[0]; + distance[1]=ent->s.origin[1] - ent->selectedsentry->s.origin[1]; + distance[2]=ent->s.origin[2] - ent->selectedsentry->s.origin[2]; + dist=VectorLength(distance); + if(dist>100) + { + safe_cprintf(ent, PRINT_HIGH, "Sentry too far away.\n"); + PMenu_Close(ent); + return; + } + if (ent->selectedsentry->healthselectedsentry->max_health) + { + max = ent->selectedsentry->max_health; + + //How much armor is needed? + armor = ent->selectedsentry->max_health - ent->selectedsentry->health; + + //Can't give more than 100 points armor in one shot + if(armor > 100) armor=100; + + //Each cell gives 4 points armor + currcells = ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))]; + maxcells = armor / 4; + + if (currcells ==0) + { + safe_cprintf(ent, PRINT_HIGH, "You need Cells to repair the sentry gun.\n"); + if (ent->selectedsentry) gi.sound(ent->selectedsentry, CHAN_VOICE, gi.soundindex("misc/keytry.wav"), 1, ATTN_NORM, 0); + + PMenu_Close(ent); + return; + } + + //If we don't have enough cells, adjust the amount of armor to give + if(currcells < maxcells) + { + cells = currcells; + armor = cells * 4; + } + else + { + cells = maxcells; + } + + ent->selectedsentry->health+= armor; + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= cells; + if (ent->selectedsentry->health > max) + ent->selectedsentry->health = max; + gi.sound(ent->selectedsentry, CHAN_ITEM, gi.soundindex("misc/w_pkup.wav"), 1, ATTN_NORM, 0); + } + PMenu_Close(ent); +} + +void SentryUpgrade(edict_t *ent, pmenu_t *p) +{ + float dist; + vec3_t distance; + + if (!ent->selectedsentry) + return; + + distance[0]=ent->s.origin[0] - ent->selectedsentry->s.origin[0]; + distance[1]=ent->s.origin[1] - ent->selectedsentry->s.origin[1]; + distance[2]=ent->s.origin[2] - ent->selectedsentry->s.origin[2]; + dist=VectorLength(distance); + if(dist>100) + { + safe_cprintf(ent, PRINT_HIGH, "Sentry too far away.\n"); + PMenu_Close(ent); + return; + } + if (ent->selectedsentry->count == 3) + { + safe_cprintf(ent, PRINT_HIGH, "Sentry gun already at level 3\n"); + return; + } + + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < 60) + { + safe_cprintf(ent, PRINT_HIGH, "You need 60 cells to upgrade sentry gun.\n"); + PMenu_Close(ent); + return; + } + + + + + if (ent->selectedsentry->count < 3) + ent->selectedsentry->count++; + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= 60; + if (ent->selectedsentry->count == 2) + { + ent->selectedsentry->health= 200; + ent->selectedsentry->max_health =200; + ent->selectedsentry -> s.modelindex = gi.modelindex ("models/sentry/turret2/tris.md2"); + ent->selectedsentry->delay = StatusIdle; + ent->selectedsentry->s.frame = 0; + ent->selectedsentry->yaw_speed = 40; + ent->selectedsentry->gib_health = 150;//Max Ammo + ent->selectedsentry->light_level = 150; + //ent->selectedsentry->turretsoundcountdown = 0; + gi.sound(ent->selectedsentry, CHAN_VOICE, gi.soundindex("misc/pc_up.wav"), 1, ATTN_NORM, 0); + } + if (ent->selectedsentry->count == 3) + { + ent->selectedsentry->health= 300; + ent->selectedsentry->max_health = 300; + ent->selectedsentry -> s.modelindex = gi.modelindex ("models/sentry/turret3/tris.md2"); + ent->selectedsentry->delay = StatusIdle; + ent->selectedsentry->s.frame = 0; + ent->selectedsentry->yaw_speed = 45; + ent->selectedsentry->gib_health = 225;//Max Ammo + ent->selectedsentry->light_level = 225; + //ent->selectedsentry->turretsoundcountdown = 0; + gi.sound(ent->selectedsentry, CHAN_VOICE, gi.soundindex("misc/pc_up.wav"), 1, ATTN_NORM, 0); + } + PMenu_Close(ent); +} + +void Sentry_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->client) other->selectedsentry = ent; + + //Skip if they already have a menu up + if ((other->client) && (other->client->menu)) + return; + + if(other->sentrydelay>level.time) + return; + + //Only show menu a max of every 1 second + + //Only allow team mates access to menu + if (!other->bot_client)//42 + if (other->client && (other->wf_team == ent->wf_team) && (other->client->player_special & SPECIAL_SENTRY_GUN)) + { + PMenu_Close(other); + + sprintf(other->client->wfsentrystr[0], "*%s's Sentry", ent->standowner->client->pers.netname); + sprintf(other->client->wfsentrystr[1], " Armor:%d/%d", ent->health, ent->max_health); + sprintf(other->client->wfsentrystr[2], " Ammo:%d/%d", ent->light_level,ent->gib_health); + sprintf(other->client->wfsentrystr[3], " Level:%d", ent->count); + other->client->sentrymenu[0].text = other->client->wfsentrystr[0]; + other->client->sentrymenu[0].SelectFunc = NULL; + other->client->sentrymenu[0].align = PMENU_ALIGN_CENTER; + other->client->sentrymenu[0].arg = 0; + other->client->sentrymenu[1].text = other->client->wfsentrystr[1]; + other->client->sentrymenu[1].SelectFunc = NULL; + other->client->sentrymenu[1].align = PMENU_ALIGN_LEFT; + other->client->sentrymenu[1].arg = 0; + other->client->sentrymenu[2].text = other->client->wfsentrystr[2]; + other->client->sentrymenu[2].SelectFunc = NULL; + other->client->sentrymenu[2].align = PMENU_ALIGN_LEFT; + other->client->sentrymenu[2].arg = 0; + other->client->sentrymenu[3].text = other->client->wfsentrystr[3]; + other->client->sentrymenu[3].SelectFunc = NULL; + other->client->sentrymenu[3].align = PMENU_ALIGN_LEFT; + other->client->sentrymenu[3].arg = 0; + other->client->sentrymenu[4].text = "1. Upgrade"; + other->client->sentrymenu[4].SelectFunc = SentryUpgrade; + other->client->sentrymenu[4].align = PMENU_ALIGN_LEFT; + other->client->sentrymenu[4].arg = 0; + other->client->sentrymenu[5].text = "2. Repair"; + other->client->sentrymenu[5].SelectFunc = SentryRepair; + other->client->sentrymenu[5].align = PMENU_ALIGN_LEFT; + other->client->sentrymenu[5].arg = 0; + other->client->sentrymenu[6].text = "3. Reload"; + other->client->sentrymenu[6].SelectFunc = SentryReload; + other->client->sentrymenu[6].align = PMENU_ALIGN_LEFT; + other->client->sentrymenu[6].arg = 0; + +// other->selectedsentry = ent->standowner->sentry; + other->selectedsentry = ent; + + PMenu_Open(other, other->client->sentrymenu, -1, sizeof(other->client->sentrymenu) / sizeof(pmenu_t), true, false); + other->sentrydelay = level.time + 3; + + //Set timeout for menu (4 seconds) + if (other->client->menu) other->client->menu->MenuTimeout = level.time + 4; + } +} +//Stand think function +void Stand_Think (edict_t *ent) +{ + //For some reason, entity needs a think function + if (ent->health <= 0) + G_FreeEdict(ent); + else + ent->nextthink = level.time + 5.0; +} + + +//Build the turret +void place_turret (edict_t *ent) +{ + vec3_t forward,up,right,wallp, pos,try1,try2,try3,try4; + edict_t *blip = NULL; + int cells; + trace_t tr; + edict_t *sentrystand; + int armor; + int armorindex; + + // valid ent ? + if ((!ent->client) || (ent->health<=0)) + return; + + if (ent->sentry) + { + turret_remove(ent); + + if (ent->client->oldplayer) + G_FreeEdict(ent->client->oldplayer); + return; + } + + // cells for sentry gun ? + if ((int)wfflags->value & WF_ANARCHY) + { + armor = 150; + armorindex = ArmorIndex (ent); + if (ent->client->pers.inventory[armorindex] < armor) + { + safe_cprintf(ent, PRINT_HIGH, "You need %d points of armor to create sentry gun.\n", armor); + return; + } + } + else + { + cells = 50; + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < cells) + { + safe_cprintf(ent, PRINT_HIGH, "You need %d cells to create sentry gun.\n", cells); + return; + } + } + + + // Setup "little look" to close wall + VectorCopy(ent->s.origin,wallp); + + // Cast along view angle + AngleVectors (ent->client->v_angle, forward, right, up); + + // Setup end point + pos[0]=ent->s.origin[0]+forward[0]*75; + pos[1]=ent->s.origin[1]+forward[1]*75; + pos[2]=ent->s.origin[2]+forward[2]*75+30; + wallp[0]=ent->s.origin[0]+forward[0]*150; + wallp[1]=ent->s.origin[1]+forward[1]*150; + wallp[2]=ent->s.origin[2]+forward[2]*150+30; + try1[0]=ent->s.origin[0]+forward[0]*150+right[0]*20; + try1[1]=ent->s.origin[1]+forward[1]*150+right[1]*20; + try1[2]=ent->s.origin[2]+forward[2]*150+30+right[2]*20; + try2[0]=ent->s.origin[0]+forward[0]*150+right[0]*-20; + try2[1]=ent->s.origin[1]+forward[1]*150+right[1]*-20; + try2[2]=ent->s.origin[2]+forward[2]*150+30+right[2]*-20; + try3[0]=ent->s.origin[0]+forward[0]*75+right[0]*20; + try3[1]=ent->s.origin[1]+forward[1]*75+right[1]*20; + try3[2]=ent->s.origin[2]+forward[2]*75+30+right[2]*20; + try4[0]=ent->s.origin[0]+forward[0]*75+right[0]*-20; + try4[1]=ent->s.origin[1]+forward[1]*75+right[1]*-20; + try4[2]=ent->s.origin[2]+forward[2]*75+30+right[2]*-20; + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, wallp, ent, MASK_SOLID); + + + // Line complete ? (ie. no collision) + if (tr.fraction != 1.0) + {//42 ebc + safe_cprintf (ent, PRINT_HIGH, "Not enough room.\n"); + return; + } + wallp[2]+=22; + tr = gi.trace (pos, NULL, NULL, wallp, ent, MASK_SOLID); + // Line complete ? (ie. no collision) + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room. Try aiming lower\n"); + return; + } + wallp[2]-=40; + tr = gi.trace (pos, NULL, NULL, wallp, ent, MASK_SOLID); + // Line complete ? (ie. no collision) + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room. Try aiming higher\n"); + return; + } + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, try1, ent, MASK_SOLID); + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room.\n"); + return; + } + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, try2, ent, MASK_SOLID); + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room.\n"); + return; + } + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, try3, ent, MASK_SOLID); + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room.\n"); + return; + } + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, try4, ent, MASK_SOLID); + if (tr.fraction != 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Not enough room.\n"); + return; + } + + // Hit sky ? + if ((tr.surface) && (tr.surface->flags & SURF_SKY)) + return; + + while (blip = findradius (blip, pos, 50)) + { + if ( (!strcmp(blip->classname, "item_flag_team1") ) + || (!strcmp(blip->classname, "item_flag_team2") ) + || (!strcmp(blip->classname, "worldspawn") ) + || (!strcmp(blip->classname, "info_player_start") ) + || (!strcmp(blip->classname, "info_player_deathmatch") ) + || (!strcmp(blip->classname, "item_flagreturn_team1") ) + || (!strcmp(blip->classname, "item_flagreturn_team2") ) + || (!strcmp(blip->classname, "misc_teleporter_dest") ) + || (!strcmp(blip->classname, "info_teleport_destination") ) ) + { + safe_cprintf (ent, PRINT_HIGH, "Too Close to Items.\n"); + return ; + } + } + + if ((int)wfflags->value & WF_ANARCHY) + { + ent->client->pers.inventory[armorindex] -= armor; + } + else + { + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= cells; + } + + + safe_cprintf(ent, PRINT_HIGH, "Sentry Gun on.\n"); + ent->sentry = G_Spawn(); + + //ent->sentry->s.sound = gi.soundindex ("world/amb3.wav"); + //ent->sentry->turretsoundcountdown = 0; + + VectorClear (ent->sentry->mins); + VectorClear (ent->sentry->maxs); + VectorCopy (pos, ent->sentry->s.origin); + ent->sentry ->s.angles[0]=0; +// ent->sentry -> movetype = MOVETYPE_STEP; +// ent->sentry -> clipmask = MASK_SHOT; + ent->sentry -> movetype = MOVETYPE_TOSS; + ent->sentry -> clipmask = MASK_PLAYERSOLID; + ent->sentry->mass = 400; + ent->s.renderfx=RF_FRAMELERP|RF_TRANSLUCENT|RF_GLOW; + //grenade -> solid = SOLID_NOT; + ent->sentry->solid = SOLID_BBOX; +//GR - reduce size of bounding box since I reduced size of model +// VectorSet(ent->sentry->mins, -95,-44,-80); +// VectorSet(ent->sentry->maxs, 57, 41, 24); + VectorSet(ent->sentry->mins, -50,-20,-40); + VectorSet(ent->sentry->maxs, 30, 21, 22); + ent->sentry->takedamage=DAMAGE_YES; + ent->sentry -> s.modelindex = gi.modelindex ("models/sentry/turret1/tris.md2"); + ent->sentry -> creator = ent; + ent->sentry ->sentrydelay =level.time + 1; + ent->sentry->standowner=ent; + ent->sentry->think = Turret_Think; +// ent->sentry->nextthink = level.time + 0.1; + ent->sentry->nextthink = level.time + 2.0; + ent->sentry->touch = Sentry_Touch; + ent->sentry->die = turret_die; + ent->sentry->health= 80; + ent->sentry->max_health = 100; + ent->sentry->count = 1; + ent->sentry->s.sound = gi.soundindex ("weapons/rg_hum.wav"); + //ent->sentry->noise_index2 = gi.soundindex ("weapons/rg_hum.wav"); + ent->sentry->attenuation =1; + ent->sentry->volume = 0.5; + ent->sentry->classname = "SentryGun"; + + + ent->sentry->noteamdamage = true; //Don't let teammates damage it + ent->sentry->yaw_speed = 35; + ent->sentry->gib_health = 75;//Max Ammo + ent->sentry->light_level = 75;//Ammo Total + + ent->sentry->delay=StatusIdle; + + if ((int)wfflags->value & WF_ANARCHY) + { + ent->sentry->wf_team = 0; //fire at anybody + ent->sentry->count = 3; + ent->sentry->health= 300; + ent->sentry->max_health = 300; + ent->sentry -> s.modelindex = gi.modelindex ("models/sentry/turret3/tris.md2"); + ent->sentry->s.frame = 0; + ent->sentry->yaw_speed = 45; + ent->sentry->gib_health = 225;//Max Ammo + ent->sentry->light_level = 225; + gi.sound(ent->sentry, CHAN_VOICE, gi.soundindex("misc/pc_up.wav"), 1, ATTN_NORM, 0); + } + else + ent->sentry->wf_team = ent->wf_team; + + gi.linkentity (ent->sentry); + +if (wfdebug) + { + ent->sentry->s.modelindex2 = gi.modelindex ("models/stand/tris.md2"); + ent->sentry->sentry = NULL; + } +else + { + sentrystand = G_Spawn(); + VectorClear (sentrystand->mins); + VectorClear (sentrystand->maxs); + VectorCopy (pos,sentrystand->s.origin); + sentrystand->s.angles[0]=0; + sentrystand->movetype = MOVETYPE_TOSS; + sentrystand->mass = 400; + //grenade -> solid = SOLID_NOT; + sentrystand->solid = SOLID_NOT; + VectorSet(sentrystand->mins, -45,-15,-40); + VectorSet(sentrystand->maxs, 25, 18, 18); + botDebugPrint("mins: %s\n", vtos(sentrystand->mins)); + botDebugPrint("mins: %s\n", vtos(ent->sentry->s.origin)); + botDebugPrint("mins: %s\n", vtos(sentrystand->s.origin)); + botDebugPrint("mins: %s\n", vtos(ent->sentry->s.angles)); + botDebugPrint("mins: %s\n", vtos(sentrystand->s.angles)); + sentrystand->takedamage=DAMAGE_NO; + sentrystand->health = 10; + sentrystand->s.modelindex = gi.modelindex ("models/stand/tris.md2"); + sentrystand->classname = "SentryStand"; + sentrystand->creator = ent->sentry; + sentrystand->standowner = ent; + sentrystand->wf_team = ent->wf_team; +// sentrystand->touch = Sentry_Touch;//Touching it will then pop up a menu + sentrystand->noteamdamage = true; //Don't let teammates damage it + sentrystand->think = Stand_Think; + sentrystand->nextthink = level.time + 0.5; + + gi.linkentity (sentrystand); + ent->sentry->sentry = sentrystand; + } +} + +void UpgradeSentry(edict_t *self) +{ + edict_t *blip; + trace_t tr; + qboolean found; + + blip = NULL; + found = false; +// while (blip = findradius (blip, self->s.origin, 2048)) + while (blip = findradius (blip, self->s.origin, 128)) + { + if (Q_stricmp("SentryGun", blip->classname)) + continue; + if (!self->bot_client) + if (self->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < 60) + {//42 ebc + safe_cprintf(self, PRINT_HIGH, "You need 60 cells to upgrade sentry gun.\n"); + return; + } + + found = true; + + tr = gi.trace (self->s.origin, NULL, NULL, blip->s.origin, self, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + if (blip->creator != self) + continue; + if (blip->count == 3)//42 sbc + safe_cprintf(self, PRINT_HIGH, "Sentry gun already at level 3\n"); + + if (blip->count < 3) + blip->count++; + if(!self->bot_client)//42 + self->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= 60; + if (blip->count == 2) + { + blip->health= 200; + blip->max_health =200; + blip -> s.modelindex = gi.modelindex ("models/sentry/turret2/tris.md2"); + blip->delay = StatusIdle; + blip->s.frame = 0; + blip->yaw_speed = 40; + blip->gib_health = 150;//Max Ammo + blip->light_level = 150;//5/99 suggest removing this whole section and rework/use sentryupdate() + //blip->turretsoundcountdown = 110; + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/pc_up.wav"), 1, ATTN_NORM, 0); + } + if (blip->count == 3) + { + blip->health= 300; + blip->max_health =300; + blip -> s.modelindex = gi.modelindex ("models/sentry/turret3/tris.md2"); + blip->delay = StatusIdle; + blip->s.frame = 0; + blip->yaw_speed = 45; + blip->gib_health = 225;//Max Ammo + blip->light_level = 225;//5/99 suggest removing this whole section and rework/use sentryupdate() + //blip->turretsoundcountdown = 110; + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/pc_up.wav"), 1, ATTN_NORM, 0); + } + } + + if (found == false)//42 sbc + safe_cprintf(self, PRINT_HIGH, "Sorry, you aren't close enough.\n"); +} + +//test_Sentry() - see if there is a sentry close enough to work on + +//Returns 0 if sentry does not exist or is too far away +//Returns 1 if sentry exists and is close enough +int test_Sentry (edict_t *ent) +{ + float dist; + float tdist; + edict_t *sentry; + vec3_t distance; + edict_t *blip; + + if (!ent->sentry) + { + safe_cprintf(ent, PRINT_HIGH, "Sentry not found.\n"); + return 0; + } + + blip = NULL; + dist = 0; + sentry = NULL; + while (blip = findradius (blip, ent->s.origin, 100)) + { + if (strcmp(blip->classname, "SentryGun")) + continue; //Not a sentry gun + + distance[0]=ent->s.origin[0] - blip->s.origin[0]; + distance[1]=ent->s.origin[1] - blip->s.origin[1]; + distance[2]=ent->s.origin[2] - blip->s.origin[2]; + tdist=VectorLength(distance); + if ((dist == 0) || (tdist < dist)) + { + dist = tdist; + sentry = blip; + } + } + if(dist>100 || sentry == NULL) + { + safe_cprintf(ent, PRINT_HIGH, "Sentry too far away.\n"); + return 0; + } + else + { + ent->selectedsentry = sentry; + return 1; + } + +} + +void cmd_Sentry (edict_t *ent) +{ + char *string; + + string=gi.args(); + + if (!ent->client) return; + + //argument = "build", "remove", "upgrade", "repair" and "reload" + if (Q_stricmp ( string, "build") == 0) + { + if (!ent->sentry) + place_turret(ent); + else + safe_cprintf(ent, PRINT_HIGH, "Sentry already exists.\n"); + } + else if (Q_stricmp ( string, "remove") == 0) + { + if (ent->sentry) + turret_remove(ent); + else + safe_cprintf(ent, PRINT_HIGH, "Sentry not found.\n"); + } + else if (Q_stricmp ( string, "upgrade") == 0) + { + if (test_Sentry(ent)) SentryUpgrade(ent, NULL); + } + else if (Q_stricmp ( string, "repair") == 0) + { + if (test_Sentry(ent)) SentryRepair(ent, NULL); + } + else if (Q_stricmp ( string, "reload") == 0) + { + if (test_Sentry(ent)) SentryReload(ent, NULL); + } + + //Otherwise toggle on/off + else if (Q_stricmp ( string, "") == 0) + { + place_turret(ent); + } + else + safe_cprintf(ent, PRINT_HIGH, "Invalid sentry command.\n"); + +} diff --git a/bot_ai.c b/bot_ai.c new file mode 100644 index 0000000..ed811fd --- /dev/null +++ b/bot_ai.c @@ -0,0 +1,2882 @@ +/***************************************************************** + + Eraser Bot source code - by Ryan Feltrin, Added to by Acrid- + + .............................................................. + + This file is Copyright(c) 1998, Ryan Feltrin, All Rights Reserved. + + .............................................................. + + All other files are Copyright(c) Id Software, Inc. + + Please see liscense.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 Eraser, you MUST + include the following text (minus the BEGIN and END lines) in the + documentation for your modification. + + --- BEGIN --- + + The Eraser Bot is a product of Ryan Feltrin, and is available from + the Eraser Bot homepage, at http://impact.frag.com. + + This program is a modification of the Eraser Bot, and is therefore + in NO WAY supported by Ryan Feltrin. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Ryan Feltrin immediately, via + the Eraser Bot homepage. + + --- END --- + + .............................................................. + + You will find p_trail.c has not been included with the Eraser + source code release. This is NOT an error. I am unable to + distribute this file because it contains code that is bound by + legal documents, and signed by myself, never to be released + to the public. Sorry guys, but law is law. + + I have therefore include the compiled version of these files + in .obj form in the src\Release and src\Debug directories. + So while you cannot edit and debug code within these files, + you can still compile this source as-is. Although these will only + work in MSVC v5.0, linux versions can be made available upon + request. + + NOTE: When compiling this source, you will get a warning + message from the compiler, regarding the missing p_trail.c + file. Just ignore it, it will still compile fine. + + .............................................................. + + I, Ryan Feltrin/Acrid-, hold no responsibility for any harm caused by the + use of this source code. I also am NOT willing to provide any form + of help or support for this source code. It is provided as-is, + as a service by me, with no documentation, other then the comments + contained within the code. If you have any queries, I suggest you + visit the "official" Eraser source web-board, at + http://www.telefragged.com/epidemic/. I will stop by there from + time to time, to answer questions and help with any problems that + may arise. + + Otherwise, have fun, and I look forward to seeing what can be done + with this. + + -Ryan Feltrin + -Acrid- + + *****************************************************************/ +// bot_ai.c +#include "g_local.h" +#include "m_player.h" +#include "g_items.h" + +#include "p_trail.h" + +#include "bot_procs.h" + +mmove_t bot_move_attack; + +float last_roam_time; + +void CTFDropFlagTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); +void CTFFlagThink(edict_t *ent); +void CTFGrappleFire (edict_t *ent, vec3_t g_offset, int damage, int effect); + +/* +======================= +bot_roam + + This is the main searching routine, whichs looks around for + enemies or items to head for. + + set "no_paths" to disable all path checking (faster) +======================= +*/ +void bot_roam (edict_t *self, int no_paths) +{ + edict_t *search, *closest, /**enemy,*/ *closest_nonvisible=NULL, *save_goal=NULL, *save_movetarget=NULL; + float closest_dist, this_dist, nonvis_dist=999999; + int player_index = 0; + float save_suicide_time; + int i; + int n, carrying_flag; + edict_t *flag, *enemy_flag; + int flaggoal=false; + +// vec3_t vec; +//testing +// edict_t *blip; +// float dist; +// vec3_t v; +// trace_t tr; +// blip = NULL; +//testing + + if (no_paths) + { + if (self->last_nopaths_roam > (level.time - (0.05*num_players + 0.1))) + return; + self->last_nopaths_roam = level.time; + } + else + { + if (self->last_roam_time > (level.time - (0.05*num_players*3 + 1))) + return; + } + + save_suicide_time = self->bored_suicide_time; + self->bored_suicide_time = -1; // restored only if not found anything + + // look for a visible enemy + closest = closest_nonvisible = NULL; + closest_dist = 800; + + search = players[player_index]; + +// enable the following line to disable bots attacking others +//if (false) + if (no_paths && (!ctf->value || !self->enemy || !CarryingFlag(self->enemy))) + { // don't attack someone else if current enemy has our flag! + carrying_flag = false; + + while (player_index < num_players) + { + if ( !(search->flags & FL_NOTARGET) && +(search != self) && (self->enemy != search) && (!search->disguised)&& +(search->health > 0) && (search->bot_client || (search->light_level > 5)) +&& search->solid && !SameTeam(self, search) +/*|| SameTeam(self, search) && (search->disease)&& +(self->client->player_class == 2)&& +(self->client->pers.inventory[ITEM_INDEX(item_shells)] != 0))*///Acrid Nurse + // we can see this person, is it worth attacking them? + && (!(search->client->invincible_framenum > level.framenum)) + + && ( ((self->client->quad_framenum > level.framenum) + || self->client->invincible_framenum > level.framenum) + || (ctf->value && (carrying_flag = CarryingFlag(search))) + || !self->enemy + || (self->enemy->health > search->health) + || (search->client->pers.weapon == item_blaster))) // if Quad, or enemy is weaker than self + { + if (((carrying_flag) || ((this_dist = entdist(self, search)) < closest_dist)) + && visible(self, search) && CanSee(self, search)) + { // go for em!//fixme testing + botDebugPrint("Attacking %s\n",search->classname);// search->client->pers.netname); + + self->enemy = search; + closest_dist = this_dist; + //I think don't go for any other goal but enemy +if ( (carrying_flag) || (self->movetarget && + ((entdist(self, self->movetarget) > 256) && + ((search->health < 15) || + (search->client->pers.weapon == item_blaster)) && + ((self->bot_fire != botBlaster) && + (self->bot_fire != botShotgun))) || + ((self->bot_stats->aggr/5)*0.2 > random()))) + { + botDebugPrint(" - ABORTING MOVETARGET !!"); + self->movetarget = NULL; + + if (carrying_flag) // go for flag carrier! + break; + } + + botDebugPrint("\n"); + } + else if (this_dist < nonvis_dist) + { + closest_nonvisible = search; + nonvis_dist = this_dist; + } + + } + + search = players[++player_index]; + } + } +/////////////////////////////////////testing +/* if(!self->enemy) + { + + //Reduce range + while (blip = findradius (blip, self->s.origin, 550)) + { + + if (blip->solid == SOLID_NOT) + continue; //don't see observers + + if (blip == self) + continue; + if (blip->wf_team == self->wf_team) + continue; + if (!blip->takedamage) + continue; + if (blip->disguised) + continue; + +/* tr = gi.trace (self->s.origin, NULL, NULL, blip->s.origin, self, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + VectorSubtract (self->s.origin, blip->s.origin, v); + + dist = VectorLength(v); + + if (!visible(self, blip) && dist > 300) + continue;*/ + /* botDebugPrint("Attacking %s\n",blip->classname); + self->enemy = blip; + closest_dist = this_dist; + + self->movetarget = NULL; + if (this_dist < nonvis_dist) + { + closest_nonvisible = blip; + nonvis_dist = this_dist; + } + } + }*/ +/////////////////////////////////////////////////testing + // only go beyond this point a few times per server frame + if (roam_calls_this_frame > 10) + return; + + if (no_paths) + { + roam_calls_this_frame++; + } + else // checking paths + { + self->last_roam_time = level.time; + roam_calls_this_frame += 3; + } + + if (ctf->value && CarryingFlag(self)) + { + // don't go for items if carrying flag, at enemy base, and there is an enemy around +if (CarryingFlag(self) && (((int)wfflags->value & WF_ZOID_FLAGCAP) == 0)//$ + && !(flagreturn1_ent == NULL || flagreturn2_ent == NULL))//$ +{//$ +botDebugPrint("zoid1\n"); + if (self->client->resp.ctf_team == CTF_TEAM1)//$ + {//$ + flag = flagreturn1_ent;//$ + enemy_flag = flag2_ent;//$ + }//$ + else//$ + {//$ + flag = flagreturn2_ent;//$ + enemy_flag = flag1_ent;//$ + }//$ +}//$ +else//$ +{//$ +botDebugPrint("normal1\n"); + if (self->client->resp.ctf_team == CTF_TEAM1) + { + flag = flag1_ent; + enemy_flag = flag2_ent; + } + else + { + flag = flag2_ent; + enemy_flag = flag1_ent; + } +}//$ + // we don't really need anything, and enemy is close + if (self->enemy && (entdist(self, self->enemy) < 384) && (self->health > 25))//acrid fixme && (self->bot_fire != botBlaster)) + return; + + // don't check paths if we have flag, unless at base + if ((entdist(self, flag) > 800) || (flag->solid)) + flaggoal = true; + } + +////////////// look for an item to get//////////////FIXME LOOK HERE ACRID + + if ( (!self->movetarget || (self->movetarget->routes) || + (no_paths && (entdist(self->movetarget, self) > 512))) + // if going for enemy flag, don't look for a new target + && !(self->movetarget && self->movetarget->solid && + self->movetarget->item && + (self->movetarget->item->pickup == CTFPickup_Flag) && + (self->movetarget->count != self->client->resp.ctf_team) && + (entdist(self->movetarget, self) < 600)) + && ((self->bot_fire == botBlaster) || + (((0.3 * self->bot_stats->aggr) / 5.0) < random()))) // if really aggressive, don't always look for items + { + closest_dist = 999999; +//aco +// botDebugPrint("%s looking for an item.. ", self->client->pers.netname); +// if (no_paths) +// botDebugPrint("(no paths)\n"); +// else +// botDebugPrint("(checking paths)\n"); + + self->save_movetarget = self->movetarget; // used in RoamFindBestWeapon() + self->save_goalentity = self->goalentity; + + save_goal = self->goalentity; + save_movetarget = self->movetarget; + + // look for health + this_dist = RoamFindBestItem(self, health_head, !no_paths); + if (this_dist > -1) + { + closest_dist = this_dist; + // go for the item + if (self->health < 10) + { + goto gotgoal; + } + // we kinda need health + else if (self->health < 50) + { + this_dist = this_dist / 3; + } + + save_goal = self->goalentity; + save_movetarget = self->movetarget; + + if (no_paths && (this_dist < 128)) + { // this will do + goto gotgoal; + } + } + + // look for weapons//look here might be able to use this for depots/healing stations + this_dist = RoamFindBestItem(self, weapons_head, !no_paths); + if ((this_dist > -1) && (this_dist < closest_dist)) + { + closest_dist = this_dist; + save_goal = self->goalentity; + save_movetarget = self->movetarget; + + if (no_paths && (this_dist < 128)) + { // this will do + goto gotgoal; + } + } + else // restore goals + { + self->goalentity = save_goal; + self->movetarget = save_movetarget; + } + + // look for bonuses + this_dist = RoamFindBestItem(self, bonus_head, !no_paths); + if ((this_dist > -1) && (this_dist < closest_dist))/* && //acrid + (self->goalentity->wf_team == self->wf_team) + &&(self->goalentity != flag))*///acrid added fixme fixme fixme for armor + { // go for this item instead + + closest_dist = this_dist; + save_goal = self->goalentity; + save_movetarget = self->movetarget; + + if (no_paths && (this_dist < 128)) + { // this will do + goto gotgoal; + }//REF goal->item->pickup == CTFPickup_Flag + }//whole acrid section//FIXME LOOK HERE ACRID +/* else if ((this_dist > -1) && (this_dist < closest_dist) && + (self->goalentity == flag))//acrid added fixme + { // go for this item instead + closest_dist = this_dist; + save_goal = self->goalentity; + save_movetarget = self->movetarget; + botDebugPrint(" flagger\n"); + if (no_paths && (this_dist < 128)) + { // this will do + goto gotgoal; + } + }*/ + else // restore goals + { + self->goalentity = save_goal; + self->movetarget = save_movetarget; + } + + this_dist = RoamFindBestItem(self, ammo_head, !no_paths); + if (this_dist > closest_dist) // revert back to ammo targets + { + self->goalentity = save_goal; + self->movetarget = save_movetarget; + } + else + { + closest_dist = this_dist; + } + +gotgoal: + + if (self->movetarget) + { + if (closest_dist < 999999) + { + if (flaggoal) + { + if (entdist(self->movetarget, self) > 128) + { // restore goals + self->movetarget = self->save_movetarget; + self->goalentity = self->save_goalentity; + } + } + else + { +// botDebugPrint("%s going for %s ", self->client->pers.netname, self->movetarget->classname); +// botDebugPrint("(%i)\n", (int)closest_dist); + } + } + } + } +////////////////////////////////////////////////////////////////////////// + if (no_paths) // don't go passed here + return; + + if (self->movetarget || self->enemy) + {//botDebugPrint("RETURN if self-enemy\n"); + return; + } + + if (ClosestNodeToEnt(self, false, true) == -1) + { // can't get to a node + goto roam_no_node; + } + else if (self->target_ent) + { // no need to roam, just go for the target_ent + return; + } + + // walk around aimlessly, if possible + + // pick a random node and go for it, hopefully once we get there, it'll lead us to sometihng else + for (n=0; n<5; n++) // try 10 different nodes + { + i = (int) ((trail_head-1) * random()) + 1; + + if (!trail[i]->timestamp) + continue; + + if (entdist(self, trail[i]) < 800) + continue; + + if (PathToEnt(self, trail[i], false, false) == -1) + continue; + + if ((PathToEnt_Node->enemy == self) && (PathToEnt_Node->ignore_time > level.time)) + continue; + + botDebugPrint("Roaming towards node #%i\n", i); + + // found a distant node + self->movetarget = trail[i]; + self->goalentity = PathToEnt_Node; + return; + } + +roam_no_node: + + self->last_roam_time = level.time + 2; // don't roam again for a few seconds + self->movetogoal_time = level.time + 2; + botRoamFindBestDirection(self); + + // nothing to do, if we've been lost for long enough, suicide +// self->enemy = self->goalentity = self->movetarget = NULL; + + self->bored_suicide_time = save_suicide_time; +} + + +/* +============= +bot_run + + This is the core thinking routine +============= +*/ +void G_SetClientEffects (edict_t *ent); + +void bot_run (edict_t *self) +{ + float dist; + vec3_t vec; + edict_t *old_goal; +botDebugPrint("frozen #%i\n",self->frozen); +botDebugPrint("frozen time #%i\n",self->frozentime); + if (self->frozen)//botfreeze + return; + + if (self->health <= 0) + { + self->s.modelindex2 = 0; + self->s.modelindex3 = 0; + return; + } + + if (self->waterlevel > 2) + self->flags |= FL_SWIM; // so walkmove() works + else + self->flags &= ~FL_SWIM; + + // fix: somehow non-client's are becoming enemies, which causes crashes + if (self->enemy && !self->enemy->client && + ((strcmp(self->enemy->classname, "SentryGun") != 0) + || (strcmp(self->enemy->classname, "turret") != 0))) + self->enemy = NULL;//Acrid + + CTFEffects(self); + + CTFApplyRegeneration(self); + +/* +if ((self->client->resp.ctf_flagsince > 0) && (self->client->resp.ctf_flagsince > (level.time - 10))) +dist = 0; +*/ + // do Quad Damage AI + if ((self->client->quad_framenum > level.framenum) || self->client->invincible_framenum > level.framenum) + { + bot_roam(self, true); // keep scanning for visible enemies, even though we may be chasing down a current enemy + } + else + { +//Acrid removed old Co code + bot_roam(self, true); +//Acrid removed old Co code + } + + G_SetClientEffects(self); + + // support infinite AMMO + if ((int)dmflags->value & DF_INFINITE_AMMO) + { + if (self->client->ammo_index) + self->client->pers.inventory[self->client->ammo_index] = 999; + } + + // check for taunting + if ((self->s.frame <= FRAME_point12) && (self->s.frame >= FRAME_salute01)) + { + if ((skill->value >= 3) && self->enemy && gi.inPVS(self->s.origin, self->enemy->s.origin)) // abort taunt + { + self->s.frame = FRAME_run1; + } + else + { + bot_ChangeYaw(self); + bot_ChangeYaw(self); + return; + } + } + + // this is the default moving distance per frame + dist = (float) BOT_RUN_SPEED * bot_frametime; + + // ducking + if (self->goalentity && (self->goalentity->maxs[2] < 32) && (self->goalentity->routes)) + { + if (self->maxs[2] != self->goalentity->maxs[2]) + { + if (self->maxs[2] < 32) + { // try to stand + if (CanStand(self)) + self->maxs[2] = self->goalentity->maxs[2]; + } + else // duck + { + self->maxs[2] = self->goalentity->maxs[2]; + } + } + } + + // move slower if ducking + if (self->maxs[2] == 4) + { + dist *= 0.5; + self->viewheight = -2; + } + else { + self->viewheight = 22; +/* + if ((self->waterlevel > 2) || (!self->groundentity && (self->waterlevel > 0))) + { + dist *= 0.75; + } +*/ + } +////////////////////////////////////////////////////////////////////////// + // GRAPPLE + if (self->client->ctf_grapple) + { + vec3_t oldorg; + + // so we don't fire immediately after switching from grapple + self->last_fire = level.time + BOT_CHANGEWEAPON_DELAY; + + VectorCopy(self->s.origin, oldorg); + + CTFGrapplePull(self->client->ctf_grapple); + // set visible model vwep with 3.20 code + ShowGun(self); + // it could have been killed in CTFGrapplePull() + if (self->client->ctf_grapple) + { + // face in direction of grapple + if (self->groundentity) + { + VectorSubtract( self->client->ctf_grapple->s.origin, self->s.origin, vec); + VectorNormalize(vec); + self->s.angles[YAW] = vectoyaw(vec); + } + + if (self->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + { // gravity will be applied in gi.Pmove() + self->velocity[2] += sv_gravity->value*FRAMETIME; + VectorCopy(self->velocity, self->jump_velocity); + // so we move really fast, and don't do any course adjustments + self->groundentity = NULL; + if (CanJump(self)) + { + self->s.origin[2] += 1; + gi.linkentity(self); + } + bot_move(self, dist); + + if (self->client->ctf_grapple) + { + static vec3_t goal_dir, grapple_dir, diff_dir; + static edict_t *nextgoal; + qboolean abort_grapple=false; + + // if grapple is not in direction of goal, abort + if (self->goalentity && (self->groundentity || self->waterlevel > 1) && self->last_movegoal && (self->goalentity->node_type != NODE_LANDING)) + { + VectorSubtract(self->client->ctf_grapple->s.origin, self->s.origin, grapple_dir); + VectorNormalize(grapple_dir); + + VectorSubtract(self->goalentity->s.origin, self->s.origin, goal_dir); + VectorNormalize(goal_dir); + + VectorSubtract(grapple_dir, goal_dir, diff_dir); + + if (VectorLength(diff_dir) > 0.25) + { // see if the next goal is ahead + + abort_grapple = true; + + if (PathToEnt(self->goalentity, self->last_movegoal, false, false) > -1) + { + VectorSubtract(PathToEnt_Node->s.origin, self->s.origin, goal_dir); + VectorNormalize(goal_dir); + + VectorSubtract(grapple_dir, goal_dir, diff_dir); + + if (VectorLength(diff_dir) < 0.25) + { + self->goalentity = PathToEnt_Node; + abort_grapple = false; + } + } + } + } + + if ( (abort_grapple) + || ((fabs(self->client->ctf_grapple->s.origin[2] - (self->s.origin[2])) < 40) && (entdist(self->client->ctf_grapple, self) < (44*(((!self->goalentity) || (self->goalentity->node_type != NODE_LANDING)) + 1)))) + || (self->client->ctf_grapplestart < (level.time - 3))) + { // give up, or drop to floor + CTFResetGrapple(self->client->ctf_grapple); + // set visible model vwep with 3.20 code + ShowGun(self); // update view model + + if (self->goalentity && (PathToEnt(self->goalentity, self->last_movegoal, false, false) > -1)) + { + VectorSubtract(PathToEnt_Node->s.origin, self->s.origin, self->velocity); + VectorNormalize(self->velocity); + VectorScale(self->velocity, 240, self->velocity); + + self->goalentity = PathToEnt_Node; + } + else + { + AngleVectors(self->s.angles, vec, NULL, NULL); + VectorScale(vec, 80, self->velocity); + VectorCopy(self->velocity, self->jump_velocity); + } + + self->goalentity = NULL; + } + } + + return; + } + else // only allow the bot to move while firing grapple under certain conditions + { + if ( (self->waterlevel > 2) + || (self->goalentity && (fabs(self->s.origin[2] - self->client->ctf_grapple->s.origin[2]) < 128))) + { + bot_move(self, dist); + return; + } + + if (self->goalentity && self->goalentity->s.origin[2] > self->s.origin[2]) + return; + } + } + + } + // END GRAPPLE + + // are these still required?? + if (self->enemy == world) + self->enemy = NULL; + if (self->enemy == self) + self->enemy = NULL; + + // teamplay, prevent attacking someone on our team + if (self->enemy && self->enemy->client && SameTeam(self, self->enemy) + && ((strcmp(self->enemy->classname, "SentryGun") != 0) + || (strcmp(self->enemy->classname, "turret") != 0))) + self->enemy = NULL; + +// ACRID NURSE HEALING +/*if((self->client->player_class == 2) && + (self->client->pers.inventory[ITEM_INDEX(FindItem("Shells"))] > 0))*/ + + // this stuff checks our current movetarget, to see if we should still be going for it + if (self->movetarget && (self->movetarget->item)) + { // check that the movetarget is still reachable + + if ( (self->movetarget->solid != SOLID_TRIGGER) // can't get it anymore + // if this item is REALLY desirable, hover around it, waiting for it to respawn + && ( (self->movetarget_want < WANT_SHITYEAH) + || ( (self->movetarget->nextthink > (level.time + 4)) + && ( (self->movetarget->item->pickup != CTFPickup_Flag) + || !CarryingFlag(self)))))//ACRID LOOK HERE FOR FLAG GUARDING + { + self->last_roam_time = 0; + self->movetarget = NULL; + } + else if (self->movetarget->item->pickup == Pickup_Health) + { // check that we still need this health + if (!(self->movetarget->style & HEALTH_IGNORE_MAX)) + if (self->health >= self->max_health) + self->movetarget = NULL; + }//flagref + else if (self->movetarget->item->pickup == CTFPickup_Flag) + { + if (self->movetarget == self->target_ent) + { + if (entdist(self->movetarget, self) < 384) + self->movetarget = NULL; + } + else if (self->movetarget->solid != SOLID_TRIGGER) + { + if (entdist(self->movetarget, self) < 256) + self->movetarget = NULL; + } + } + } + + // check for dead enemy + if (self->enemy && (self->enemy->health <= 0) && (self->groundentity) + && self->enemy->client) + { // enemy is dead, taunt if no enemies around + + self->goalentity = NULL; + + // check for the enemy dropping a weapon + RoamFindBestItem(self, weapons_head, false); + + if ((self->bot_fire != botBlaster) && (entdist(self, self->enemy) < 384) && (random() < 0.5)) + { + vec3_t vec; + int in_range=false; + + VectorSubtract(self->enemy->s.origin, self->s.origin, vec); + + if (!in_range) + { // taunt + float rnd; + edict_t *chat; + + chat = G_Spawn(); + chat->owner = self; + chat->enemy = self->enemy; + chat->think = BotInsultStart; + chat->nextthink = level.time + 1.5 + random(); + + // face towards player + self->ideal_yaw = vectoyaw(vec); + + rnd = random() * 3; + if (rnd < 1) + { + self->s.frame = FRAME_taunt01; + self->radius_dmg = FRAME_taunt17; + } + else if (rnd < 2) + { + self->s.frame = FRAME_salute01; + self->radius_dmg = FRAME_salute11; + } + else + { + self->s.frame = FRAME_point01; + self->radius_dmg = FRAME_point12; + } + + return; + } + + } + + self->enemy = NULL; + + if (!self->movetarget) + { + self->last_roam_time = 0; // force check for new enemy + self->last_nopaths_roam = 0; + self->search_time = 0; + bot_roam(self, false); + } + + } +//////////////////////////////////check for dead end////////////////////// + if (!self->enemy && !self->movetarget && !self->target_ent) + self->goalentity = NULL; + + old_goal = self->goalentity; + + // if roaming aimlessly, and we have found an enemy, aborting roaming + if (self->movetarget && self->movetarget->routes && self->enemy) // we have an enemy! + { + if (PathToEnt(self, self->enemy, false, false) > -1) // make sure we can reach it still + { + self->goalentity = PathToEnt_Node; + self->movetarget = NULL; + } + else // no path, so abort enemy + { + self->enemy = NULL; + } + } +//fixme acrid remove bot blaster dumb lines called alot might be a prob + if ((!self->movetarget || self->movetarget->routes) + && (!self->enemy || (self->bot_fire == botBlaster))) + { + edict_t *oldgoalentity = self->goalentity; + bot_roam(self, false); +//aco if (self->target_ent) + self->goalentity = oldgoalentity; + } + + if (!self->enemy && !self->movetarget && !self->target_ent) + { + botRoamFindBestDirection(self); + bot_move(self, dist); + } + else // we have a goal, so go for it, and attack if necessary + { + // set ideal yaw + if ((self->last_ladder_touch > (level.time - 0.2)) && self->goalentity) + {botDebugPrint("else 1\n"); + VectorSubtract(self->goalentity->s.origin, self->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + } + else if (!self->groundentity && self->goalentity && ((self->goalentity->s.origin[2] - 80) > self->s.origin[2])) + {botDebugPrint("else 2\n"); + VectorSubtract(self->goalentity->s.origin, self->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + } + else if (self->goalentity && self->goalentity->goalentity && self->waterlevel && (self->waterlevel < 3) && (!self->enemy || (entdist(self, self->enemy) > 256))) + {botDebugPrint("else 3\n"); + VectorSubtract(self->goalentity->goalentity->s.origin, self->goalentity->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + } + else if (self->enemy && ((entdist(self, self->enemy) < 600) || (gi.inPVS(self->s.origin, self->enemy->s.origin)))) + { + VectorSubtract(self->enemy->s.origin, self->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + } + else if (self->target_ent && (self->s.frame <= FRAME_stand40) && (entdist(self->target_ent, self) < 600)) + {//self standing still acrid + VectorSubtract(self->s.origin, self->target_ent->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + }//called alot must be >2 + else if (self->goalentity && (entdist(self, self->goalentity) > 2)) + { + VectorSubtract(self->goalentity->s.origin, self->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + } + + bot_ChangeYaw(self); + + bot_MoveAI(self, dist); + + bot_SuicideIfStuck(self); + + if (self->enemy) + { + bot_Attack(self); + } + else + { + if ((self->health > 0) && (self->s.frame >= FRAME_attack1) && (self->s.frame <= FRAME_attack8)) + { // get out of attack frames + self->s.frame = FRAME_run1; + } + } + + } + + if (self->goalentity && (self->goalentity->node_type == NODE_PLAT)) + { // check for platform pausing, while going upwards + if (self->groundentity && (self->groundentity->use == Use_Plat) && + (self->groundentity->moveinfo.state == STATE_UP)) + { // wait for plat to rise/fall + self->bot_plat_pausetime = level.time + 0.3; + goto noabortcheck; + } + } + + // keep track of each time our goalentity changes + if (self->goalentity != self->giveup_lastgoal) + { + self->giveup_lastgoal = self->goalentity; + self->last_reached_trail = level.time; + } + + if (!self->client->ctf_grapple && self->goalentity /*&& self->goalentity->routes*/ + && ((self->last_movegoal != self->enemy) || + (self->last_enemy_sight < (level.time - 0.5)))) // if we've been going for this node for too long, give up + { + if ( ((self->last_reached_trail < (level.time - 2)) && + (entdist(self, self->goalentity) > 128)) + || (self->last_reached_trail < (level.time - 4))) + { +// edict_t *goal; + + if (self->movetarget) + { +//acrid co botDebugPrint("Giving up search for %s\n", self->movetarget->classname); + self->movetarget->ignore_time = level.time + 3; + self->movetarget->enemy = self; + self->movetarget = NULL; + } + + if (self->enemy) + { +//acrid co botDebugPrint("Giving up search for %s\n", self->enemy->client->pers.netname); + self->enemy->ignore_time = level.time + 1; + self->enemy->enemy = self; + self->enemy = NULL; + } + + self->goalentity->ignore_time = level.time + 0.5; + self->goalentity->enemy = self; + self->goalentity = NULL; + + bot_roam(self, false); + + // go for the new target for at least a few seconds + self->last_reached_trail = level.time; + } + } + +noabortcheck: + + if (self->movetarget && !self->movetarget->item && (self->last_roam_time < (level.time - 5))) + { // look for new targets, since we're roaming + bot_roam(self, false); + } + +} + +void bot_ChangeYaw(edict_t *self) +{ // this used to do special handling + M_ChangeYaw(self); +}; + +void BotLadderEnd(edict_t *self) +{ // end of ladder, jump forward + vec3_t dir; + + self->last_ladder_touch = -1; + + AngleVectors(self->s.angles, dir, NULL, NULL); + VectorScale(dir, 240, self->velocity); + self->velocity[2] = 300; + VectorCopy(self->velocity, self->jump_velocity); +}; + +qboolean botTouchingLadder(edict_t *self) +{ + vec3_t org; + + VectorCopy(self->s.origin, org); + + org[2] += 48; + + if (gi.pointcontents(org) & CONTENTS_LADDER) + return true; + + org[0] += 8; org[1] += 8; + if (gi.pointcontents(org) & CONTENTS_LADDER) + return true; + + org[0] += -16; + if (gi.pointcontents(org) & CONTENTS_LADDER) + return true; + + org[1] += -16; + if (gi.pointcontents(org) & CONTENTS_LADDER) + return true; + + org[0] += 16; + if (gi.pointcontents(org) & CONTENTS_LADDER) + return true; + + org[0] -= 8; org[1] += 8; + org[2] -= 48; + if (gi.pointcontents(org) & CONTENTS_LADDER) + return true; + + return false; +}; + +void bot_MoveAI(edict_t *self, int dist) +{ + vec3_t dir, angle; + edict_t *goal=NULL; + int goal_in_range = false; + float goal_dist; + int done_move=false; + +//gi.dprintf("%s moving (%i)\n", self->map, (int) entdist(self, the_client)); + + // check platform riding + if (self->groundentity && + (self->goalentity) && (self->goalentity->node_type == NODE_PLAT)) + { + if (self->groundentity->use == Use_Plat) + { + if (self->groundentity->moveinfo.state == STATE_UP) + { // make sure we keep waiting + vec3_t center; + float length; + + self->bot_plat_pausetime = level.time + 0.3; + + // walk towards the center + VectorCopy(self->goalentity->s.origin, center); + + center[2] = self->s.origin[2]; + + VectorSubtract(center, self->s.origin, dir); + + if ((length = VectorLength(dir)) > 12) + { + VectorNormalize(dir); + M_walkmove(self, vectoyaw(dir), 10); + } + else + { + VectorNormalize(dir); + M_walkmove(self, vectoyaw(dir), length); + } + } + else if (self->groundentity->moveinfo.state == STATE_BOTTOM) + { // move towards the center of the platform + qboolean activator_set=false; + vec3_t center; + + if (!self->activator) // HACK: to walk in ideal_yaw direction, bot_move() likes to see a self->activator + { + self->activator = self->goalentity; + activator_set = true; + } + + // find center of plat, at current Z + VectorSubtract(self->groundentity->maxs, self->groundentity->mins, dir); + VectorMA(self->groundentity->mins, 0.5, dir, center); + center[2] = self->s.origin[2]; + + VectorSubtract(center, self->s.origin, dir); + vectoangles(dir, angle); + self->ideal_yaw = angle[1]; + + bot_move(self, dist); + + if (activator_set) + self->activator = NULL; + } + } + // if it's not ready, wait here for a bit + else if (self->goalentity->target_ent->moveinfo.state != STATE_BOTTOM) + { + self->bot_plat_pausetime = level.time + 0.3; + + if (entdist(self, self->goalentity) < 200) + { + if (self->last_goal && (self->last_goal != self->goalentity)) + { // face towards old goal +// if (entdist(self, self->last_goal) > 10) +// { + VectorSubtract(self->last_goal->s.origin, self->goalentity->s.origin, dir); + VectorNormalize2(dir, dir); + vectoangles(dir, angle); + + self->s.angles[1] = angle[1]; + if (!M_walkmove(self, self->s.angles[1], 10)) + if (!M_walkmove(self, self->s.angles[1]+45, 10)) + if (!M_walkmove(self, self->s.angles[1]-45, 10)) + if (!M_walkmove(self, self->s.angles[1]+100, 10)) + M_walkmove(self, self->s.angles[1]-100, 10); +// } + } + else + { + // move backwards + M_walkmove(self, self->s.angles[1] + 180, 10); + } + } + } + + if (self->bot_plat_pausetime > level.time) + return; + } + else if (self->bot_plat_pausetime > -1) + { // set new goalentity, since we're at the top of the platform +/* + if (self->movetarget) + goal = self->movetarget; + else if (self->enemy) + goal = self->enemy; + else + goal = NULL; + + if (goal && (PathToEnt(self, goal, false, true) > -1)) + { + self->goalentity = PathToEnt_Node; + } +*/ +// self->goalentity = NULL; + self->bot_plat_pausetime = -1; + } + + + // make sure we crouch when we have to + if (self->goalentity && self->goalentity->routes && (self->goalentity->maxs[2] < 32)) + { // ^^ don't set maxs to that of an item + if (self->maxs[2] != self->goalentity->maxs[2]) + { + if (self->maxs[2] < 32) + { // try to stand + if (CanStand(self)) + self->maxs[2] = self->goalentity->maxs[2]; + } + else // duck + { + self->maxs[2] = self->goalentity->maxs[2]; + } + } + } + else if (self->movetarget && (self->goalentity == self->movetarget) && self->movetarget->movetarget + && (self->movetarget->movetarget->maxs[2] < 32)) + { // duck + self->maxs[2] = 4; + } + else if (self->maxs[2] < 32 && (self->crouch_attack_time < level.time)) + { + if (CanStand(self)) + self->maxs[2] = 32; + } + + if (!self->groundentity && !self->waterlevel) + { // in-air, also check for ladder movement + vec3_t dir; + + if ((self->goalentity) && ((self->goalentity->s.origin[2]+8) > self->s.origin[2]) + && botTouchingLadder(self)) + { + AngleVectors(self->goalentity->s.angles, dir, NULL, NULL); + VectorScale(dir, 48, self->velocity); + self->velocity[2] = 310; + + self->last_ladder_touch = level.time; + } + else if (self->last_ladder_touch > (level.time - 0.3)) + { + BotLadderEnd(self); + } + + bot_move(self, dist); + return; + } + else if (self->last_ladder_touch > (level.time - 0.3)) + { + BotLadderEnd(self); + + bot_move(self, dist); + return; + } + + if (self->activator) + { // going for a button + + if (self->activator_time < (level.time - 5)) + { // abort it + self->activator = self->goalentity = NULL; + } +/* + else if ((self->activator->moveinfo.state == STATE_BOTTOM) && !CarryingFlag(self)) + { + float dist1; + vec3_t postvec; + + VectorSubtract(self->activator->absmin, self->s.origin, dir); + dist1 = VectorLength(dir); + VectorNormalize2(dir, dir); + + self->ideal_yaw = vectoyaw(dir); + + bot_move(self, dist); + + // did we move towards the button? + VectorSubtract(self->activator->absmin, self->s.origin, postvec); + if (VectorLength(postvec) >= dist1) + self->activator = NULL; + + return; + } + else + { + self->activator = self->goalentity = NULL; + } +*/ + // get to the button + goal = self->activator; + + goto got_goal; + } + + // We're kinda lost, so walk in the ideal_yaw direction, and randomly jump if stuck + if (self->movetogoal_time > level.time) + { + vec3_t oldorg; + + VectorCopy(self->s.origin, oldorg); + + bot_move(self, dist); + + VectorSubtract(self->s.origin, oldorg, oldorg); + + // jump randomly, so as not to get hung up + if (VectorLength(oldorg) < 3) + { + self->movetogoal_time = 0; +/* + if ((random() < 0.5) && (CanJump(self))) + { +botDebugPrint("MoveToGoal: random jump\n"); + botRandomJump(self); + } +*/ + } + + return; + } + + // HACK, make sure we head for home if we have the flag! + if (ctf->value && CarryingFlag(self))// && +// (!self->movetarget || !self->movetarget->item || (self->movetarget->item->pickup != CTFPickup_Flag))) + { + edict_t *flag, *enemy_flag, *plyr; + int i=0, count=0, ideal; + static float last_checkhelp=0; +if (CarryingFlag(self) && (((int)wfflags->value & WF_ZOID_FLAGCAP) == 0)//$ + && !(flagreturn1_ent == NULL || flagreturn2_ent == NULL))//$ +{//$ + botDebugPrint("zoid2\n"); + if (self->client->resp.ctf_team == CTF_TEAM1)//$ + {//$ + flag = flagreturn1_ent;//$ + enemy_flag = flag2_ent;//$ + }//$ + else//$ + {//$ + flag = flagreturn2_ent;//$ + enemy_flag = flag1_ent;//$ + }//$ +}//$ +else//$ +{//$ + botDebugPrint("normal2\n"); + if (self->client->resp.ctf_team == CTF_TEAM1) + { + flag = flag1_ent; + enemy_flag = flag2_ent; + } + else + { + flag = flag2_ent; + enemy_flag = flag1_ent; + } +}//$ + // look for some helpers +// if (last_checkhelp < (level.time - 0.5)) + { + for (i=0; iclient->resp.ctf_team != self->client->resp.ctf_team) + { + if ( (plyr->enemy != self) +// && (!plyr->target_ent || (plyr->target_ent->think != CTFFlagThink) || (entdist(plyr, plyr->target_ent) > 1000)) + && (entdist(plyr, self) < 2000)) + { // send this enemy to us + plyr->enemy = self; + } +// continue; + } + else if ((plyr != self) && (plyr->target_ent == self)) + count++; + } + + ideal = ((int)ceil((1.0*(float)num_players)/4.0)); + + if (count < ideal) + { + for (i=0; (iclient->resp.ctf_team != self->client->resp.ctf_team) + continue; + + if (plyr->target_ent == self) + continue; + + if (entdist(plyr, self) > 700) + continue; + + if (!gi.inPVS(plyr->s.origin, self->s.origin)) + continue; + + plyr->target_ent = self; + if (++count >= ideal) + break; + } + } + else if (count > ideal) // release a defender + { + for (i=0; (iclient->resp.ctf_team != self->client->resp.ctf_team) + continue; + + if (plyr->target_ent != self) + continue; + + plyr->target_ent = NULL; + break; + } + } + + last_checkhelp = level.time + random()*0.5; + } + //flagref + if ((flag->solid == SOLID_TRIGGER) || (entdist(self, flag) > 700) + || CarryingFlag(self))// FLAG CAPPING FIX ACRID + { + if ( (!self->movetarget) + || ( (self->movetarget != flag) + && ( (entdist(self->movetarget, self) > 128) + || (entdist(self, enemy_flag) < 1000)))) + { + + self->movetarget = flag; + } + + if (self->movetarget == flag) + { + if ((self->target_ent == flag) && (flag->solid == SOLID_TRIGGER)) + self->target_ent = NULL; + botDebugPrint("movetarg flag2\n"); + goal = flag; + goto got_goal; + } + } + else if (self->movetarget && self->movetarget->item && + (self->movetarget->item->pickup == CTFPickup_Flag)) // do something else? + { + self->movetarget = NULL; + } + + self->target_ent = NULL; + } + + // ------------------------------------------------------------ + // OK, now choose which goal to head for (enemy or movetarget) + // and go for it. + + goal = NULL; + + // always head for flag carrier //fixme acrid pain bots getting attacked by other players + if (self->enemy && !CarryingFlag(self) && CarryingFlag(self->enemy)) + { + goal = self->enemy; + self->last_seek_enemy = level.time; + + goto got_goal; + } + + // start off with the movetarget, then overwrite if necessary + if (self->movetarget) + { + goal = self->movetarget; + + if (goal->touch == CTFDropFlagTouch) + goto got_goal; + else if (((strcmp(goal->classname, "item_flagreturn_team1") == 0) ||//$ + (strcmp(goal->classname, "item_flagreturn_team2") == 0)) &&//$ + CarryingFlag(self) && !(flagreturn1_ent == NULL || flagreturn2_ent == NULL))//$ + goto got_goal;//$ + else if (goal->item && (goal->item->pickup == CTFPickup_Flag) && goal->solid && (goal->count != self->client->resp.ctf_team) && (entdist(goal, self) < 1000)) + goto got_goal; + } + + if (self->enemy && (!CarryingFlag(self) || !goal) + && ((!ctf->value || !self->movetarget || !self->movetarget->item || (self->movetarget->item->pickup != CTFPickup_Flag)))) // attacking enemy, may be going for an item also, decide which to go for + { + if (! ( (goal) + && ( (self->movetarget_want >= WANT_SHITYEAH) + || (self->bot_fire == botBlaster) + || (self->bot_fire == botShotgun) + || ((goal_dist = entdist(self->movetarget, self)) < 512) + || (self->enemy->health > self->health) + || (goal_dist < entdist(self, self->enemy)) + ) + ) + || ( ctf->value + && (HomeFlagDist(self) < 1500)) // defend base! + ) + { // enemy is best goal + goal = self->enemy; + self->last_seek_enemy = level.time; + } + } + // check for team goal + if ( (self->target_ent && !CarryingFlag(self)) + && (!self->enemy || !CarryingFlag(self->enemy)) + && ( !goal + || (self->target_ent->client) + || ( (goal == self->movetarget) + && ((entdist(goal, self->target_ent) / self->movetarget_want) > BOT_GUARDING_RANGE)))) + { + + if (self->target_ent->client) + { + if (self->target_ent->health <= 0) + { // leader is dead, resume normal roaming + self->target_ent = NULL; + } + else if (!self->movetarget || (entdist(self->target_ent, self->movetarget) > 400)) // must follow leader! + { + if (self->movetarget) + { + self->movetarget->ignore_time = level.time + 2; + self->movetarget = NULL; + } + + goal = self->target_ent; + } + } + else if (self->target_ent->item) // defending an item (CTF flag, etc) + { + if (self->bot_fire == botBlaster) + { + } + if ((entdist(self, self->target_ent) > BOT_GUARDING_RANGE) || + !gi.inPVS(self->s.origin, self->target_ent->s.origin)) + { // go to our target_ent + goal = self->target_ent; + self->movetarget = NULL; + } + else // hang around for a bit + { + self->group_pausetime = level.time + 1; + self->checkstuck_time = level.time; + + if (self->movetarget == self->target_ent) + self->movetarget = NULL; + } + } + + } +got_goal: + + // flagpath stuff + if (self->flagpath_goal) + {botDebugPrint("flaggoalpath\n"); + if (CarryingFlag(self)) + goal = self->flagpath_goal; + else + self->flagpath_goal = NULL; + } + // end: flagpath stuff + + self->last_movegoal = goal; + + // if enemy has moved out of view, look for a new goal + if ( (goal) + && (goal->client)//fixme? + && (self->goalentity == goal) && !visible_box(self, goal)) + { + botDebugPrint("goal->client\n"); + self->goalentity = NULL; + } + + // ------------------------------------------------------------ +////////////////////////////////////////////////////////////////////////// + // check for a node jump + if (goal && self->goalentity && self->goalentity->routes + && self->goalentity->goalentity) + if (entdist(self->goalentity, self) < 32)//fix this? + if (CanJump(self)) + { + if ((PathToEnt(self->goalentity, goal, false, false) > -1) && + (PathToEnt_Node == self->goalentity->goalentity)) + { // jumping from here, will get us closer to the goal + vec3_t jump_vec, org; + trace_t trace; + int i; + + VectorCopy(self->goalentity->velocity, jump_vec); + jump_vec[2] = 0; + + // grapple?//Acrid FIXME this is the prob + if ((self->goalentity->node_type == NODE_NORMAL)//FIXME ACRID WAS NODE_GRAPPLE +&& (self->goalentity->s.origin[2] <= PathToEnt_Node->s.origin[2])) +//&& ((self->client->player_special & SPECIAL_GRAPPLE) != 0))//future fix + { + // find the target point, and face towards it + VectorScale(self->goalentity->velocity, 8000, org); + VectorAdd(self->goalentity->s.origin, org, org); + trace = gi.trace(self->goalentity->s.origin, NULL, NULL, org, NULL, MASK_SOLID); + + VectorSubtract(trace.endpos, self->s.origin, jump_vec); + VectorNormalize(jump_vec); + + vectoangles(jump_vec, self->client->v_angle); + CTFGrappleFire (self, vec3_origin, 10, 0); + // set visible model vwep with 3.20 code + ShowGun(self); + + self->goalentity = PathToEnt_Node; + return; + } + + if (self->movetarget && (entdist(self, self->movetarget) < 64)) + { // movetarget is in range, so jump towards it + VectorSubtract(self->movetarget->s.origin, self->goalentity->s.origin, dir); + dir[2] = 0; + VectorScale(dir, 2, dir); + } + else if (VectorLength(jump_vec) > 80) + { // the velocity of the jump node is good enough + VectorCopy(self->goalentity->velocity, dir); + } + else + { // just jump in the direction we're going +//Acrid removed old co code + + if ((self->goalentity->goalentity->s.origin[2] - 64) < self->goalentity->s.origin[2]) + { + if (self->goalentity->waterlevel && !self->goalentity->goalentity->waterlevel) + { // coming out of water, jump in the direction the goal is facing + AngleVectors(self->goalentity->s.angles, dir, NULL, NULL); + VectorScale(dir, 240, dir); + dir[2] = 310; + goto gotvel; + } + else + { + VectorSubtract(self->goalentity->goalentity->s.origin, self->goalentity->s.origin, jump_vec); + } + } + else // must be a ladder + { + AngleVectors(self->goalentity->s.angles, jump_vec, NULL, NULL); + VectorScale(jump_vec, 10, jump_vec); + } + + jump_vec[2] = 0; + + if (VectorLength(jump_vec) > 8) + VectorCopy(jump_vec, dir); + else + { + AngleVectors(self->s.angles, dir, NULL, NULL); + } + + dir[2] = 0; + VectorNormalize2(dir, dir); + VectorScale(dir, 50, dir); +// } + } + + dir[2] = 0; + if (VectorLength(dir) > 300) + { + VectorNormalize2(dir, dir); + VectorScale(dir, 300, dir); + } + else if (VectorLength(dir) < 20) + { + VectorNormalize2(dir, dir); + VectorScale(dir, 20, dir); + } + + dir[2] = self->goalentity->velocity[2]; + +gotvel: + + if (dir[2] < 80) + { // if directly ahead is blocked we should jump higher + static vec3_t joffs = {0,0,8}; + vec3_t vec, org; + + VectorAdd(self->s.origin, joffs, org); + VectorCopy(dir, vec); + vec[2] = 0; + VectorNormalize2(vec, vec); + VectorScale(vec, 32, vec); + VectorAdd(org, vec, vec); + trace = gi.trace(org, VEC_ORIGIN, VEC_ORIGIN, vec, self, MASK_SOLID); + + if (trace.fraction < 1) + dir[2] = 310; + else if ((self->goalentity->goalentity->s.origin[2] + 96) < self->goalentity->s.origin[2]) + { + VectorSubtract(self->goalentity->goalentity->s.origin, self->goalentity->s.origin, jump_vec); + jump_vec[2] = 0; + + if (VectorLength(jump_vec) < 96) + { + VectorNormalize2(jump_vec, dir); + VectorScale(dir, 150, dir); + dir[2] = 160; + } + } + } + else + { + dir[2] += 80; + } + + VectorCopy(dir, self->velocity); + + if (self->velocity[2] > 191) + { + self->velocity[2] = 300; + } + else // ladder? + { + if (botTouchingLadder(self) && (self->goalentity->goalentity->s.origin[2] > self->goalentity->s.origin[2])) + { + self->velocity[2] = 300; + } + else if (self->velocity[2] < 40) + { + self->velocity[2] = 40; + } + } + + VectorCopy(self->goalentity->s.origin, org); +// org[2] += 1; + trace = gi.trace(org, self->mins, self->maxs, org, self, MASK_PLAYERSOLID); + + if (trace.fraction == 1) + VectorCopy(org, self->s.origin); + + self->groundentity = NULL; + + gi.linkentity(self); + + VectorCopy(self->velocity, self->jump_velocity); + + // make sure once we land, we go for the next node + if (((i = PathToEnt(self->goalentity->goalentity, goal, false, false)) > -1) && (PathToEnt_Node->s.origin[2] > self->s.origin[2])) + self->goalentity = PathToEnt_Node; + else /// just head for the landing node + self->goalentity = self->goalentity->goalentity; + + if (self->velocity[2] > 200) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, 2, 0); + } +//botDebugPrint("Trail jump: %s\n", vtos(self->velocity)); + + return; + } + else // there is a better route than jumping + { + self->goalentity = PathToEnt_Node; + } + } + // if we've found a valid goal, make sure we have a visible destination (goalentity) + if (goal) + { + if (!self->enemy && (self->target_ent == self)) + { + if (self->group_pausetime < level.time) + { // start this leader on a path of destruction + self->target_ent = NULL; + } + else // hang around for a bit + { + return; + } + } +//ref(self->client->pers.weapon != item_blaster) + // Squadron following + if (goal == self->target_ent) + { +//Acrid removed old co code here flag thinking + if ((goal_dist = entdist(goal, self)) < (BOT_IDEAL_DIST_FROM_ENEMY / 2) + && (self->client->pers.weapon != item_knife)) + { + // too close, move back + vec3_t vec; +//fixme acrid look here for attacking sentrys distance + self->goalentity = NULL; + botDebugPrint("ideal dis 1\n"); + VectorSubtract(self->s.origin, goal->s.origin, vec); + VectorNormalize2(vec, vec); + self->ideal_yaw = vectoyaw(vec); + + bot_move(self, dist); + } + + // if in proximity, then hang around for a bit longer + if (goal_dist < (BOT_IDEAL_DIST_FROM_ENEMY * (1+((goal->think == CTFFlagThink)*2))) + && (self->client->pers.weapon != item_knife)) + { botDebugPrint("ideal dis 3\n"); + if (goal->client && self->movetarget) + self->movetarget = NULL; + self->group_pausetime = level.time + 0.2; // don't check_stuck + return; + } + + // otherwise, move towards leader as you would any other item + } + + if (!self->goalentity) + { // find a path damnit! + if (PathToEnt(self, goal, false, false) > -1) + { + self->goalentity = PathToEnt_Node; + } + else // no path, so stop looking for this "thing" + { + if (goal == self->movetarget) + self->movetarget = NULL; + else if (goal == self->enemy) + self->enemy = NULL; + else if (goal == self->target_ent) + self->target_ent = NULL; + } + } + else if ((self->goalentity == self->enemy) && + ((goal_dist = entdist(self->enemy, self)) < BOT_IDEAL_DIST_FROM_ENEMY) + && (self->client->pers.weapon != item_knife)) + { + // in range + if (goal_dist < (BOT_IDEAL_DIST_FROM_ENEMY / 2)) + { + // too close, move back + vec3_t vec; + botDebugPrint("ideal dis 4\n"); + self->goalentity = NULL; + + VectorSubtract(self->s.origin, self->enemy->s.origin, vec); + VectorNormalize2(vec, vec); + self->ideal_yaw = vectoyaw(vec); + + bot_move(self, dist); + } + + return; + } + } + else // no goal, so clear goalentity and get out of here! + { + self->goalentity = NULL; + return; + } +////////////////////////////////////////////////////////////////////////// + // ********************************************************* + // + // This is the MAIN movement AI + // + // If we have a goalentity, walk towards it, when we reach it, + // find the next goal for the next frame + + if ( (self->goalentity) // if we don't have a goalentity, then no point trying to go anywhere + && ( ( (self->last_inair < level.time) // if just landed check for a new goalentity from landing position + || (self->goalentity->s.origin[2] <= self->s.origin[2]) + || (!visible_box(self, self->goalentity)) + || ( (PathToEnt(self->goalentity, goal, false, false) > -1) + && (visible_box(self, PathToEnt_Node)) + && (self->goalentity = PathToEnt_Node))) + + // we need a new goalentity, since we've just landed + || ( (PathToEnt(self, goal, false, false) > -1) + && (self->goalentity = PathToEnt_Node)) + || ((self->goalentity = NULL) && false))) // couldn't find a path, so clear goalentity + { // going for an item, so follow the set path + + VectorSubtract(self->goalentity->s.origin, self->s.origin, dir); + if (self->goalentity->node_type == NODE_PLAT) // could be riding the plat + dir[2] = 0; + vectoangles(dir, angle); + + if (VectorLength(dir) <= (dist)) + { + dist = VectorLength(dir); + goal_in_range = true; + } + + // do the actual movement + bot_move(self, dist); + + if (self->goalentity) // goalentity could have been cleared during bot_move() + { + if (goal_in_range) + { // see if we've reached the marker + if (bot_ReachedTrail(self)) + { +//f botDebugPrint("reached goal (%i)\n", self->goalentity->trail_index); + if ( !( (PathToEnt(self->goalentity, goal, false, false) > -1) && (self->goalentity = PathToEnt_Node))) + { + self->goalentity = NULL; + //f botDebugPrint("Couldn't find next goal, roaming\n"); + bot_roam(self, false); + } + else + { +//f botDebugPrint("heading for goal (%i)\n", self->goalentity->trail_index); + + // if goal is plat, and it's no ready, wait here for a bit + if ( (self->goalentity->node_type == NODE_PLAT) + && (self->goalentity->target_ent->moveinfo.state != STATE_BOTTOM)) + { + // stay here for a bit + self->bot_plat_pausetime = level.time + 0.5; + + // move backwards + M_walkmove(self, self->s.angles[1] + 180, 10); + } + + // use grapple to get there faster?//FIXME ACRID + + else if ( (bot_tarzan->value)/*(grapple->value || ctf->value)*///acrid coed and added tarzan + && (bot_tarzan->value || (goal->item && goal->item->pickup == CTFPickup_Flag) /*CarryingFlag(self)*/) + && ((self->waterlevel > 2) || (self->last_enemy_sight < (level.time - 1)) || !self->enemy || (entdist(self, self->enemy) > 512)) + && (PathToEnt(self->goalentity, goal, false, false) > -1)) + { + static vec3_t goaldir, nextgoaldir, diffdir; + + + VectorSubtract(self->goalentity->s.origin, self->s.origin, goaldir); + VectorSubtract(PathToEnt_Node->s.origin, self->s.origin, nextgoaldir); + + if (VectorLength(nextgoaldir) > 300) + { + VectorNormalize(goaldir); + VectorNormalize(nextgoaldir); + + VectorSubtract(nextgoaldir, goaldir, diffdir); + + if (VectorLength(diffdir) < 0.4) + { + trace_t tr; + + VectorScale(nextgoaldir, 1024, diffdir); + VectorAdd(self->s.origin, diffdir, diffdir); + + tr = gi.trace(self->s.origin, NULL, NULL, diffdir, self, MASK_PLAYERSOLID); + + if ((tr.fraction < 1) && (tr.fraction > 0.4) && (tr.ent == world)) + { // fire away + vectoangles(nextgoaldir, self->client->v_angle); + CTFGrappleFire (self, vec3_origin, 10, 0); + // set visible model vwep with 3.20 code + ShowGun(self); + } + } + } + } + } + } + } + } + else // goalentity has been whiped + { + return; + } + + if (self->movetarget && !self->movetarget->item && (self->last_roam_time < (level.time - 3))) + { // walking around aimlessly, check for new targets every 3 seconds + self->movetarget = NULL; + bot_roam(self, false); + } + } + else + { + bot_move(self, dist); // just go anywhere + } + + // ********************************************************* + + if ((self->flags & FL_SHOWPATH) && (goal)) + { // show path to goal + Debug_ShowPathToGoal(self, goal); + } +}; + +float bot_ReachedTrail(edict_t *self) +{ + vec3_t vec; + int retval = false; + + VectorSubtract(self->s.origin, self->goalentity->s.origin, vec); + + if (retval = (abs(vec[2]) < 16)) + { + vec[2] = 0; + retval = (VectorLength(vec) < 12); + } + + // check if this is a plat node, and the plat is not responding + if (self->goalentity->node_type == NODE_PLAT) + { + if ( (!self->goalentity->target_ent) + || ( (self->groundentity != self->goalentity->target_ent) // not standing on the platform + && (self->goalentity->target_ent->moveinfo.state != STATE_BOTTOM))) // and it's not waiting + { // give up searching for this ent + edict_t *goal = NULL; + + botDebugPrint("Aborting plat node\n"); + + if (self->movetarget) + goal = self->movetarget; + else if (self->enemy) + goal = self->enemy; + + // make sure nothing else searches for this ent for a while + if (goal) + { + goal->ignore_time = level.time + 0.5; + goal->enemy = self; + } + + self->goalentity->ignore_time = level.time + 0.2; + self->movetarget = self->enemy = NULL; + + bot_roam(self, false); + + return false; + } + else if ( (self->groundentity == self->goalentity->target_ent) + && (self->goalentity->target_ent->moveinfo.state == STATE_TOP)) + { // standing on the platform, and it's reached the top + return true; + } + + } + + if (retval || (self->client->reached_goal_time == level.time)) + { // ignore the current node for a bit + if ((self->goalentity->enemy != self) || (self->goalentity->ignore_time < (level.time - 3))) + self->last_reached_trail = level.time; + + self->goalentity->enemy = self; + self->goalentity->ignore_time = level.time + 0.2; + + // standing/crouching + if (self->goalentity->routes) + { + if (self->maxs[2] != self->goalentity->maxs[2]) + { + if (self->maxs[2] < 32) + { // try to stand + if (CanStand(self)) + self->maxs[2] = self->goalentity->maxs[2]; + } + else // duck + { + self->maxs[2] = self->goalentity->maxs[2]; + } + } + } + + self->client->reached_goal_time = 0; + retval = true; + } + + return retval; +} + +extern qboolean touched_player; + +// Simulated movement physics, adapted from player-like movement +int bot_move(edict_t *self, float dist) +{ + trace_t trace; + vec3_t dir, vec1/*, vec2*/, dest, dropdest; //, save_vel, save2; + usercmd_t ucmd; // fake ucmd for client emulation + vec3_t angles, oldorg, move; + int ret, got_dir=false; + qboolean going_for_goalentity=false; + edict_t *last_groundentity; + float save_nocloser; + int start_waterlevel; + + + //botfreeze 3/99 + if (level.time < self->frozentime) + return false; + + + // only restore last_move_nocloser if we don't move any closer + save_nocloser = self->last_move_nocloser; + self->last_move_nocloser = level.time; +/* + // make sure we crouch when we have to + if (self->goalentity && !self->goalentity->item && (self->goalentity->maxs[2] < 32)) + { // don't set maxs to that of an item + self->maxs[2] = self->goalentity->maxs[2]; + } + else if (self->movetarget && (self->goalentity == self->movetarget) && self->movetarget->movetarget + && (self->movetarget->movetarget->maxs[2] < 32)) + { + self->maxs[2] = self->movetarget->movetarget->maxs[2]; + } +*/ + +// BEGIN, SABIN code: prepare the ucmd for client simulation, but we have to clear the structure manually, since we don't have actual client's do it for us + // delta_angles must be manually cleared + VectorClear (self->client->ps.pmove.delta_angles); // otherwise the actual facing direction may be modified prior to movement + + memset (&ucmd, 0, sizeof (ucmd)); +// END, SABIN code + + ucmd.msec = 100; // bot's think every 100 ms +//acrid fixme look here for speed + if (dist < BOT_RUN_SPEED*FRAMETIME) + ucmd.forwardmove = sv_maxvelocity->value * 0.5; // walking speed + else + { + if ((self->waterlevel > 2) || (!self->groundentity && + (self->waterlevel > 0))) + ucmd.forwardmove = sv_maxvelocity->value * 0.6; // go slower if swimming + else + ucmd.forwardmove = sv_maxvelocity->value; + } + + if (self->waterlevel && ((self->air_finished - 2.5) < level.time) + && ( !self->movetarget || !self->movetarget->item +|| (entdist(self, self->movetarget) > (384 * ((self->movetarget->item->pickup == CTFPickup_Flag) + 1))))) // go for air! + { + // can we get to air? + VectorCopy(self->s.origin, dest); + dest[2] += 512; + + trace = gi.trace(self->s.origin, NULL, NULL, dest, self, MASK_PLAYERSOLID); + + VectorCopy(trace.endpos, dest); + dest[2] -= 1; + + if (botTouchingLadder(self) || !(gi.pointcontents(dest) & MASK_WATER)) + ucmd.upmove = sv_maxvelocity->value; + } + else if (!self->waterlevel && self->goalentity && +(self->goalentity->node_type == NODE_LANDING) && +(self->goalentity->s.origin[2] > self->s.origin[2]+64)) + {//the jumping fixme + ucmd.upmove = sv_maxvelocity->value; + } + else if (!self->groundentity && (!self->waterlevel))// && self->goalentity && ((self->goalentity->s.origin[2]+32) > self->s.origin[2])) + { + ucmd.upmove = sv_maxvelocity->value; + } + else if (!self->groundentity && !self->waterlevel && self->goalentity) + { // ladder? + vec3_t vec; + + VectorSubtract(self->s.origin, self->goalentity->s.origin, vec); + vec[2] = 0; + + if ((self->goalentity->s.origin[2] > self->s.origin[2]) && (VectorLength(vec) < 128)) + ucmd.upmove = sv_maxvelocity->value; + } + + + VectorCopy(self->s.origin, oldorg); + VectorCopy(self->s.angles, angles); + + if (self->avoid_ent && !self->avoid_ent->inuse) + self->avoid_ent = NULL; + + if (self->groundentity || self->waterlevel) + { + // find walking direction + if (self->avoid_ent) + { + if (self->avoid_dir_time > level.time) + { + VectorCopy(self->avoid_dir, dir); + got_dir = true; + } + // find a direction away from danger + else if (!(ret = botJumpAvoidEnt(self, self->avoid_ent))) + { + // move away from danger + VectorSubtract(self->s.origin, self->avoid_ent->s.origin, dir); + self->avoid_dir_time = level.time + 0.5; + got_dir = true; + } + else if (ret == 1) // successful jump from danger + { + if (self->groundentity) + { + VectorCopy(self->avoid_dir, dir); + got_dir = true; + } + return true; + } + } + + if (!got_dir) + { + if (self->goalentity && !self->activator) + { + VectorSubtract(self->goalentity->s.origin, self->s.origin, dir); + + if (VectorLength(dir) < 32) + { // move straight to the goalentity + VectorCopy(dir, self->velocity); + + // convert to "per second" + VectorScale(self->velocity, 10, self->velocity); + } + + VectorNormalize2(dir, vec1); + going_for_goalentity = true; + } + else + { + VectorClear(dest); + dest[1] = self->ideal_yaw; + AngleVectors(dest, dir, NULL, NULL); + } + } + +// dir[2] = 0; + + VectorNormalize2(dir, dir); + vectoangles(dir, angles); + } + else if (!self->client->ctf_grapple) + { + if (self->velocity[2] > 310) + self->velocity[2] = 310; + + // have we gone passed the goalentity? if so slow down velocity + if (self->goalentity && (fabs(self->s.origin[2] - self->goalentity->s.origin[2]) > 16)) + { + VectorCopy(self->velocity, dir); + dir[2] = 0; + + if (VectorLength(dir) > 8) + { + VectorNormalize(dir); + VectorSubtract(self->goalentity->s.origin, self->s.origin, dest); + dest[2]=0; + + VectorNormalize2(dest, vec1); + + VectorSubtract(dir, vec1, dir); + + if ((VectorLength(dest) < 64) && (VectorLength(dir) > 1.5)) + { // slow down! + self->velocity[0] = self->jump_velocity[0] = 0; + self->velocity[1] = self->jump_velocity[1] = 0; + } + } + } + + } + +// BEGIN, SABIN code: convert angles to 16-bit int's, and pass them into the ucmd for simulation + ucmd.angles[PITCH] = ANGLE2SHORT(angles[PITCH]); + ucmd.angles[YAW] = ANGLE2SHORT(angles[YAW]); + ucmd.angles[ROLL] = ANGLE2SHORT(angles[ROLL]); +// END, SABIN code + + last_groundentity = self->groundentity; + start_waterlevel = self->waterlevel; + + BotMoveThink(self, &ucmd); + + VectorSubtract(self->s.origin, oldorg, move); + + if ((!start_waterlevel) && !self->groundentity && last_groundentity) + { + float drop_dist=400; + + VectorCopy(self->velocity, self->jump_velocity); // just to be safe + +// if our goal is close by, and is slightly above, jump//fixme??? + if (self->goalentity /*&& (entdist(self, self->goalentity) < 256)*/ + && (self->s.origin[2] < (self->goalentity->s.origin[2] /*- 48*/))) + { + if (self->s.origin[2] < (self->goalentity->s.origin[2]+32)) // only jump if goal is above us + { + // don't jump if ground is closeby + trace_t tr; + + VectorCopy(self->s.origin, dropdest); + dropdest[2] -= 20; + + tr = gi.trace(self->s.origin, self->mins, self->maxs, dropdest, self, MASK_PLAYERSOLID | CONTENTS_LAVA | CONTENTS_SLIME); +//lavacode + if ((tr.fraction == 1) || (tr.plane.normal[2] < 0.5) || (tr.contents && (CONTENTS_LAVA | CONTENTS_SLIME))) + { // jump to be safe + + VectorSubtract(self->goalentity->s.origin, self->s.origin, self->velocity); + VectorScale( self->velocity, 1.5, self->velocity); + self->velocity[2] = 0; + + if (VectorLength(self->velocity) > 300) + { + VectorNormalize(self->velocity); + VectorScale(self->velocity, 300, self->velocity); + } + + self->velocity[2] = 310; + VectorCopy(self->velocity, self->jump_velocity); + + gi.sound(self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, 2, 0); + } + } + } + else + { + + if (self->goalentity) + { + if (self->goalentity->s.origin[2]+64 < self->s.origin[2]) + drop_dist= 200 + (-1 * (self->goalentity->s.origin[2] - self->s.origin[2])); + else if (self->goalentity->s.origin[2] > self->s.origin[2]) + drop_dist = 22; + } +//lavacode + // if monster had the ground pulled out, go ahead and fall, but only if safe (No lava or slime) + // trace downwards, to see if it's dangerous + VectorSubtract(self->s.origin, tv(0,0,drop_dist), dropdest); + VectorMA(dropdest, 0.3 * (drop_dist/400), self->velocity, dropdest); + trace = gi.trace(self->s.origin, VEC_ORIGIN, VEC_ORIGIN, dropdest, self, MASK_SOLID | MASK_WATER); + + VectorCopy(trace.endpos, dest); + dest[2] += 1; +//lavacode + if ( ( ( (trace.fraction == 1) + || (self->goalentity && (self->goalentity->s.origin[2]-64 > trace.endpos[2]))) + && (!self->enemy || (self->enemy->s.origin[2] < self->s.origin[2]))) // don't follow a player too far down + || (trace.contents) & (CONTENTS_LAVA | CONTENTS_SLIME)) // falling into lava + // || (self->goalentity && (trace.fraction > 0.4) && (fabs(self->s.origin[2] - self->goalentity->s.origin[2]) < 8)) ) // we shouldn't be falling here + { // lava below, see if we can safely proceed + VectorCopy(self->velocity, dropdest); + dropdest[2] = -64; + VectorAdd(self->s.origin, dropdest, dropdest); + + trace = gi.trace(self->s.origin, self->mins, self->maxs, dropdest, self, MASK_SOLID | CONTENTS_LAVA | CONTENTS_SLIME); + + if ( (drop_dist < 32) + || (trace.fraction == 1) + || (trace.contents & (CONTENTS_LAVA | CONTENTS_SLIME)) + || (trace.plane.normal[2] < 0.5)) // too steep + { + + // abort the current path, so we can look for an alternate path next frame + if (self->goalentity) + self->goalentity->ignore_time = level.time + 0.5; + self->goalentity = NULL; + self->groundentity = last_groundentity; + VectorCopy(oldorg, self->s.origin); + gi.linkentity(self); + + } + } + + else if ((self->velocity[2] <= 0) && self->goalentity && (self->goalentity->s.origin[2] < self->s.origin[2])) + { // keep on ground when going down stairs or ramp + vec3_t dest; + + VectorCopy(self->s.origin, dest); + dest[2] -= 32; + + trace = gi.trace(self->s.origin, self->mins, self->maxs, dest, self, MASK_PLAYERSOLID); + + // only really inair if way above ground + if (trace.fraction < 1) + { + VectorCopy(trace.endpos, self->s.origin); + self->groundentity = trace.ent; + gi.linkentity(self); + } + } + + } + + } + else + { + if (self->groundentity && !last_groundentity) + { // just landed, let ClosestNodeToEnt check for a better node + self->last_closest_time = 0; + } + else if (!self->groundentity && !last_groundentity && (self->velocity[2] > 0) /*&& (!self->goalentity || (self->goalentity->s.origin[2] > self->s.origin[2]))*/) + { // prevent not jumping high enough when scraping a wall/ledge + float f; + + f = move[2]; + move[2] = 0; + + if (VectorLength(move) < 5) + { + self->velocity[2] += 13; + } + + move[2] = f; + } + + if (self->groundentity && (VectorLength(move) < dist*0.1)) + { // try walkmove() + + if (!M_walkmove(self, angles[YAW], dist)) + { +// if (!M_walkmove(self, angles[YAW], dist*0.3)) +// if (!M_walkmove(self, angles[YAW+100], dist*0.5)) +// M_walkmove(self, angles[YAW-100], dist*0.5); + } + + VectorSubtract(self->s.origin, oldorg, move); + } + + if ((self->groundentity) && (VectorLength(move) < dist*0.1)) + { + + if (self->goalentity /*&& self->groundentity*/) + { +// if ((random() < 0.7)) // && (self->s.origin[2] + 16 < self->goalentity->s.origin[2])) + { + vec3_t start, dir, org, mins; + + VectorCopy(self->s.origin, start); +// start[2] -= 12; + + AngleVectors(angles, dir, NULL, NULL); + VectorScale(dir, 24, dir); + VectorAdd(start, dir, org); + + VectorCopy(self->mins, mins); + mins[2] += 16; + + trace = gi.trace(start, mins, self->maxs, org, self, MASK_SOLID); + + if ((trace.fraction < 0.3) && (fabs(trace.plane.normal[2]) < 0.3)) + { + // set ideal velocity +// VectorScale(trace.plane.normal, -40, dir); + dir[2] = 310; + + // see if clear at head + start[2] += 32; + org[2] += 32; + + trace = gi.trace(start, VEC_ORIGIN, VEC_ORIGIN, org, self, MASK_SOLID); + + if (trace.fraction == 1) + { + if (CanJump(self)) + { // safe to jump +// self->s.origin[2] += 1; + + self->groundentity = NULL; + + gi.linkentity(self); + + VectorCopy(dir, self->velocity); + VectorCopy(self->velocity, self->jump_velocity); + + gi.sound(self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, 2, 0); + } + } + else // blocked at head also, abort goal + { + self->goalentity = NULL; + } + } + } + } + +// if (touched_player) + if (self->groundentity) + { // stuck, try and strafe + int dir=1; + + if ((((int)(level.time)) % 6) < 3) + dir = -1; + + if (!M_walkmove(self, + self->s.angles[YAW] - 110 * dir, + BOT_STRAFE_SPEED * FRAMETIME * 0.5)) + { // try the other way + M_walkmove( self, + self->s.angles[YAW] + 110 * dir, + BOT_STRAFE_SPEED * FRAMETIME * 0.5); + } + + self->client->slide_time = level.time + 0.5; + } + } + else if (self->client->slide_time > level.time) + { + int dir=1; + + if ((((int)(level.time)) % 6) < 3) + dir = -1; + + M_walkmove( self, + self->s.angles[YAW] - 110 * dir, + BOT_STRAFE_SPEED * FRAMETIME ); + + } + + if (self->groundentity && (VectorLength(move) < dist*0.1)) + { // try walkmove() + + M_walkmove(self, angles[YAW] + 180 * (random()*2 - 1), dist*0.5); + + VectorSubtract(self->s.origin, oldorg, move); + } + } + + if (going_for_goalentity && self->goalentity /*&& !self->goalentity->item*/) + { + vec3_t goalvec, oldgoalvec; + vec3_t u_goalvec, u_oldgoalvec; + float vdist; + + VectorSubtract(self->s.origin, self->goalentity->s.origin, goalvec); + VectorSubtract(oldorg, self->goalentity->s.origin, oldgoalvec); + + if ((vdist = VectorLength(goalvec)) < 40) + { // see if we can move to the goal + + if (vdist > 12) + { + VectorNormalize2(goalvec, u_goalvec); + VectorNormalize2(oldgoalvec, u_oldgoalvec); + + VectorSubtract(u_goalvec, u_oldgoalvec, dir); + } + + if ((vdist <= 12) || (VectorLength(dir) > 0.4)) + { // close enough, and we've passed the goal, so try to move to it +// trace_t trace; + + self->client->reached_goal_time = level.time; + + // if this is a jump node, move to it + if (self->goalentity->goalentity) + { + trace = gi.trace (self->goalentity->s.origin, self->goalentity->mins, self->goalentity->maxs, self->goalentity->s.origin, self, MASK_PLAYERSOLID); + + if (!trace.startsolid) + { + VectorCopy(self->goalentity->s.origin, self->s.origin); + gi.linkentity(self); + } + } + + } + } + else if (self->groundentity || self->waterlevel) // make sure we keep heading towards the goal + { + if (VectorLength(goalvec) >= VectorLength(oldgoalvec)) + { // we haven't moved any closer, something's wrong + self->last_move_nocloser = save_nocloser; + + if (self->last_move_nocloser < (level.time - 0.3)) + { + botDebugPrint("%s didn't move closer to goalentity\n", self->client->pers.netname); + + self->goalentity->ignore_time = level.time + 1; + self->goalentity->enemy = self; + self->goalentity = NULL; + self->last_move_nocloser = level.time; + } + } + } + + } + + return true; +} + +int bot_BetterTarget (edict_t *self, edict_t *other) +{ + botDebugPrint(" class other %s\n",other->classname); + botDebugPrint(" self team %d\n",self->wf_team); + botDebugPrint(" other team %d\n",other->wf_team); + //if attacker is not a sentry or turret grenade dont attack +/* if (!other->client && ((strcmp(other->classname, "SentryGun") != 0) + ||(strcmp(other->classname, "turret") != 0))) + { botDebugPrint("false not sentry or turret\n",other->classname); + return false; + }*/ +return false; + // attack sentry if not same team + if ((strcmp(other->classname, "SentryGun") == 0) && + (other->wf_team != self->wf_team)) + { botDebugPrint(" true\n",other->classname); + return true; + } + // don't attack same team sentry if it accidently hits bot + if ((strcmp(other->classname, "SentryGun") == 0) && + (other->wf_team == self->wf_team)) + { + return false; + } + + if ((strcmp(other->classname, "turret") == 0)&& + (other->wf_team != self->wf_team)) + return true; + + if ((strcmp(other->classname, "turret") == 0)&& + (other->wf_team == self->wf_team)) + return false; +/*if((self->client->player_class == 2) && +(self->client->pers.inventory[ITEM_INDEX(FindItem("Shells"))] > 0)) + { if (SameTeam(other, self) && (other->disease)) + return true; + }*/ + + if (SameTeam(other, self)) + return false; + + if (ctf->value) + { + if (self->enemy && CarryingFlag(self->enemy)) + return false; + if (CarryingFlag(other)) + return true; + } + + // FIXME: weigh up other's weapon against their health? + + if ((self->enemy) && (entdist(self, self->enemy) < 512)) + return (other->health < self->enemy->health); + else + return (other->health > 0); +} + +void bot_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + int r, l; +// botDebugPrint(" class 2222 %s\n",other->classname); +// botDebugPrint(" name 2222 %1\n",other->health); + + self->last_pain = level.time; +//lavacode + if ( (!ctf->value) + && (self->client->team) + && (other->client) // ignore if hurt by door or lava + && (self->client->team->last_grouping < (level.time - 5)) + && (last_bot_chat[CHAT_TEAMPLAY_HELP] < (level.time - 5)) + && (self->client->team != other->client->team) + && (!self->target_ent) + && ((self->bot_fire == botBlaster) || (self->bot_fire == botShotgun)) + && (self->health < other->health)) + { // signal for help! + botDebugPrint("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"); + TeamGroup(self); + } + + if (bot_BetterTarget(self,other)) + { + // get mad at this person + self->enemy = other; + } +/* + if ((self->health < 20) && (self->bot_stats->aggr < random() * 5)) + { // health required! +// self->movetarget = NULL; + + bot_roam(self, false); + } +*/ + // play an apropriate pain sound + if (level.time > self->pain_debounce_time) + { + int i=0; + + r = 1 + (rand()&1); + self->pain_debounce_time = level.time + 0.7; + if (self->health < 25) + l = 25; + else if (self->health < 50) + l = 50; + else if (self->health < 75) + l = 75; + else + l = 100; + + gi.sound (self, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0); + if(!self->frozen)//botfreeze + if (self->client->anim_priority < ANIM_PAIN) + { + self->client->anim_priority = ANIM_PAIN; + + if (self->maxs[2] == 4) + { + self->s.frame = FRAME_crpain1; + self->client->anim_end = FRAME_crpain4; + } + else + { + i = (i+1)%3; + switch (i) + { + case 0: + self->s.frame = FRAME_pain101; + self->client->anim_end = FRAME_pain104; + break; + case 1: + self->s.frame = FRAME_pain201; + self->client->anim_end = FRAME_pain204; + break; + case 2: + self->s.frame = FRAME_pain301; + self->client->anim_end = FRAME_pain304; + break; + } + } + } + } + +} + +//======================================================================================== +// +// Miscellaneous routines +// + +// Checks if the entity can be moved upwards for jumping +int CanJump(edict_t *ent) +{ + trace_t trace; + vec3_t dest; + + VectorCopy(ent->s.origin, dest); + dest[2] += 1; + + trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_PLAYERSOLID); + + return (!trace.startsolid && (trace.fraction == 1)); +} + +// check that the targ is visible from self (restricted to FOV) +int CanSee(edict_t *self, edict_t *targ) +{ + vec3_t dir, forward, vec; + + VectorSubtract(targ->s.origin, self->s.origin, dir); + + if (VectorLength(dir) < 256) // close enough + return true; + +// dir[2] = 0; + VectorNormalize2(dir, dir); + + AngleVectors(self->s.angles, forward, NULL, NULL); + + VectorSubtract(forward, dir, vec); + + return (VectorLength(vec) < (1 + (self->bot_stats->combat / 5))); +} + +// check that the path from self to targ is valid (doesn't walk on air) +int CanReach(edict_t *self, edict_t *targ) +{ + vec3_t dir, midpos, end_trace, start; + vec3_t midpos2, mins; + trace_t trace; + float dist, progressive_dist; + int inc=32; + + // special water case (since tracing for WATER doesn't work + if ( (self->waterlevel && targ->waterlevel) + || ( (targ->waterlevel && (!targ->groundentity || (targ->waterlevel > 2))) + && ((self->s.origin[2]+16) > targ->s.origin[2]))) + return true; + + VectorAdd(self->mins, tv(0,0,12), mins); + VectorCopy(self->s.origin, start); + + VectorSubtract(targ->s.origin, self->s.origin, dir); + VectorMA(start, 0.5, dir, midpos); + VectorSubtract(midpos, tv(0,0,40), end_trace); + + // check that the midpos is onground (down 28 units) + trace = gi.trace(midpos, mins, self->maxs, end_trace, self, MASK_SOLID); + + if (trace.fraction == 1) + return false; + + if ((dist = VectorLength(dir)) < 32) + return true; + + VectorNormalize2(dir, dir); + + if (!bot_calc_nodes->value || (self->bot_client)) // only do thorough checking when laying nodes + { + return true; +// if (dist > 66) +// dist = 66; + } + else + { + inc = 12; // do more thourough checking + } + + for (progressive_dist = 32; progressive_dist < (dist - 16) ; progressive_dist += inc) + { + VectorMA(start, progressive_dist, dir, midpos2); + VectorSubtract(midpos2, tv(0,0,28), end_trace); + + trace = gi.trace(midpos2, mins, self->maxs, end_trace, self, MASK_SOLID); + + if (trace.fraction == 1) + return false; + } + + return true; +} + +int CanStand(edict_t *self) +{ + static vec3_t maxs = {16, 16, 32}; + trace_t trace; + + trace = gi.trace(self->s.origin, self->mins, maxs, self->s.origin, self, MASK_PLAYERSOLID); + + return (!trace.startsolid || ((trace.ent->svflags & SVF_MONSTER) && (trace.ent->health <= 0))); +} + +//======================================================================================== + +void bot_SuicideIfStuck(edict_t *self) +{ + if ((self->checkstuck_time < (level.time - 1)) && (self->group_pausetime < (level.time - 1)) && (self->bot_plat_pausetime < (level.time - 1))) + { + vec3_t move; + + VectorSubtract(self->s.origin, self->checkstuck_origin, move); + + if (VectorLength(move) < 4) + { // bot is stuck + if (self->checkstuck_time < (level.time - 5)) + { // suicide after long enough + botDebugPrint("Bot suicide (stuck)\n"); + T_Damage(self, self, self, tv(0,0,0), self->s.origin, tv(0,0,0), self->health + 1, 0, DAMAGE_NO_PROTECTION, MOD_SUICIDE); + } + else + { // jump randomly + botDebugPrint("BotStuck: Random jump\n"); + botRandomJump(self); + } + + self->checkstuck_time = level.time; + return; + } + else + { + VectorCopy(self->s.origin, self->checkstuck_origin); + self->checkstuck_time = level.time; + } + } +} + +void botButtonThink(edict_t *ent) +{ + static vec3_t mins = {-16, -16, -8}; + int i, count=0; +// trace_t tr; + edict_t *plyr; + float dist; + + if (ent->skill_level > num_players) + ent->skill_level = 0; + + for (; ent->skill_levelskill_level++) + { + i = ent->skill_level; + plyr = players[i]; + + if (count++ > 6) + break; + + if (!plyr->bot_client || plyr->activator) + continue; + + if (plyr->activator_time > level.time) + continue; + + if (entdist(ent, plyr) > 900) + continue; + + count += 2; // so we don't do too many PathToEnt()'s per think + + if ((dist = PathToEnt(ent->owner, ent, false, false)) == -1) + continue; + + if (dist > 1200) + continue; + + // push me! + plyr->activator = ent; + plyr->goalentity = PathToEnt_Node; + plyr->activator_time = level.time; + plyr->last_reached_trail = level.time; + + ent->nextthink = level.time + 4; // wait a bit longer than usual + + break; + } + + ent->nextthink = level.time + 0.2 + random()*0.3; // so multiple buttons don't all go at once +} diff --git a/bot_die.c b/bot_die.c new file mode 100644 index 0000000..acdaff7 --- /dev/null +++ b/bot_die.c @@ -0,0 +1,180 @@ +/***************************************************************** + + Eraser Bot source code - by Ryan Feltrin, Added to by Acrid- + + .............................................................. + + This file is Copyright(c) 1998, Ryan Feltrin, All Rights Reserved. + + .............................................................. + + All other files are Copyright(c) Id Software, Inc. + + Please see liscense.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 Eraser, you MUST + include the following text (minus the BEGIN and END lines) in the + documentation for your modification. + + --- BEGIN --- + + The Eraser Bot is a product of Ryan Feltrin, and is available from + the Eraser Bot homepage, at http://impact.frag.com. + + This program is a modification of the Eraser Bot, and is therefore + in NO WAY supported by Ryan Feltrin. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Ryan Feltrin immediately, via + the Eraser Bot homepage. + + --- END --- + + .............................................................. + + You will find p_trail.c has not been included with the Eraser + source code release. This is NOT an error. I am unable to + distribute this file because it contains code that is bound by + legal documents, and signed by myself, never to be released + to the public. Sorry guys, but law is law. + + I have therefore include the compiled version of these files + in .obj form in the src\Release and src\Debug directories. + So while you cannot edit and debug code within these files, + you can still compile this source as-is. Although these will only + work in MSVC v5.0, linux versions can be made available upon + request. + + NOTE: When compiling this source, you will get a warning + message from the compiler, regarding the missing p_trail.c + file. Just ignore it, it will still compile fine. + + .............................................................. + + I, Ryan Feltrin/Acrid-, hold no responsibility for any harm caused by the + use of this source code. I also am NOT willing to provide any form + of help or support for this source code. It is provided as-is, + as a service by me, with no documentation, other then the comments + contained within the code. If you have any queries, I suggest you + visit the "official" Eraser source web-board, at + http://www.telefragged.com/epidemic/. I will stop by there from + time to time, to answer questions and help with any problems that + may arise. + + Otherwise, have fun, and I look forward to seeing what can be done + with this. + + -Ryan Feltrin + -Acrid- + + *****************************************************************/ +#include "g_local.h" +#include "bot_procs.h" +#include "m_player.h" + +void TossClientWeapon (edict_t *self); +qboolean IsFemale (edict_t *ent); +void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker); +void WFPlayer_Die (edict_t *self); + +void bot_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + float rnd; + + VectorClear (self->avelocity); + + self->takedamage = DAMAGE_YES; + self->movetype = MOVETYPE_TOSS; + + self->s.modelindex2 = 0; // remove linked weapon model +//ZOID + self->s.modelindex3 = 0; // remove linked ctf flag +//ZOID + + self->s.angles[0] = 0; + self->s.angles[2] = 0; + + self->s.sound = 0; + self->client->weapon_sound = 0; + + self->maxs[2] = -8; + +// self->solid = SOLID_NOT; + self->svflags |= SVF_DEADMONSTER; + + if (!self->deadflag) + { + self->client->respawn_time = level.time + 1.0; + self->client->ps.pmove.pm_type = PM_DEAD; + ClientObituary (self, inflictor, attacker); +//ZOID + CTFFragBonuses(self, inflictor, attacker); +//ZOID + TossClientWeapon (self); +//ZOID + CTFPlayerResetGrapple(self); + CTFDeadDropFlag(self); + CTFDeadDropTech(self); +//ZOID + } + + //WF cleanup (like flames) + WFPlayer_Die (self); + + // remove powerups + self->client->quad_framenum = 0; + self->client->invincible_framenum = 0; + self->client->breather_framenum = 0; + self->client->enviro_framenum = 0; + + self->s.effects = 0; + + // clear inventory + memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory)); + +// check for gib + if (self->health <= -1440)//Acrid fix crash? was -40 + { + 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); + +//ZOID + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = 0; +//ZOID + self->takedamage = DAMAGE_NO; + + self->timestamp = level.time; + } + else if (self->frozen && !self->deadflag)//acrid 3/99 botfrozen death animation + { + self->s.frame = FRAME_stand01; + self->frozenbody = 1; + } + else if (!self->deadflag) + { + rnd = random() * 3; + + if (self->viewheight < 0) + self->s.frame = FRAME_crdeath1; + else if (rnd <= 1) + self->s.frame = FRAME_death101; + else if (rnd <= 2) + self->s.frame = FRAME_death201; + else + self->s.frame = FRAME_death301; + + gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0); + } + + // regular death + self->deadflag = DEAD_DEAD; + + gi.linkentity(self); +} diff --git a/bot_items.c b/bot_items.c new file mode 100644 index 0000000..c59ea7a --- /dev/null +++ b/bot_items.c @@ -0,0 +1,531 @@ +/***************************************************************** + + Eraser Bot source code - by Ryan Feltrin, Added to by Acrid- + + .............................................................. + + This file is Copyright(c) 1998, Ryan Feltrin, All Rights Reserved. + + .............................................................. + + All other files are Copyright(c) Id Software, Inc. + + Please see liscense.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 Eraser, you MUST + include the following text (minus the BEGIN and END lines) in the + documentation for your modification. + + --- BEGIN --- + + The Eraser Bot is a product of Ryan Feltrin, and is available from + the Eraser Bot homepage, at http://impact.frag.com. + + This program is a modification of the Eraser Bot, and is therefore + in NO WAY supported by Ryan Feltrin. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Ryan Feltrin immediately, via + the Eraser Bot homepage. + + --- END --- + + .............................................................. + + You will find p_trail.c has not been included with the Eraser + source code release. This is NOT an error. I am unable to + distribute this file because it contains code that is bound by + legal documents, and signed by myself, never to be released + to the public. Sorry guys, but law is law. + + I have therefore include the compiled version of these files + in .obj form in the src\Release and src\Debug directories. + So while you cannot edit and debug code within these files, + you can still compile this source as-is. Although these will only + work in MSVC v5.0, linux versions can be made available upon + request. + + NOTE: When compiling this source, you will get a warning + message from the compiler, regarding the missing p_trail.c + file. Just ignore it, it will still compile fine. + + .............................................................. + + I, Ryan Feltrin/Acrid-, hold no responsibility for any harm caused by the + use of this source code. I also am NOT willing to provide any form + of help or support for this source code. It is provided as-is, + as a service by me, with no documentation, other then the comments + contained within the code. If you have any queries, I suggest you + visit the "official" Eraser source web-board, at + http://www.telefragged.com/epidemic/. I will stop by there from + time to time, to answer questions and help with any problems that + may arise. + + Otherwise, have fun, and I look forward to seeing what can be done + with this. + + -Ryan Feltrin + -Acrid- + + *****************************************************************/ +#include "g_local.h" +#include "g_items.h" +#include "p_trail.h" +#include "bot_procs.h" + +extern gitem_t *titems[4]; + +void botSetWant(edict_t *self, int dist_divide) +{ + if (dist_divide <= 1) + self->movetarget_want = WANT_KINDA; + else if (dist_divide <= 3) + self->movetarget_want = WANT_YEH_OK; + else + self->movetarget_want = WANT_SHITYEAH; +} + +/* +============= +RoamFindBestItem + + Searches for the best reachable item, in list_head + set check_paths to enable reaching items that aren't visible + ** be careful not to call this too often, it will slow things down +============= +*/ +int RoamFindBestItem(edict_t *self, edict_t *list_head, int check_paths) +{ + float closest_dist=999999, this_dist; + edict_t *best=NULL, *node, *trav; + int si; // node closest to self + int dist_divide=1; // divide distance by this, simulates wieghts for better targets + int best_divide; + + if (!list_head) // nothing to look for + return -1; + + si = ClosestNodeToEnt(self, false, false); + + for (trav = list_head ; trav ; trav = trav->node_target) + { + if (trav->solid != SOLID_TRIGGER)//crash here + continue; + + this_dist = 0; + + if (((trav->item->pickup != CTFPickup_Flag) && (trav->item->pickup != Pickup_Weapon)) + && (this_dist = entdist(self, trav)) > 2000) // too far away + continue; + + // CTF, if guarding base, don't go too far away//FIXME LOOK HERE + if(!bot_melee->value)//fixme + if (self->target_ent && self->target_ent->item && (self->target_ent->item->pickup == CTFPickup_Flag) + && (this_dist > BOT_GUARDING_RANGE)) + continue; + // CTF + + if (trav->ignore_time >= level.time) + continue; + // make sure we can pickup the ammo + if ((list_head == ammo_head) && + (!((dist_divide = (2*botHasWeaponForAmmo(self->client, trav->item) + (this_dist < 256))) && + (dist_divide *= 2*botCanPickupAmmo(self->client, trav->item)) && + (dist_divide *= 3*(self->bot_fire == botBlaster))))) // no weapon to use this ammo, or ammo full + { + continue; + } + else if (list_head == bonus_head) + { + if (trav->item->pickup == Pickup_Armor) + { + if (!(dist_divide = botCanPickupArmor(self, trav))) + { // can't pickup this armor item (already have better armor) + dist_divide = 1; + continue; + } + } +/////////////////////////////////////specials///////////////////////////// + else if (trav->item->pickup == Place_Special) + { +// dist_divide += 9999; + if (!(dist_divide = botCanPlaceSpecial(self, trav))) + { //can't pickup already have a sentry placed or not enough cells +// botDebugPrint("sentry place 1\n"); + dist_divide = 1; + continue; + } + } +////////////////////////////////specials end////////////////////////////// + + else if (trav->item->pickup == Pickup_Pack) + { + if (!(dist_divide = botCanPickupPack(self, trav))) + { // can't pickup this pack item (already full) + dist_divide = 1; + continue; + } + } + if (((strcmp(trav->classname, "item_flagreturn_team1") == 0) ||//$ + (strcmp(trav->classname, "item_flagreturn_team2") == 0)) &&//$ + !CarryingFlag(self))//$ + {//$ + continue;//$ + }//$ +/////////////////////////////////////////////////////////////////////////// + else if (trav->item->pickup == CTFPickup_Flag) + { +//f dist_divide += 4;//FIXME ACRID WAS = 6 MAYBE ONE PROBLEM + +//f if (self->bot_fire != botBlaster && self->bot_fire != botShotgun) +//f dist_divide += 100; + + if ( ( (self->client->resp.ctf_team == CTF_TEAM1) + && (self->client->pers.inventory[ITEM_INDEX(flag2_item)]) + && (trav == flag1_ent || trav == flagreturn1_ent))//flagreturn code + || ( (self->client->resp.ctf_team == CTF_TEAM2) + && (self->client->pers.inventory[ITEM_INDEX(flag1_item)]) + && (trav == flag2_ent || trav == flagreturn2_ent)))//flagreturn code + { // we have their flag, so HEAD FOR OUR FLAG! + dist_divide += 9999; + + } + else if ( ((self->client->resp.ctf_team == CTF_TEAM1) && (trav == flag1_ent) && (flag1_ent->solid = SOLID_TRIGGER)) + || ((self->client->resp.ctf_team == CTF_TEAM2) && (trav == flag2_ent) && (flag2_ent->solid = SOLID_TRIGGER))) + { // flag is sitting at home, don't try and get it + + continue; + } + + } + else if (trav->item->pickup == CTFPickup_Tech) + { + qboolean has_tech=false; + + if ( (self->client->pers.inventory[ITEM_INDEX(item_tech1)]) + || (self->client->pers.inventory[ITEM_INDEX(item_tech2)]) + || (self->client->pers.inventory[ITEM_INDEX(item_tech3)]) + || (self->client->pers.inventory[ITEM_INDEX(item_tech4)])) + { + has_tech = true; + } + + if (has_tech) // already have a tech, so ignore + continue; + + if (!self->client->ctf_has_tech) + dist_divide = 3; + } + else // item is a powerup + { + if (trav->item->use == Use_Quad) + { + dist_divide = 4; + + if (skill->value > 1) + dist_divide += 4 * (skill->value - 1); + + if (self->bot_stats->quad_freak) + dist_divide *= 2; // REALLY REALLY WANT THIS SUCKER!! + } + } + + } + else if (list_head == weapons_head) // for weapons, apply weights for good weapons + {//fixme acrid use this for something else? + // don't go for a (non-droppped) weapon we already have, if in "weapons stay" mode + if ( ((int)(dmflags->value) & DF_WEAPONS_STAY) + && (!(trav->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) )) + && (self->client->pers.inventory[ITEM_INDEX(trav->item)])) + { + continue; + } + + else if ((trav->item == item_rocketlauncher) || + (trav->item == item_chaingun) ||//42 Acrid note,3.4 code + (trav->item == item_railgun)) + dist_divide = 4; + else if (trav->item == item_bfg10k) + { + dist_divide = 3; + + if (num_players > 4) // BFG rocks with lots of people + dist_divide += 3; + } + + if (trav->item == self->bot_stats->fav_weapon) + dist_divide += 3; + + if (self->bot_fire == botBlaster) // we really need a weapon + dist_divide += 4; + } + else if (list_head == health_head) + { + if (trav->count == 100) + dist_divide = 4; + else if (self->health > 90) // don't need it + continue; + else if ((self->health > 50) && check_paths) // only check routes for health when really low + continue; + } + + if ((this_dist < 384) && visible_box(self, trav) && CanReach(self, trav)) // go for it! + { + if (trav != self->save_movetarget) + self->goalentity = trav; + else + self->goalentity = self->save_goalentity; + + self->movetarget = trav; + + botSetWant(self, dist_divide); + + this_dist = this_dist/dist_divide; + +// this_dist = this_dist/256; // always grab something close by + + return this_dist; + } + + if (!check_paths) + continue; + // can't see a node, so don't bother checking routes + if (si == -1) + continue; + + // don't look for a path to shards + if (list_head == health_head) + { + if (trav->count < 10) + continue; + } + else if (list_head == bonus_head) + { + if (trav->item->tag == ARMOR_SHARD) + continue; + } + + // see if any of our visible nodes, has a route to one of the + // item's visible nodes //crash here + if (((this_dist = PathToEnt(trail[si], trav, false, false)) > -1) + && ((this_dist / dist_divide) < closest_dist)) + { + this_dist = this_dist / dist_divide; + closest_dist = this_dist; + best = trav; + node = trail[si]; // go for the nearest node first + best_divide = dist_divide; + + if (this_dist < 128) // OPTIMIZE: go for this one! + { + if (node != self->save_movetarget) + self->goalentity = node; + else + self->goalentity = self->save_goalentity; + + self->movetarget = best; + + botSetWant(self, dist_divide); + + return this_dist; + } + } +//ACO BOTH +botDebugPrint("Found item %s: %i away\n", trav->classname, (int) this_dist); + } + + if (best) + { +botDebugPrint("Best item %s: %i away\n", best->classname, (int) closest_dist); + if (node != self->save_movetarget) + self->goalentity = node; + else + self->goalentity = self->save_goalentity; + + self->movetarget = best; + + botSetWant(self, best_divide); + + return closest_dist; + } + else + { + return -1; + } +} + +//////////////////////////////Specials/////////////////////////////// +// Similar to canuse +int botCanPlaceSpecial (edict_t *self, edict_t *ent) +{ + gclient_t *client; + + if (self->client) + client = self->client; + else + return false; +if (strcmp(ent->item->classname, "item_sentryspot") == 0) +{ + if (!ent->owner && self->sentry) + return false; +//code problem still says owned by previous owner,see if added eos helps + if (ent->owner && ent->owner->sentry) + { + return false; + } +} + +if (strcmp(ent->item->classname, "item_depotspot") == 0) +{ + if (!ent->owner && self->supply) + return false; +//code problem still says owned by previous owner,see if added eos helps + if (ent->owner && ent->owner->supply) + { + return false; + } +} + +if ( (strcmp(ent->item->classname, "item_sentryspot") == 0) + && ( (client->player_special & SPECIAL_SENTRY_GUN + && client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] >= 60) || + (client->player_special & SPECIAL_BIOSENTRY + && client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] >= 50) )) + return 9999;//tried 4 but they almost totally ignore for enemy flag + +else if ( (strcmp(ent->item->classname, "item_depotspot") == 0) + && (client->player_special & SPECIAL_SUPPLY_DEPOT + || client->player_special & SPECIAL_HEALING) ) + return 9999;//tried 4 but they almost totally ignore for enemy flag + else + return false; +}//ent->client->player_special & SPECIAL_SUPPLY_DEPOT +///////////////////////////Specials end////////////////////////////// +//Acrid pack code +int botCanPickupPack (edict_t *self, edict_t *ent) +{ + gclient_t *client; + + if (self->client) + client = self->client; + else + return false; + + +//Team=9 means that any team can pick it up, but it will respawn in 5 seconds +if ((ent->wf_team) && (ent->wf_team != self->wf_team) && (ent->wf_team != 9)) +return false; +//ref self->client->pers.inventory[self->client->ammo_index] +//ref client->pers.inventory[ITEM_INDEX(item_bullets)] + if ((client->pers.inventory[ITEM_INDEX(item_bullets)] != client->pers.max_bullets)|| + (client->pers.inventory[ITEM_INDEX(item_shells)] != client->pers.max_shells) || + (client->pers.inventory[ITEM_INDEX(item_rockets)] != client->pers.max_rockets)|| + (client->pers.inventory[ITEM_INDEX(item_grenades)] != client->pers.max_grenades)|| + (client->pers.inventory[ITEM_INDEX(item_cells)] != client->pers.max_cells ) || + (client->pers.inventory[ITEM_INDEX(item_slugs)] != client->pers.max_slugs)) + return 2; + else + return false; + +} +// FIXME: these are in g_items.c also!! +extern gitem_armor_t jacketarmor_info; +extern gitem_armor_t combatarmor_info; +extern gitem_armor_t bodyarmor_info; + +// returns 0 if this armour is of no use to the bot, a higher number is returned for more useful armour +int botCanPickupArmor (edict_t *self, edict_t *ent) +{ + int old_armor_index; + gitem_armor_t *oldinfo; + gitem_armor_t *newinfo; + int newcount; + float salvage; + int salvagecount; + gclient_t *client; + qboolean canuse; + + if (self->client) + client = self->client; + else + return false; + return false; //fixme + // get info on new armor + newinfo = (gitem_armor_t *)ent->item->info; + + old_armor_index = ArmorIndex (self); +botDebugPrint("armor testing 1\n"); +//ACRID WF ARMOR add the wfflags for playerclasses fixme +canuse = false; + if ((ent->item->tag == ARMOR_BODY) && +(self->client->player_items & ITEM_BODYARMOR)){ +canuse = true;} + + if ((ent->item->tag == ARMOR_COMBAT) && +(self->client->player_items & ITEM_COMBATARMOR)) +{canuse = true;} + + if ((ent->item->tag == ARMOR_JACKET) && +(self->client->player_items & ITEM_JACKETARMOR)){ +canuse = true;} + if (canuse == false) + {botDebugPrint("armor testing 2\n"); + return false; + } +//ACRID WF ARMOR END +botDebugPrint("armor testing 3\n"); + // handle armor shards specially + if (ent->item->tag == ARMOR_SHARD) + { + return 1; + } + // if bot has no armor, just use it + else if (!old_armor_index) + { + return 4; // any armour is FUCKING GOOD armour!//FIXME ACRID was 4 + } + // 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 * client->pers.inventory[old_armor_index]; + newcount = newinfo->base_count + salvagecount; + if (newcount > newinfo->max_count) + newcount = newinfo->max_count; + + return (int) (((newcount - client->pers.inventory[old_armor_index]) / 50)*3 + 1); + } + else + { + // calc new armor values + salvage = newinfo->normal_protection / oldinfo->normal_protection; + salvagecount = salvage * newinfo->base_count; + newcount = 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 (client->pers.inventory[old_armor_index] >= newcount) + return false; + + // armour is useful, return 4 if VERY useful :) + return (int) (((newcount - client->pers.inventory[old_armor_index]) / 50)*3 + 1); + } + } + + return false; +} diff --git a/bot_misc.c b/bot_misc.c new file mode 100644 index 0000000..3f21e73 --- /dev/null +++ b/bot_misc.c @@ -0,0 +1,1423 @@ +/***************************************************************** + + Eraser Bot source code - by Ryan Feltrin, Added to by Acrid- + + .............................................................. + + This file is Copyright(c) 1998, Ryan Feltrin, All Rights Reserved. + + .............................................................. + + All other files are Copyright(c) Id Software, Inc. + + Please see liscense.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 Eraser, you MUST + include the following text (minus the BEGIN and END lines) in the + documentation for your modification. + + --- BEGIN --- + + The Eraser Bot is a product of Ryan Feltrin, and is available from + the Eraser Bot homepage, at http://impact.frag.com. + + This program is a modification of the Eraser Bot, and is therefore + in NO WAY supported by Ryan Feltrin. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Ryan Feltrin immediately, via + the Eraser Bot homepage. + + --- END --- + + .............................................................. + + You will find p_trail.c has not been included with the Eraser + source code release. This is NOT an error. I am unable to + distribute this file because it contains code that is bound by + legal documents, and signed by myself, never to be released + to the public. Sorry guys, but law is law. + + I have therefore include the compiled version of these files + in .obj form in the src\Release and src\Debug directories. + So while you cannot edit and debug code within these files, + you can still compile this source as-is. Although these will only + work in MSVC v5.0, linux versions can be made available upon + request. + + NOTE: When compiling this source, you will get a warning + message from the compiler, regarding the missing p_trail.c + file. Just ignore it, it will still compile fine. + + .............................................................. + + I, Ryan Feltrin/Acrid-, hold no responsibility for any harm caused by the + use of this source code. I also am NOT willing to provide any form + of help or support for this source code. It is provided as-is, + as a service by me, with no documentation, other then the comments + contained within the code. If you have any queries, I suggest you + visit the "official" Eraser source web-board, at + http://www.telefragged.com/epidemic/. I will stop by there from + time to time, to answer questions and help with any problems that + may arise. + + Otherwise, have fun, and I look forward to seeing what can be done + with this. + + -Ryan Feltrin + -Acrid- + + *****************************************************************/ +#include "g_local.h" +#include "bot_procs.h" +#include "m_player.h" + +void CopyToBodyQue (edict_t *ent); + +void bot_AnimateFrames(edict_t *self) +{ + int minframe, maxframe; + vec3_t themove; + + VectorSubtract(self->s.origin, self->animate_org, themove); + VectorCopy(self->s.origin, self->animate_org); + + themove[2] = 0; + + if (self->s.modelindex != 255) + { // gibbed, ready to spawn + self->s.frame = 0; + + if (self->timestamp < (level.time - 2)) // wait a bit + respawn_bot(self); + + return; + } +//NEW TESTING botfreeze note needed more than 1 frame to body didnt disappear.. + if (self->s.frame >= FRAME_stand01 && self->frozen && + self->health <=0) + { + self->client->anim_priority = ANIM_DEATH; + + if ((self->s.frame >= FRAME_stand01) && (self->s.frame < FRAME_stand03)) + { + self->s.frame++; + } + else // must be ready to spawn + { + CopyToBodyQue(self); + respawn_bot(self); + } + + return; + } +//NEW TESTING + if (self->s.frame >= FRAME_crdeath1) + { + self->client->anim_priority = ANIM_DEATH; + + if (((self->s.frame >= FRAME_crdeath1) && (self->s.frame < FRAME_crdeath5)) || + ((self->s.frame >= FRAME_death101) && (self->s.frame < FRAME_death106)) || + ((self->s.frame >= FRAME_death201) && (self->s.frame < FRAME_death206)) || + ((self->s.frame >= FRAME_death301) && (self->s.frame < FRAME_death308))) + { + self->s.frame++; + } + else // must be ready to spawn + { + CopyToBodyQue(self); + respawn_bot(self); + } + + return; + } +//NEW TESTING + else if (self->frozen && self->health <= 0)//acrid 3/99 botfrozen death animation + { + self->s.frame = FRAME_stand01; + } +//NEW TESTING + else if (self->health <= 0 && !self->frozen) // FIXME: this shouldn't happen, but it does + { // pick a random death frame, and go with it + float rnd; + + rnd = random() * 3; + + if (self->viewheight < 0) + self->s.frame = FRAME_crdeath1; + else if (rnd <= 1) + self->s.frame = FRAME_death101; + else if (rnd <= 2) + self->s.frame = FRAME_death201; + else + self->s.frame = FRAME_death301; + } + else if (self->client->anim_priority == ANIM_PAIN) + { + self->s.frame++; + + if (self->s.frame > self->client->anim_end) + { + self->s.frame = FRAME_stand01; + self->client->anim_priority = ANIM_BASIC; + } + + } + else if (!self->groundentity && (self->waterlevel || (self->last_inair > (level.time - 0.3)))) + { // jumping + self->client->anim_priority = ANIM_JUMP; + + minframe = FRAME_jump1; + maxframe = FRAME_jump3; + + self->s.frame++; + if (self->s.frame < minframe) + self->s.frame = minframe; + else if (self->s.frame > maxframe) + self->s.frame = maxframe; + } + else if ((self->s.frame >= FRAME_jump3) && + (self->s.frame < FRAME_jump6)) // jump landing + { + self->client->anim_priority = ANIM_JUMP; + + maxframe = FRAME_jump3; + + self->s.frame++; + } + else if ((self->s.frame <= FRAME_point12) && (self->s.frame >= FRAME_salute01)) + { + self->client->anim_priority = ANIM_WAVE; + + self->s.frame++; + + if (self->enemy || (self->s.frame > self->radius_dmg)) // abort if enemy found + self->s.frame = FRAME_stand01; + } + else if ((self->s.frame >= FRAME_attack1) && (self->s.frame <= FRAME_attack3)) + { + self->client->anim_priority = ANIM_ATTACK; + + maxframe = FRAME_attack3; + + self->s.frame++; + if (self->s.frame > maxframe) + self->s.frame = FRAME_run1; // finshed firing, loop is created by each fire, in bot_Attack() + } + else if ((self->s.frame >= FRAME_crattak1) && (self->s.frame < FRAME_crattak3)) + { + self->client->anim_priority = ANIM_ATTACK; + + maxframe = FRAME_crattak3; + + self->s.frame++; + if (self->s.frame > maxframe) + self->s.frame = FRAME_run1; // finshed firing, loop is created by each fire, in bot_Attack() + } + else if (VectorLength(themove) > 4) // running + { + self->client->anim_priority = ANIM_BASIC; + + if (self->maxs[2] == 4) + { + minframe = FRAME_crwalk1; + maxframe = FRAME_crwalk6; + } + else + { + minframe = FRAME_run1; + maxframe = FRAME_run6; + + if (!self->waterlevel && self->client->pickup_msg_time < (level.time - 0.3)) + { + self->s.event = EV_FOOTSTEP; + self->client->pickup_msg_time = level.time; + } + } + + self->s.frame++; + if (self->s.frame < minframe) + self->s.frame = minframe; + else if (self->s.frame > maxframe) + self->s.frame = minframe; + + } + else // standing + { + self->client->anim_priority = ANIM_BASIC; + + if (self->maxs[2] == 4) + { + minframe = FRAME_crstnd01; + maxframe = FRAME_crstnd19; + } + else + { + minframe = FRAME_stand01; + maxframe = FRAME_stand40; + } + + self->s.frame++; + if (self->s.frame < minframe) + self->s.frame = minframe; + else if (self->s.frame > maxframe) + self->s.frame = minframe; + } + +} + +void botDebugPrint(char *msg, ...) +{ + if (!bot_debug->value) + return; + else + { + char bigbuffer[0x10000]; + int len; + va_list argptr; + + va_start (argptr,msg); + len = vsprintf (bigbuffer,msg,argptr); + va_end (argptr); + gi.dprintf(bigbuffer); + } +} + +void ReadBotChat(void) +{ + FILE *f; + int section_index, line_count, i; + char filename[256]; + char buffer; + cvar_t *game_dir; + + game_dir = gi.cvar ("game", "", 0); + +#ifdef _WIN32 + i = sprintf(filename, ".\\"); + i += sprintf(filename + i, game_dir->string); + i += sprintf(filename + i, "\\chat.txt"); +#else + strcpy(filename, "./"); + strcat(filename, game_dir->string); + strcat(filename, "/chat.txt"); +#endif + + f = fopen (filename, "r"); + if (!f) + { + gi.error("\nUnable to read chat.txt\nChat functions not available.\n\n"); + return; + } + + memset(bot_chat_text, 0, sizeof(bot_chat_text)); + + section_index = -1; + + while (!feof(f)) + { + fscanf(f, "%c", &buffer); + + if (buffer == '#') + { // read to the end of the line + while (!feof(f) && (buffer != '\n')) + fscanf(f, "%c", &buffer); + } + else if (buffer == '-') + { + // increment section + section_index++; + line_count = -1; + + while (!feof(f) && (buffer != '\n')) + fscanf(f, "%c", &buffer); + } + else if (((buffer >= 'a') && (buffer <= 'z')) || + ((buffer >= 'A') && (buffer <= 'Z')) || + (buffer == '%')) + { // read this entire line + i = 0; + line_count++; + + // allocate memory for new string + bot_chat_text[section_index][line_count] = gi.TagMalloc(256, TAG_GAME); + memset(bot_chat_text[section_index][line_count], 0, 256); + + while (!feof(f) && (buffer != '\n')) + { + bot_chat_text[section_index][line_count][i++] = buffer; + + fscanf(f, "%c", &buffer); + } + + if (i > 0) + { + bot_chat_text[section_index][line_count][i] = '\0'; + } + + // update the count now + bot_chat_count[section_index] = line_count; + } + } + + fclose(f); +} +//FIXME ACRID GOES WITH BOTS.CFG +gitem_t *GetWeaponForNumber(int i) +{ + switch (i) + { + case 2 : + return item_shotgun; + case 3 : + return item_supershotgun; + case 4 : + return item_machinegun; + case 5 : + return item_chaingun; + case 6 : + return item_grenadelauncher; + case 7 : + return item_rocketlauncher; + case 8 : + return item_railgun; + case 9 : + return item_hyperblaster; + case 0 : + return item_bfg10k; + case 10 : + return item_sniperrifle;//Acrid test + case 11 : + return item_lightninggun; + case 12 : + return item_infecteddart; + case 13 : + return item_pulsecannon; + case 14 : + return item_telsacoil; + case 15 : + return item_flamethrower; + case 16 : + return item_pelletrocketlauncher; + case 17 : + return item_rocketnapalmlauncher; + case 18 : + return item_rocketclusterlauncher; + case 19 : + return item_needler; + case 20 : + return item_shc; + case 21 : + return item_handgrenades; + case 22 : + return item_poisondart;//armordart + case 23 : + return item_ak47; + case 24 : + return item_pistol; + case 25 : + return item_stingerrocketlauncher; + case 26 : + return item_knife; + default: + gi.dprintf("GetWeaponForNumber: Unknown item number: %d\n",i); + return item_shotgun; + } +} +//ACRID I THINK THIS IS READING NON TEAM LISTING FOR TEAMS DATA,SKINS, SCORE +bot_info_t *GenerateBotData(bot_team_t *bot_team, char *botname) +{ + int i, gen; + bot_info_t *botdata; + + botdata = gi.TagMalloc (sizeof(bot_info_t), TAG_GAME); + + botdata->ingame_count = 0; + + // name + botdata->name = gi.TagMalloc (128, TAG_GAME); + strcpy(botdata->name, botname); + + // skin + botdata->skin = gi.TagMalloc (128, TAG_GAME); + strcpy(botdata->skin, bot_team->default_skin); + + i = gen = 0; + while (botname[i]) + { + gen += botname[i] + i; + i++; + } + + // stats + botdata->bot_stats.accuracy = (float) (1 + ((int)(gen*3.234) % 5)); + botdata->bot_stats.aggr = (float) (1 + ((int)(gen*5.132) % 5)); + botdata->bot_stats.combat = (float) (1 + ((int)(gen*4.476) % 5)); + + botdata->bot_stats.fav_weapon = GetWeaponForNumber(3 + ((int)(gen*2.356) % 7)); + + botdata->bot_stats.quad_freak = (((int)(gen*1.453) % 3) < 2); + botdata->bot_stats.camper = (((int)(gen*2.376) % 3) == 2); + botdata->bot_stats.avg_ping = ((int)(gen*0.678) % 4) * 100; + + return botdata; +} + +// Advances the pointer to the FILE to the next non-space, non-tab character +char next_nonspace(FILE **f) +{ + char ch=9; + + while (!feof(*f) && ((ch==9) || (ch==' '))) + { + fscanf(*f, "%c", &ch); // fscanf will skip spaces, but not TABS + } + + return ch; +}; + +bot_team_t *ReadTeamData(FILE **f) +{ + char strbuf[256], ch; + int i, numbots; + bot_team_t *bot_team; + + bot_team = gi.TagMalloc (sizeof(bot_team_t), TAG_GAME); + + // name + i=0; + fscanf(*f, "%c", &strbuf[i]); + while ((strbuf[i] != '\"') && (i < 255)) + { + i++; + fscanf(*f, "%c", &strbuf[i]); + } + strbuf[i] = '\0'; // strip the trailing " + + bot_team->teamname = gi.TagMalloc (strlen(strbuf)+1, TAG_GAME); + strcpy(bot_team->teamname, strbuf); + + // abbrev + i=0; + next_nonspace(f); + fscanf(*f, "%c", &strbuf[i]); + while ((strbuf[i] != '\"') && (i < 255)) + { + i++; + fscanf(*f, "%c", &strbuf[i]); + } + strbuf[i] = '\0'; // strip the trailing " + + bot_team->abbrev = gi.TagMalloc (strlen(strbuf)+1, TAG_GAME); + strcpy(bot_team->abbrev, strbuf); + + // default model/skin + i=0; + next_nonspace(f); + fscanf(*f, "%c", &strbuf[i]); + while ((strbuf[i] != '\"') && (i < 255)) + { + i++; + fscanf(*f, "%c", &strbuf[i]); + } + strbuf[i] = '\0'; // strip the trailing " + + bot_team->default_skin = G_CopyString(strbuf); + bot_team->default_skin = gi.TagMalloc (strlen(strbuf)+1, TAG_GAME); + strcpy(bot_team->default_skin, strbuf); + + // start of the bots + ch = 0; + while ((ch != '[') && !feof(*f)) + fscanf(*f, "%c", &ch); + + if (feof(*f)) + { + gi.error("Bot team \"%s\"doesn't have bot list\n", bot_team->teamname); + return NULL; + } + + fscanf(*f, "%c", &ch); // skip the opening " + + numbots = 0; + while ((numbots < MAX_PLAYERS_PER_TEAM) && (ch != ']')) + { + // read the name + + i=0; + fscanf(*f, "%c", &strbuf[i]); + + while ((strbuf[i] != '\"') && (i < 255)) + { + i++; + fscanf(*f, "%c", &strbuf[i]); + } + strbuf[i] = '\0'; // strip the trailing " + + if (!(bot_team->bots[numbots] = GetBotData(strbuf))) + { + bot_team->bots[numbots] = GenerateBotData(bot_team, strbuf); + + // add the bot to the teambot_list + if (!teambot_list) + { + teambot_list = bot_team->bots[numbots]; + teambot_list->next = NULL; + } + else // add to the start of the list + { + bot_team->bots[numbots]->next = teambot_list; + teambot_list = bot_team->bots[numbots]; + } + + } + + numbots++; + + if (next_nonspace(f) == ']') + break; // end of group + } + + // clear any remaing bot slots + while (numbots < MAX_PLAYERS_PER_TEAM) + bot_team->bots[numbots++] = NULL; + + bot_team->score = 0; + bot_team->ingame = 0; + bot_team->num_players = 0; + + return bot_team; +}; +//FIXME ACRID +// reads a line of bot data from bots.cfg +bot_info_t *ReadBotData(FILE **f) +{ + char strbuf[256]; + char buffer; + int i; + bot_info_t *botdata; + + botdata = gi.TagMalloc (sizeof(bot_info_t), TAG_GAME); + + botdata->ingame_count = 0; + + // name + i=0; + fscanf(*f, "%c", &strbuf[i]); + while ((strbuf[i] != '\"') && (i < 255)) + { + i++; + fscanf(*f, "%c", &strbuf[i]); + + if (strbuf[i] == '\n') + return NULL; + } + strbuf[i] = '\0'; // strip the trailing " + + botdata->name = gi.TagMalloc (128, TAG_GAME); + strcpy(botdata->name, strbuf); + + while (!feof(*f) && (buffer != '"')) + { + fscanf(*f, "%c", &buffer); + if (buffer == '\n') + return NULL; + } + + // skin + i=0; + fscanf(*f, "%c", &strbuf[i]); + if (strbuf[i] == '\n') + return NULL; + + while ((strbuf[i] != '\"') && (i < 255)) + { + i++; + fscanf(*f, "%c", &strbuf[i]); + + if (strbuf[i] == '\n') + return NULL; + } + strbuf[i] = '\0'; // strip the trailing " + + botdata->wfclass = gi.TagMalloc (128, TAG_GAME);//Acrid + strcpy(botdata->wfclass, strbuf);//Acrid + + fscanf(*f, "%c", &buffer); + + if (buffer == '\n') + return NULL; + + // stats + fscanf(*f, "%i", &i); + botdata->bot_stats.accuracy = (float) i; + fscanf(*f, "%i", &i); + botdata->bot_stats.aggr = (float) i; + fscanf(*f, "%i", &i); + botdata->bot_stats.combat = (float) i; + fscanf(*f, "%i", &i); + + if (i == 1) // overwrite blaster with RL + i = 7; + + botdata->bot_stats.fav_weapon = GetWeaponForNumber(i); + + fscanf(*f, "%i", &(botdata->bot_stats.quad_freak)); + fscanf(*f, "%i", &(botdata->bot_stats.camper)); + fscanf(*f, "%i", &(botdata->bot_stats.avg_ping)); + + return botdata; +} + +void ReadViewWeaponModel(FILE **f) +{ + char buffer; + int i=0; + + while (!feof(*f) && (buffer != '\n')) + { + fscanf(*f, "%c", &buffer); + + if ((buffer != '"') && (buffer != '\n') && (buffer != '\r')) + view_weapon_models[num_view_weapons][i++] = buffer; + } + + if (i>0) + { + view_weapon_models[num_view_weapons][i] = 0; + num_view_weapons++; + } +} + +qboolean read_bot_cfg = false; + +// Reads data from bots.cfg (called from worldspawn) +void ReadBotConfig() +{ + FILE *f; + int i, mode=0; // mode 0 for reading bots, 1 for teams + char filename[256]; + char buffer; + bot_info_t *botdata=NULL, *last_botdata=NULL; + cvar_t *game_dir; + + if (read_bot_cfg) // must have already read in the config + return; + + game_dir = gi.cvar ("game", "", 0); + +#ifdef _WIN32 + i = sprintf(filename, ".\\"); + i += sprintf(filename + i, game_dir->string); + i += sprintf(filename + i, "\\bots.cfg"); +#else + strcpy(filename, "./"); + strcat(filename, game_dir->string); + strcat(filename, "/bots.cfg"); +#endif + + f = fopen (filename, "r"); + if (!f) + { + gi.error("Unable to read bots.cfg. Cannot continue.\n"); + return; + } + + // initialise the teams + for (i=0; iingame_count = 0; + botinfo_list->name = "Eraser"; + botinfo_list->wfclass = "1";//was skin + + botinfo_list->bot_stats.accuracy = 5; + botinfo_list->bot_stats.aggr = 0; + botinfo_list->bot_stats.combat = 5; + botinfo_list->bot_stats.fav_weapon = GetWeaponForNumber(2); + botinfo_list->bot_stats.quad_freak = 0; + botinfo_list->bot_stats.camper = 0; + botinfo_list->bot_stats.avg_ping = 50; + // done. + + botdata = botinfo_list; + + total_bots = 1; + total_teams = 0; + teambot_list = NULL; + num_view_weapons = 0; + memset(view_weapon_models, 0, sizeof(view_weapon_models)); + + while (!feof(f)) + { + fscanf(f, "%c", &buffer); + + if (feof(f)) + break; + + if (buffer == '#') // commented line + { + while (!feof(f) && (buffer != '\n')) + fscanf(f, "%c", &buffer); + } + else if (buffer == '[') // mode specifier (bots/teams) + { + fscanf(f, "%c", &buffer); + + if (buffer == 'b') + mode = 0; + else if (buffer == 't') + { +// if (!teamplay->value /*|| ctf->value*/) +// break; + mode = 1; + } + else if (buffer == 'v') + { +// if (!view_weapons->value) + break; + mode = 2; + } + + fscanf(f, "\n"); + } + else if (buffer == '"') // start of some data + { + if (mode == 0) + { + last_botdata = botdata; + if (!(botdata = ReadBotData(&f))) + { + gi.error("\nError in BOTS.CFG: Invalid BOT (#%i)\nEither re-install Eraser, or check your bots.cfg file for errors\n\n", total_bots); + break; + } + +#ifdef _WIN32 + if (!_stricmp(botdata->name, "Eraser")) +#else + if (!strcasecmp(botdata->name, "Eraser")) +#endif + { // ignore this bot + gi.TagFree(botdata); + botdata = last_botdata; + } + else + { + total_bots++; + + if (last_botdata) + last_botdata->next = botdata; + else // first bot + botinfo_list = botdata; + + botdata->next = NULL; + } + } + else if (mode == 1) // teamplay data + { + if (!(bot_teams[total_teams] = ReadTeamData(&f))) + { + gi.error("\nError in BOTS.CFG: Invalid TEAM (#%i)\nEither re-install Eraser, or check your bots.cfg file for errors\n\n", total_teams); + break; + } + + total_teams++; + + if (total_teams == MAX_TEAMS) + { + gi.dprintf("Warning: MAX_TEAMS reached, unable to process all teams\n"); + break; + } + } + else if (mode == 2) // view weapon models + { + ReadViewWeaponModel(&f); + } + } + + } + + gi.dprintf("%i bots read.\n", total_bots); + if (teamplay->value) + gi.dprintf("%i teams read.\n", total_teams); + + gi.dprintf("\n"); + +/* if (view_weapons->value && (num_view_weapons == 0)) + { + gi.dprintf("WARNING: view_weapons enabled, but no [view weapon] section in bots.cfg\n You should re-install Eraser to restore the [view weapons] section.\n\n"); + }*/ + + fclose (f); + + ReadBotChat(); + + read_bot_cfg = true; // don't load again +} + +qboolean ViewModelSupported(char *model) +{ + int i; + + for (i=0; i < num_view_weapons; i++) + { + if (!strcmp(view_weapon_models[i], model)) + return true; + } + + return false; +} + +bot_info_t *GetBotData(char *botname) +{ + bot_info_t *trav, *fallback=NULL; + qboolean done; + + if (!botinfo_list) + { + gi.dprintf("No bots available!\n"); + return NULL; + } + + if (!botname) + { // pick a random bot + int i, repeat_count=0; + + if ((total_bots-1) == bot_count) // already using all bots! + return NULL; + + i = (int) ceil(random() * ((total_bots-1) - bot_count)); + + // prevent Eraser from joining unless he is specifically asked for + trav = botinfo_list->next; + + while (trav && (i > 0)) + { + if (!trav->ingame_count) // not being used + i--; + + if (i>0) + trav = trav->next; + } + + done = false; + while (!done && trav) + { + done = true; + + if (!fallback) + fallback = trav; + + if (trav->ingame_count) + done = false; + /*else if (view_weapons->value)//ACRID FIXME HAD TO CO THIS FOR VIEWWEAPONS + { // if this model isn't supported by view weapons, look for another one + char heldmodel[128]; + int len; + + strcpy(heldmodel, trav->skin);//fixme? + + for(len = 0; heldmodel[len]; len++) + { + if(heldmodel[len] == '/') + { + heldmodel[len] = '\0'; + break; + } + } + + if (!ViewModelSupported(heldmodel)) + { // model not supported by view_weapons + done = false; + } + }*/ + + if (!done) + { + if (!(trav = trav->next)) + { + if (repeat_count < 2) + trav = botinfo_list->next; + + repeat_count++; + } + } + } + + if (trav) + { + return trav; + } + else if (fallback) + { + return fallback; + } + else + { + gi.dprintf("GetBotData(): random selection didn't work\n"); + return NULL; + } + } + else // see if we can find the bot in the list + { + char name[128], checkname[128]; + int i; + + trav = botinfo_list; + + // remove the team abbrev. + strcpy(checkname, botname); + i=0; + while (checkname[i] && (checkname[i] != '[')) + i++; + + if (checkname[i] == '[') + checkname[i] = 0; + + // remove the team abbrev. + strcpy(name, trav->name); + i=0; + while (i<128 && name[i] && (name[i] != '[')) + i++; + + if (name[i] == '[') + name[i] = 0; + +#ifdef _WIN32 + while (trav && _stricmp(checkname, name)) // not case-sensitive + { + trav = trav->next; +#else + while (trav && strcasecmp(checkname, name)) // not case-sensitive + { + trav = trav->next; +#endif + if (trav) + { + // remove the team abbrev. + strcpy(name, trav->name); + i=0; + while (i<128 && name[i] && (name[i] != '[')) + i++; + + if (name[i] == '[') + name[i] = 0; + } + } + + if (!trav) + { // search through the teamplay bots + trav = teambot_list; + + if (trav) + { + // remove the team abbrev. + strcpy(name, trav->name); + i=0; + while (i<128 && name[i] && (name[i] != '[')) + i++; + + if (name[i] == '[') + name[i] = 0; + } + +#ifdef _WIN32 + while (trav && _stricmp(checkname, name)) // not case-sensitive + { + trav = trav->next; +#else + while (trav && strcasecmp(checkname, name)) // not case-sensitive + { + trav = trav->next; +#endif + if (trav) + { + // remove the team abbrev. + strcpy(name, trav->name); + i=0; + while (i<128 && name[i] && (name[i] != '[')) + i++; + + if (name[i] == '[') + name[i] = 0; + } + } + } + + return trav; + } +} + +void FindVisibleItemsFromNode(edict_t *node) +{ + int list; + int pi=0, besti=-1; + float bestdist=99999, thisdist; + vec3_t org; + edict_t *trav; + + for (list=0; list<4; list++) + { + if (list==0) + trav = weapons_head; + else if (list==1) + trav = health_head; + else if (list==2) + trav = ammo_head; + else + trav = bonus_head; + + while (trav) + {//lavacode + // make sure it's not in a dangerous position + VectorSubtract(trav->s.origin, tv(0,0,8), org); + if (!(gi.pointcontents(org) & (CONTENTS_LAVA | CONTENTS_SLIME))) + { + + if ((thisdist = entdist(trav, node)) > 256) + goto next_ent; + + // find the end of the item's visible node list + pi = 0; + while ((trav->paths[pi] > -1) && (pi < MAX_PATHS)) + pi++; + + if (pi == MAX_PATHS) + goto next_ent; + + if (visible_box(node, trav) && CanReach(node, trav)) + { + trav->paths[pi] = node->trail_index; + + // see if this node is closer than the previous closest node + if (!trav->movetarget || (thisdist < entdist(trav, trav->movetarget))) + { + trav->movetarget = node; + } + } + } + +next_ent: + + trav = trav->node_target; + } + } +} + +// skill_level ranges from 0 -> 3 +void AdjustRatingsToSkill(edict_t *self) +{ + self->bot_stats->accuracy = self->botdata->bot_stats.accuracy + (float)(self->skill_level - 1) * 2.5; + if (self->bot_stats->accuracy > 5) + self->bot_stats->accuracy = 5; + else if (self->bot_stats->accuracy < 1) + self->bot_stats->accuracy = 1; + + self->bot_stats->combat = self->botdata->bot_stats.combat + (float)(self->skill_level - 1) * 2.5; + if (self->bot_stats->combat > 5) + self->bot_stats->combat = 5; + else if (self->bot_stats->combat < 1) + self->bot_stats->combat = 1; + + self->bot_stats->aggr = self->botdata->bot_stats.aggr - (float)(self->skill_level - 1) * 2; // a good player, will make sure they stock up on armour/items before attacking + if (self->bot_stats->aggr > 5) + self->bot_stats->aggr = 5; + else if (self->bot_stats->aggr < 1) + self->bot_stats->aggr = 1; +} +//SHOWPATH PROBLE? +void target_laser_think (edict_t *self); + +// draws a translucent line from spos to epos +edict_t *DrawLine(edict_t *owner, vec3_t spos, vec3_t epos) +{ + edict_t *beam; + + beam = G_Spawn(); + + beam->owner = owner; + beam->spawnflags = 1 | 4; + beam->classname = "path_beam"; + + beam->movetype = MOVETYPE_NONE; + beam->solid = SOLID_NOT; + beam->s.renderfx |= RF_BEAM|RF_TRANSLUCENT; + beam->s.modelindex = 1; //gi.modelindex ("models/objects/gibs/skull/tris.md2"); // must be non-zero + + // set the beam diameter + beam->s.frame = 4; + // set the color (green) + beam->s.skinnum = 0xd0d1d2d3; + + if (!beam->owner) + beam->owner = beam; + + VectorSet (beam->mins, -8, -8, -8); + VectorSet (beam->maxs, 8, 8, 8); + + beam->spawnflags |= 0x80000001; + beam->svflags &= ~SVF_NOCLIENT; + beam->flags |= FL_TEAMSLAVE; + + VectorCopy(spos, beam->s.origin); + VectorCopy(epos, beam->s.old_origin); + + VectorSubtract(epos, spos, beam->movedir); +// VectorNormalize2(beam->movedir, beam->movedir); + + beam->dmg = 0; + beam->enemy = NULL; + + beam->think = target_laser_think; + beam->nextthink = level.time + FRAMETIME; + beam->think(beam); + + gi.linkentity (beam); + + return beam; +} + +// Do team checking, which is different for CTF and non-CTF modes +qboolean botOnSameTeam(edict_t *p1, edict_t *p2) +{ + if (!ctf->value) + return (p1->client->team == p2->client->team); + else + return (p1->client->resp.ctf_team == p2->client->resp.ctf_team); +} + +/* +============ +TeamGroup + + Issues a team grouping message, and alerts all bots accordingly +============ +*/ +void TeamGroup(edict_t *ent) +{ + int i, chat_line, chat_type; + edict_t *closest, *trav; + float best_dist; + + if (!ent->client->team && !ctf->value) + return; + + if (ent->last_pain == level.time) + chat_type = CHAT_TEAMPLAY_HELP; + else + chat_type = CHAT_TEAMPLAY_GROUP; + + chat_line = (int) (random() * (float) bot_chat_count[chat_type]); + + // find closest item + closest = NULL; + best_dist = 512; + + trav = weapons_head; + while (trav) + { + if (trav->item && (entdist(trav, ent) < best_dist)) + { + closest = trav; + } + + trav = trav->node_target; + } + + if (!closest || (best_dist > 256)) + { + trav = bonus_head; + while (trav) + { + if (trav->item && (trav->item->tag != ARMOR_SHARD) && (entdist(trav, ent) < best_dist)) + { + closest = trav; + } + + trav = trav->node_target; + } + } + + if (!closest) // abort if no item is close by + return; + + for (i=0; itarget_ent) || (random() < 0.4)))) + { + if (!players[i]->bot_client) + { + safe_cprintf(players[i], PRINT_CHAT, "%s: ", ent->client->pers.netname); + safe_cprintf(players[i], PRINT_CHAT, bot_chat_text[chat_type][chat_line], closest->item->pickup_name); + safe_cprintf(players[i], PRINT_CHAT, "\n"); + } + else if (!players[i]->target_ent) + { // set bot to go straight to this player + players[i]->target_ent = ent; + } + } + } + + ent->group_pausetime = level.time + 10 + 5 * random(); + + if (ent->client->team) + ent->client->team->last_grouping = level.time + 5 + (random() * 10); +} + +// tells all following bots to disperse +void TeamDisperse(edict_t *self) +{ + int i, count=0; + + for (i=0; itarget_ent == self) + { + players[i]->target_ent = NULL; + count++; + } + } + + if (count) + { + safe_cprintf(self, PRINT_CHAT, "%s: ", self->client->pers.netname); + safe_cprintf(self, PRINT_CHAT, "all units disperse!\n"); + } +} + +void BotGreeting(edict_t *chat) +{ + int i; + + if (!bot_chat->value) + return; + + i = (int) (random() * (float) bot_chat_count[CHAT_GREETINGS]); + + my_bprintf(PRINT_CHAT, "%s: ", chat->owner->client->pers.netname); + my_bprintf(PRINT_CHAT, bot_chat_text[CHAT_GREETINGS][i]); + my_bprintf(PRINT_CHAT, "\n"); + + G_FreeEdict(chat); +} + +void BotComeback(edict_t *self) +{ + int i; + + if (!bot_chat->value) + return; + + i = (int) (random() * (float) bot_chat_count[CHAT_COMEBACKS]); + + my_bprintf(PRINT_CHAT, "%s: ", self->owner->client->pers.netname); + my_bprintf(PRINT_CHAT, bot_chat_text[CHAT_COMEBACKS][i], self->enemy->client->pers.netname); + my_bprintf(PRINT_CHAT, "\n"); + + G_FreeEdict(self); +} + +void BotInsultStart(edict_t *self) +{ + // insult? + if (fabs(self->owner->client->resp.score - self->enemy->client->resp.score) < 5) + { // general insult + if (last_bot_chat[CHAT_INSULTS_GENERAL] < (level.time - 5)) + BotInsult(self->owner, self->enemy, CHAT_INSULTS_GENERAL); + } + else + { + if (self->owner->client->resp.score > self->enemy->client->resp.score) + { // kickin ass + if (last_bot_chat[CHAT_INSULTS_KICKASS] < (level.time - 5)) + BotInsult(self->owner, self->enemy, CHAT_INSULTS_KICKASS); + } + else + { + if (last_bot_chat[CHAT_INSULTS_LOSING] < (level.time - 5)) + BotInsult(self->owner, self->enemy, CHAT_INSULTS_LOSING); + } + } + + G_FreeEdict(self); +} + +void BotInsult(edict_t *self, edict_t *enemy, int chat_type) +{ + int i; + + if (!bot_chat->value) + return; + + i = (int) (random() * (float) bot_chat_count[chat_type]); + + my_bprintf(PRINT_CHAT, "%s: ", self->client->pers.netname); + my_bprintf(PRINT_CHAT, bot_chat_text[chat_type][i], enemy->client->pers.netname); + my_bprintf(PRINT_CHAT, "\n"); + + last_bot_chat[chat_type] = level.time; + + if (enemy->bot_client && (random() < 0.3) && (last_bot_chat[CHAT_COMEBACKS] < (level.time - 3))) + { + edict_t *comeback; + + comeback = G_Spawn(); + comeback->think = BotComeback; + comeback->nextthink = level.time + 2 + random(); + comeback->enemy = self; + comeback->owner = enemy; + + last_bot_chat[CHAT_COMEBACKS] = level.time + 5; + } +} + +qboolean SameTeam(edict_t *plyr1, edict_t *plyr2) +{ // returns true if the 2 players are on the same team + if (ctf->value) + { + return (plyr1->client->resp.ctf_team == plyr2->client->resp.ctf_team); + } + else + { + // neutral players attack anyone + if (!plyr1->client->team || !plyr2->client->team) + return false; + + return (plyr1->client->team == plyr2->client->team); + } +} + +float HomeFlagDist(edict_t *self) +{ + edict_t *flag; + + if (self->client->resp.ctf_team == CTF_TEAM1) + flag = flag1_ent; + else + flag = flag2_ent; + + return entdist(self, flag); +} + +qboolean CarryingFlag(edict_t *ent) +{ // return true if ent is carrying the enemy flag +/* + if (!ctf->value) + return false; + if (!ent->client) + return false; + + if (ent->client->resp.ctf_team == CTF_TEAM1) + return ent->client->pers.inventory[ITEM_INDEX(flag2_item)]; + else + return ent->client->pers.inventory[ITEM_INDEX(flag1_item)]; +*/ + return (ent->s.effects & (EF_FLAG1|EF_FLAG2)); +} diff --git a/bot_nav.c b/bot_nav.c new file mode 100644 index 0000000..22171b2 --- /dev/null +++ b/bot_nav.c @@ -0,0 +1,451 @@ +/***************************************************************** + + Eraser Bot source code - by Ryan Feltrin, Added to by Acrid- + + .............................................................. + + This file is Copyright(c) 1998, Ryan Feltrin, All Rights Reserved. + + .............................................................. + + All other files are Copyright(c) Id Software, Inc. + + Please see liscense.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 Eraser, you MUST + include the following text (minus the BEGIN and END lines) in the + documentation for your modification. + + --- BEGIN --- + + The Eraser Bot is a product of Ryan Feltrin, and is available from + the Eraser Bot homepage, at http://impact.frag.com. + + This program is a modification of the Eraser Bot, and is therefore + in NO WAY supported by Ryan Feltrin. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Ryan Feltrin immediately, via + the Eraser Bot homepage. + + --- END --- + + .............................................................. + + You will find p_trail.c has not been included with the Eraser + source code release. This is NOT an error. I am unable to + distribute this file because it contains code that is bound by + legal documents, and signed by myself, never to be released + to the public. Sorry guys, but law is law. + + I have therefore include the compiled version of these files + in .obj form in the src\Release and src\Debug directories. + So while you cannot edit and debug code within these files, + you can still compile this source as-is. Although these will only + work in MSVC v5.0, linux versions can be made available upon + request. + + NOTE: When compiling this source, you will get a warning + message from the compiler, regarding the missing p_trail.c + file. Just ignore it, it will still compile fine. + + .............................................................. + + I, Ryan Feltrin/Acrid-, hold no responsibility for any harm caused by the + use of this source code. I also am NOT willing to provide any form + of help or support for this source code. It is provided as-is, + as a service by me, with no documentation, other then the comments + contained within the code. If you have any queries, I suggest you + visit the "official" Eraser source web-board, at + http://www.telefragged.com/epidemic/. I will stop by there from + time to time, to answer questions and help with any problems that + may arise. + + Otherwise, have fun, and I look forward to seeing what can be done + with this. + + -Ryan Feltrin + -Acrid- + + *****************************************************************/ +// +// bot_nav.c +// +// This file conatins mostly (simple) environment sampling, which +// in no way is related to the the navigation code, contained within +// p_trail.c +// + +#include "g_local.h" +#include "m_player.h" +#include "bot_procs.h" +#include "p_trail.h" + +/* +================ +botRoamFindBestDirection + +Finds the best direction to walk for the next few seconds +Set ideal_yaw accordingly +================ +*/ +#define TRACE_DIST 256 +void botRoamFindBestDirection(edict_t *self) +{ + float best_dist=0, this_dist, best_yaw; + int i; + vec3_t dir, dest, angle, this_angle; + vec3_t mins; + trace_t trace; + + if (self->last_best_direction > (level.time - 1)) + return; + self->last_best_direction = level.time; + + bestdirection_callsthisframe++; + if (bestdirection_callsthisframe > 2) + return; + + best_yaw = self->ideal_yaw; + VectorAdd(self->mins, tv(0,0,STEPSIZE), mins); + + // check eight compass directions + + VectorClear(angle); + VectorClear(this_angle); + angle[1] = self->ideal_yaw; + + // start at center, then fan out in 45 degree intervals, swapping between + and - + for (i=1; i<8; i++) + { +// if (i<7 && random() < 0.4) // skip random intervals +// i++; + + if (i==4) + i=6; + + this_angle[1] = anglemod(angle[1] + ((((i % 2)*2 - 1) * (int) (floor(i/2))) * 45)); + AngleVectors(this_angle, dir, NULL, NULL); + + VectorMA(self->s.origin, TRACE_DIST, dir, dest); + + trace = gi.trace(self->s.origin, mins, self->maxs, dest, self, MASK_SOLID); + + if (trace.fraction > 0) + { // check that destination is onground, or not above lava/slime + dest[0] = trace.endpos[0]; + dest[1] = trace.endpos[1]; + dest[2] = trace.endpos[2] - 32; + + if (gi.pointcontents(dest) & MASK_PLAYERSOLID) + goto nocheckground; + + this_dist = trace.fraction * TRACE_DIST; + + VectorCopy(trace.endpos, dest); + dest[2] -= 256; + trace = gi.trace(trace.endpos, VEC_ORIGIN, VEC_ORIGIN, dest, self, MASK_SOLID | MASK_WATER); + + if ( ! ((trace.fraction == 1) || (trace.contents & MASK_WATER))) // avoid ALL forms of liquid for now + { +//gi.dprintf("Dist %i: %i\n", (int) this_angle[1], (int) this_dist); + + if (trace.fraction > 0.4) // if there is a drop in this direction, try to avoid it if possible + this_dist *= 0.5; + +nocheckground: + if (this_dist > best_dist) + { + best_dist = this_dist; + best_yaw = this_angle[1]; + + if (this_dist == TRACE_DIST) + break; + } + } + } + + } + +//gi.dprintf("Best yaw: %i\n", (int) best_yaw); + + self->ideal_yaw = best_yaw; +} + +void botRandomJump(edict_t *self) +{ + vec3_t dir, right, angles; + + if (self->groundentity) + { + if (self->last_jump > (level.time - 0.5)) + return; + } + else + { +// if (self->last_jump > (level.time - 2)) + return; + } + + if (!CanJump(self)) + return; + + botRoamFindBestDirection(self); + + VectorClear(angles); + angles[1] = self->ideal_yaw; + + AngleVectors(angles, dir, right, NULL); +// VectorScale(dir, (crandom() + 0.5) / 1.5, dir); +// VectorScale(right, crandom() * 0.5, right); +// VectorAdd(dir, right, dir); + VectorNormalize2(dir, dir); + + VectorScale(dir, 300 * ((random() * 0.6) + 0.4), dir); + VectorCopy(dir, self->velocity); + + self->velocity[2] = 300; + self->groundentity = NULL; +// self->s.origin[2] += 1; + + gi.linkentity(self); + + VectorCopy(self->velocity, self->jump_velocity); + + if (self->groundentity) + gi.sound(self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, 2, 0); + + self->last_jump = level.time; +} + +extern edict_t *pm_passent; +extern trace_t PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + +qboolean touched_player; + +void BotMoveThink (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; + } + + pm_passent = ent; + + // 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 if (level.time < ent->frozentime)//botfreeze 3/99 needed still? + client->ps.pmove.pm_type = PM_DEAD;//3/99 + else + client->ps.pmove.pm_type = PM_NORMAL; + client->ps.pmove.gravity = sv_gravity->value; + if (ent->maxs[2] == 4) + { + client->ps.pmove.pm_flags |= PMF_DUCKED; + ucmd->upmove = -400; + } + 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"); +// } +*/ +// ucmd->buttons = 128; + + pm.cmd = *ucmd; + + pm.trace = PM_trace; // adds default parms + pm.pointcontents = gi.pointcontents; + + // perform a pmove + gi.Pmove (&pm); + + // save results of pmove + client->ps.pmove = pm.s; + client->old_pmove = pm.s; + + 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; + } + + 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]); + + 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); + + touched_player = false; + + // touch other objects + for (i=0 ; iclient) + touched_player = true; + + if (!other->touch) + continue; + other->touch (other, ent, NULL, NULL); + } + +} + +int botJumpAvoidEnt(edict_t *self, edict_t *e_avoid) +{ + vec3_t dir, trail_vec, vec, tr_end; + float avoid_dist; + trace_t tr; + + if (!CanJump(self)) + return false; + + if (e_avoid->owner && (((int) dmflags->value) & DF_NO_FRIENDLY_FIRE) && SameTeam(self, e_avoid->owner)) + return 2; +//fixme acrid + if ((avoid_dist = entdist(self, e_avoid)) > 300) + { + self->avoid_ent = NULL; + return 2; // just keep going for our current goal + } + + if (self->movetarget && (entdist(self, self->movetarget) < 256)) + { + return 2; + } + + if (!visible(self, e_avoid)) + return 2; + + // make sure we attack this person, if not currently attacking anything + if (!self->enemy && e_avoid->owner && e_avoid->owner->client) + self->enemy = e_avoid->owner; + + VectorSubtract(self->s.origin, e_avoid->s.origin, dir); + VectorNormalize2(dir, dir); + + trail_vec[0] = dir[1]; + trail_vec[1] = dir[0]; + trail_vec[2] = 0; + + // determine which side we're on, so we know which way to try and dodge + VectorMA(e_avoid->s.origin, 4, trail_vec, vec); + VectorSubtract(self->s.origin, vec, vec); + + if (avoid_dist < 200) + { + if (VectorLength(vec) > avoid_dist) + { + VectorScale(trail_vec, -1, trail_vec); + } + } + else if (random() < 0.5) // pick a random direction + { + VectorScale(trail_vec, -1, trail_vec); + } + + // add some forwards/backwards movement + VectorMA(trail_vec, crandom(), dir, trail_vec); + VectorNormalize2(trail_vec, trail_vec); + + if (self->groundentity) + { + VectorMA(e_avoid->s.origin, 200, trail_vec, vec); + tr = gi.trace(self->s.origin, vec3_origin, vec3_origin, vec, self, MASK_PLAYERSOLID); + + VectorCopy(tr.endpos, tr_end); + tr_end[2] -= 512; + tr = gi.trace(tr.endpos, vec3_origin, vec3_origin, tr_end, self, MASK_PLAYERSOLID | MASK_WATER); + + if (!(tr.contents & (CONTENTS_LAVA | CONTENTS_SLIME)))/* && ((e_avoid->s.effects & EF_GRENADE) || (avoid_dist < 160)))*/ + { // go ahead and jump! + VectorScale(trail_vec, BOT_RUN_SPEED, dir); + dir[2] = 300; + + VectorCopy(dir, self->velocity); + VectorCopy(dir, self->jump_velocity); + + self->groundentity = NULL; +// self->s.origin[2] += 1; + gi.linkentity(self); + + gi.sound(self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, 2, 0); + } + else // strafe away + { + VectorCopy(trail_vec, self->avoid_dir); + self->avoid_dir_time = level.time + 0.3; + } + } + else if (self->waterlevel) + { + VectorCopy(trail_vec, self->avoid_dir); + self->avoid_dir_time = level.time + 1; + } + + return 1; +} diff --git a/bot_procs.h b/bot_procs.h new file mode 100644 index 0000000..e2daa31 --- /dev/null +++ b/bot_procs.h @@ -0,0 +1,282 @@ +/***************************************************************** + + Eraser Bot source code - by Ryan Feltrin, Added to by Acrid- + + .............................................................. + + This file is Copyright(c) 1998, Ryan Feltrin, All Rights Reserved. + + .............................................................. + + All other files are Copyright(c) Id Software, Inc. + + Please see liscense.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 Eraser, you MUST + include the following text (minus the BEGIN and END lines) in the + documentation for your modification. + + --- BEGIN --- + + The Eraser Bot is a product of Ryan Feltrin, and is available from + the Eraser Bot homepage, at http://impact.frag.com. + + This program is a modification of the Eraser Bot, and is therefore + in NO WAY supported by Ryan Feltrin. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Ryan Feltrin immediately, via + the Eraser Bot homepage. + + --- END --- + + .............................................................. + + You will find p_trail.c has not been included with the Eraser + source code release. This is NOT an error. I am unable to + distribute this file because it contains code that is bound by + legal documents, and signed by myself, never to be released + to the public. Sorry guys, but law is law. + + I have therefore include the compiled version of these files + in .obj form in the src\Release and src\Debug directories. + So while you cannot edit and debug code within these files, + you can still compile this source as-is. Although these will only + work in MSVC v5.0, linux versions can be made available upon + request. + + NOTE: When compiling this source, you will get a warning + message from the compiler, regarding the missing p_trail.c + file. Just ignore it, it will still compile fine. + + .............................................................. + + I, Ryan Feltrin/Acrid-, hold no responsibility for any harm caused by the + use of this source code. I also am NOT willing to provide any form + of help or support for this source code. It is provided as-is, + as a service by me, with no documentation, other then the comments + contained within the code. If you have any queries, I suggest you + visit the "official" Eraser source web-board, at + http://www.telefragged.com/epidemic/. I will stop by there from + time to time, to answer questions and help with any problems that + may arise. + + Otherwise, have fun, and I look forward to seeing what can be done + with this. + + -Ryan Feltrin + -Acrid- + + *****************************************************************/ +#define ERASER_VERSION 1.01 + +#define MAX_BOTS 25 + +#define STEPSIZE 24 +#define BOT_RUN_SPEED 300 +#define BOT_STRAFE_SPEED 200 +#define BOT_IDEAL_DIST_FROM_ENEMY 160 + +#define WANT_KINDA 1 +#define WANT_YEH_OK 2 +#define WANT_SHITYEAH 3 + +#define BOT_GUARDING_RANGE 600.0 + +// bot_ai.c +// these define how long the bot will search for it's enemy before giving up +#define BOT_SEARCH_LONG 4 +#define BOT_SEARCH_MEDIUM 2 +#define BOT_SEARCH_SHORT 1 + +#define SIGHT_FIRE_DELAY 0.8 // so bot's don't fire straight away after sighting an enemy + +int spawn_bots; +int roam_calls_this_frame; +int bestdirection_callsthisframe; + +// ---- BOT CHAT DATA ---- + +#define CHAT_GREETINGS 0 +#define CHAT_INSULTS_GENERAL 1 +#define CHAT_INSULTS_KICKASS 2 +#define CHAT_INSULTS_LOSING 3 +#define CHAT_COMEBACKS 4 +#define CHAT_TEAMPLAY_HELP 5 +#define CHAT_TEAMPLAY_DROPITEM 6 +#define CHAT_TEAMPLAY_GROUP 7 + +#define NUM_CHAT_SECTIONS 8 +#define MAX_CHAT_PER_SECTION 64 + +char *bot_chat_text[NUM_CHAT_SECTIONS][MAX_CHAT_PER_SECTION]; +int bot_chat_count[NUM_CHAT_SECTIONS]; +float last_bot_chat[NUM_CHAT_SECTIONS]; + +int num_view_weapons; +char view_weapon_models[64][64]; + +// ----------------------- + +int RoamFindBestItem(edict_t *self, edict_t *list_head, int check_paths); +void bot_ChangeYaw(edict_t *self); +void bot_MoveAI(edict_t *self, int dist); +float bot_ReachedTrail(edict_t *self); +void botMachineGun (edict_t *self); +int bot_move(edict_t *self, float dist); +int bot_oldmove(edict_t *self, float dist); +void respawn_bot (edict_t *self); +void bot_SuicideIfStuck(edict_t *self); + +void bot_pain (edict_t *self, edict_t *other, float kick, int damage); +void bot_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +void bot_run (edict_t *self); + +edict_t *bot_FindAnyTrail(edict_t *bot); +int CanJump(edict_t *ent); + +void bot_AnimateFrames(edict_t *self); +void bot_roam (edict_t *self, int force_enemy); +int botCheckStuck(edict_t *self); +int CanStand(edict_t *self); +int CanSee(edict_t *self, edict_t *targ); +int CanReach(edict_t *self, edict_t *targ); + +int botdebug; +void botDebugPrint(char *msg, ...); + +// bot_wpns.c + +#define FIRE_INTERVAL_BLASTER 0.6 +#define FIRE_INTERVAL_ROCKETLAUNCHER 0.8 +#define FIRE_INTERVAL_GRENADELAUNCHER 0.9 +#define FIRE_INTERVAL_RAILGUN 1.5 +#define FIRE_INTERVAL_HYPERBLASTER 0 +#define FIRE_INTERVAL_CHAINGUN 0 +#define FIRE_INTERVAL_MACHINEGUN 0 +#define FIRE_INTERVAL_SHOTGUN 1 +#define FIRE_INTERVAL_SSHOTGUN 1 +#define FIRE_INTERVAL_BFG 2.8 +//ACRID NEW DEFINES FOR WF WEAPONS +#define FIRE_INTERVAL_SNIPERRIFLE 1 +#define FIRE_INTERVAL_LIGHTNINGGUN 1.5 +#define FIRE_INTERVAL_INFECTEDDART 0.9 +#define FIRE_INTERVAL_PULSECANNON 0 +#define FIRE_INTERVAL_TELSACOIL 0//42 Acrid +#define FIRE_INTERVAL_NEEDLER 0 +#define FIRE_INTERVAL_FLAMETHROWER 0.3 +#define FIRE_INTERVAL_ROCKETNAPALMLAUNCHER 0.8 +#define FIRE_INTERVAL_PELLETROCKETLAUNCHER 0.8 +#define FIRE_INTERVAL_ROCKETCLUSTERLAUNCHER 0.8 +#define FIRE_INTERVAL_STINGERROCKETLAUNCHER 0.8 +#define FIRE_INTERVAL_SHC 1 +#define FIRE_INTERVAL_GRENADES 1 +#define FIRE_INTERVAL_POISONDART 0.9 +#define FIRE_INTERVAL_AK47 0 +#define FIRE_INTERVAL_PISTOL 0.6 +#define FIRE_INTERVAL_KNIFE 1 +//ACRID NEW DEFINES FOR WF WEAPONS + +#define BOT_CHANGEWEAPON_DELAY 0.9 + +void bot_FireWeapon(edict_t *self); +void bot_Attack(edict_t *self); + +void botBlaster (edict_t *self); +void botMachineGun (edict_t *self); +void botShotgun (edict_t *self); +void botSuperShotgun (edict_t *self); +void botChaingun (edict_t *self); +void botRailgun (edict_t *self); +void botRocketLauncher (edict_t *self); +void botGrenadeLauncher (edict_t *self); +void botHyperblaster (edict_t *self); +void botBFG (edict_t *self); +//ACRID WF STUFF +void botSniperRifle (edict_t *self); +void botLightningGun (edict_t *self); +void botInfectedDart (edict_t *self); +void botPulseCannon (edict_t *self); +void botTelsaCoil (edict_t *self);//42 +void botFlameThrower (edict_t *self); +void botNeedler (edict_t *self); +void botPelletRocketLauncher (edict_t *self); +void botRocketNapalmLauncher (edict_t *self); +void botRocketClusterLauncher (edict_t *self); +void botStingerRocketLauncher (edict_t *self); +void botSHC (edict_t *self); +void botGrenades (edict_t *self); +void botPoisonDart (edict_t *self); +void botAk47 (edict_t *self); +void botPistol (edict_t *self); +//ACRID WF STUFF + +void botPickBestWeapon(edict_t *self); +void botPickBestGrenade(edict_t *self);//acrid +int botHasWeaponForAmmo (gclient_t *client, gitem_t *item); +//int ClientHasAnyWeapon(gclient_t *client);//acrid coed +int botCanPickupAmmo (gclient_t *client, gitem_t *item); +int botCanPickupArmor (edict_t *self, edict_t *ent); +void botPickBestFarWeapon(edict_t *self); +void botPickBestCloseWeapon(edict_t *self); +int botAmmoIndex(gitem_t *weapon); +qboolean botHasWeaponInInventory(edict_t *self, gitem_t *weapon); +qboolean botHasAmmoForWeapon(edict_t *self, gitem_t *weapon); +qboolean botHasThisWeapon(edict_t *self, gitem_t *weapon); + +void GetBotFireForWeapon(gitem_t *weapon, void (**bot_fire)(edict_t *self)); +int botCanPickupPack (edict_t *self, edict_t *ent);//acrid +int botCanPlaceSpecial (edict_t *self, edict_t *ent);//42 acrid +// bot_spawn.c +edict_t *spawn_bot (char *botname); +void botDisconnect(edict_t *self); + +// bot_misc.c +void ReadBotConfig(); +bot_info_t *GetBotData(char *botname); +void NodeDebug(char *fmt, ...); +void FindVisibleItemsFromNode(edict_t *node); +void AdjustRatingsToSkill(edict_t *self); +edict_t *DrawLine(edict_t *owner, vec3_t spos, vec3_t epos); +void TeamGroup(edict_t *ent); +void TeamDisperse(edict_t *self); +void BotGreeting(edict_t *chat); +void BotInsultStart(edict_t *self); +void BotInsult(edict_t *self, edict_t *enemy, int chat_type); +qboolean SameTeam(edict_t *plyr1, edict_t *plyr2); +float HomeFlagDist(edict_t *self); +qboolean CarryingFlag(edict_t *ent); + +// bot_nav.c +void botRoamFindBestDirection(edict_t *self); +void botRandomJump(edict_t *self); +void BotMoveThink (edict_t *ent, usercmd_t *ucmd); +int botJumpAvoidEnt(edict_t *self, edict_t *e_avoid); + + +// FIXME: this should go in g_local.h +qboolean monster_start (edict_t *self); +qboolean monster_start_go (edict_t *self); +void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles); +void Use_Quad (edict_t *ent, gitem_t *item); +void ClientDisconnect (edict_t *ent); +void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator); +void ShowGun(edict_t *ent); +void FlagPathTouch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +extern char respawn_bots[64][256]; + +// CTF stuff +extern gitem_t *flag1_item; +extern gitem_t *flag2_item; + +extern edict_t *flag1_ent; +extern edict_t *flag2_ent; + +extern gitem_t *item_tech1, *item_tech2, *item_tech3, *item_tech4; + +extern edict_t *flagreturn1_ent;//$ +extern edict_t *flagreturn2_ent;//$ \ No newline at end of file diff --git a/bot_spawn.c b/bot_spawn.c new file mode 100644 index 0000000..0849080 --- /dev/null +++ b/bot_spawn.c @@ -0,0 +1,482 @@ +/***************************************************************** + + Eraser Bot source code - by Ryan Feltrin, Added to by Acrid- + + .............................................................. + + This file is Copyright(c) 1998, Ryan Feltrin, All Rights Reserved. + + .............................................................. + + All other files are Copyright(c) Id Software, Inc. + + Please see liscense.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 Eraser, you MUST + include the following text (minus the BEGIN and END lines) in the + documentation for your modification. + + --- BEGIN --- + + The Eraser Bot is a product of Ryan Feltrin, and is available from + the Eraser Bot homepage, at http://impact.frag.com. + + This program is a modification of the Eraser Bot, and is therefore + in NO WAY supported by Ryan Feltrin. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Ryan Feltrin immediately, via + the Eraser Bot homepage. + + --- END --- + + .............................................................. + + You will find p_trail.c has not been included with the Eraser + source code release. This is NOT an error. I am unable to + distribute this file because it contains code that is bound by + legal documents, and signed by myself, never to be released + to the public. Sorry guys, but law is law. + + I have therefore include the compiled version of these files + in .obj form in the src\Release and src\Debug directories. + So while you cannot edit and debug code within these files, + you can still compile this source as-is. Although these will only + work in MSVC v5.0, linux versions can be made available upon + request. + + NOTE: When compiling this source, you will get a warning + message from the compiler, regarding the missing p_trail.c + file. Just ignore it, it will still compile fine. + + .............................................................. + + I, Ryan Feltrin/Acrid-, hold no responsibility for any harm caused by the + use of this source code. I also am NOT willing to provide any form + of help or support for this source code. It is provided as-is, + as a service by me, with no documentation, other then the comments + contained within the code. If you have any queries, I suggest you + visit the "official" Eraser source web-board, at + http://www.telefragged.com/epidemic/. I will stop by there from + time to time, to answer questions and help with any problems that + may arise. + + Otherwise, have fun, and I look forward to seeing what can be done + with this. + + -Ryan Feltrin + -Acrid- + + *****************************************************************/ +/* bot_spawn.c */ + +#include "g_local.h" +#include "m_player.h" +#include "bot_procs.h" +///Q2 Camera Begin +#include "camclient.h" +///Q2 Camera End +#include "stdlog.h" + +qboolean ClientConnect (edict_t *ent, char *userinfo, qboolean loadgame); + +// BEGIN: SABIN code +edict_t *bot_GetLastFreeClient (void) +{ + edict_t *bot; + int i; + + for (i = maxclients->value; i > 0; i--) + { + bot = g_edicts + i + 1; + + if (!bot->inuse) + break; + } + + if (bot->inuse) + bot = NULL; + + return bot; +} +// END: SABIN code + +void respawn_bot (edict_t *self) +{ + if (level.intermissiontime) + return; + + self->s.event = EV_PLAYER_TELEPORT; + + PutClientInServer(self); + + self->last_goal = NULL; + self->enemy = self->goalentity = self->movetarget = NULL; + self->viewheight = 22; + + // reset weapon to blaster + self->last_fire = level.time + 0.2; + self->fire_interval = FIRE_INTERVAL_BLASTER; + self->bot_fire = botBlaster; + + self->bored_suicide_time = -1; + self->checkstuck_time = level.time; + self->last_reached_trail = level.time + 1; + + self->client->killer_yaw = 0; // chaingun wind-up + self->avoid_ent = NULL; + self->flagpath_goal = NULL; + self->last_move_nocloser = level.time; + + // go for it + walkmonster_start(self); +} + +// Find an available edict, and initialize some default values +edict_t *G_SpawnBot () +{ + edict_t *bot; + + if (!deathmatch->value) + { + return NULL; + } + + if ((!bot_calc_nodes->value) && !loaded_trail_flag) + { + my_bprintf(PRINT_HIGH, "Route-table not found!\n"); + return NULL; + } + + last_bot_spawn = level.time; + +// bot = G_Spawn(); + bot = bot_GetLastFreeClient(); + + if (!bot) + { + gi.dprintf("No client spots available!\n"); + return NULL; + } + +// bot->bot_client = gi.TagMalloc (sizeof(struct gclient_s), TAG_GAME); + bot->bot_stats = gi.TagMalloc (sizeof(bot_stats_t), TAG_GAME); + + if (!bot->bot_stats) + { + gi.dprintf("Could not allocate Bot Stats!\n"); + return NULL; + } + + + bot->classname = "player"; + + bot->movetype = MOVETYPE_WALK; + bot->solid = SOLID_BBOX; + + //K2:Begin//fixme remove + bot->takedamage = DAMAGE_YES; + //K2:End + + VectorSet (bot->mins, -16, -16, -24); + VectorSet (bot->maxs, 16, 16, 32); + + bot->health = bot->max_health = 100; + bot->mass = 200; + bot->gravity = 1; + + bot->last_goal = NULL; + + bot->pain = bot_pain; + bot->die = bot_die; + + bot->monsterinfo.stand = bot_run; //bot_stand; + bot->monsterinfo.walk = bot_run; //bot_walk; + bot->monsterinfo.run = bot_run; + bot->monsterinfo.attack = bot_run; + bot->monsterinfo.melee = NULL; + bot->monsterinfo.sight = NULL; + + bot->monsterinfo.scale = MODEL_SCALE; + + bot->enemy = bot->goalentity = bot->movetarget = NULL; + + players[num_players++] = bot; + + bot->last_fire = level.time + 0.2; + bot->fire_interval = FIRE_INTERVAL_BLASTER; + bot->bot_fire = botBlaster; + + bot->bored_suicide_time = -1; + bot->checkstuck_time = level.time; + + bot->viewheight = 22; + bot->yaw_speed = 50; // turn at yaw_speed degrees per FRAME + + bot->last_reached_trail = level.time + 1; + + bot->avoid_ent = NULL; + bot->last_move_nocloser = level.time; + + return bot; +} + +// Perform the ClientBegin() and ClientBeginDeathmatch() functions in one routine, that is bot-specific +edict_t *spawn_bot (char *botname) +{ + edict_t *bot, *chat; + bot_info_t *botdata=NULL; +//fixme debugger char skin[256]; + char userinfo[MAX_INFO_STRING]; + vec3_t spawn_origin, spawn_angles; + char WFclass[256]; + + if (!(botdata = GetBotData(botname))) + { + gi.dprintf("Unable to find bot, or no bots left\n"); + return NULL; + } + + + bot = G_SpawnBot(); + + if (!bot) + { + gi.dprintf("Unable to spawn bot: cannot create entity\n"); + return NULL; + } + + bot->bot_client = true; + bot->client = &game.clients[bot-g_edicts-1]; + + memset(bot->client, 0, sizeof(*(bot->client))); + + // copy the stats across from the bot config + botdata->ingame_count++; + bot->botdata = botdata; + + strcpy(WFclass, botdata->wfclass); + +// BEGIN: SABIN code + // initialise userinfo + memset (userinfo, 0, sizeof(userinfo)); + + // add bot's name/skin/hand to userinfo + Info_SetValueForKey (userinfo, "name", botdata->name); + Info_SetValueForKey (userinfo, "WFclass", botdata->wfclass); + Info_SetValueForKey (userinfo, "hand", "2"); // bot is center handed for now +// END: SABIN code + + ///Q2 Camera Begin + EntityListAdd(bot); + ///Q2 Camera End +//WF START ACRID INITIAL CLASS SPAWNING + + //set respawn protection time + bot->client->protecttime = level.time + RESPAWN_PROTECT_TIME; +// bot->client->pers.player_class = bot->client->pers.next_player_class; +if (strcmp("1", WFclass) == 0) + { bot->client->pers.player_class = 1; + bot->client->pers.next_player_class = 1;} +else if (strcmp("2", WFclass) == 0) + { bot->client->pers.player_class = 2; + bot->client->pers.next_player_class = 2;} +else if (strcmp("3", WFclass) == 0) + { bot->client->pers.player_class = 3; + bot->client->pers.next_player_class = 3;} +else if (strcmp("4", WFclass) == 0) + {bot->client->pers.player_class = 4; + bot->client->pers.next_player_class = 4;} +else if (strcmp("5", WFclass) == 0) + { bot->client->pers.player_class = 5; + bot->client->pers.next_player_class = 5;} +else if (strcmp("6", WFclass) == 0) + { bot->client->pers.player_class = 6; + bot->client->pers.next_player_class = 6;} +else if (strcmp("7", WFclass) == 0) + { bot->client->pers.player_class = 7; + bot->client->pers.next_player_class = 7;} +else if (strcmp("8", WFclass) == 0) + { bot->client->pers.player_class = 8; + bot->client->pers.next_player_class = 8;} +else if (strcmp("9", WFclass) == 0) + { bot->client->pers.player_class = 9; + bot->client->pers.next_player_class = 9;} +else if (strcmp("10", WFclass) == 0) + { bot->client->pers.player_class = 10; + bot->client->pers.next_player_class = 10;} +//WF END ACRID FIXES SKIN BUGS,CLASS BUGS,WEAPONS BUG clean this up + ClientConnect (bot, userinfo, false); + + if (ctf->value) + { + my_bprintf(PRINT_HIGH, "%s joined the %s team.\n", + bot->client->pers.netname, CTFTeamName(bot->client->resp.ctf_team)); + sl_LogPlayerTeamChange( &gi, + bot->client->pers.netname, + CTFTeamName(bot->client->resp.ctf_team)); + } + + SelectSpawnPoint (bot, spawn_origin, spawn_angles); +//INSERT TESTS HERE +// botDebugPrint("SPAWN_BOT %s (ACRID)\n",userinfo); + + VectorCopy(spawn_origin, bot->s.origin); +// bot->s.origin[2] += 1; // make sure off ground + + VectorCopy(spawn_angles, bot->s.angles); + + bot->client->killer_yaw = 0; // chaingun wind-up + + // clear playerstate values + memset (&bot->client->ps, 0, sizeof(bot->client->ps)); + + bot->client->ps.pmove.origin[0] = bot->s.origin[0]*8; + bot->client->ps.pmove.origin[1] = bot->s.origin[1]*8; + bot->client->ps.pmove.origin[2] = bot->s.origin[2]*8; + + bot->client->ps.fov = 90; + + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (bot-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (bot->s.origin, MULTICAST_PVS); + + // copy the bot stats + memcpy(bot->bot_stats, &(botdata->bot_stats), sizeof(bot_stats_t)); + + // set starting skill level + bot->skill_level = skill->value; + AdjustRatingsToSkill(bot); + +// bot->s.modelindex = gi.modelindex(model); + bot->s.skinnum = bot-g_edicts - 1; + bot->s.modelindex = 255; + bot->s.modelindex2 = 255; +// botDebugPrint("SPAWN_BOT (ACRID)\n"); +/* + bot->client->buttons = bot->s.modelindex; + bot->client->oldbuttons = bot->s.skinnum; +*/ + bot->map = G_CopyString(botdata->name); + strcpy(bot->client->pers.netname, botdata->name); + my_bprintf(PRINT_HIGH, "%s entered the game", bot->client->pers.netname); + + // set visible model vwep with 3.20 + ShowGun(bot); + +/* + if (view_weapons->value && (bot->s.modelindex2 == 255) && bot_show_connect_info->value) + { + my_bprintf(PRINT_HIGH, " (no view weapon)"); + } +*/ + my_bprintf(PRINT_HIGH, "\n"); + + bot_count++; + + // generic bot stuff + if (!KillBox (bot)) + { // could't spawn in? + } + + gi.linkentity (bot); + + bot->viewheight = 22; + + bot->inuse = true; + + // go for it + walkmonster_start(bot); + + if (random() < 0.3) + { + // spawn the greetings thinker + chat = G_Spawn(); + chat->owner = bot; + chat->think = BotGreeting; + chat->nextthink = level.time + 1.5 + random(); + } + bot->wf_team = bot->client->resp.ctf_team; + wf_InitPlayerClass(bot->client);//ACRID + + //K2 botcam acrid + bot->client->resp.inServer=true; + + return bot; +} + +extern int num_clients; +//K2:Added for camera..must add player back to players array +void botAddPlayer(edict_t *self) +{ + players[num_players] = self; + num_players++; + num_clients++; + return; + +} +void botRemovePlayer(edict_t *self) +{ + int i; + + self->health = 0; // so other bots stop looking for us + + // remove the client from the players array + for (i=0; iclient->team) + { + self->client->team->num_players--; + self->client->team->num_bots--; + self->client->team = NULL; + } + + self->client->resp.ctf_team = CTF_NOTEAM; + + num_players--; + + if (self->bot_client) + { + bot_count--; + self->botdata->ingame_count--; + } + else + { + num_clients--; + } + + // tell all other bots not to look for this bot anymore + for (i=0; ienemy == self) + players[i]->enemy = players[i]->goalentity = NULL; +} + +void botDisconnect(edict_t *self) +{ // disconnects a bot from the game + + ClientDisconnect(self); + + self->bot_client = false; + self->bot_stats = NULL; +} diff --git a/bot_wpns.c b/bot_wpns.c new file mode 100644 index 0000000..bd08fcf --- /dev/null +++ b/bot_wpns.c @@ -0,0 +1,3704 @@ +/***************************************************************** + + Eraser Bot source code - by Ryan Feltrin, Added to by Acrid- + + .............................................................. + + This file is Copyright(c) 1998, Ryan Feltrin, All Rights Reserved. + + .............................................................. + + All other files are Copyright(c) Id Software, Inc. + + Please see liscense.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 Eraser, you MUST + include the following text (minus the BEGIN and END lines) in the + documentation for your modification. + + --- BEGIN --- + + The Eraser Bot is a product of Ryan Feltrin, and is available from + the Eraser Bot homepage, at http://impact.frag.com. + + This program is a modification of the Eraser Bot, and is therefore + in NO WAY supported by Ryan Feltrin. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Ryan Feltrin immediately, via + the Eraser Bot homepage. + + --- END --- + + .............................................................. + + You will find p_trail.c has not been included with the Eraser + source code release. This is NOT an error. I am unable to + distribute this file because it contains code that is bound by + legal documents, and signed by myself, never to be released + to the public. Sorry guys, but law is law. + + I have therefore include the compiled version of these files + in .obj form in the src\Release and src\Debug directories. + So while you cannot edit and debug code within these files, + you can still compile this source as-is. Although these will only + work in MSVC v5.0, linux versions can be made available upon + request. + + NOTE: When compiling this source, you will get a warning + message from the compiler, regarding the missing p_trail.c + file. Just ignore it, it will still compile fine. + + .............................................................. + + I, Ryan Feltrin/Acrid-, hold no responsibility for any harm caused by the + use of this source code. I also am NOT willing to provide any form + of help or support for this source code. It is provided as-is, + as a service by me, with no documentation, other then the comments + contained within the code. If you have any queries, I suggest you + visit the "official" Eraser source web-board, at + http://www.telefragged.com/epidemic/. I will stop by there from + time to time, to answer questions and help with any problems that + may arise. + + Otherwise, have fun, and I look forward to seeing what can be done + with this. + + -Ryan Feltrin + -Acrid- + + *****************************************************************/ +#include "g_local.h" +#include "bot_procs.h" +#include "m_player.h" +#include "g_items.h" + +#include + +int aborted_fire; +void fire_stab (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int mod); +void fire_stinger (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_pellet_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_napalmrocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_clusterrocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_pulse (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod); +void fire_needle(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread); +void fire_flamethrower(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius); +void fire_telsa(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread); +void fire_armordart (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect); +void fire_poisondart (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect); +void fire_infecteddart (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect); +void fire_lightning (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int kick); +void fire_shc (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread); +//int is_quad; + +void bot_FireWeapon(edict_t *self) +{ + if (!self->enemy) + return; + + if (!self->bot_client) //Gregg added + return; + + + //no firing while botfrozen 3/99 + if (level.time < self->frozentime) + return; + +//ACRID ADDED + if (K2_IsProtected(self)) + self->client->protecttime = 0; +//END + // set Quad flag + is_quad = (self->client->quad_framenum > level.framenum); + + aborted_fire = false; + self->bot_fire(self); + + if (!aborted_fire) + { + if (!CTFApplyStrengthSound(self)) + if (is_quad) + gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + self->last_fire = level.time; + + if (self->maxs[2] == 4) + self->s.frame = FRAME_crattak1; + else + self->s.frame = FRAME_attack1; + + if (self->bot_fire != botBlaster) + self->last_reached_trail = level.time; + } + // check for no ammo ON CURRENT WEAPON//WORKS + if ((self->client->pers.weapon != item_blaster) && (!self->client->pers.inventory[self->client->ammo_index])) + { + botPickBestWeapon(self); + botDebugPrint("BOT No Ammo (ACRID)\n"); + } + + // check for using knife while carring flag + if ((self->client->pers.weapon == item_knife) && + (CarryingFlag(self))) + { + botPickBestWeapon(self); + botDebugPrint("BOT change knife for other (ACRID)\n"); + } + +/*ACRID CHANGE WEAPONS IF IN ITEMS LIST AND HAVE AMMO AND FIRING BLASTER. + */ +// if ( client->pers.inventory[ITEM_INDEX(item_bullets)] +// && client->pers.inventory[ITEM_INDEX(item_needler)] && +// (self->client->pers.inventory[self->client->ammo_index] <=50) +if ((self->bot_fire == botBlaster) && + (self->client->pers.inventory[ITEM_INDEX(item_shells)] != 0)) + { + if ((self->client->pers.inventory[ITEM_INDEX(item_supershotgun)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_shotgun)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_shc)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_poisondart)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_knife)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_infecteddart)] != 0)) + { + botPickBestWeapon(self); +// botDebugPrint("BOT new fire (SHELLS ACRID)\n"); + } +} +else if ((self->bot_fire == botNeedler) && + (self->client->pers.inventory[ITEM_INDEX(item_bullets)] >= 50)) + { + botPickBestWeapon(self); +// botDebugPrint("BOT new needler switch (NEEDLER ACRID)\n"); + } + +else if ((self->bot_fire == botBlaster) && + (self->client->pers.inventory[ITEM_INDEX(item_slugs)] != 0)) + { + if ((self->client->pers.inventory[ITEM_INDEX(item_sniperrifle)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_railgun)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_lightninggun)] != 0)) + { + botPickBestWeapon(self); +// botDebugPrint("BOT new fire (SLUGS ACRID)\n"); + } +} +else if ((self->bot_fire == botBlaster) && + (self->client->pers.inventory[ITEM_INDEX(item_bullets)] != 0)) + { + if ((self->client->pers.inventory[ITEM_INDEX(item_machinegun)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_pulsecannon)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_needler)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_ak47)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_pistol)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_chaingun)] != 0)) + { + botPickBestWeapon(self); + // botDebugPrint("BOT new fire (BULLETS ACRID)\n"); + } +} +else if ((self->bot_fire == botBlaster) && + (self->client->pers.inventory[ITEM_INDEX(item_cells)] != 0)) + { + if ((self->client->pers.inventory[ITEM_INDEX(item_hyperblaster)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_telsacoil)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_flamethrower)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_bfg10k)] != 0)) + { + botPickBestWeapon(self); + // botDebugPrint("BOT new fire (CELLS ACRID)\n"); + } +} +else if ((self->bot_fire == botBlaster) && + (self->client->pers.inventory[ITEM_INDEX(item_rockets)] != 0)) + { + if ((self->client->pers.inventory[ITEM_INDEX(item_rocketlauncher)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_rocketnapalmlauncher)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_pelletrocketlauncher)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_stingerrocketlauncher)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_rocketclusterlauncher)] != 0)) + { + botPickBestWeapon(self); + // botDebugPrint("BOT new fire (ROCKETS ACRID)\n"); + } +} +else if ((self->bot_fire == botBlaster) && + (self->client->pers.inventory[ITEM_INDEX(item_grenades)] != 0)) + { + if ((self->client->pers.inventory[ITEM_INDEX(item_grenadelauncher)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_handgrenades)] != 0) || + (self->client->pers.inventory[ITEM_INDEX(item_grenadelauncher)] != 0)) + { + botPickBestWeapon(self); +// botDebugPrint("BOT new fire (GRENADES ACRID)\n"); + } +} +//ACRID END FIX CHANGE WEAPON ON AMMO PICK UP + if ((self->bot_fire == botBlaster) && (self->enemy) && !CarryingFlag(self->enemy)) + { // abort attacking enemy? + gclient_t *client; + + client = self->enemy->client; + + if (( (self->health < 20) && + (self->enemy->health > 15) && + ((self->enemy->health > self->health) || client->pers.weapon != item_blaster /*ClientHasAnyWeapon(client)*/))) + { // abort the attack + self->enemy = NULL; + } + } +} + + +void bot_Attack(edict_t *self) +{ + float strafe_speed=BOT_STRAFE_SPEED; + + if (!self->enemy || (self->health <= 0))// || (self->enemy->health <= 0)) + return;//sentry co fixme + + if (!self->bot_client) //Gregg added + return; + + // see if the enemy is visible + if ( (self->last_enemy_sight > (level.time - 0.2)) + || ( (visible(self, self->enemy) && gi.inPVS(self->s.origin, self->enemy->s.origin)) + && (self->last_enemy_sight = level.time))) + { + trace_t trace; + + // make sure we don't hit a friend + if (!((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) && (ctf->value || self->client->team)) + { + trace = gi.trace (self->s.origin, vec3_origin, vec3_origin, self->enemy->s.origin, self, MASK_PLAYERSOLID); + +/*if ((self->client->player_class == 2) && + trace.ent && trace.ent->client && SameTeam(trace.ent, self) && + (trace.ent->disease) && + (self->client->pers.inventory[ITEM_INDEX(item_shells)] != 0)) +{botDebugPrint("trtrace ent im a nurse");} + +else*/ if (trace.ent && (trace.ent->client) && SameTeam(trace.ent,self)) + //nurse + { // we might hit a good guy! + return; + } + } + + // BFG delayed firing + if ((self->bot_fire == botBFG) && (self->last_fire > level.time) && (self->last_fire <= (level.time+0.1))) + { + gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/bfg__f1y.wav"), 1, 2, 0); + self->last_fire = level.time - FIRE_INTERVAL_BFG + 0.5; + } + + if ( ((self->last_fire + self->fire_interval) < level.time) + && (self->sight_enemy_time < (level.time - (SIGHT_FIRE_DELAY * ((5 - self->bot_stats->combat*0.5)/5))))) + { + bot_FireWeapon(self); + + if (CTFApplyHaste(self)) + CTFApplyHasteSound(self); + } + // can't strafe if bad Combat skills + if (self->bot_stats->combat == 1) + strafe_speed = 0; + + if (self->enemy != self->last_movegoal) + { // only strafe slowly, so we don't go too far off course + strafe_speed = 0; + } + else if (self->maxs[2] == 4) + { + strafe_speed *= 0.5; + } + + // do attack movements, like strafing + if ((strafe_speed > 0) && self->enemy && self->groundentity && (self->strafe_changedir_time < level.time) && (self->bot_stats->combat > 1)) + { + self->strafe_dir = !self->strafe_dir; + self->strafe_changedir_time = level.time + 0.5 + random() * 1.5; + + // check for ducking or jumping + if (self->crouch_attack_time < level.time) + { + float rnd, dist; + + dist = entdist(self, self->enemy); + rnd = random()*4; + + // if low combat, then skip jumping for a bit + if (self->bot_stats->combat < rnd) + { + self->crouch_attack_time = level.time + 1; + goto nojump; + } + // crouch if far away + if ((self->maxs[2] > 4) && (dist > 400) && (rnd < 3)) + { + if (self->bot_stats->combat > 4) + { + self->crouch_attack_time = level.time + random()*0.5 + 0.5; + self->maxs[2] = 4; + } + } + else if ( (dist < 700) + && ( (self->last_seek_enemy < level.time) + || (entdist(self->last_movegoal, self) > 256)) + && (CanJump(self))) // jump + { + vec3_t right, dest, mins; + trace_t trace; + // if combat = 2, jump less frequently + if ((self->bot_stats->combat >= 3) || (random() < 0.3)) + { + vec3_t rvec; + + AngleVectors(self->s.angles, NULL, right, NULL); + VectorCopy(right, rvec); + VectorScale(right, ((self->strafe_dir * 2) - 1), right); + VectorScale(right, BOT_STRAFE_SPEED, right); + + // check that the jump will be safe +// VectorAdd(self->mins, tv(0,0,12), mins); + VectorAdd(self->s.origin, right, dest); + trace = gi.trace(self->s.origin, mins, self->maxs, dest, self, MASK_SOLID); + VectorSubtract(trace.endpos, rvec, trace.endpos); + VectorAdd(trace.endpos, tv(0,0,-256), dest); + trace = gi.trace(trace.endpos, VEC_ORIGIN, VEC_ORIGIN, dest, self, MASK_SOLID | MASK_WATER); +//lavacode + if ((trace.fraction < 1) && !(trace.contents & (CONTENTS_LAVA | CONTENTS_SLIME))) + { + VectorCopy(right, self->velocity); + + self->velocity[2] = 300; + self->groundentity = NULL; +// self->s.origin[2] += 1; + + gi.linkentity(self); + + VectorCopy(self->velocity, self->jump_velocity); + + gi.sound(self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, 2, 0); + } + else // jump straight up + { + VectorClear(self->velocity); + + self->velocity[2] = 300; + self->groundentity = NULL; +// self->s.origin[2] += 1; + + gi.linkentity(self); + + VectorCopy(self->velocity, self->jump_velocity); + + gi.sound(self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, 2, 0); + } + } + } + + if ( (rnd >= 1) && (self->maxs[2] == 4) + && (!self->goalentity || (self->goalentity->maxs[2] > 4)) + && (CanStand(self))) + { // resume standing + self->maxs[2] = 32; + } + } + } + +nojump: + + if (self->groundentity && (strafe_speed > 0)) + { + if (!M_walkmove(self, + self->s.angles[YAW] + (90 * ((self->strafe_dir * 2) - 1)), + strafe_speed * bot_frametime )) + { + self->strafe_dir = !self->strafe_dir; + self->strafe_changedir_time = level.time + 0.5 + (random() * 0.5); + } + } + + + } + + else // once we sight them again, don't fire instantaneously (super-human powers) + { + self->sight_enemy_time = level.time; +//FIXME ACRID SENTRY + // abort chasing a RL welding human, with enough health//Acrid sec + if (!self->enemy->bot_client && (self->enemy->client) && +(self->enemy->client->pers.weapon == item_rocketlauncher) && +!CarryingFlag(self->enemy) +&& ((self->enemy->health > 25)))//3/99 || (self->bot_fire == botBlaster || self->bot_fire == botShotgun))) + { // abort the attack + // move away + if (self->goalentity) + { + self->goalentity->ignore_time = level.time + 1; + self->goalentity = NULL; + } + + if (self->enemy->client->pers.weapon == item_rocketlauncher) + self->enemy->ignore_time = level.time + 2; + + self->enemy = NULL; + } + } +} +/***************************************** + * Bot firing code * + * uses g_spawn g_local.h * + * bot_procs.h and bot_misc.c * + *****************************************/ +void botBlaster (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + float dist, tf; + int damage; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + dist = entdist(self, self->enemy); + + if (self->enemy && infront(self, self->enemy)) + { + + if ((self->enemy->health > 0) && (skill->value > 1) && (self->enemy->client) && (dist > 64)) + { + VectorMA (self->enemy->s.origin, dist * (1/1000), self->enemy->velocity, target); + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + { + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= 0.5 + (VectorLength(self->enemy->velocity)/600); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + AngleVectors (self->s.angles, forward, NULL, NULL); + } + + damage = 15; + if (is_quad) + damage *= 4; + + monster_fire_blaster (self, start, forward, damage, 1000, MZ_BLASTER, EF_BLASTER); + +} + +//knife +void botKnife (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + int damage = DAMAGE_KNIFE; + int kick = 4; + +botDebugPrint("knife\n"); + if ((int)(level.time*10) & 1) // only calculate every other frame + { + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + self->client->kick_angles[0] = -2; + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + + if (self->enemy && infront(self, self->enemy)) + { + float dist, tf; + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + + if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.1), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + // don't go more than 15 degrees up or down + if (abs(self->s.angles[PITCH]) > 15) + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; + } + + VectorCopy(forward, self->last_forward); + VectorCopy(start, self->last_start); + } + else + { + VectorCopy(self->last_forward, forward); + VectorCopy(self->last_start, start); + } + + if (is_quad) + { + damage *= 4; + kick *=4; + } + fire_stab (self, start, forward, damage, kick, MOD_KNIFE); + self->client->pers.inventory[self->client->ammo_index]--; +} +void botMachineGun (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + int damage; + + if ((int)(level.time*10) & 1) // only calculate every other frame + { + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + float dist, tf; + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + + if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.1), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; + } + + VectorCopy(forward, self->last_forward); + VectorCopy(start, self->last_start); + } + else + { + VectorCopy(self->last_forward, forward); + VectorCopy(self->last_start, start); + } + + damage = 3; + if (is_quad) + damage *= 4; + + monster_fire_bullet (self, start, forward, damage, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_ACTOR_MACHINEGUN_1, MOD_MACHINEGUN); + + self->client->pers.inventory[self->client->ammo_index]--; + +/* +#ifdef _WIN32 + _ftime(&self->lastattack_time); +#else + ftime(&self->lastattack_time); +#endif +*/ +} +void botAk47 (edict_t *self) +{ + int i; + vec3_t start, target; + vec3_t forward, right, ofs; + int damage = 13; + int kick = 3; + + if ((int)(level.time*10) & 1) // only calculate every other frame + { + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + float dist, tf; + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + + if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.1), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; + } + + VectorCopy(forward, self->last_forward); + VectorCopy(start, self->last_start); + } + else + { + VectorCopy(self->last_forward, forward); + VectorCopy(self->last_start, start); + } + for (i=1 ; i<3 ; i++) + { + self->client->kick_origin[i] = crandom() * 0.35; + self->client->kick_angles[i] = crandom() * 0.7; + } + self->client->kick_origin[0] = crandom() * 0.35; + self->client->kick_angles[0] = self->client->machinegun_shots * -1.5; + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + fire_bullet (self, start, forward, damage, kick, 200, 300, MOD_AK47); + + self->client->pers.inventory[self->client->ammo_index]--; +//send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_MACHINEGUN | is_silenced); + gi.multicast (self->s.origin, MULTICAST_PVS); +} +void botPistol (edict_t *self)//WF34 FIXME +{ + int i; + vec3_t start, target; + vec3_t forward, right, ofs; + int damage = 35; + int kick = 2; + + if ((int)(level.time*10) & 1) // only calculate every other frame + { + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + float dist, tf; + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + + if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.1), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; + } + + VectorCopy(forward, self->last_forward); + VectorCopy(start, self->last_start); + } + else + { + VectorCopy(self->last_forward, forward); + VectorCopy(self->last_start, start); + } + for (i=1 ; i<3 ; i++) + { + self->client->kick_origin[i] = crandom() * 0.35; + self->client->kick_angles[i] = crandom() * 0.7; + } + self->client->kick_origin[0] = crandom() * 0.35; + self->client->kick_angles[0] = self->client->machinegun_shots * -1.5; + if (is_quad) + { + damage *= 4; + kick *= 4; + } +gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/pistol.wav"), 1, ATTN_NORM, 0);//FIXME ACRID + fire_bullet (self, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD / 2, DEFAULT_BULLET_VSPREAD / 2, MOD_PISTOL); + + self->client->pers.inventory[self->client->ammo_index]--; +} +void botShotgun (edict_t *self)//WF34 FIXME +{ + vec3_t start, target; + vec3_t forward, right, ofs; + int damage; + float dist=0, tf; +botDebugPrint("shotgun\n"); + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + + if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + damage = 3; + if (is_quad) + damage *= 4; + + monster_fire_shotgun (self, start, forward, damage, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MZ_SHOTGUN, MOD_SHOTGUN); + self->client->pers.inventory[self->client->ammo_index]--; + + if (dist > 600) + { // check for a better long distance weapon + + botPickBestFarWeapon(self); + } +} + +void botSuperShotgun (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + vec3_t angles; + int damage; + float dist=0, tf; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + + if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + damage = 3*2; // OPTIMIZE: increase damage, decrease number of bullets + if (is_quad) + damage *= 4; + + vectoangles(forward, angles); + + angles[YAW] += 5; + AngleVectors(angles, forward, NULL, NULL); + monster_fire_shotgun (self, start, forward, damage, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, DEFAULT_SSHOTGUN_COUNT/4, MZ_SSHOTGUN, MOD_SSHOTGUN); + + angles[YAW] -= 10; + AngleVectors(angles, forward, NULL, NULL); + monster_fire_shotgun (self, start, forward, damage, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, DEFAULT_SSHOTGUN_COUNT/4, MZ_SSHOTGUN | MZ_SILENCED, MOD_SSHOTGUN); + + self->client->pers.inventory[self->client->ammo_index] -= 2; + if (self->client->pers.inventory[self->client->ammo_index] < 0) + self->client->pers.inventory[self->client->ammo_index] = 0; + + if (dist > 600) + { // check for a better long distance weapon + + botPickBestFarWeapon(self); + } +} + +void botChaingun (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + int shots, damage, kick; + + if ((int)(level.time*10) & 1) // only calculate every other frame + { + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + float dist, tf; + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + + if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + + if (tf > 0) + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; + } + + VectorCopy(forward, self->last_forward); + VectorCopy(start, self->last_start); + } + else + { + VectorCopy(self->last_forward, forward); + VectorCopy(self->last_start, start); + } + + if (self->client->killer_yaw < (level.time - 0.3)) + { + if (self->client->killer_yaw < (level.time - 0.5)) + { // must have stopped firing, so need to restart wind-up + self->client->machinegun_shots = 0; + } + + self->client->killer_yaw = level.time; + self->client->machinegun_shots++; + } + + shots = self->client->machinegun_shots; + if (shots > 3) + shots = self->client->machinegun_shots = 3; + + // optimize, simulate more shots by increasing the damage, but still only firing one shot + // chaingun is responsible for a LOT of cpu usage + damage = 3 * shots; + kick = 2; + + if (is_quad) + damage *= 4; + +// for (i=0 ; iclient->pers.inventory[self->client->ammo_index] -= shots) < 0) + { + self->client->pers.inventory[self->client->ammo_index] = 0; + } + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte ((MZ_CHAINGUN1 + shots - 1)); + gi.multicast (self->s.origin, MULTICAST_PVS); + +/* +#ifdef _WIN32 + _ftime(&self->lastattack_time); +#else + ftime(&self->lastattack_time); +#endif +*/ +} + +void botRailgun (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + int damage, kick; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + float dist, tf=0; + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + +// if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; +//sentry ->client + if (self->enemy->client && !self->enemy->bot_client) + tf = (VectorLength(self->enemy->velocity) / 300) * 100; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + tf = 32; + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf += (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + } + + if (tf > 0) + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.1), target); + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + damage = 100; + kick = 200; + + if (is_quad) + damage *= 4; + + fire_rail (self, start, forward, damage, kick, MOD_RAILGUN);//WF ADDED false + self->client->pers.inventory[self->client->ammo_index]--; + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_RAILGUN); + gi.multicast (self->s.origin, MULTICAST_PVS); + +} + +void botRocketLauncher (edict_t *self) +{ + vec3_t start, target, end_trace; + vec3_t forward, right, ofs; + vec3_t oldorg, vel; + int damage; + float damage_radius; + int radius_damage; + float dist=0, tf; + trace_t trace; + + // fire at peak of jump + if ((self->bot_stats->combat > 3) && !self->groundentity && (self->velocity[2] > 50)) + { + aborted_fire = true; + return; + } + + damage = 100 + (int)(random() * 20.0); + radius_damage = 120; + damage_radius = 120; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + dist = entdist(self, self->enemy); + //sentry ->client + if ((skill->value > 1) && (self->enemy->health > 0) && (self->enemy->client && !self->enemy->bot_client) && (dist > 64)) + { + VectorCopy(self->enemy->velocity, vel); + if (vel[2] > 0) + vel[2] = 0; + + VectorMA (self->enemy->s.origin, (float) dist / 650, vel, target); + target[2] += self->enemy->viewheight - 8; + + if (self->bot_stats->combat > 3) + { // aim towards the ground? + trace_t tr; + + VectorCopy(target, end_trace); + end_trace[2] -= 64; + tr = gi.trace(target, NULL, NULL, end_trace, self->enemy, CONTENTS_SOLID); + + if (tr.fraction < 1) + { + vec3_t end, org; + + VectorCopy(tr.endpos, end); + + VectorCopy(self->s.origin, org); + org[2] += self->viewheight; + + tr = gi.trace(org, NULL, NULL, end, self, CONTENTS_SOLID); + + if (tr.fraction == 1) + { + VectorCopy(end, target); + } + } + } + + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + // check to make sure the rocket won't explode in our face +//sentry ->client + if (!self->enemy->bot_client)// && self->enemy->client)//3/99 reversed + { + // move the enemy to the predicted position + VectorCopy(self->enemy->s.origin, oldorg); + VectorMA (self->enemy->s.origin, (float) dist / 650, self->enemy->velocity, self->enemy->s.origin); + gi.linkentity(self->enemy); + } + + VectorScale(forward, 130, end_trace); + VectorAdd(start, end_trace, end_trace); + trace = gi.trace(start, tv(-12,-12,-4), tv(12,12,4), end_trace, self, MASK_PLAYERSOLID); +//sentry ->client + if (!self->enemy->bot_client)// && self->enemy->client)//3/99 reversed + { + // move the enemy back to their correct position + VectorCopy(oldorg, self->enemy->s.origin); + gi.linkentity(self->enemy); + } + + if ( (trace.fraction < 1) + && ( (self->health > 15) + || (!trace.ent))) // dangerous situation, only fire if almost dead and obstacle is another player + { + // walk backwards + if (!M_walkmove(self, self->s.angles[YAW] + 180, BOT_RUN_SPEED * bot_frametime)) + { + // FIXME: can't walk backwards, select a good close-range weapon + + botPickBestCloseWeapon(self); + } + + aborted_fire = true; + return; + } + + if (is_quad) + damage *= 4; + + fire_rocket (self, start, forward, damage, 650, damage_radius, radius_damage, MOD_ROCKET);//WF ADDED + self->client->pers.inventory[self->client->ammo_index]--; + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_ROCKET); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (dist > 700) + { // check for a better long distance weapon + + botPickBestFarWeapon(self); + } +} +void botStingerRocketLauncher (edict_t *self) +{ + vec3_t start, target, end_trace; + vec3_t forward, right, ofs; + vec3_t oldorg, vel; + int damage; + float damage_radius; + int radius_damage; + float dist=0, tf; + trace_t trace; + + // fire at peak of jump + if ((self->bot_stats->combat > 3) && !self->groundentity && (self->velocity[2] > 50)) + { + aborted_fire = true; + return; + } + + damage = 100 + (int)(random() * 20.0); + radius_damage = 120; + damage_radius = 120; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + dist = entdist(self, self->enemy); + //sentry ->client + if ((skill->value > 1) && (self->enemy->health > 0) && (self->enemy->client && !self->enemy->bot_client) && (dist > 64)) + { + VectorCopy(self->enemy->velocity, vel); + if (vel[2] > 0) + vel[2] = 0; + + VectorMA (self->enemy->s.origin, (float) dist / 650, vel, target); + target[2] += self->enemy->viewheight - 8; + + if (self->bot_stats->combat > 3) + { // aim towards the ground? + trace_t tr; + + VectorCopy(target, end_trace); + end_trace[2] -= 64; + tr = gi.trace(target, NULL, NULL, end_trace, self->enemy, CONTENTS_SOLID); + + if (tr.fraction < 1) + { + vec3_t end, org; + + VectorCopy(tr.endpos, end); + + VectorCopy(self->s.origin, org); + org[2] += self->viewheight; + + tr = gi.trace(org, NULL, NULL, end, self, CONTENTS_SOLID); + + if (tr.fraction == 1) + { + VectorCopy(end, target); + } + } + } + + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + // check to make sure the rocket won't explode in our face +//sentry ->client + if (self->enemy->client && !self->enemy->bot_client) + { + // move the enemy to the predicted position + VectorCopy(self->enemy->s.origin, oldorg); + VectorMA (self->enemy->s.origin, (float) dist / 650, self->enemy->velocity, self->enemy->s.origin); + gi.linkentity(self->enemy); + } + + VectorScale(forward, 130, end_trace); + VectorAdd(start, end_trace, end_trace); + trace = gi.trace(start, tv(-12,-12,-4), tv(12,12,4), end_trace, self, MASK_PLAYERSOLID); +//sentry ->client + if (self->enemy->client && !self->enemy->bot_client) + { + // move the enemy back to their correct position + VectorCopy(oldorg, self->enemy->s.origin); + gi.linkentity(self->enemy); + } + + if ( (trace.fraction < 1) + && ( (self->health > 15) + || (!trace.ent))) // dangerous situation, only fire if almost dead and obstacle is another player + { + // walk backwards + if (!M_walkmove(self, self->s.angles[YAW] + 180, BOT_RUN_SPEED * bot_frametime)) + { + // FIXME: can't walk backwards, select a good close-range weapon + + botPickBestCloseWeapon(self); + } + + aborted_fire = true; + return; + } + + if (is_quad) + damage *= 4; + + fire_stinger (self, start, forward, damage, 800, damage_radius, radius_damage); + self->client->pers.inventory[self->client->ammo_index]--; + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_ROCKET); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (dist > 700) + { // check for a better long distance weapon + + botPickBestFarWeapon(self); + } +} +void botPelletRocketLauncher (edict_t *self) +{ + vec3_t start, target, end_trace; + vec3_t forward, right, ofs; + vec3_t oldorg, vel; + int damage; + float damage_radius; + int radius_damage; + float dist=0, tf; + trace_t trace; + + // fire at peak of jump + if ((self->bot_stats->combat > 3) && !self->groundentity && (self->velocity[2] > 50)) + { + aborted_fire = true; + return; + } + + damage = 100 + (int)(random() * 20.0); + radius_damage = 120; + damage_radius = 120; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + dist = entdist(self, self->enemy); + //sentry ->client + if ((skill->value > 1) && (self->enemy->health > 0) && (self->enemy->client && !self->enemy->bot_client) && (dist > 64)) + { + VectorCopy(self->enemy->velocity, vel); + if (vel[2] > 0) + vel[2] = 0; + + VectorMA (self->enemy->s.origin, (float) dist / 650, vel, target); + target[2] += self->enemy->viewheight - 8; + + if (self->bot_stats->combat > 3) + { // aim towards the ground? + trace_t tr; + + VectorCopy(target, end_trace); + end_trace[2] -= 64; + tr = gi.trace(target, NULL, NULL, end_trace, self->enemy, CONTENTS_SOLID); + + if (tr.fraction < 1) + { + vec3_t end, org; + + VectorCopy(tr.endpos, end); + + VectorCopy(self->s.origin, org); + org[2] += self->viewheight; + + tr = gi.trace(org, NULL, NULL, end, self, CONTENTS_SOLID); + + if (tr.fraction == 1) + { + VectorCopy(end, target); + } + } + } + + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + // check to make sure the rocket won't explode in our face +//sentry ->client + if (self->enemy->client && !self->enemy->bot_client) + { + // move the enemy to the predicted position + VectorCopy(self->enemy->s.origin, oldorg); + VectorMA (self->enemy->s.origin, (float) dist / 650, self->enemy->velocity, self->enemy->s.origin); + gi.linkentity(self->enemy); + } + + VectorScale(forward, 130, end_trace); + VectorAdd(start, end_trace, end_trace); + trace = gi.trace(start, tv(-12,-12,-4), tv(12,12,4), end_trace, self, MASK_PLAYERSOLID); +//sentry ->client + if (self->enemy->client && !self->enemy->bot_client) + { + // move the enemy back to their correct position + VectorCopy(oldorg, self->enemy->s.origin); + gi.linkentity(self->enemy); + } + + if ( (trace.fraction < 1) + && ( (self->health > 15) + || (!trace.ent))) // dangerous situation, only fire if almost dead and obstacle is another player + { + // walk backwards + if (!M_walkmove(self, self->s.angles[YAW] + 180, BOT_RUN_SPEED * bot_frametime)) + { + // FIXME: can't walk backwards, select a good close-range weapon + + botPickBestCloseWeapon(self); + } + + aborted_fire = true; + return; + } + + if (is_quad) + damage *= 4; + + fire_pellet_rocket (self, start, forward, damage, 650, damage_radius, radius_damage);//WF ADDED + self->client->pers.inventory[self->client->ammo_index]--; + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_ROCKET); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (dist > 600)//FIXME > FAR + { // check for a better long distance weapon + + botPickBestFarWeapon(self); + } + +} +void botRocketNapalmLauncher (edict_t *self) +{ + vec3_t start, target, end_trace; + vec3_t forward, right, ofs; + vec3_t oldorg, vel; + int damage; + float damage_radius; + int radius_damage; + float dist=0, tf; + trace_t trace; + + // fire at peak of jump + if ((self->bot_stats->combat > 3) && !self->groundentity && (self->velocity[2] > 50)) + { + aborted_fire = true; + return; + } + + damage = 100 + (int)(random() * 20.0); + radius_damage = 120; + damage_radius = 120; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + dist = entdist(self, self->enemy); + //sentry ->client + if ((skill->value > 1) && (self->enemy->health > 0) && (self->enemy->client && !self->enemy->bot_client) && (dist > 64)) + { + VectorCopy(self->enemy->velocity, vel); + if (vel[2] > 0) + vel[2] = 0; + + VectorMA (self->enemy->s.origin, (float) dist / 650, vel, target); + target[2] += self->enemy->viewheight - 8; + + if (self->bot_stats->combat > 3) + { // aim towards the ground? + trace_t tr; + + VectorCopy(target, end_trace); + end_trace[2] -= 64; + tr = gi.trace(target, NULL, NULL, end_trace, self->enemy, CONTENTS_SOLID); + + if (tr.fraction < 1) + { + vec3_t end, org; + + VectorCopy(tr.endpos, end); + + VectorCopy(self->s.origin, org); + org[2] += self->viewheight; + + tr = gi.trace(org, NULL, NULL, end, self, CONTENTS_SOLID); + + if (tr.fraction == 1) + { + VectorCopy(end, target); + } + } + } + + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + // check to make sure the rocket won't explode in our face +//sentry ->client + if (self->enemy->client && !self->enemy->bot_client) + { + // move the enemy to the predicted position + VectorCopy(self->enemy->s.origin, oldorg); + VectorMA (self->enemy->s.origin, (float) dist / 650, self->enemy->velocity, self->enemy->s.origin); + gi.linkentity(self->enemy); + } + + VectorScale(forward, 130, end_trace); + VectorAdd(start, end_trace, end_trace); + trace = gi.trace(start, tv(-12,-12,-4), tv(12,12,4), end_trace, self, MASK_PLAYERSOLID); +//sentry ->client + if (self->enemy->client && !self->enemy->bot_client) + { + // move the enemy back to their correct position + VectorCopy(oldorg, self->enemy->s.origin); + gi.linkentity(self->enemy); + } + + if ( (trace.fraction < 1) + && ( (self->health > 15) + || (!trace.ent))) // dangerous situation, only fire if almost dead and obstacle is another player + { + // walk backwards + if (!M_walkmove(self, self->s.angles[YAW] + 180, BOT_RUN_SPEED * bot_frametime)) + { + // FIXME: can't walk backwards, select a good close-range weapon + + botPickBestCloseWeapon(self); + } + + aborted_fire = true; + return; + } + + if (is_quad) + damage *= 4; + + fire_napalmrocket (self, start, forward, damage, 650, damage_radius, radius_damage);//WF ADDED + self->client->pers.inventory[self->client->ammo_index]--; + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_ROCKET); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (dist > 700)//FIXME > FAR 700 + { // check for a better long distance weapon + + botPickBestFarWeapon(self); + } + +} +void botRocketClusterLauncher (edict_t *self) +{ + vec3_t start, target, end_trace; + vec3_t forward, right, ofs; + vec3_t oldorg, vel; + int damage; + float damage_radius; + int radius_damage; + float dist=0, tf; + trace_t trace; + + // fire at peak of jump + if ((self->bot_stats->combat > 3) && !self->groundentity && (self->velocity[2] > 50)) + { + aborted_fire = true; + return; + } + + damage = 100 + (int)(random() * 20.0); + radius_damage = 120; + damage_radius = 120; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + dist = entdist(self, self->enemy); + //sentry ->client + if ((skill->value > 1) && (self->enemy->health > 0) && (self->enemy->client && !self->enemy->bot_client) && (dist > 64)) + { + VectorCopy(self->enemy->velocity, vel); + if (vel[2] > 0) + vel[2] = 0; + + VectorMA (self->enemy->s.origin, (float) dist / 650, vel, target); + target[2] += self->enemy->viewheight - 8; + + if (self->bot_stats->combat > 3) + { // aim towards the ground? + trace_t tr; + + VectorCopy(target, end_trace); + end_trace[2] -= 64; + tr = gi.trace(target, NULL, NULL, end_trace, self->enemy, CONTENTS_SOLID); + + if (tr.fraction < 1) + { + vec3_t end, org; + + VectorCopy(tr.endpos, end); + + VectorCopy(self->s.origin, org); + org[2] += self->viewheight; + + tr = gi.trace(org, NULL, NULL, end, self, CONTENTS_SOLID); + + if (tr.fraction == 1) + { + VectorCopy(end, target); + } + } + } + + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + // check to make sure the rocket won't explode in our face +//sentry ->client + if (self->enemy->client && !self->enemy->bot_client) + { + // move the enemy to the predicted position + VectorCopy(self->enemy->s.origin, oldorg); + VectorMA (self->enemy->s.origin, (float) dist / 650, self->enemy->velocity, self->enemy->s.origin); + gi.linkentity(self->enemy); + } + + VectorScale(forward, 130, end_trace); + VectorAdd(start, end_trace, end_trace); + trace = gi.trace(start, tv(-12,-12,-4), tv(12,12,4), end_trace, self, MASK_PLAYERSOLID); +//sentry ->client + if (self->enemy->client && !self->enemy->bot_client) + { + // move the enemy back to their correct position + VectorCopy(oldorg, self->enemy->s.origin); + gi.linkentity(self->enemy); + } + + if ( (trace.fraction < 1) + && ( (self->health > 15) + || (!trace.ent))) // dangerous situation, only fire if almost dead and obstacle is another player + { + // walk backwards + if (!M_walkmove(self, self->s.angles[YAW] + 180, BOT_RUN_SPEED * bot_frametime)) + { + // FIXME: can't walk backwards, select a good close-range weapon + + botPickBestCloseWeapon(self); + } + + aborted_fire = true; + return; + } + + if (is_quad) + damage *= 4; + + fire_clusterrocket (self, start, forward, damage, 650, damage_radius, radius_damage);//WF ADDED + self->client->pers.inventory[self->client->ammo_index]--; + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_ROCKET); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (dist < 600)//FIXME FAR > 700 + { // check for a better long distance weapon + + botPickBestCloseWeapon(self); + } + +} +void botGrenadeLauncher (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs, angles; + int damage; + int radius; + float dist=0, tf; + + damage = 120; + radius = damage + 40; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + dist = entdist(self, self->enemy); + + if (self->enemy && infront(self, self->enemy)) + {//sentry ->client + if ((self->enemy->health > 0) && (self->enemy->client && !self->enemy->bot_client) && (dist > 64)) + { + VectorMA (self->enemy->s.origin, dist / 550, self->enemy->velocity, target); + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + if (is_quad) + damage *= 4; + + vectoangles(forward, angles); + + // angle upwards a bit + angles[PITCH] -= 15 * ((dist < 384) ? ((dist / 384) * 2) - 1: 1); + AngleVectors(angles, forward, NULL, NULL); + + fire_grenade (self, start, forward, damage, 600, 2.5, radius); + self->client->pers.inventory[self->client->ammo_index]--; + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_GRENADE); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (dist > 700) + { // check for a better long distance weapon + + botPickBestFarWeapon(self); + } + else if (dist < radius) + { + botPickBestCloseWeapon(self); + } +} +#define GRENADE_TIMER 3.0 +#define GRENADE_MINSPEED 400 +#define GRENADE_MAXSPEED 800 + +void botGrenades (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs, angles; + int damage; + int radius; + float dist=0, tf; +// qboolean held; + int lbdamage = 15; //reduce damage + float timer; + int speed; + damage = 125; + radius = damage + 40; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + dist = entdist(self, self->enemy);//fixme???? + timer = self->client->grenade_time - level.time; + speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER); + + if (self->enemy && infront(self, self->enemy)) + {//sentry ->client + if ((self->enemy->health > 0) && (self->enemy->client && !self->enemy->bot_client) && (dist > 64)) + { + VectorMA (self->enemy->s.origin, dist / 550, self->enemy->velocity, target); + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; + AngleVectors (self->s.angles, forward, NULL, NULL); + } + + if (is_quad) + damage *= 4; + + vectoangles(forward, angles); + + // angle upwards a bit + angles[PITCH] -= 15 * ((dist < 384) ? ((dist / 384) * 2) - 1: 1); + AngleVectors(angles, forward, NULL, NULL); + + fire_grenade2 (self, start, forward, damage, 600, 2.5, radius, false);//fixme held + self->client->pers.inventory[self->client->ammo_index]--; + + if (dist > 700) + { // check for a better long distance weapon + + botPickBestFarWeapon(self); + } + else if (dist < radius) + { + botPickBestCloseWeapon(self); + } + self->client->grenade_time = level.time + 1.0; +}//TEST ACRID FIXME GRENADES +void botHyperblaster (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + float dist=0, tf; + int damage, effect; + + damage = 15; + if (is_quad) + damage *= 4; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + dist = entdist(self, self->enemy); + //sentry ->client + if ((skill->value > 1) && (self->enemy->health > 0) && (self->enemy->client) && (dist > 64)) + { + VectorMA (self->enemy->s.origin, dist/1000, self->enemy->velocity, target); + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (VectorLength(self->enemy->velocity)/600); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + + if ((random() * 3) < 1) + effect = EF_HYPERBLASTER; + else + effect = 0; + + fire_blaster (self, start, forward, damage, 1000, effect, true); + self->client->pers.inventory[self->client->ammo_index]--; + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_HYPERBLASTER); + gi.multicast (self->s.origin, MULTICAST_PVS); + +} + +void botBFG (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + int damage; + float damage_radius; + float dist=0, tf; + + damage = 500; + damage_radius = 1000; + if (is_quad) + damage *= 4; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + dist = entdist(self, self->enemy); + + if (self->enemy && infront(self, self->enemy)) + { +//sentry ->client + if ((self->enemy->health > 0) && (self->enemy->client && !self->enemy->bot_client) && (dist > 64)) + { + VectorMA (self->enemy->s.origin, entdist(self, self->enemy) * (1/550), self->enemy->velocity, target); + target[2] += self->enemy->viewheight - 8; + + if ((dist > 200) && self->enemy->groundentity) // aim towards ground + target[2] -= (4 * self->bot_stats->combat); + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + fire_bfg (self, start, forward, damage, 400, damage_radius); + if ((self->client->pers.inventory[self->client->ammo_index] -= 60) < 0) + self->client->pers.inventory[self->client->ammo_index] = 0; + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_BFG); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (dist > 1000) + { // check for a better long distance weapon + botPickBestFarWeapon(self); + } +} +//ACRID START WF WEAPONS +void botPulseCannon (edict_t *self) +{ +//fixme int i; + vec3_t start, target; + vec3_t forward, right, ofs;// , up; +//fixme float r, u; + int shots, damage, kick = 8; + + if (deathmatch->value) + damage = 3; + else + damage = 6; + + if ((int)(level.time*10) & 1) // only calculate every other frame + { + AngleVectors (self->s.angles, forward, right, NULL); + + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + float dist, tf; + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + + if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + + if (tf > 0) + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; + } + + VectorCopy(forward, self->last_forward); + VectorCopy(start, self->last_start); + } + else + { + VectorCopy(self->last_forward, forward); + VectorCopy(self->last_start, start); + } + + if (self->client->killer_yaw < (level.time - 0.3)) + { + if (self->client->killer_yaw < (level.time - 0.5)) + { // must have stopped firing, so need to restart wind-up + self->client->machinegun_shots = 0; + } + + self->client->killer_yaw = level.time; + self->client->machinegun_shots++; + } + + shots = self->client->machinegun_shots; + if (shots > 3) + shots = self->client->machinegun_shots = 3; + + // optimize, simulate more shots by increasing the damage, but still only firing one shot + // chaingun is responsible for a LOT of cpu usage + damage = 3 * shots; + kick = 2; + + if (is_quad) + damage *= 4; + + self->ShotNumber++; + if (self->client->ping >500) + { + if (self->ShotNumber>3) + { + fire_pulse (self, start, forward, damage*4, kick*4, 0, 250, 250, MOD_MBPC); + self->ShotNumber =0; + } + } + else + { + if (self->ShotNumber>1) + { +fire_pulse (self, start, forward, damage*2, kick*2, 0, 250, 250, MOD_MBPC); + self->ShotNumber =0; + } + } + + if ((self->client->pers.inventory[self->client->ammo_index] -= shots) < 0) + { + self->client->pers.inventory[self->client->ammo_index] = 0; + } + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte ((MZ_CHAINGUN1 + shots - 1) | is_silenced); + gi.multicast (self->s.origin, MULTICAST_PVS); + +/* +#ifdef _WIN32 + _ftime(&self->lastattack_time); +#else + ftime(&self->lastattack_time); +#endif +*/ +}//NEEDLER +void botNeedler (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + int damage, kick; + int xspread; + int yspread; + + + + if ((int)(level.time*10) & 1) // only calculate every other frame + { + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + self->client->kick_angles[0] = -2; + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + + if (self->enemy && infront(self, self->enemy)) + { + float dist, tf; + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + + if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.1), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; + } + + VectorCopy(forward, self->last_forward); + VectorCopy(start, self->last_start); + } + else + { + VectorCopy(self->last_forward, forward); + VectorCopy(self->last_start, start); + } + + if (deathmatch->value) + { // normal damage is too extreme in dm + damage = 3 + (((int)(random()*1000)) % 5); + xspread = 125; + yspread = 125; + kick = 2 + (((int)(random()*1000)) % 8); + } + else + { + damage = 4 + (((int)(random()*1000)) % 5); + xspread = 325; + yspread = 375; + kick = 3 + (((int)(random()*1000)) % 6); + + } + + if (is_quad) + { + damage *= 4; + xspread *=2.5; + yspread *=2.5; + kick *=4; + } + +fire_needle(self, start, forward, damage, kick, TE_BLASTER, xspread, yspread); +self->client->pers.inventory[self->client->ammo_index]--; +}//FLAME THROWER +void botFlameThrower (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + int damage = 5; + float damage_radius = 1000; + float dist=0, tf; + + + + if (is_quad) + damage *= 4; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + dist = entdist(self, self->enemy); + + if (self->enemy && infront(self, self->enemy)) + { +//sentry ->client + if ((self->enemy->health > 0) && (self->enemy->client && !self->enemy->bot_client) && (dist > 64)) + { + VectorMA (self->enemy->s.origin, entdist(self, self->enemy) * (1/550), self->enemy->velocity, target); + target[2] += self->enemy->viewheight - 8; + + if ((dist > 200) && self->enemy->groundentity) // aim towards ground + target[2] -= (4 * self->bot_stats->combat); + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + +fire_flamethrower (self, start, forward, damage*3, 600, damage_radius*2.5); +self->client->pers.inventory[self->client->ammo_index]--;//find all these fixme acrid + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_ROCKET | is_silenced); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (dist > 700)//FIXME 1000 + { // check for a better long distance weapon + botPickBestFarWeapon(self); + } +} +//PoisonDart//ArmorDart +void botPoisonDart (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + int damage; + float dist=0, tf; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + + if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + if (deathmatch->value) + { // normal damage is too extreme in dm + damage = 20; + } + else + { + damage = 25; + } + if (is_quad) + damage *= 4; + //confusing someone coded a poisondart and armor dart??? +// fire_poisondart (self, start, forward, damage, 750, EF_GREENGIB); + fire_armordart (self, start, forward, damage, 750, EF_GREENGIB); + self->client->pers.inventory[self->client->ammo_index]--; + + if (dist > 600) + { // check for a better long distance weapon + botPickBestFarWeapon(self); + } + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_SHOTGUN | is_silenced); + gi.multicast (self->s.origin, MULTICAST_PVS); +} +void botTeslaCoil (edict_t *self)//fixme acrid +{ + vec3_t forward, right ,start, target; + vec3_t ofs; + float dist=0, tf; +//fixme trace_t tr; + int damage, kick; + int xspread; + int yspread; + + if (deathmatch->value) + { // normal damage is too extreme in dm + damage = 3 + (((int)(random()*1000)) % 4); + xspread = 25; + yspread = 25; + kick = 2 + (((int)(random()*1000)) % 8); + } + else + { + damage = 6 + (((int)(random()*1000)) % 4); + xspread = 75; + yspread = 75; + kick = 3 + (((int)(random()*1000)) % 6); + + } + + if (is_quad) + { + damage *= 4; + xspread *=2.5; + yspread *=2.5; + kick *=4; + } + AngleVectors (self->client->v_angle, forward, right, NULL);//WF +// AngleVectors (self->s.angles, forward, right, NULL);//ERASER + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + VectorScale(forward, 8, ofs); + + VectorAdd(self->s.origin, ofs, start); +// start[2] += self->viewheight - 8;//ERASER + + if (self->enemy && infront(self, self->enemy)) + { + dist = entdist(self, self->enemy); +//sentry ->client + if ((skill->value > 1) && (self->enemy->health > 0) && (self->enemy->client) && (dist > 64)) + { + VectorMA (self->enemy->s.origin, dist/1000, self->enemy->velocity, target); + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (VectorLength(self->enemy->velocity)/600); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + +// VectorCopy (self->enemy->s.origin, end); + + fire_telsa(self, start, forward, damage, kick, TE_BLASTER, xspread, yspread); + self->client->pers.inventory[self->client->ammo_index]--; + + +} + +void botInfectedDart (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + int damage, speed; + float dist=0, tf; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + + if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + if (deathmatch->value) + { // normal damage is too extreme in dm + damage = 20; + } + else + { + damage = 25; + } + if (is_quad) + damage *= 4; + speed = 180; //was 850 + fire_infecteddart (self, start, forward, damage, speed, EF_GIB); + self->client->pers.inventory[self->client->ammo_index]--; + + if (dist > 600)//fixme acrid nurse + { // check for a better long distance weapon + botPickBestFarWeapon(self); + } + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_SHOTGUN | is_silenced); + gi.multicast (self->s.origin, MULTICAST_PVS); +} +void botLightningGun (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + int damage, kick, speed; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + float dist, tf=0; + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + +// if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; +//sentry ->client + if (self->enemy->client && !self->enemy->bot_client) + tf = (VectorLength(self->enemy->velocity) / 300) * 100; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + tf = 32; + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf += (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + } + + if (tf > 0) + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.1), target); + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + if (deathmatch->value) + { // normal damage is too extreme in dm + speed = 880; //Slow it down a little + damage = 100 + (((int)(random()*1000)) % 30); + kick = 165+ (((int)(random()*1000)) % 30) ; + } + else + { + speed = 960; + damage = 150 + (((int)(random()*1000)) % 55); + kick = 175 + (((int)(random()*1000)) % 55); + } + + + if (is_quad) + damage *= 4; + + fire_lightning (self, start, forward, damage, speed, kick);//fixme, false); + self->client->pers.inventory[self->client->ammo_index]--; + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_RAILGUN | is_silenced); + gi.multicast (self->s.origin, MULTICAST_PVS); + +} +void botSHC (edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right, ofs; + int damage = 3; + int kick = 8; + float dist=0, tf; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, tv(8,8,self->viewheight-8), forward, right, start); + + VectorScale(forward, 8, ofs); + VectorAdd(self->s.origin, ofs, start); + start[2] += self->viewheight - 8; + + if (self->enemy && infront(self, self->enemy)) + { + + dist = entdist(self, self->enemy); + + if (self->enemy->health > 0) + { + VectorCopy (self->enemy->s.origin, target); + + if (skill->value <= 1) + { // trail the player's velocity + VectorMA(target, -0.2, self->enemy->velocity, target); + } + + target[2] += self->enemy->viewheight - 8; + } + else + { + VectorCopy (self->enemy->s.origin, target); + target[2] += self->enemy->viewheight - 8; + } + + if (self->bot_stats->accuracy < 5) + {//sentry ->client + tf = (dist < 256) ? dist/2 : 256; + tf *= (float) ((5.0 - self->bot_stats->accuracy) / 5.0) * 2; + if (self->enemy->client && !self->enemy->bot_client) + tf *= (1 - (VectorLength(self->enemy->velocity)/600)); + VectorAdd(target, tv(crandom() * tf, crandom() * tf, crandom() * tf * 0.2), target); + } + + VectorSubtract (target, start, forward); + VectorNormalize (forward); + + vectoangles(forward, self->s.angles); + if (abs(self->s.angles[PITCH]) > 15) // don't go more than 15 degrees up or down + self->s.angles[PITCH] = (((self->s.angles[PITCH] > 0) * 2) - 1) * 15; + } + else + { + aborted_fire = true; + return; +// AngleVectors (self->s.angles, forward, NULL, NULL); + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + if (deathmatch->value) + fire_shc (self, start, forward, damage, kick,TE_GUNSHOT, 500, 500); + else + fire_shc (self, start, forward, damage, kick,TE_GUNSHOT, 500, 500); + self->client->pers.inventory[self->client->ammo_index]--; + + if (dist > 700) + { // check for a better long distance weapon + botPickBestFarWeapon(self); + } + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_SHOTGUN | is_silenced); + gi.multicast (self->s.origin, MULTICAST_PVS); +} +//ACRID END +/**********************/ +/* BOT BEST,CLOSE,FAR */ +/**********************/ +//////////////////////////////////////////////////////// +// Returns the ammo index for the given new weapon item/ +//////////////////////////////////////////////////////// +int botAmmoIndex(gitem_t *weapon) { +//Shell Weapons + if (weapon==item_shotgun) return ITEM_INDEX(item_shells); + if (weapon==item_supershotgun) return ITEM_INDEX(item_shells); + if (weapon==item_infecteddart) return ITEM_INDEX(item_shells); + if (weapon==item_poisondart) return ITEM_INDEX(item_shells); + if (weapon==item_shc) return ITEM_INDEX(item_shells); +//3/99 if (weapon==item_knife) return ITEM_INDEX(item_shells); +//Bullet Weapons + if (weapon==item_pistol) return ITEM_INDEX(item_bullets); + if (weapon==item_needler) return ITEM_INDEX(item_bullets); + if (weapon==item_machinegun) return ITEM_INDEX(item_bullets); + if (weapon==item_chaingun) return ITEM_INDEX(item_bullets); + if (weapon==item_ak47) return ITEM_INDEX(item_bullets); + if (weapon==item_pulsecannon) return ITEM_INDEX(item_bullets); +//Rocket Weapons + if (weapon==item_rocketlauncher) return ITEM_INDEX(item_rockets); + if (weapon==item_rocketnapalmlauncher) return ITEM_INDEX(item_rockets); + if (weapon==item_pelletrocketlauncher) return ITEM_INDEX(item_rockets); + if (weapon==item_rocketclusterlauncher) return ITEM_INDEX(item_rockets); + if (weapon==item_stingerrocketlauncher) return ITEM_INDEX(item_rockets); +//Slug Weapons + if (weapon==item_railgun) return ITEM_INDEX(item_slugs); + if (weapon==item_lightninggun) return ITEM_INDEX(item_slugs); + if (weapon==item_sniperrifle) return ITEM_INDEX(item_slugs); +//Cell Weapons + if (weapon==item_hyperblaster) return ITEM_INDEX(item_cells); + if (weapon==item_bfg10k) return ITEM_INDEX(item_cells); + if (weapon==item_flamethrower) return ITEM_INDEX(item_cells); + if (weapon==item_telsacoil) return ITEM_INDEX(item_cells); +//Grenade Weapons + if (weapon==item_handgrenades) return ITEM_INDEX(item_grenades); + if (weapon==item_grenadelauncher) return ITEM_INDEX(item_grenades); + + return 0; // Else blaster +} +/////////////////////////////////////////////////////////////////////////////// +// Check inventory ammo against new weapon and ammo specials like homing cells/ +/////////////////////////////////////////////////////////////////////////////// +qboolean botHasAmmoForWeapon(edict_t *self, gitem_t *weapon) { + + if ((int)dmflags->value & DF_INFINITE_AMMO) + return true; + + if (weapon==item_blaster) + return true; + + if (weapon == item_knife)//3/99 + return true; + + if (weapon==item_bfg10k) + return (self->client->pers.inventory[botAmmoIndex(item_bfg10k)]>=50); + + if (weapon==item_supershotgun) + return (self->client->pers.inventory[botAmmoIndex(item_supershotgun)]>=2); + + return (self->client->pers.inventory[botAmmoIndex(weapon)]); +} +///////////////////////////////////////// +// Return a weapon in the bots inventory/ +///////////////////////////////////////// +qboolean botHasWeaponInInventory(edict_t *self, gitem_t *item) { + return (self->client->pers.inventory[ITEM_INDEX(item)]); +} +/////////////////////////////////////// +// Returns true if bot has that weapon/ +/////////////////////////////////////// +qboolean botHasThisWeapon(edict_t *self, gitem_t *weapon) +{ + gclient_t *client; + gitem_t *oldweapon;//test + client = self->client; + + oldweapon = client->pers.weapon;//test + + + if (botHasWeaponInInventory(self,weapon)) + if (botHasAmmoForWeapon(self,weapon)) { + client->newweapon = weapon; + + // already using this weapon + if (client->pers.weapon == client->newweapon) + return true; + + GetBotFireForWeapon(self->client->newweapon, &self->bot_fire); + self->last_fire = level.time + BOT_CHANGEWEAPON_DELAY; + + if (client->newweapon == item_blaster) + self->fire_interval = FIRE_INTERVAL_BLASTER; + if (client->newweapon == item_shotgun) + self->fire_interval = FIRE_INTERVAL_SHOTGUN; + else if (client->newweapon == item_supershotgun) + self->fire_interval = FIRE_INTERVAL_SSHOTGUN; + else if (client->newweapon == item_rocketlauncher) + self->fire_interval = FIRE_INTERVAL_ROCKETLAUNCHER; + else if (client->newweapon == item_grenadelauncher) + self->fire_interval = FIRE_INTERVAL_GRENADELAUNCHER; + else if (client->newweapon == item_railgun) + self->fire_interval = FIRE_INTERVAL_RAILGUN; + else if (client->newweapon == item_hyperblaster) + self->fire_interval = FIRE_INTERVAL_HYPERBLASTER; + else if (client->newweapon == item_chaingun) + self->fire_interval = FIRE_INTERVAL_CHAINGUN; + else if (client->newweapon == item_machinegun) + self->fire_interval = FIRE_INTERVAL_MACHINEGUN; + else if (client->newweapon == item_bfg10k) + self->fire_interval = FIRE_INTERVAL_BFG; +//ACRID NEW WF STUFF + else if (client->newweapon == item_sniperrifle) + self->fire_interval =FIRE_INTERVAL_SNIPERRIFLE; + else if (client->newweapon == item_lightninggun) + self->fire_interval =FIRE_INTERVAL_LIGHTNINGGUN; + else if (client->newweapon == item_infecteddart) + self->fire_interval =FIRE_INTERVAL_INFECTEDDART; + else if (client->newweapon == item_pulsecannon) + self->fire_interval =FIRE_INTERVAL_PULSECANNON; + else if (client->newweapon == item_telsacoil) + self->fire_interval =FIRE_INTERVAL_TELSACOIL; + else if (client->newweapon == item_telsacoil) + self->fire_interval =FIRE_INTERVAL_FLAMETHROWER; + else if (client->newweapon == item_pelletrocketlauncher) + self->fire_interval =FIRE_INTERVAL_PELLETROCKETLAUNCHER; + else if (client->newweapon == item_rocketnapalmlauncher) + self->fire_interval =FIRE_INTERVAL_ROCKETNAPALMLAUNCHER; + else if (client->newweapon == item_rocketclusterlauncher) + self->fire_interval =FIRE_INTERVAL_ROCKETCLUSTERLAUNCHER; + else if (client->newweapon == item_stingerrocketlauncher) + self->fire_interval = FIRE_INTERVAL_STINGERROCKETLAUNCHER; + else if (client->newweapon == item_needler) + self->fire_interval =FIRE_INTERVAL_NEEDLER; + else if (client->newweapon == item_shc) + self->fire_interval =FIRE_INTERVAL_SHC; + else if (client->newweapon == item_handgrenades) + self->fire_interval =FIRE_INTERVAL_GRENADES; + else if (client->newweapon == item_poisondart) + self->fire_interval =FIRE_INTERVAL_POISONDART; + else if (client->newweapon == item_ak47) + self->fire_interval =FIRE_INTERVAL_AK47; + else if (client->newweapon == item_pistol) + self->fire_interval =FIRE_INTERVAL_PISTOL; + else if (client->newweapon == item_knife) + self->fire_interval =FIRE_INTERVAL_KNIFE; + + if (CTFApplyHaste(self)) + self->fire_interval *= 0.5; + + self->client->ammo_index=botAmmoIndex(weapon); + client->pers.weapon = client->newweapon; + self->client->pers.weapon = client->newweapon; + + // set visible model vwep with 3.20 code + if (oldweapon != client->pers.weapon) + ShowGun(self); + + + return true; } + + return false; +} +/* +=================== +botPickBestWeapon + + called everytime a weapon/ammo is picked up, or ammo runs out +=================== +*/ +void botPickBestWeapon(edict_t *self) +{ + gclient_t *client; + gitem_t *oldweapon; + client = self->client; + + oldweapon = client->pers.weapon; + // check favourite weapon +// if ( client->pers.inventory[self->bot_stats->fav_weapon->tag] +// && //acrid added if ( +//NURSE Acrid FIXME +/* if ((self->enemy->client) && (self->enemy->disease) && + SameTeam(self->enemy, self)&& + (self->client->player_class == 2)) && + (self->client->pers.inventory[ITEM_INDEX(item_shells)] > 0))//crashing here + {botDebugPrint("Nurse testing 2\n"); +goto NCure; + }*/ + if ((self->client->pers.weapon == item_knife) && + (CarryingFlag(self))) + { + botDebugPrint("Best knife for other (ACRID)\n"); + goto skip; + } + //new check favourite weapon// fav not knife 3/99 +if ((client->pers.inventory[ITEM_INDEX(self->bot_stats->fav_weapon)]&& + self->bot_stats->fav_weapon != item_knife && + client->pers.inventory[ITEM_INDEX(FindItem(self->bot_stats->fav_weapon->ammo))]) + //or fav is knife3/99 + ||(client->pers.inventory[ITEM_INDEX(self->bot_stats->fav_weapon)] && + self->bot_stats->fav_weapon == item_knife)) + { +// botDebugPrint("%s picked favourite weapon\n", self->client->pers.netname); + client->newweapon = self->bot_stats->fav_weapon; + + if (client->pers.weapon == client->newweapon) + return; // already using this weapon + + GetBotFireForWeapon(self->bot_stats->fav_weapon, &self->bot_fire); + + self->last_fire = level.time + BOT_CHANGEWEAPON_DELAY; + + if (client->newweapon == item_shotgun) + self->fire_interval = FIRE_INTERVAL_SHOTGUN; + else if (client->newweapon == item_supershotgun) + self->fire_interval = FIRE_INTERVAL_SSHOTGUN; + else if (client->newweapon == item_rocketlauncher) + self->fire_interval = FIRE_INTERVAL_ROCKETLAUNCHER; + else if (client->newweapon == item_grenadelauncher) + self->fire_interval = FIRE_INTERVAL_GRENADELAUNCHER; + else if (client->newweapon == item_railgun) + self->fire_interval = FIRE_INTERVAL_RAILGUN; + else if (client->newweapon == item_hyperblaster) + self->fire_interval = FIRE_INTERVAL_HYPERBLASTER; + else if (client->newweapon == item_chaingun) + self->fire_interval = FIRE_INTERVAL_CHAINGUN; + else if (client->newweapon == item_machinegun) + self->fire_interval = FIRE_INTERVAL_MACHINEGUN; + else if (client->newweapon == item_bfg10k) + self->fire_interval = FIRE_INTERVAL_BFG; +//ACRID NEW WF STUFF + else if (client->newweapon == item_sniperrifle) + self->fire_interval =FIRE_INTERVAL_SNIPERRIFLE; + else if (client->newweapon == item_lightninggun) + self->fire_interval =FIRE_INTERVAL_LIGHTNINGGUN; + else if (client->newweapon == item_infecteddart) + self->fire_interval =FIRE_INTERVAL_INFECTEDDART; + else if (client->newweapon == item_pulsecannon) + self->fire_interval =FIRE_INTERVAL_PULSECANNON; + else if (client->newweapon == item_telsacoil) + self->fire_interval =FIRE_INTERVAL_TELSACOIL; + else if (client->newweapon == item_telsacoil) + self->fire_interval =FIRE_INTERVAL_FLAMETHROWER; + else if (client->newweapon == item_pelletrocketlauncher) + self->fire_interval =FIRE_INTERVAL_PELLETROCKETLAUNCHER; + else if (client->newweapon == item_rocketnapalmlauncher) + self->fire_interval =FIRE_INTERVAL_ROCKETNAPALMLAUNCHER; + else if (client->newweapon == item_rocketclusterlauncher) + self->fire_interval =FIRE_INTERVAL_ROCKETCLUSTERLAUNCHER; + else if (client->newweapon == item_stingerrocketlauncher) + self->fire_interval = FIRE_INTERVAL_STINGERROCKETLAUNCHER; + else if (client->newweapon == item_needler) + self->fire_interval =FIRE_INTERVAL_NEEDLER; + else if (client->newweapon == item_shc) + self->fire_interval =FIRE_INTERVAL_SHC; + else if (client->newweapon == item_handgrenades) + self->fire_interval =FIRE_INTERVAL_GRENADES; + else if (client->newweapon == item_poisondart) + self->fire_interval =FIRE_INTERVAL_POISONDART; + else if (client->newweapon == item_ak47) + self->fire_interval =FIRE_INTERVAL_AK47; + else if (client->newweapon == item_pistol) + self->fire_interval =FIRE_INTERVAL_PISTOL; + else if (client->newweapon == item_knife) + self->fire_interval =FIRE_INTERVAL_KNIFE; +//ACRID NEW WF STUFF + + if (CTFApplyHaste(self)) + { + self->fire_interval *= 0.5; + } + +// client->ammo_index = self->bot_stats->fav_weapon->tag; + if (client->newweapon != item_knife)//3/99 + client->ammo_index = ITEM_INDEX(FindItem(self->bot_stats->fav_weapon->ammo)); + client->pers.weapon = client->newweapon; + self->client->pers.weapon = client->newweapon; + goto found; + } +skip: + if (botHasThisWeapon(self,item_bfg10k)) return; + if (botHasThisWeapon(self,item_pulsecannon)) return; + if (botHasThisWeapon(self,item_needler)) return; + if (botHasThisWeapon(self,item_flamethrower)) return; + if (botHasThisWeapon(self,item_lightninggun)) return; + if (botHasThisWeapon(self,item_infecteddart)) return; + if (botHasThisWeapon(self,item_pelletrocketlauncher)) return; + if (botHasThisWeapon(self,item_rocketnapalmlauncher)) return; + if (botHasThisWeapon(self,item_rocketclusterlauncher)) return; + if (botHasThisWeapon(self,item_shc)) return; + if (botHasThisWeapon(self,item_telsacoil)) return; + if (botHasThisWeapon(self,item_ak47)) return; + if (botHasThisWeapon(self,item_poisondart)) return; + if (botHasThisWeapon(self,item_pistol)) return; + if (botHasThisWeapon(self,item_railgun)) return; + if (botHasThisWeapon(self,item_hyperblaster)) return; + if (botHasThisWeapon(self,item_rocketlauncher)) return; + if (botHasThisWeapon(self,item_chaingun)) return; + if (botHasThisWeapon(self,item_supershotgun)) return; + if (botHasThisWeapon(self,item_grenadelauncher))return; + if (botHasThisWeapon(self,item_machinegun)) return; + if (botHasThisWeapon(self,item_stingerrocketlauncher)) return; + if (botHasThisWeapon(self,item_shotgun)) return; + if (botHasThisWeapon(self,item_handgrenades)) return; + if (botHasThisWeapon(self,item_knife)) return; + + if (botHasThisWeapon(self,item_blaster)) return; + +found: +//FOUND + // set visible model vwep with 3.20 code + if (oldweapon != client->pers.weapon) + ShowGun(self); +}; + +int botHasWeaponForAmmo (gclient_t *client, gitem_t *item)//WORKS ACRID +{ + switch (item->tag) + { + case (AMMO_SHELLS) : + { + return (client->pers.inventory[ITEM_INDEX(item_shotgun)] || + client->pers.inventory[ITEM_INDEX(item_supershotgun)] || + client->pers.inventory[ITEM_INDEX(item_infecteddart)] || + client->pers.inventory[ITEM_INDEX(item_poisondart)] || + client->pers.inventory[ITEM_INDEX(item_shc)]);// || + //3/99 client->pers.inventory[ITEM_INDEX(item_knife)] ); + } + + case (AMMO_ROCKETS) : + { + return (client->pers.inventory[ITEM_INDEX(item_rocketlauncher)] || + client->pers.inventory[ITEM_INDEX(item_pelletrocketlauncher)] || + client->pers.inventory[ITEM_INDEX(item_rocketnapalmlauncher)] || + client->pers.inventory[ITEM_INDEX(item_stingerrocketlauncher)] || + client->pers.inventory[ITEM_INDEX(item_rocketclusterlauncher)]); + } + + case (AMMO_CELLS) : + { + return (client->pers.inventory[ITEM_INDEX(item_hyperblaster)] || + client->pers.inventory[ITEM_INDEX(item_telsacoil)] || + client->pers.inventory[ITEM_INDEX(item_flamethrower)] || + client->pers.inventory[ITEM_INDEX(item_bfg10k)]); + } + + case (AMMO_BULLETS) : + { + return (client->pers.inventory[ITEM_INDEX(item_chaingun)] || + client->pers.inventory[ITEM_INDEX(item_pulsecannon)]|| + client->pers.inventory[ITEM_INDEX(item_needler)] || + client->pers.inventory[ITEM_INDEX(item_ak47)] || + client->pers.inventory[ITEM_INDEX(item_pistol)] || + client->pers.inventory[ITEM_INDEX(item_machinegun)]); + } + + case (AMMO_SLUGS) : + { + return (client->pers.inventory[ITEM_INDEX(item_railgun)] || + client->pers.inventory[ITEM_INDEX(item_sniperrifle)] || + client->pers.inventory[ITEM_INDEX(item_lightninggun)]); + } + + case (AMMO_GRENADES) : + { + return (client->pers.inventory[ITEM_INDEX(item_grenadelauncher)] || + client->pers.inventory[ITEM_INDEX(item_handgrenades)]); + } + + default : + { + gi.dprintf("botHasWeaponForAmmo: unkown ammo type - %i\n", item->ammo); + return false; + } + + } +} +/*//Acrid coed +int ClientHasAnyWeapon(gclient_t *client) +{ + if (client->pers.weapon != item_blaster) + return true; + + if (client->pers.inventory[ITEM_INDEX(item_shotgun)]) + botDebugPrint("HAS ANY WEP (SHOTGUN)\n"); + return true; + if (client->pers.inventory[ITEM_INDEX(item_supershotgun)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_machinegun)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_chaingun)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_grenadelauncher)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_rocketlauncher)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_railgun)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_hyperblaster)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_bfg10k)]) + return true; +//FIXME ACRID NEW WEAPONS + if (client->pers.inventory[ITEM_INDEX(item_sniperrifle)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_lightninggun)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_pulsecannon)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_telsacoil)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_needler)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_flamethrower)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_pelletrocketlauncher)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_rocketnapalmlauncher)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_rocketclusterlauncher)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_shc)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_handgrenades)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_ak47)]) + return true; + if (client->pers.inventory[ITEM_INDEX(item_pistol)]) + return true; + return false; +} +*///acrid coed +int botCanPickupAmmo (gclient_t *client, gitem_t *item) +{ + int max, index; + + switch (item->tag) + { + case AMMO_BULLETS : max = client->pers.max_bullets; break; + case AMMO_SHELLS : max = client->pers.max_shells; break; + case AMMO_ROCKETS : max = client->pers.max_rockets; break; + case AMMO_GRENADES : max = client->pers.max_grenades; break; + case AMMO_CELLS : max = client->pers.max_cells; break; + case AMMO_SLUGS : max = client->pers.max_slugs; break; + default : return false; + } + +//gi.dprintf("Max ammo set\n"); + + index = ITEM_INDEX(item); + + if (client->pers.inventory[index] == max) + return false; + +//gi.dprintf("Can pickup ammo\n"); + + return true; +} + +void GetBotFireForWeapon(gitem_t *weapon, void (**bot_fire)(edict_t *self)) +{ + if (weapon == item_blaster) + *bot_fire = botBlaster;//acrid + if (weapon == item_rocketlauncher) + *bot_fire = botRocketLauncher; + else if (weapon == item_chaingun) + *bot_fire = botChaingun; + else if (weapon == item_supershotgun) + *bot_fire = botSuperShotgun; + else if (weapon == item_grenadelauncher) + *bot_fire = botGrenadeLauncher; + else if (weapon == item_railgun) + *bot_fire = botRailgun; + else if (weapon == item_hyperblaster) + *bot_fire = botHyperblaster; + else if (weapon == item_bfg10k) + *bot_fire = botBFG; + else if (weapon == item_shotgun) + *bot_fire = botShotgun; + else if (weapon == item_machinegun) + *bot_fire = botMachineGun; +//ACRID + else if (weapon == item_lightninggun) + *bot_fire = botLightningGun; + else if (weapon == item_infecteddart) + *bot_fire = botInfectedDart; + else if (weapon == item_pulsecannon) + *bot_fire = botPulseCannon; + else if (weapon == item_telsacoil) + *bot_fire = botTeslaCoil; + else if (weapon == item_sniperrifle) + *bot_fire = botTeslaCoil; + else if (weapon == item_flamethrower) + *bot_fire = botFlameThrower; + else if (weapon == item_pelletrocketlauncher) + *bot_fire = botPelletRocketLauncher; + else if (weapon == item_rocketnapalmlauncher) + *bot_fire = botRocketNapalmLauncher; + else if (weapon == item_rocketclusterlauncher) + *bot_fire = botRocketClusterLauncher; + else if (weapon == item_stingerrocketlauncher) + *bot_fire = botStingerRocketLauncher; + else if (weapon == item_needler) + *bot_fire = botNeedler; + else if (weapon == item_shc) + *bot_fire = botSHC; + else if (weapon == item_handgrenades) + *bot_fire = botGrenades; + else if (weapon == item_poisondart) + *bot_fire = botPoisonDart; + else if (weapon == item_ak47) + *bot_fire = botAk47; + else if (weapon == item_pistol) + *bot_fire = botPistol; + else if (weapon == item_knife) + *bot_fire = botKnife; +//ACRID END +} + +/* +=================== +botPickBestCloseWeapon + + called when close to enemy, don't use RL, GL, BFG +=================== +*/ +void botPickBestCloseWeapon(edict_t *self) +{ +botDebugPrint("best close weapon\n"); +//INFECTED DART CLOSE +//PELLET ROCKETLAUNCHER CLOSE +//PISTOL CLOSE +//AK47 CLOSE +//PULSECANNON CLOSE +//LIGHNING GUN CLOSE +//NEEDLER CLOSE +//FLAME THROWER CLOSE +//NAPALM ROCKETLAUNCHER CLOSE +//SHC CLOSE +//CHAINGUN CLOSE +//SUPER SHOTGUN CLOSE +//HYPERBLASTER CLOSE +//GRENADE FIXME ACRID CLOSE +//MACHINEGUN CLOSE +//POISON DART CLOSE +//TESLA COIL CLOSE +//BFG CLOSE +//RAILGUN CLOSE +//CLUSTER ROCKETLAUNCHER CLOSE +//SHOTGUN CLOSE +//GRENADE LAUNCHER CLOSE +//ROCKETS CLOSE + if (botHasThisWeapon(self,item_knife)) return; + if (botHasThisWeapon(self,item_infecteddart)) return; + if (botHasThisWeapon(self,item_pelletrocketlauncher)) return; + if (botHasThisWeapon(self,item_pistol)) return; + if (botHasThisWeapon(self,item_ak47)) return; + if (botHasThisWeapon(self,item_pulsecannon)) return; + if (botHasThisWeapon(self,item_lightninggun)) return; + if (botHasThisWeapon(self,item_needler)) return; + if (botHasThisWeapon(self,item_flamethrower)) return; + if (botHasThisWeapon(self,item_rocketnapalmlauncher)) return; + if (botHasThisWeapon(self,item_shc)) return; + if (botHasThisWeapon(self,item_chaingun)) return; + if (botHasThisWeapon(self,item_supershotgun)) return; + if (botHasThisWeapon(self,item_hyperblaster)) return; + if (botHasThisWeapon(self,item_handgrenades)) return; + if (botHasThisWeapon(self,item_machinegun)) return; + if (botHasThisWeapon(self,item_poisondart)) return; + if (botHasThisWeapon(self,item_telsacoil)) return; + if (botHasThisWeapon(self,item_bfg10k)) return; + if (botHasThisWeapon(self,item_railgun)) return; + if (botHasThisWeapon(self,item_rocketclusterlauncher)) return; + if (botHasThisWeapon(self,item_shotgun)) return; + if (botHasThisWeapon(self,item_grenadelauncher))return; + if (botHasThisWeapon(self,item_stingerrocketlauncher)) return; + if (botHasThisWeapon(self,item_rocketlauncher)) return; + + if (botHasThisWeapon(self,item_blaster)) return; + +}; + +/* +=================== +botPickBestFarWeapon + + called when far from enemy +=================== +*/ +void botPickBestFarWeapon(edict_t *self) +{ + +botDebugPrint("best far weapon\n"); +//BFG FAR +//AK47 FAR +//NAPALM ROCKETLAUNCHER FAR +//CLUSTER ROCKETLAUNCHER FAR +//ROCKETS FAR +//RAILGUN FAR +//TESLA COIL FAR +//LIGHNING GUN FAR +//INFECTED DART FAR +//POISON DART FAR +//HYPERBLASTER FAR +//PELLET ROCKETLAUNCHER FAR +//CHAINGUN FAR +//MACHINE GUN FAR +//FLAME THROWER FAR +//GRENADE LAUNCHER FAR +//SHC FAR +//SUPER SHOTGUN FAR +//PISTOL FAR +//SHOTGUN FAR +//NEEDLER FAR + if (botHasThisWeapon(self,item_bfg10k)) return; + if (botHasThisWeapon(self,item_rocketnapalmlauncher)) return; + if (botHasThisWeapon(self,item_rocketclusterlauncher)) return; + if (botHasThisWeapon(self,item_rocketlauncher)) return; + if (botHasThisWeapon(self,item_railgun)) return; + if (botHasThisWeapon(self,item_ak47)) return; + if (botHasThisWeapon(self,item_stingerrocketlauncher)) return; + if (botHasThisWeapon(self,item_pulsecannon)) return; + if (botHasThisWeapon(self,item_lightninggun)) return; + if (botHasThisWeapon(self,item_chaingun)) return; + if (botHasThisWeapon(self,item_hyperblaster)) return; + if (botHasThisWeapon(self,item_pelletrocketlauncher)) return; + if (botHasThisWeapon(self,item_telsacoil)) return; + if (botHasThisWeapon(self,item_infecteddart)) return; + if (botHasThisWeapon(self,item_poisondart)) return; + if (botHasThisWeapon(self,item_grenadelauncher))return; + if (botHasThisWeapon(self,item_flamethrower)) return; + if (botHasThisWeapon(self,item_machinegun)) return; + if (botHasThisWeapon(self,item_shc)) return; + if (botHasThisWeapon(self,item_supershotgun)) return; + if (botHasThisWeapon(self,item_pistol)) return; + if (botHasThisWeapon(self,item_shotgun)) return; + if (botHasThisWeapon(self,item_needler)) return; + if (botHasThisWeapon(self,item_handgrenades)) return; + if (botHasThisWeapon(self,item_knife)) return; + + if (botHasThisWeapon(self,item_blaster)) return; +}; diff --git a/bots.cfg b/bots.cfg new file mode 100644 index 0000000..4acfe69 --- /dev/null +++ b/bots.cfg @@ -0,0 +1,107 @@ +# Eraser Configuration file +# +# (thanks to calcu0, calcu0@prtc.net, for the documentation and formatting) +# +# BOT NOTES: +# + Each bot occupies one line. +# + String fields are enclosed in quotation marks (") +# + Obviously, a hash (#) character signifies a comment line +# + Skins will be ignored in teamplay mode, with color coded skins being used instead (see +# Teamplay section) +# + Individual bots can be added manually using "bot_name " +# + A bot with the Quad powerup, will automatically go into "psycho" mode :) +# +# SETTING NOTES: +# 1) Firing Accuracy Referes to aim quality, (1[shoot for luck] - 5[THRESH]) +# 2) A high aggressiveness doesn't necessarily result in a kick-ass player. They'll +# be less likely to build up a sufficient arsenal of weapons, and may not retreat from an +# unsuccessful attack until it's too late. On the other hand, they'll be more likely to stick +# out an attack at the chance of getting more frags, rather than less kills (as a retreat +# would yeild). A high aggressiveness could also produce more frags by shooting others first. +# 3) A bot with high combat skills will yeild a harder to hit bot, it will improve the +# tactics it will use on jumping, strafing and dodging not only your shots but your sight; +# while a bot with a low combat skill rating will generally stand in one spot shooting +# sometimes. +# 4) The Favorite Weapon, need I explain. Anyways, it signifies the bot will always try to +# either get first and use most often. +# 5) QuadFreak toggles the bots use or anxiety to get a quad... Watch out for them mood +# swings ;) +# 6) Camper toggles if the bot will tend to camp out or not DUH!! +# 7) Average ping signifies the aparent average ping of bots, just a static addition anyways, +# cool if ya wanna fool your friends ;) + +[bots] + +# "BotName" +# | "Class" +# | | FiringAccuracy (1-5) +# | | | Aggressiveness (1-5) +# | | | | CombatSkills (1-5) +# | | | | | FavoriteWeapon (2-24]) +# | | | | | | QuadFreak (1[yes] - 0[no]) +# | | | | | | | Camper (1[yes] - 0[no]) +# | | | | | | | | AveragePing (0-400 [for realism's sake]) +# | | | | | | | | | +# | | | | | | | | | +# Cyborgs +"ReconBot1" "1" 5 5 5 3 0 0 300 +"NurseBot1" "2" 5 5 5 12 0 0 400 +"EngineerBot1" "3" 5 1 5 22 0 1 400 +"MarineBot1" "4" 5 5 5 7 0 0 600 +"CyborgBot1" "5" 5 5 5 6 0 0 430 +"ArsonistBot1" "6" 5 5 5 17 0 0 200 +"GunnerBot1" "7" 5 1 5 5 0 1 200 +"SniperBot1" "8" 5 1 5 8 0 1 230 +"SpyBot1" "9" 5 5 5 4 0 0 300 +"MercenaryBot1" "10" 5 5 5 23 0 0 200 + +# Males +"ReconBot2" "1" 5 5 5 3 0 0 300 +"NurseBot2" "2" 5 5 5 12 0 0 400 +"EngineerBot2" "3" 5 1 5 22 0 1 400 +"MarineBot2" "4" 5 5 5 7 0 0 600 +"CyborgBot2" "5" 5 5 5 6 0 0 430 +"ArsonistBot2" "6" 5 5 5 17 0 0 200 +"GunnerBot2" "7" 5 1 5 5 0 1 200 +"SniperBot2" "8" 5 1 5 8 0 1 230 +"SpyBot2" "9" 5 5 5 4 0 0 300 +"MercenaryBot2" "10" 5 5 5 23 0 0 200 + +# Females +"ReconBot3" "1" 5 5 5 3 0 0 300 +"NurseBot3" "2" 5 5 5 12 0 0 400 +"EngineerBot3" "3" 5 1 5 22 0 1 400 +"MarineBot3" "4" 5 5 5 7 0 0 600 +"CyborgBot3" "5" 5 5 5 6 0 0 430 +"ArsonistBot3" "6" 5 5 5 17 0 0 200 +"GunnerBot3" "7" 5 1 5 5 0 1 200 +"SniperBot3" "8" 5 1 5 8 0 1 230 +"SpyBot3" "9" 5 5 5 4 0 0 300 +"MercenaryBot3" "10" 5 5 5 23 0 0 200 + +# TEAM NOTES: +# +# + Each team consists of a series of bots, maximum of 32 per team +# + When a human joins a team, the last bot on the list is removed from the game +# + Unknown bots are assigned skills automatically, according to their name (so they'll be the +# same each game) +# + SPACES NOT ALLOWED IN TEAMNAMES !! +# +# FIELDS: Team name, Abbrev, Default Skin, [players] + +[teams] + +"WF" "BOT" "" ["Reconbot1" "Nursebot1" "Engineerbot1" "Marinebot1" "Cyborgbot1" "Arsonistbot1" "Spybot1" "Mercenarybot1" "Gunnerbot1" "Sniperbot1"] + +"GibBrothers" "GB" "" ["Cipher" "Nightops" "Major" "Razor"] + +"GirlPower" "GP" "" ["Athena" "Jezebel" "Stiletto" "Voodoo"] + + +"MeOnly" "MO" "male/kw_white" [] + +# As the View Weapons patch grows to include more models, add them in here.. +[view weapons] +"male" +"female" +"cyborg" \ No newline at end of file diff --git a/camclient.c b/camclient.c new file mode 100644 index 0000000..3524e04 --- /dev/null +++ b/camclient.c @@ -0,0 +1,1076 @@ +/***************************************************************** + + Cam Client source code - by Paul Jordan, updated by Acrid- + + .............................................................. + + This file is Copyright(c) 1998, Paul Jordan, All Rights Reserved. + + .............................................................. + + All other files are Copyright(c) Id Software, Inc. + + Please see liscense.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 the Camera, you + MUST include the following text (minus the BEGIN and END lines) in + the documentation for your modification, and also on all web pages + related to your modification, should they exist. + + --- BEGIN --- + + The Client Camera is a product of Paul Jordan, and is available from + the Quake2 Camera homepage at http://www.prismnet.com/~jordan/q2cam, + Or as part of the Eraser Bot at http://impact.frag.com. + + This program is a modification of the Quake2 Client Camera, and is + therefore in NO WAY supported by Paul. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Paul Jordan immediately, via + the Quake2 Camera Client homepage. + + --- END --- + + Adios and have fun, + + Paul Jordan + + *****************************************************************/ + + +/* camclient.c */ + +#include "g_local.h" +#include "camclient.h" +#include "extra.h" + + + +enum { + CAM_FOLLOW_MODE, + CAM_NORMAL_MODE}; + +#define DAMP_ANGLE_Y 6 +#define DAMP_VALUE_XY 6 +#define DAMP_VALUE_Z 3 + + +sPlayerList *EntityListHead(); +void CameraStaticThink(edict_t *ent, usercmd_t *ucmd); + +//K2 +void botAddPlayer(edict_t *ent); +void botRemovePlayer(edict_t *ent); +void ClientBegin (edict_t *ent);//acrid 3/99 +//K2 + +edict_t + *pDeadPlayer=NULL; + +int +CameraCmd(edict_t *ent, char *arg) +{ + double + fTemp; + + + if ( Q_stricmp(arg, "on") == 0) + { + //K2: If client is not already in server, no cam mode + if(!ent->client->resp.inServer) + { + // if(ctf->value) + // CTFJoinTeam1(ent,NULL); + // else + // K2EnterGame(ent,NULL); + + } + + // clear entity values + ent->groundentity = NULL; + ent->takedamage = DAMAGE_NO; + ent->movetype = MOVETYPE_FLY; + ent->viewheight = 0; + ent->classname = "camera"; + ent->mass = 0; + ent->solid = SOLID_NOT; + ent->deadflag = DEAD_NO; + ent->clipmask = MASK_ALL; + ent->model = ""; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags = FL_FLY;// | FL_GODMODE; + ent->client->bIsCamera = 1; + + ent->client->ps.fov = 90; + ent->client->iMode = CAM_FOLLOW_MODE; + ent->client->bWatchingTheDead = FALSE; + ent->client->fXYLag = DAMP_VALUE_XY; + ent->client->fZLag = DAMP_VALUE_Z; + ent->client->fAngleLag = DAMP_ANGLE_Y; + + // Ridah, clear the view + ent->client->pers.weapon = NULL; + ent->client->ps.gunindex = 0; + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->pers.player_class = 0; //5/99 for g_cmds + memset(ent->client->ps.stats, 0, sizeof(ent->client->ps.stats)); + +// CTFPlayerResetGrapple(ent); + CTFPlayerResetGrapple2(ent);//newgrap 4/99 + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); + // Ridah, done. + + // clear entity state values + ent->s.effects = 0; + ent->s.skinnum = 0;//ent - g_edicts - 1; + ent->s.modelindex = 0;//255; // will use the skin specified model + ent->s.modelindex2 = 0;//255; // custom gun model + ent->s.frame = 0; + + ent->s.angles[PITCH] = 0; + ent->s.angles[YAW] = 0; + ent->s.angles[ROLL] = 0; + VectorCopy (ent->s.angles, ent->client->ps.viewangles); + VectorCopy (ent->s.angles, ent->client->v_angle); + + ent->client->showscores = false; + ent->client->showinventory = false; + ent->client->pers.hand = CENTER_HANDED; + ent->client->ps.stats[STAT_HELPICON] = 0; + + //K2 + ent->client->resp.inServer=false; + botRemovePlayer(ent); + //K2 + + return true; + } + //K2:Begin - Add cam off command + else if (Q_stricmp(arg, "off") == 0) + { + if(!ent->client->bIsCamera) + return false; + + ent->client->bIsCamera = false; + ClientBegin (ent); + return true; + + } + //K2:End + else if (ent->classname[0] != 'c') + { // not already in CAM mode, so don't go any further + return false; + } + else if ( Q_stricmp(arg, "follow") == 0) + { + ent->client->iMode = CAM_FOLLOW_MODE; + } + else if ( Q_stricmp(arg, "normal") == 0) + { + ent->client->iMode = CAM_NORMAL_MODE; + } + else if (( Q_stricmp(arg, "min_xy") == 0) && ent->client->bIsCamera) + { + if ((fTemp = atof(arg)) < 1) + { + safe_cprintf (ent, PRINT_HIGH, "Min X/Y delta of %f unchanged!\n",ent->client->fXYLag); + } + else + { + ent->client->fXYLag = fTemp; + safe_cprintf (ent, PRINT_HIGH, "Min X/Y delta of %f. set.\n",ent->client->fXYLag); + } + } + else if (( Q_stricmp(arg, "min_z") == 0) && ent->client->bIsCamera) + { + if ((fTemp = atof(arg)) < 1) + { + safe_cprintf (ent, PRINT_HIGH, "Min Z delta of %f unchanged!\n",ent->client->fZLag); + } + else + { + ent->client->fZLag = fTemp; + safe_cprintf (ent, PRINT_HIGH, "Min Z delta of %f set.\n",ent->client->fZLag); + } + } + else if (( Q_stricmp(arg, "min_angle") == 0) && ent->client->bIsCamera) + { + if ((fTemp = atof(arg)) < 1) + { + safe_cprintf (ent, PRINT_HIGH, "Min Yaw Angle delta of %f unchanged!\n",ent->client->fAngleLag); + } + else + { + ent->client->fAngleLag = fTemp; + safe_cprintf (ent, PRINT_HIGH, "Min Yaw Angle delta of %f set.\n",ent->client->fAngleLag); + } + } + + return false; +} + +void +PlayerDied(edict_t *pPlayer) +{ + if ( pPlayer->client ) + { + pDeadPlayer = pPlayer; + } +} + +qboolean +IsVisible(edict_t *pPlayer1, edict_t *pPlayer2) +{ + vec3_t + vLength; + int + distance; + trace_t + trace; + + // Ridah, added this so se check for looking through non-transparent water + if (!gi.inPVS(pPlayer1->s.origin, pPlayer2->s.origin)) + return FALSE; + // done. + + trace = gi.trace (pPlayer1->s.origin, vec3_origin, vec3_origin, pPlayer2->s.origin, pPlayer1, MASK_SOLID); + + vLength[0] = pPlayer1->s.origin[0] - pPlayer2->s.origin[0]; + vLength[1] = pPlayer1->s.origin[1] - pPlayer2->s.origin[1]; + vLength[2] = pPlayer1->s.origin[2] - pPlayer2->s.origin[2]; + distance = VectorLength(vLength); + + if ((distance < MAX_VISIBLE_RANGE) && + (trace.fraction == 1.0)) + { + return TRUE; + } + return FALSE; +} +//============================================================================ +//============================================================================ +int +NumPlayersVisible(edict_t *pViewer) +{ + int + iCount=0; + sPlayerList + *pTarget; + + for ( pTarget = EntityListHead(); + pTarget != NULL; + pTarget=pTarget->pNext) + { +// if ((!pTarget->pEntity->deadflag) && + if (!pTarget->pEntity->client->bIsCamera) + { + if (IsVisible(pTarget->pEntity, pViewer)) + { + iCount++; + + // Ridah, added this so we follow the flag carrier if visible + if ( (pTarget->pEntity->s.effects & (EF_FLAG1 | EF_FLAG2)) + && (!pViewer->client->pTarget || !(pViewer->client->pTarget->s.effects & (EF_FLAG1 | EF_FLAG2)))) + { // view this person instead + pViewer->client->pTarget = pTarget->pEntity; + } + // Ridah, done. + } + } + } + return iCount; +} + +edict_t * +ClosestVisible(edict_t *ent) +{ + vec3_t + vDistance; + sPlayerList + *pTarget, + *pBest=NULL; + unsigned int + iCurrent, + iClosest=0xffffffff; + + for ( pTarget = EntityListHead(); + pTarget != NULL; + pTarget = pTarget->pNext) + { + if (!pTarget->pEntity->client->bIsCamera && + IsVisible(pTarget->pEntity, ent)) + { + VectorSubtract(pTarget->pEntity->s.origin, ent->s.origin, vDistance); + iCurrent = VectorLength(vDistance); + if (iCurrent < iClosest) + { + pBest = pTarget; + iClosest = iCurrent; + } + } + } + if (pBest == NULL) + { + return NULL; + } + return pBest->pEntity; +} +//============================================================================ +//============================================================================ +edict_t * +PlayerToFollow() +{ + sPlayerList + *pViewer, + *pBest=NULL; + int + iPlayers, + iBestCount=0; + + for ( pViewer = EntityListHead(); + pViewer != NULL; + pViewer = pViewer->pNext) + { + iPlayers = 0; + // + // Don't switch to dead people + // + if ( (!pViewer->pEntity->deadflag) && + (!pViewer->pEntity->client->bIsCamera) + //K2:Not observers + && (pViewer->pEntity->client->resp.inServer) + ) + { + iPlayers = NumPlayersVisible(pViewer->pEntity); + if (iPlayers > iBestCount) + { + iBestCount = iPlayers; + pBest = pViewer; + } + else if ((iPlayers != 0) && (iPlayers == iBestCount) ) + { + if (pBest->pEntity->client->resp.score < + pViewer->pEntity->client->resp.score) + { + pBest = pViewer; + } + } + } + } + if (pBest == NULL) + { + return NULL; + } + return pBest->pEntity; +} + + +//============================================================================ +//============================================================================ +void +CameraAloneThink(edict_t *ent, usercmd_t *ucmd) +{ + CameraStaticThink(ent, ucmd); +} + +//============================================================================ +//============================================================================ +void +PointCamAtOrigin(edict_t *ent, vec3_t vLocation) +{ + vec3_t + vDiff, + vAngles; + + VectorSubtract(vLocation,ent->s.origin,vDiff); + + + vectoangles(vDiff, vAngles); + + VectorCopy (vAngles, ent->s.angles); + VectorCopy (vAngles, ent->client->ps.viewangles); + VectorCopy (vAngles, ent->client->v_angle); +} + +//============================================================================ +//============================================================================ +void +PointCamAtTarget(edict_t *ent) +{ + vec3_t + vDiff, + vAngles; + float + fDifference; + + VectorSubtract(ent->client->pTarget->s.origin,ent->s.origin,vDiff); + + vectoangles(vDiff, vAngles); + + ent->s.angles[0] = vAngles[0]; + ent->s.angles[2] = 0; + fDifference = vAngles[1] - ent->s.angles[1]; + + while (abs(fDifference) > 180) + { + if (fDifference > 0) + { + fDifference -= 360; + } + else + { + fDifference += 360; + } + } + + if (abs(fDifference) > ent->client->fAngleLag) + { + if (fDifference > 0) + { + ent->s.angles[1] += ent->client->fAngleLag; + } + else + { + ent->s.angles[1] -= ent->client->fAngleLag; + } + } + else + { + ent->s.angles[1] = vAngles[1]; + } + + VectorCopy (ent->s.angles, ent->client->ps.viewangles); + VectorCopy (ent->s.angles, ent->client->v_angle); +} + +//============================================================================ +//============================================================================ +void +RepositionAtTarget(edict_t *ent, vec3_t vOffsetPosition) +{ + vec3_t + vDiff; +// vAngles; + vec3_t + vCamPos, + forward;//, up; + + trace_t + trace; + + AngleVectors(ent->client->pTarget->client->v_angle, forward, NULL,NULL); + forward[2] = 0; + + VectorNormalize(forward); + + vCamPos[0] = ent->client->pTarget->s.origin[0] + + (vOffsetPosition[0] * forward[0]); + + vCamPos[1] = ent->client->pTarget->s.origin[1] + + (vOffsetPosition[1] * forward[1]); + + vCamPos[2] = ent->client->pTarget->s.origin[2] + + vOffsetPosition[2]; + + trace = gi.trace( ent->client->pTarget->s.origin, NULL, NULL, vCamPos, + ent->client->pTarget, CONTENTS_SOLID); + + // Ridah, changed this, so we are moved away from the intersection point + if (trace.fraction < 1) + { + VectorSubtract(trace.endpos, ent->client->pTarget->s.origin, vDiff); + VectorNormalize(vDiff); + VectorMA(trace.endpos, -8, vDiff, trace.endpos); + + if (trace.plane.normal[2] > 0.8) + trace.endpos[2] += 4; +/* + trace.endpos[0] += (5 * forward[0]); + trace.endpos[1] += (5 * forward[1]); + trace.endpos[2] -= 5; +*/ + } + // Ridah , done. + + if (abs(trace.endpos[0]-ent->s.origin[0]) > ent->client->fXYLag) + { + if (trace.endpos[0] > ent->s.origin[0]) + { + ent->s.origin[0] += ent->client->fXYLag; + } + else + { + ent->s.origin[0] -= ent->client->fXYLag; + } + } + else + { + ent->s.origin[0] = trace.endpos[0]; + } + + if (abs(trace.endpos[1]-ent->s.origin[1]) > ent->client->fXYLag) + { + if (trace.endpos[1] > ent->s.origin[1]) + { + ent->s.origin[1] += ent->client->fXYLag; + } + else + { + ent->s.origin[1] -= ent->client->fXYLag; + } + } + else + { + ent->s.origin[1] = trace.endpos[1]; + } + + if (abs(trace.endpos[2]-ent->s.origin[2]) > ent->client->fZLag) + { + if (trace.endpos[2] > ent->s.origin[2]) + { + ent->s.origin[2] += ent->client->fZLag; + } + else + { + ent->s.origin[2] -= ent->client->fZLag; + } + } + else + { + ent->s.origin[2] = trace.endpos[2]; + } + + trace = gi.trace( ent->client->pTarget->s.origin, NULL, NULL, ent->s.origin, + ent->client->pTarget, CONTENTS_SOLID); + + // Ridah, changed this, so we are moved away from the intersection point + if (trace.fraction < 1) + { + VectorSubtract(trace.endpos, ent->client->pTarget->s.origin, vDiff); + VectorNormalize(vDiff); + VectorMA(trace.endpos, -8, vDiff, trace.endpos); + + if (trace.plane.normal[2] > 0.8) + trace.endpos[2] += 4; + + VectorCopy(trace.endpos, ent->s.origin); + } +/* + if (trace.fraction != 1) + { + + VectorSubtract(ent->client->pTarget->s.origin,ent->s.origin,vDiff); + + vectoangles(vDiff, vAngles); + + AngleVectors(vAngles, forward, NULL,NULL); + + forward[2] = 0; + + VectorNormalize(forward); + + trace.endpos[0] += (5 * forward[0]); + trace.endpos[1] += (5 * forward[1]); + trace.endpos[2] -= 5; + VectorCopy(trace.endpos, ent->s.origin); + } +*/ + // Ridah , done. +} + +//============================================================================ +//============================================================================ +void +RepositionAtOrigin(edict_t *ent, vec3_t vOffsetPosition) +{ + vec3_t + vCamPos; + + trace_t + trace; + + vCamPos[0] = vOffsetPosition[0] + 40; + + vCamPos[1] = vOffsetPosition[1] + 40; + + vCamPos[2] = vOffsetPosition[2] + 30; + + trace = gi.trace( vOffsetPosition, NULL, NULL, vCamPos, + ent->client->pTarget, CONTENTS_SOLID); + + // Ridah, added this, so we are moved away from the intersection point + if (trace.fraction < 1) + { + vec3_t vDiff; + + VectorSubtract(trace.endpos, vOffsetPosition, vDiff); + VectorNormalize(vDiff); + VectorMA(trace.endpos, -8, vDiff, trace.endpos); + + if (trace.plane.normal[2] > 0.8) + trace.endpos[2] += 4; + } + // Ridah, done. + + if (abs(trace.endpos[0]-ent->s.origin[0]) > ent->client->fXYLag) + { + if (trace.endpos[0] > ent->s.origin[0]) + { + ent->s.origin[0] += ent->client->fXYLag; + } + else + { + ent->s.origin[0] -= ent->client->fXYLag; + } + } + else + { + ent->s.origin[0] = trace.endpos[0]; + } + + if (abs(trace.endpos[1]-ent->s.origin[1]) > ent->client->fXYLag) + { + if (trace.endpos[1] > ent->s.origin[1]) + { + ent->s.origin[1] += ent->client->fXYLag; + } + else + { + ent->s.origin[1] -= ent->client->fXYLag; + } + } + else + { + ent->s.origin[1] = trace.endpos[1]; + } + + if (abs(trace.endpos[2]-ent->s.origin[2]) > ent->client->fZLag) + { + if (trace.endpos[2] > ent->s.origin[2]) + { + ent->s.origin[2] += ent->client->fZLag; + } + else + { + ent->s.origin[2] -= ent->client->fZLag; + } + } + else + { + ent->s.origin[2] = trace.endpos[2]; + } + + trace = gi.trace( vOffsetPosition, NULL, NULL, ent->s.origin, + ent->client->pTarget, CONTENTS_SOLID); + + // Ridah, added this, so we are moved away from the intersection point + if (trace.fraction < 1) + { + vec3_t vDiff; + + VectorSubtract(trace.endpos, vOffsetPosition, vDiff); + VectorNormalize(vDiff); + VectorMA(trace.endpos, -8, vDiff, trace.endpos); + + if (trace.plane.normal[2] > 0.8) + trace.endpos[2] += 4; + } + // Ridah, done. + + if (trace.fraction != 1) + { + VectorCopy(trace.endpos, ent->s.origin); + } +} +void +UpdateValues(edict_t *ent) +{ + edict_t + *pTarget; + + pTarget = ent->client->pTarget; + + if (pTarget != NULL) + { +// ent->client->resp.score = pTarget->client->resp.score; +// ent->health = pTarget->health; +// ent->client->ammo_index = pTarget->client->ammo_index; +// ent->client->pers.inventory[ent->client->ammo_index] = +// pTarget->client->pers.inventory[ent->client->ammo_index]; + } + else + { + ent->client->resp.score = 0; + ent->health = 0; + } +} + + +//============================================================================ +//============================================================================ +void +CameraFollowThink(edict_t *ent, usercmd_t *ucmd) +{ + vec3_t + vCameraOffset; +// Ridah, changed this so we keep tracking the same player until a new player is selected + if (ent->client->pTarget || (ent->client->pTarget = PlayerToFollow(ent)) != NULL) + { + // + // Just keep looking for action! + // + vCameraOffset[0] = -60; + vCameraOffset[1] = -60; + vCameraOffset[2] = 40; + RepositionAtTarget(ent, vCameraOffset); + PointCamAtTarget(ent); + } + UpdateValues(ent); +} + +//============================================================================ +//============================================================================ +void +CameraNormalThink(edict_t *ent, usercmd_t *ucmd) +{ + vec3_t + vCameraOffset; + int + iNumVis; + + iNumVis = NumPlayersVisible(ent); + + // Ridah, changed this so it only changes when our following target dies +// if (pDeadPlayer && IsVisible(ent, pDeadPlayer)) + if (!ent->client->bWatchingTheDead && ent->client->pTarget && ent->client->pTarget->deadflag) + { + ent->client->bWatchingTheDead = TRUE; +// ent->client->pTarget = pDeadPlayer; + ent->last_move_time = level.time + CAMERA_DEAD_SWITCH_TIME; + PointCamAtTarget(ent); + } + else if (ent->client->bWatchingTheDead) + { + if (ent->last_move_time < level.time) + { + ent->client->bWatchingTheDead = FALSE; + } + else + { + if (ent->client->pTarget->deadflag) + { + VectorCopy(ent->client->pTarget->s.origin,ent->client->vDeadOrigin); + } + PointCamAtOrigin(ent, ent->client->vDeadOrigin); + RepositionAtOrigin(ent, ent->client->vDeadOrigin); + } + } + else if ( iNumVis < 2 ) + { + vCameraOffset[0] = -60; + vCameraOffset[1] = -60; + vCameraOffset[2] = 40; + + if (ent->last_move_time > level.time) + { + if (iNumVis > 0) + { + // + // This should always be true but who knows lets check anyway. + // + if ((ent->client->pTarget = ClosestVisible(ent)) != NULL) + { + RepositionAtTarget(ent, vCameraOffset); + PointCamAtTarget(ent); + } + } + else if ((ent->client->pTarget = PlayerToFollow()) != NULL) + { + // + // Look for someone new! + // + RepositionAtTarget(ent, vCameraOffset); + PointCamAtTarget(ent); + ent->last_move_time = 0; + } + } + else if ((ent->client->pTarget = PlayerToFollow(ent)) != NULL) + { + // + // Just keep looking for action! + // + vCameraOffset[0] = -60; + vCameraOffset[1] = -60; + vCameraOffset[2] = 40; + RepositionAtTarget(ent, vCameraOffset); + PointCamAtTarget(ent); + } + } + else if ((ent->last_move_time < level.time) || (ent->client->pTarget && !gi.inPVS(ent->s.origin, ent->client->pTarget->s.origin))) + { + if ((ent->client->pTarget = PlayerToFollow()) != NULL) + { + vCameraOffset[0] = -60; + vCameraOffset[1] = -60; + vCameraOffset[2] = 80; + PointCamAtTarget(ent); + RepositionAtTarget(ent, vCameraOffset); + ent->last_move_time = level.time + CAMERA_SWITCH_TIME; + } + } + else if (ent->client->pTarget != NULL) + { +// Ridah, added this to give the camera some movement + if ( (ent->last_move_time > (level.time + CAMERA_SWITCH_TIME - 3)) + || (ent->last_move_time < (level.time + 5))) + { + RepositionAtOrigin(ent, ent->client->pTarget->s.origin); + } +// Ridah, done. + + PointCamAtTarget(ent); + } + + pDeadPlayer = NULL; + + if (ent->client->pTarget == NULL) + { + return; + } + + UpdateValues(ent); +} + +//============================================================================ +//============================================================================ +void +CameraStaticThink(edict_t *ent, usercmd_t *ucmd) +{ + trace_t + trace; + vec3_t + vEndFloor, + vEndCeiling; + + vEndFloor[0] = ent->s.origin[0]; + vEndFloor[1] = ent->s.origin[1]; + vEndFloor[2] = ent->s.origin[2] - 40000; + trace = gi.trace( ent->s.origin, NULL, NULL, vEndFloor, ent, CONTENTS_SOLID); + + VectorCopy ( trace.endpos, vEndFloor ); + + vEndCeiling[0] = vEndFloor[0]; + vEndCeiling[1] = vEndFloor[1]; + vEndCeiling[2] = vEndFloor[2] + 175; + trace = gi.trace( vEndFloor, NULL, NULL, vEndCeiling, ent, CONTENTS_SOLID); + + VectorCopy ( trace.endpos, ent->s.origin ); + + if ( ent->last_move_time < level.time ) + { + ent->last_move_time = level.time + 2; + ent->s.angles[0] = 45; + ent->s.angles[1] = 0; + ent->s.angles[2] = 0; + VectorCopy (ent->s.angles, ent->client->ps.viewangles); + VectorCopy (ent->s.angles, ent->client->v_angle); + } +} + +//============================================================================ +//============================================================================ +void +CameraThink(edict_t *ent, usercmd_t *ucmd) +{ + // Ridah, added this to cycle through players + if ((ent->client->pTarget && !ent->client->pTarget->inuse) || ((ucmd->buttons & BUTTON_ATTACK) && !(ent->client->oldbuttons & BUTTON_ATTACK))) + { + int count=0; + sPlayerList *start, *trav; + + trav = EntityListHead(); + + while (trav && ent->client->pTarget && ent->client->pTarget->inuse && (trav->pEntity != ent->client->pTarget)) + { + trav = EntityListNext(trav); + } + + start = trav; + + if (!(trav = EntityListNext(trav))) + trav = EntityListHead(); + + while (!trav->pEntity->solid) + { + if (!(trav = EntityListNext(trav))) + trav = EntityListHead(); + count++; + + if (count >= maxclients->value) + { + break; + } + } + + if (count < maxclients->value) + { + ent->client->pTarget = trav->pEntity; + safe_cprintf(ent, PRINT_HIGH, "Now showing %s\n", ent->client->pTarget->client->pers.netname); + } + } + + ent->client->oldbuttons = ucmd->buttons; + // Ridah, done. + + ent->client->ps.pmove.pm_type = PM_FREEZE; + ent->client->ps.pmove.gravity = 0; + + if (EntityListNumber() == 0) + { + CameraAloneThink(ent,ucmd); + } + else + { + switch (ent->client->iMode) + { + case CAM_FOLLOW_MODE: + CameraFollowThink(ent,ucmd); + break; + case CAM_NORMAL_MODE: + default: + CameraNormalThink(ent,ucmd); + break; + + } + } +} + + +sPlayerList + *pEntityListHead; +unsigned long + ulCount=0; + +//============================================================================ +//============================================================================ +void +EntityListRemove(edict_t *pEntity) +{ + sPlayerList + *pTrail, *pLead; + + pTrail = pLead = pEntityListHead; + + while ( pLead != NULL ) + { + if (pLead->pEntity->client->bIsCamera) + { + // + // Force rethink on all cameras + // + pLead->pEntity->last_move_time = level.time; + } + if (pLead->pEntity == pEntity) + { + if ( pLead == pTrail) + { + pEntityListHead = pLead->pNext; + + } + else + { + pTrail->pNext = pLead->pNext; + } + + free(pLead); + ulCount--; + pLead = NULL; + } + else + { + pTrail = pLead; + pLead = pLead->pNext; + } + } +} + +//============================================================================ +//============================================================================ +void +EntityListAdd(edict_t *pEntity) +{ + sPlayerList *pNew; + + pNew = malloc (sizeof(sPlayerList)); + pNew->pEntity = pEntity; + pNew->pNext = pEntityListHead; + pEntityListHead = pNew; + ulCount++; +} + +//============================================================================ +//============================================================================ +unsigned long +EntityListNumber() +{ + return ulCount; +} + +//============================================================================ +//============================================================================ +sPlayerList * +EntityListHead() +{ + if (pEntityListHead != NULL) + { + return (pEntityListHead); + } + else + { + return NULL; + } +} + +sPlayerList * +EntityListNext(sPlayerList *pCurrent) +{ + return(pCurrent->pNext); +} +//============================================================================ +//============================================================================ +void +PrintEntityList() +{ + sPlayerList + *pNode; + + unsigned long ultemp=0; + gi.dprintf("PrintEntityList\n"); + + for ( pNode = pEntityListHead; pNode != NULL; pNode = pNode->pNext) + { + gi.dprintf("Name: %s ",pNode->pEntity->client->pers.netname); + gi.dprintf("Class: %s\n",pNode->pEntity->classname); + ultemp++; + } + gi.dprintf("Actual Count: %d List Count %d\n",ultemp,EntityListNumber()); +} + +void +EnitityListClean() +{ + sPlayerList + *pNode; + + while (NULL != (pNode=EntityListHead())) + { +// gi.dprintf("Name: %s\n",pNode->pEntity->client->pers.netname); + EntityListRemove(pNode->pEntity); + } +} diff --git a/camclient.h b/camclient.h new file mode 100644 index 0000000..bc82c5e --- /dev/null +++ b/camclient.h @@ -0,0 +1,26 @@ +#define MAX_VISIBLE_RANGE 1000 +#define CAMERA_SWITCH_TIME 20 +#define CAMERA_DEAD_SWITCH_TIME 2.5 +#define TRUE 1 +#define FALSE 0 + +void CameraThink(edict_t *ent, usercmd_t *ucmd); +void PlayerDied(edict_t *ent); + +typedef struct sPlayerList_s +{ + edict_t *pEntity; + struct sPlayerList_s *pNext; +} sPlayerList; + +int CameraCmd(); +void EntityListRemove(edict_t *pEntity); +void EntityListAdd(edict_t *pEntity); +unsigned long EntityListNumber(); +void PrintEntityList(); +void EnitityListClean(); +sPlayerList *EntityListHead(); +sPlayerList *EntityListNext(sPlayerList *pCurrent); +sPlayerList *pTempFind; + + diff --git a/chat.txt b/chat.txt new file mode 100644 index 0000000..de94d74 --- /dev/null +++ b/chat.txt @@ -0,0 +1,445 @@ +# Eraser Bot chat data +# +# Thanks to Meanstryk for the additions +# -> http://www.planetquake.com/ramshackle +# Also thanks to Shawn "Benighted1" Lisk for more additions +# -> kclisk@bright.net +# +# OK, here's the deal. Bot's can say one of the following things: +# +# 1. Greetings +# +# When a bot joins a team, or you join a bot's team, they'll usually +# say hi. This depends on what they're doing at the time. If they're +# not in battle, they'll usually give you a nice, warm welcome. +# +# 2. Insults +# +# Ahh, the insult. What would Quake be without the old fashioned +# insult. I bot will insult you randomly, depending on the status of +# the game, so this section is divided into a few sub-sections: +# +# a. The General Insults +# +# Just your average, all-purpose insult. +# +# b. Kicking your Ass +# +# The bot will use these insults when he's beating you, +# so these +# won't make much sense if he's losing. +# +# c. Losing +# +# The bot is losing, but has just killed you. A come- +# back is imminent. +# +# 3. Come-backs +# +# I personally don't like being told to wipe the floor after I messed up. +# Bots don't take to insults too well either, especially the good ones +# who have an ego to defend. +# +# 4. TEAMPLAY: Help! +# +# Some variations on the standard Help me, I'm in trouble! Note that if +# the bot is near an item such as the Rocket-Launcher, or Quad damage, +# they'll add a "Near the Rocket-Launcher!" to the end of the chat line. +# +# 5. TEAMPLAY: Drop item +# +# Bot's will let others (on their team) know if they have excess items. +# This provides some variety on they way they let you know. +# +# 6. TEAMPLAY: Let's Group! +# +# A bot may decide he's going to coordinate an attack. If so, he will issue +# one of these statements to the rest of his team, following by a location +# specifier, like "I'm near the Rocket-Launcher". He will generally wait a +# few second in that area, and then proceed. Other bot's will try to get to +# the leader Bot in time, and will then follow him around the level. +# +# Bot's will generally try to garner up a nice supply of weapons and ammo +# before doing this. +# +# +# NOTE: where you see a %s, that means a string goes there. All strings +# must appear in the same order for each section. +# + +-- Greetings +Yo! +Wassup! +Hey. +Hi. +Greetings +Eh. +I'm back! +Oi! +Welcome aboard. +Everyone aboard? Let's rock. +'sup bro? +Howdy. +Konnichiwa! +You better not suck. +Wait... what color am I? +Yull! +What da deallie yo? +Be fruitiful and frag a lot. +Let's get it on! +Who's going to lose to me today? +Anarchists of the world unite! +To the Bat Cave! +Let the show begin. +Anyone up for slapping the criminal element around? +Make 'em say 'ugggggh'! +Last one to kill a bad guy buys the beer. +Lets rock and roll! +I love the smell of BFG blasts in the morning! +G'day mate. +Let's see what you can do. +Present! +Here! +Nice night for a furball! +Eh mon! +Crap, where's a depot!? +I'm feelin' saucy today. +Kick it! + +-- Insults (general) +Geez, are you ok? +Ouch, that HAD to hurt! +Who da man! +Yeh punk! +You all banged up! +Can anyone here play this game? +Waiter! Come clean up this mess! +Take my advice, or I'll spank you without pants +Need a band-aid %s? +Boo-Ya!!! +Hey everyone, %s is a camping whore. +Everyone line up single file... I'll get the railgun. +Sorry %s. This is a NO BREATHING zone. +Hey %s, drop to the console and type KILL. +Damn %s, it should be illegal to suck so bad. +Excuse me while I urinate on your corpse, %s. +Hey %s... found your spleen. +Stick to Duke Nukem %s. +Eww, I stepped on %s's severed ear! +You want some more %s? +That was just a sample %s. +Nice try %s... A for effort. +Thanks for the frag %s. +Just give up %s. +How pathetic. +Wow, what a shot. I rule. +Sorry %s, didn't see you there. :) +That's right... run... wuss.... +Why'd ya try to run %s? +Just like a deer in headlights. +Bahahaha! +Man %s... I pity you. I really do. +Someone ELSE kill %s for a change. +Yeah %s... you're my favorite. +What do you call that shit %s? +Learn from that. +Your corpse made a fine screenshot %s. +I have now been added to %s's shitlist. +You weren't doing anything anyway %s. +Oh get up... you aint hurt! :) +Lovely chalk outline %s. +Next time %s... move. +Stick to observer mode %s. +Love those death animations %s... you too apparently. +Damn %s, you smell just like roast chicken. +I love the sound of crunching bone. :) +Nice pain WAVs %s. +Burn baby burn. +Dude, %s... your skin blows. +Hey everyone, %s wasn't wearing clean underwear! +Instant karma, %s. +24/7, %s +Does your face hurt, %s? Well, its killin' me! +Ooh, thats gotta hurt! +B-b-b-b-Bad to the bone! +Oh my god, they killed %s! You basterds! +Bull's Eye! +I bet that hurt! +Come meet my 2x4. +%s was fragged again everyone! +Step right up to be knocked right down! +Clean up in isle seven...%s's remains in isle seven. +%s's dead, Jim! +Keep the change %s. +Oh, I'm gooood. +%s...there's no reason to act like that! +Anyone got a spachula? +Danger, %s Robinson, danger! +Excuse me, is the circus in town? +Terminated. +%s's loss is my gain! +BOOM, baby! +You move like peanut butter, %s! + +-- Insults (kicking ass) +You gonna start playing or what? +Keeping practising, %s +Your in my house now %s! +Ain't nobody stoppin me! +Gun wounds again? +Try strafing sometime %s. +Can I be killed? I'm beginning to wonder. +Next time, make me work for it %s. +Am I a giant or did everyone else shrink? +Why do you people even bother? +Next time %s, don't bother dodging. Why prolong your agony? +What happened %s? >:) +Mental note to self: Find server with better opponents. +Christ people... try playing with a mouse. +Nobody can touch me in this level. +It *hurts* to be this kick-ass! It *hurts*! +I'm like the freaking energizer bunny over here. +Why do they ALWAYS try to run?? +Just like cuttin' down weeds. +BANG said the gun, SPLATTER said %s. +Get the hell out of my way! +Play dead %s... good boy. +Step right up to get knocked right down! +That's right, one free asskicking with every purchase. +Did I get your attention %s? +Am I evil? Why, yes I am. +Some people just don't die well. +Hey look, I can chat while running full tilt! +Hey %s, don't hate me just because you suck. +BOW DOWN! +Interception by %s! +He shoots... he SCORES! +You should see a doctor about that %s. +Thank you, come again. +Down boy... +I'm such a bastard. :) +Hope you have death and dismemberment insurance %s. +Boy, I'm gonna send this demo to Meanstryk. :) +Sorry sir, but thank you for playing. +Come on in %s... the slaughter's fine. +Ha! %s smeared like a red crayon! +Hi %s... I'm the enemy. +Making people feel bad feels SO good. +Sorry %s. Time to respawn. +Another one bites the dust. +Someone had better dig a mass grave. +Can't pay the bills, but damnit, this I can do. +What the fuck you think YOU'RE doin' %s? +Come share with me my hurtful thoughts. +Ha! Somedays it really pays to be a bot. +You should stick to Chess %s. +Its frag time. Do you know where you're skill is %s? +Walk into the light, %s! +Oh, I just remembered; %s's boring, and my guns work. +He chose...poorly. +I'm immortal!...So far. +Nothing can stop me now. +I must be butter cause I'm on a roll! +DEATH FROM ABOVE!!! +I'm not even working up a sweat, %s! +I'd trash talk, but I'm too tired...*yawn* +%s...I'm your father! +One small frag for me, one big head wound for you! +All too easy. +Come strong or don't come at all! +Damnit %s! You messed my hair up! + +-- Insults (losing, coming back) +It's pay-back time! +Elvis has left the building! +Time to change your diaper %s +Who gave %s the nerve to get killed here? +Oops... did I do that? Sorry, man. +Nice spine there %s. +That looks like it's going to stain. +What... a... mess. +In your face bitch... in your face. +Look out man, I'm back in my zone. Watch your ass! +NOW who has the big gun mother-fucker?!? +Oh, no wonder... I had the safety on... +Better run %s... my lag is letting up. +Hey %s... ya shoulda zigged when ya zagged. +Hey nice catch %s! +Look out %s, I'm just getting warmed up. +Looks like there's a new lawman in town, eh %s? +Getting tired there %s? +Okay %s, I have your punk-ass figured. +Killing you appears to be habit forming %s. +How the fuck do you like it %s? +Oooh! That felt so good! +Not such a badass now, eh %s? +No more Mr. Nice Guy. +Oh man, you've had it now. +Now THAT is what I'm talkin' about. +Feel that? That's my foot in your ass. +Get used to it %s. +Anyone else hear the theme from Rocky? +Your time's up %s. +Okay, now I start playing for real. +Hurt much? +Remember me %s? +My how the mighty have fallen. +Here's where you get off %s. +Merry Christmas! +This aint no petting zoo, %s. +Ever hear of the golden rule %s? +Your bullying days are over %s. +Now the shoe's on the other foot. +Wanna try for two out of three %s? +Hehehe! +Hah! +That's what I THOUGHT... loser. +Memento Mori. +The man who dies with the most toys still dies! +I'd give you the finger, but you shot them off. +Your meaty skull is beautiful to me, %s. +You all saw him...he had a gun! +I'm not gonna kill ya. Like hell I'm not! +Use the Farse, puke! +Ah...the joy of deathmatch. +Where's your crown, King Nothing? +Chill %s. +I'm spittin' on your corpse, %s! +SCOOOOOOOOOOOOOOOOORE! +Exit light, enter night %s. +Respect my authoritay! + +-- Come-backs +Screw you %s +I'll be back mutha-fucker! +I ain't even started yet. +Make the most of it %s. +Yeh? Well fuck you asshole! +Ahh, piss off! +Yadda yadda... +Wipe the shuga from yer chin, %s +Don't make me come over there %s. +Okay %s, you and me, after school. +Why do you have to be such an asshole? +Skin that smokewagon! Bring it on bitch! +One of these days I'm gonna catch your ass typing. +From now on %s, you're my bitch. Bend over. +My boot. %'s ass. Perfect fit. +Fuck off. +Bite me. +You aint shit! +Talk your shit over here %s. +I got somethin' for your ass %s. +I'll be your huckleberry. +You gonna do something other than talk? +You talkin' to me? +You don't want none of this %s. +This isn't IRC. Less talk and more play. +You're so full of shit %s. +Okay %s, let's dance. +Come learn to die. +Shut up %s. +Shut the FUCK up. +Damned phonejack. +Lllaaaagggg! +I had TIME to kill, now I have SOMEONE to kill. +Don't let your mouth overload your ass %s. +You best hush %s. +I aint playin' with you %s. +Come on %s. Let's go! You afraid? +You're such a lamer %s. +%s --you whore. +What? You think you can play now? +You don't want none of this %s. +Idiot. +Someone needs to get laid. +I'll have my way with your mom after I kill her %s! +%s seems to care; about what, I have no idea. +The answer is cold in my hand, %s. +I ain't gonna waste my hate on you, %s. +I'll see you in hell %s. +I do not want this... +Ahh, shiiit. +Its all a conspiricy! +My God, are you still talking!? +Its not my fault- my gamma correction is screwy! +Next time %s...next time. +Bull! It was freindly fire! +Well aren't you full of piss and vinegar. +By this time, my lungs were aching for air. +You'd better bite down on a BFG and pray for lock jaw, %s. +My keyboard's missing a few keys. +Does the phrase, 'Nut Case' mean anything to you, %s? +You need to grow up, %s! +I'm going to use you for a raft next time we meet, %s! +I think a retreat is in order... + +-- TEAMPLAY: Help! +Ahh!! Under heavy fire near the %s!! Heeelp!!! +I need backup at the %s!! +Backup requested, near the %s! +I'm seeing my life flash before my eyes by the %s! +Charlie is all over the %s. +All are invited to the %s. Enemy already in attendance. +Bring mop and bucket to %s. I'm about to splatter messily. +A little help here? I'm at the %s. +Visit the %s. See enemy is natural habitat. +Outnumbered. Dying. By %s. +The %s area is in a world of hurt, and so am I. +The enemy has control of the %s area. Recover. +Experiencing substanial pain near the %s. +Hating life by the %s. +I'm about to become a stain near the %s. +Enemy seems to want %s very badly. +Heavy resistance near the %s. +Come help me out over at the %s before I soil myself. +Aaarrggh! %s Death... the horror... the horror +zzzt- help! zzt...over zzzt-fizz %s! +The Man is opressin' me by the %s! +Get busy child! I'm by the %s. +Damnit, they're all over me by the %s! + +-- TEAMPLAY: Drop Item (not implemented yet) +Hey guys, need a %s? I'm near the %s! +I have a %s to spare, near the %s! +I've just dropped a %s, near the %s! +Free %s! It's by the %s. +Anyone want a %s? Come to the %s. +There's a %s available by the %s. +Come get the %s over by the %s. +Be advised: %s available near %s. +How'd I get so many %ss? Anyone want one? I'm by the %s. +There's a %s by the %s. Come get it before enemy does. +Someone come get this %s by the %s. +I don't need this %s; anyone want it? By %s. +Surplus %s avaiable near %s. +There's a %s in the open over here near the %s. +Anyone want a %s? I already have one. It's near the %s. +I'm dropping a %s near the %s. +Shiny new %s, hardly used, available near %s. +Grab this %s someone, near the %s. +Prevent enemy from getting the %s near the %s. +Dropped %s near %s. + +-- TEAMPLAY: Let's Group! +Calling all units! Group at the %s! +Attack formation at the %s! +Calling all squadron members to the %s! +Hey guys, I'm over at the %s! +Come protect the %s! +Everyone get your asses over to the %s! +Meeting at %s. Attendance mandatory. +Forming attack group at %s. +Regroup at the %s. +Preparing attack run near %s. +Fall back to %s. +Proceed to %s and await further instruction. +Gather near %s. +Everyone join me at the %s. +Meet me at the %s. Forming attack party. +Let's get our shit together people. Group at %s. +Launching attack run from the %s. +Everyone avilable please report to the %s. +Everyone...attack from the %s! +Our next organized attack will be from the %s. Hurry! +Group by the %s. \ No newline at end of file diff --git a/debug.c b/debug.c new file mode 100644 index 0000000..3dd45b4 --- /dev/null +++ b/debug.c @@ -0,0 +1,90 @@ +//Debug routine - create fake functions for bot code + +/* +#include "g_local.h" +#include "bot_procs.h" + + +void OptimizeRouteCache() +{ + int i; + i = 1; +} + +void CalcRoutes(int node_index) +{ + int i; + i = 1; +} + +int ClosestNodeToEnt(edict_t *self, int check_fullbox, int check_all_nodes) +{ + return 0; +} + +float PathToEnt(edict_t *self, edict_t *target, int check_fullbox, int check_all_nodes) +{ + return 0; +} + +void Debug_ShowPathToGoal(edict_t *self, edict_t *goalent) +{ +} + +edict_t *matching_trail(vec3_t spot) +{ + return NULL; +} + +void AddTrailToPortals(edict_t *trail) +{ +} + +int GetGridPortal(float pos) +{ + return 0; +} + +void NodeDebug(char *fmt, ...) +{ +} +void CheckMoveForNodes(edict_t *ent) +{ +} +void CalcItemPaths(edict_t *ent) +{ +} + +void PlayerTrail_Init (void) +{ +} + +void PlayerTrail_Add (edict_t *self, vec3_t spot, edict_t *goalent, int nocheck, int calc_routes, int node_type) +{ +} + +void PlayerTrail_New (vec3_t spot) +{ +} + +void WriteTrail () +{ +} + +edict_t *PlayerTrail_PickFirst (edict_t *self) +{ + return NULL; +} + +edict_t *PlayerTrail_PickNext (edict_t *self) +{ + return NULL; +} + +edict_t *PlayerTrail_LastSpot (void) +{ + return NULL; +} + + + */ \ No newline at end of file diff --git a/docker4xwf.ent b/docker4xwf.ent new file mode 100644 index 0000000..1696f34 --- /dev/null +++ b/docker4xwf.ent @@ -0,0 +1,3241 @@ +{ +"gravity" "800" +"message" "Docker4xWf - by Acrid-" +"sky" "space1" +"classname" "worldspawn" +} +{ +"model" "*1" +"_minlight" "1" +"sounds" "0" +"dmg" "2" +"lip" "8" +"wait" "0" +"speed" "100" +"health" "0" +"target" "bwhap" +"angle" "-1" +"classname" "func_door" +} +{ +"model" "*2" +"_minlight" "1" +"wait" "0" +"target" "rwhap" +"angle" "-1" +"classname" "func_door" +} +{ +"model" "*3" +"health" "0" +"_minlight" "1" +"wait" "0" +"target" "nwhap1" +"spawnflags" "16" +"angle" "-1" +"classname" "func_door" +} +{ +"model" "*4" +"_minlight" "1" +"wait" "0" +"target" "nwhap2" +"spawnflags" "16" +"angle" "-1" +"classname" "func_door" +} +{ +"model" "*5" +"angle" "-1" +"classname" "func_door" +} +{ +"model" "*6" +"angle" "-1" +"classname" "func_door" +} +{ +"model" "*7" +"_minlight" "1" +"wait" "0" +"wf_team" "1" +"target" "rssap" +"angle" "-1" +"classname" "func_door" +} +{ +"model" "*8" +"_minlight" "1" +"wait" "0" +"target" "radap" +"wf_team" "1" +"angle" "-1" +"classname" "func_door" +} +{ +"model" "*9" +"wf_team" "2" +"_minlight" "1" +"sounds" "0" +"dmg" "2" +"lip" "8" +"wait" "0" +"speed" "100" +"health" "0" +"target" "bssap" +"angle" "-1" +"classname" "func_door" +} +{ +"model" "*10" +"wf_team" "2" +"_minlight" "1" +"sounds" "0" +"dmg" "2" +"lip" "8" +"wait" "0" +"speed" "100" +"health" "0" +"target" "badap" +"angle" "-1" +"classname" "func_door" +} +{ +"style" "1" +"targetname" "bssap" +"classname" "func_areaportal" +} +{ +"style" "2" +"targetname" "badap" +"classname" "func_areaportal" +} +{ +"style" "3" +"targetname" "bwhap" +"classname" "func_areaportal" +} +{ +"origin" "1104 736 98" +"spawnflags" "1792" +"classname" "item_flag_team2" +} +{ +"style" "4" +"targetname" "radap" +"classname" "func_areaportal" +} +{ +"origin" "1104 -2048 98" +"spawnflags" "1792" +"classname" "item_flag_team1" +} +{ +"style" "5" +"targetname" "rssap" +"classname" "func_areaportal" +} +{ +"model" "*11" +"team" "rap" +"target" "rwhap2" +"_minlight" "0" +"sounds" "0" +"dmg" "2" +"lip" "8" +"wait" "3" +"health" "0" +"targetname" "door1" +"speed" "100" +"classname" "func_door" +} +{ +"model" "*12" +"target" "rwhap2" +"team" "rap" +"targetname" "door1" +"speed" "100" +"angle" "90" +"classname" "func_door" +} +{ +"model" "*13" +"target" "rwhap2" +"team" "rap" +"_minlight" "0" +"sounds" "0" +"dmg" "2" +"lip" "8" +"wait" "3" +"health" "0" +"targetname" "door1" +"speed" "100" +"angle" "180" +"classname" "func_door" +} +{ +"model" "*14" +"target" "rwhap2" +"team" "rap" +"_minlight" "0" +"sounds" "0" +"dmg" "2" +"lip" "8" +"wait" "3" +"health" "0" +"targetname" "door1" +"speed" "100" +"angle" "270" +"classname" "func_door" +} +{ +"style" "6" +"targetname" "rwhap2" +"classname" "func_areaportal" +} +{ +"model" "*15" +"delay" "0" +"wait" "3" +"target" "door1" +"classname" "trigger_multiple" +} +{ +"style" "7" +"targetname" "bwhap2" +"classname" "func_areaportal" +} +{ +"model" "*16" +"delay" "0" +"wait" "3" +"target" "door2" +"classname" "trigger_multiple" +} +{ +"style" "8" +"targetname" "rwhap" +"classname" "func_areaportal" +} +{ +"style" "9" +"targetname" "nwhap1" +"classname" "func_areaportal" +} +{ +"origin" "478 -718 248" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "532 -718 270" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "430 -718 270" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "478 -702 270" +"_cone" "10" +"style" "0" +"light" "200" +"classname" "light" +} +{ +"origin" "118 320 254" +"light" "200" +"classname" "light" +} +{ +"origin" "102 320 232" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "102 266 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "102 368 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-14 466 232" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "40 466 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-62 466 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-14 482 254" +"_cone" "10" +"style" "0" +"light" "200" +"classname" "light" +} +{ +"origin" "-142 320 254" +"_cone" "10" +"style" "10" +"light" "200" +"classname" "light" +} +{ +"origin" "-126 320 232" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-126 266 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-126 368 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-278 320 254" +"light" "200" +"classname" "light" +} +{ +"origin" "-294 320 232" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-294 266 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-294 368 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-748 -1260 28" +"classname" "info_player_start" +} +{ +"style" "10" +"targetname" "nwhap2" +"classname" "func_areaportal" +} +{ +"origin" "588 -1028 96" +"light" "64" +"classname" "light" +} +{ +"origin" "372 -1028 96" +"light" "64" +"classname" "light" +} +{ +"origin" "480 -864 270" +"_cone" "30" +"style" "0" +"target" "null1" +"light" "300" +"classname" "light" +} +{ +"origin" "480 -864 88" +"targetname" "null1" +"classname" "info_null" +} +{ +"origin" "-120 -1468 80" +"light" "64" +"classname" "light" +} +{ +"origin" "96 -1468 80" +"light" "64" +"classname" "light" +} +{ +"origin" "-12 -1632 254" +"_cone" "30" +"style" "0" +"target" "null2" +"light" "300" +"classname" "light" +} +{ +"origin" "-12 -1632 72" +"targetname" "null2" +"classname" "info_null" +} +{ +"origin" "-406 -1778 232" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-460 -1778 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-358 -1778 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-406 -1794 254" +"_cone" "10" +"style" "0" +"light" "200" +"classname" "light" +} +{ +"origin" "-538 -1632 254" +"light" "200" +"classname" "light" +} +{ +"origin" "-522 -1632 232" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-522 -1578 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-522 -1680 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-278 -1632 254" +"_cone" "10" +"style" "10" +"light" "200" +"classname" "light" +} +{ +"origin" "-294 -1632 232" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-294 -1578 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-294 -1680 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-516 -1468 80" +"light" "64" +"classname" "light" +} +{ +"origin" "-300 -1468 80" +"light" "64" +"classname" "light" +} +{ +"origin" "-408 -1632 254" +"_cone" "30" +"style" "0" +"target" "null3" +"light" "300" +"classname" "light" +} +{ +"origin" "-408 -1632 72" +"targetname" "null3" +"classname" "info_null" +} +{ +"origin" "372 -284 96" +"light" "64" +"classname" "light" +} +{ +"origin" "588 -284 96" +"light" "64" +"classname" "light" +} +{ +"origin" "96 156 80" +"light" "64" +"classname" "light" +} +{ +"origin" "-120 156 80" +"light" "64" +"classname" "light" +} +{ +"origin" "-12 320 254" +"_cone" "30" +"style" "0" +"target" "null5" +"light" "300" +"classname" "light" +} +{ +"origin" "-12 320 72" +"targetname" "null5" +"classname" "info_null" +} +{ +"origin" "-300 156 80" +"light" "64" +"classname" "light" +} +{ +"origin" "-516 156 80" +"light" "64" +"classname" "light" +} +{ +"origin" "-408 320 254" +"_cone" "30" +"style" "0" +"target" "null6" +"light" "300" +"classname" "light" +} +{ +"origin" "-408 320 72" +"targetname" "null6" +"classname" "info_null" +} +{ +"origin" "-538 320 254" +"_cone" "10" +"style" "0" +"light" "200" +"classname" "light" +} +{ +"origin" "-522 320 232" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-522 266 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-522 368 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-410 466 232" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-356 466 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-458 466 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-410 482 254" +"_cone" "10" +"style" "10" +"light" "200" +"classname" "light" +} +{ +"origin" "482 -594 248" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "428 -594 270" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "530 -594 270" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "482 -610 270" +"_cone" "10" +"style" "0" +"light" "200" +"classname" "light" +} +{ +"origin" "610 -448 270" +"_cone" "10" +"style" "0" +"light" "200" +"classname" "light" +} +{ +"origin" "594 -448 248" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "594 -394 270" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "594 -496 270" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "350 -448 270" +"style" "10" +"light" "200" +"classname" "light" +} +{ +"origin" "366 -448 248" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "366 -394 270" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "366 -496 270" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "350 -864 270" +"_cone" "10" +"style" "0" +"light" "200" +"classname" "light" +} +{ +"origin" "366 -864 248" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "366 -918 270" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "366 -816 270" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "610 -864 270" +"style" "10" +"light" "200" +"classname" "light" +} +{ +"origin" "594 -864 248" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "594 -918 270" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "594 -816 270" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-142 -1632 254" +"light" "200" +"classname" "light" +} +{ +"origin" "-126 -1632 232" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-126 -1578 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-126 -1680 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "118 -1632 254" +"_cone" "10" +"style" "0" +"light" "200" +"classname" "light" +} +{ +"origin" "102 -1632 232" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "102 -1578 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "102 -1680 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-10 -1794 254" +"_cone" "10" +"style" "10" +"light" "200" +"classname" "light" +} +{ +"origin" "-10 -1778 232" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-64 -1778 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "38 -1778 254" +"attenuation" "1" +"volume" "1" +"noise" "world/amb7.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "480 -1120 -323" +"_cone" "10" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -992 -323" +"_cone" "10" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -992 -413" +"_cone" "10" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -1120 -413" +"_cone" "10" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -319 -323" +"_cone" "10" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -191 -323" +"_cone" "10" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -191 -413" +"_cone" "10" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -319 -413" +"_cone" "10" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -720 -223" +"_cone" "10" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -720 -133" +"_cone" "10" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -576 -223" +"_cone" "10" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -576 -133" +"_cone" "10" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -909 -17" +"light" "100" +"classname" "light" +} +{ +"origin" "525 -864 -16" +"light" "100" +"classname" "light" +} +{ +"origin" "435 -864 -17" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -819 -17" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -492 -17" +"light" "100" +"classname" "light" +} +{ +"origin" "525 -447 -16" +"light" "100" +"classname" "light" +} +{ +"origin" "435 -447 -17" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -402 -17" +"light" "100" +"classname" "light" +} +{ +"origin" "319 -864 -222" +"light" "100" +"classname" "light" +} +{ +"origin" "319 -864 -130" +"light" "100" +"classname" "light" +} +{ +"origin" "319 -448 -222" +"light" "100" +"classname" "light" +} +{ +"origin" "319 -448 -130" +"light" "100" +"classname" "light" +} +{ +"origin" "152 -448 -182" +"light" "100" +"classname" "light" +} +{ +"origin" "210 -448 -124" +"light" "100" +"classname" "light" +} +{ +"origin" "152 -864 -182" +"light" "100" +"classname" "light" +} +{ +"origin" "210 -864 -124" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -819 -361" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -868 -410" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -493 -360" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -450 -408" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -909 -177" +"light" "100" +"classname" "light" +} +{ +"origin" "526 -864 -177" +"light" "100" +"classname" "light" +} +{ +"origin" "480 -402 -176" +"light" "100" +"classname" "light" +} +{ +"origin" "526 -448 -176" +"light" "100" +"classname" "light" +} +{ +"origin" "676 384 104" +"angle" "180" +"classname" "info_player_start" +} +{ +"origin" "100 -416 -26" +"light" "80" +"classname" "light" +} +{ +"origin" "128 -388 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "192 -388 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "220 -416 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "220 -480 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "192 -508 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "128 -508 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "100 -480 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "100 -832 -26" +"light" "80" +"classname" "light" +} +{ +"origin" "128 -804 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "192 -804 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "220 -832 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "220 -896 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "192 -924 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "128 -924 -24" +"light" "80" +"classname" "light" +} +{ +"origin" "100 -896 -24" +"light" "80" +"classname" "light" +} +{ +"model" "*17" +"target" "bwhap2" +"team" "bap" +"_minlight" "0" +"sounds" "0" +"dmg" "2" +"lip" "8" +"wait" "3" +"health" "0" +"targetname" "door2" +"speed" "100" +"angle" "270" +"classname" "func_door" +} +{ +"model" "*18" +"target" "bwhap2" +"team" "bap" +"_minlight" "0" +"sounds" "0" +"dmg" "2" +"lip" "8" +"wait" "3" +"health" "0" +"targetname" "door2" +"speed" "100" +"angle" "180" +"classname" "func_door" +} +{ +"model" "*19" +"target" "bwhap2" +"team" "bap" +"targetname" "door2" +"speed" "100" +"angle" "90" +"classname" "func_door" +} +{ +"model" "*20" +"team" "bap" +"target" "bwhap2" +"_minlight" "0" +"sounds" "0" +"dmg" "2" +"lip" "8" +"wait" "3" +"health" "0" +"targetname" "door2" +"speed" "100" +"classname" "func_door" +} +{ +"origin" "480 -448 88" +"targetname" "null4" +"classname" "info_null" +} +{ +"origin" "480 -448 270" +"_cone" "30" +"style" "0" +"target" "null4" +"light" "300" +"classname" "light" +} +{ +"origin" "-278 -525 -40" +"targetname" "nulla1" +"classname" "info_null" +} +{ +"origin" "-278 -525 142" +"_cone" "30" +"style" "0" +"target" "nulla1" +"light" "300" +"classname" "light" +} +{ +"origin" "1152 768 296" +"_cone" "10" +"light" "900" +"classname" "light" +} +{ +"origin" "1160 736 296" +"_cone" "10" +"light" "700" +"classname" "light" +} +{ +"origin" "692 624 244" +"light" "500" +"classname" "light" +} +{ +"origin" "256 624 244" +"light" "500" +"classname" "light" +} +{ +"origin" "220 -88 244" +"light" "300" +"classname" "light" +} +{ +"origin" "252 408 308" +"light" "300" +"classname" "light" +} +{ +"origin" "1276 -296 296" +"_cone" "10" +"light" "900" +"classname" "light" +} +{ +"origin" "-28 -88 244" +"light" "370" +"classname" "light" +} +{ +"origin" "-384 -92 212" +"light" "350" +"classname" "light" +} +{ +"origin" "-352 784 296" +"_cone" "10" +"light" "900" +"classname" "light" +} +{ +"origin" "-760 -652 228" +"light" "700" +"classname" "light" +} +{ +"origin" "-212 212 312" +"light" "300" +"classname" "light" +} +{ +"origin" "-608 212 308" +"light" "300" +"classname" "light" +} +{ +"origin" "-884 -28 244" +"light" "500" +"classname" "light" +} +{ +"origin" "376 16 244" +"light" "300" +"classname" "light" +} +{ +"origin" "416 -128 244" +"light" "200" +"classname" "light" +} +{ +"origin" "640 164 288" +"light" "200" +"classname" "light" +} +{ +"origin" "568 400 252" +"light" "300" +"classname" "light" +} +{ +"origin" "1356 904 0" +"_cone" "10" +"light" "250" +"classname" "light" +} +{ +"origin" "1352 564 0" +"_cone" "10" +"light" "250" +"classname" "light" +} +{ +"origin" "328 732 144" +"_cone" "10" +"light" "250" +"classname" "light" +} +{ +"origin" "-588 -304 192" +"light" "300" +"classname" "light" +} +{ +"origin" "576 400 124" +"light" "300" +"classname" "light" +} +{ +"origin" "-84 -336 140" +"light" "200" +"classname" "light" +} +{ +"origin" "-320 -300 172" +"light" "200" +"classname" "light" +} +{ +"origin" "-584 -316 108" +"light" "300" +"classname" "light" +} +{ +"origin" "-48 -568 140" +"light" "200" +"classname" "light" +} +{ +"origin" "984 884 0" +"_cone" "10" +"light" "250" +"classname" "light" +} +{ +"origin" "1256 736 64" +"_cone" "10" +"light" "250" +"classname" "light" +} +{ +"origin" "-212 212 148" +"light" "200" +"classname" "light" +} +{ +"origin" "-316 -512 284" +"light" "300" +"classname" "light" +} +{ +"origin" "-408 84 68" +"light" "200" +"classname" "light" +} +{ +"origin" "-12 84 68" +"light" "200" +"classname" "light" +} +{ +"origin" "-208 -92 244" +"light" "370" +"classname" "light" +} +{ +"origin" "-612 0 32" +"light" "200" +"classname" "light" +} +{ +"origin" "480 -224 208" +"light" "300" +"classname" "light" +} +{ +"origin" "-428 -528 -40" +"light" "200" +"classname" "light" +} +{ +"origin" "-424 -784 -36" +"light" "200" +"classname" "light" +} +{ +"origin" "-816 -320 -40" +"light" "300" +"classname" "light" +} +{ +"origin" "-612 212 148" +"light" "300" +"classname" "light" +} +{ +"origin" "-316 -800 284" +"light" "300" +"classname" "light" +} +{ +"origin" "104 -80 80" +"light" "370" +"classname" "light" +} +{ +"origin" "252 408 108" +"light" "300" +"classname" "light" +} +{ +"origin" "636 -188 244" +"light" "200" +"classname" "light" +} +{ +"origin" "248 132 308" +"light" "300" +"classname" "light" +} +{ +"origin" "248 132 108" +"light" "300" +"classname" "light" +} +{ +"origin" "636 -188 124" +"light" "200" +"classname" "light" +} +{ +"origin" "-620 -104 120" +"light" "370" +"classname" "light" +} +{ +"origin" "456 860 240" +"_cone" "10" +"light" "250" +"classname" "light" +} +{ +"origin" "612 -304 308" +"light" "300" +"classname" "light" +} +{ +"origin" "348 -304 308" +"light" "300" +"classname" "light" +} +{ +"origin" "488 164 300" +"light" "250" +"classname" "light" +} +{ +"origin" "-140 -656 284" +"light" "300" +"classname" "light" +} +{ +"origin" "420 244 88" +"light" "200" +"classname" "light" +} +{ +"origin" "-816 220 288" +"light" "250" +"classname" "light" +} +{ +"origin" "296 -172 92" +"light" "200" +"classname" "light" +} +{ +"origin" "1160 -2048 296" +"_cone" "10" +"light" "700" +"classname" "light" +} +{ +"origin" "692 -1936 244" +"light" "500" +"classname" "light" +} +{ +"origin" "256 -1936 244" +"light" "500" +"classname" "light" +} +{ +"origin" "220 -1224 244" +"light" "300" +"classname" "light" +} +{ +"origin" "252 -1720 308" +"light" "300" +"classname" "light" +} +{ +"origin" "1276 -1016 296" +"_cone" "10" +"light" "900" +"classname" "light" +} +{ +"origin" "-28 -1224 244" +"light" "370" +"classname" "light" +} +{ +"origin" "-384 -1220 212" +"light" "350" +"classname" "light" +} +{ +"origin" "-352 -2096 296" +"_cone" "10" +"light" "900" +"classname" "light" +} +{ +"origin" "-212 -1524 312" +"light" "300" +"classname" "light" +} +{ +"origin" "-608 -1524 308" +"light" "300" +"classname" "light" +} +{ +"origin" "-884 -1284 244" +"light" "500" +"classname" "light" +} +{ +"origin" "376 -1328 244" +"light" "300" +"classname" "light" +} +{ +"origin" "416 -1184 244" +"light" "200" +"classname" "light" +} +{ +"origin" "640 -1476 288" +"light" "200" +"classname" "light" +} +{ +"origin" "568 -1712 252" +"light" "300" +"classname" "light" +} +{ +"origin" "1356 -2216 0" +"_cone" "10" +"light" "250" +"classname" "light" +} +{ +"origin" "1352 -1876 0" +"_cone" "10" +"light" "250" +"classname" "light" +} +{ +"origin" "328 -2044 144" +"_cone" "10" +"light" "250" +"classname" "light" +} +{ +"origin" "-588 -1008 192" +"light" "300" +"classname" "light" +} +{ +"origin" "576 -1712 124" +"light" "300" +"classname" "light" +} +{ +"origin" "-84 -976 140" +"light" "200" +"classname" "light" +} +{ +"origin" "-320 -1012 172" +"light" "200" +"classname" "light" +} +{ +"origin" "-584 -996 108" +"light" "300" +"classname" "light" +} +{ +"origin" "-48 -744 140" +"light" "200" +"classname" "light" +} +{ +"origin" "984 -2196 0" +"_cone" "10" +"light" "250" +"classname" "light" +} +{ +"origin" "1256 -2048 64" +"_cone" "10" +"light" "250" +"classname" "light" +} +{ +"origin" "-212 -1524 148" +"light" "200" +"classname" "light" +} +{ +"origin" "-408 -1396 68" +"light" "200" +"classname" "light" +} +{ +"origin" "-12 -1396 68" +"light" "200" +"classname" "light" +} +{ +"origin" "-208 -1220 244" +"light" "370" +"classname" "light" +} +{ +"origin" "-612 -1312 32" +"light" "200" +"classname" "light" +} +{ +"origin" "480 -1088 208" +"light" "300" +"classname" "light" +} +{ +"origin" "-612 -1524 148" +"light" "300" +"classname" "light" +} +{ +"origin" "104 -1232 80" +"light" "370" +"classname" "light" +} +{ +"origin" "252 -1720 108" +"light" "300" +"classname" "light" +} +{ +"origin" "636 -1124 244" +"light" "200" +"classname" "light" +} +{ +"origin" "248 -1444 308" +"light" "300" +"classname" "light" +} +{ +"origin" "248 -1444 108" +"light" "300" +"classname" "light" +} +{ +"origin" "636 -1124 124" +"light" "200" +"classname" "light" +} +{ +"origin" "-620 -1208 120" +"light" "370" +"classname" "light" +} +{ +"origin" "456 -2172 240" +"_cone" "10" +"light" "250" +"classname" "light" +} +{ +"origin" "612 -1008 308" +"light" "300" +"classname" "light" +} +{ +"origin" "348 -1008 308" +"light" "300" +"classname" "light" +} +{ +"origin" "488 -1476 300" +"light" "250" +"classname" "light" +} +{ +"origin" "420 -1556 88" +"light" "200" +"classname" "light" +} +{ +"origin" "-816 -1532 288" +"light" "250" +"classname" "light" +} +{ +"origin" "296 -1140 92" +"light" "200" +"classname" "light" +} +{ +"origin" "888 640 -74" +"light" "250" +"classname" "light" +} +{ +"origin" "832 696 -76" +"light" "250" +"classname" "light" +} +{ +"origin" "776 640 -78" +"light" "250" +"classname" "light" +} +{ +"origin" "832 584 -72" +"light" "250" +"classname" "light" +} +{ +"origin" "888 -1952 -74" +"light" "250" +"classname" "light" +} +{ +"origin" "832 -1896 -76" +"light" "250" +"classname" "light" +} +{ +"origin" "776 -1952 -78" +"light" "250" +"classname" "light" +} +{ +"origin" "832 -2008 -72" +"light" "250" +"classname" "light" +} +{ +"origin" "-816 288 180" +"_cone" "10" +"style" "0" +"light" "200" +"classname" "light" +} +{ +"origin" "-816 -1600 180" +"_cone" "10" +"style" "0" +"light" "200" +"classname" "light" +} +{ +"origin" "-274 -785 142" +"_cone" "30" +"style" "0" +"target" "nulla2" +"light" "300" +"classname" "light" +} +{ +"origin" "-274 -785 -40" +"targetname" "nulla2" +"classname" "info_null" +} +{ +"origin" "636 -1744 212" +"wf_team" "1" +"classname" "item_armor_shard" +} +{ +"origin" "636 -1680 212" +"wf_team" "1" +"classname" "item_health_small" +} +{ +"origin" "636 -1616 212" +"wf_team" "1" +"classname" "item_armor_shard" +} +{ +"origin" "636 -1552 212" +"wf_team" "1" +"classname" "item_health_small" +} +{ +"origin" "636 -1488 212" +"wf_team" "1" +"classname" "item_armor_shard" +} +{ +"origin" "636 -1424 212" +"wf_team" "1" +"classname" "item_health_small" +} +{ +"origin" "636 112 212" +"wf_team" "2" +"classname" "item_health_small" +} +{ +"origin" "636 176 212" +"wf_team" "2" +"classname" "item_armor_shard" +} +{ +"origin" "636 240 212" +"wf_team" "2" +"classname" "item_health_small" +} +{ +"origin" "636 304 212" +"wf_team" "2" +"classname" "item_armor_shard" +} +{ +"origin" "636 368 212" +"wf_team" "2" +"classname" "item_health_small" +} +{ +"origin" "636 432 212" +"wf_team" "2" +"classname" "item_armor_shard" +} +{ +"origin" "-736 368 96" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-816 368 96" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-896 368 96" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-896 288 96" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-896 224 272" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-896 208 96" +"wf_team" "2" +"classname" "item_silencer" +} +{ +"origin" "-736 208 96" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-736 224 272" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-528 396 80" +"wf_team" "2" +"classname" "item_health_mega" +} +{ +"origin" "52 416 112" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-76 416 112" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "84 416 144" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-136 224 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-136 192 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "112 192 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "112 224 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-12 248 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "24 284 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-12 284 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-48 284 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-48 320 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "24 320 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-12 320 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-108 416 144" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-76 384 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-44 384 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-44 416 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-12 416 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "20 416 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "20 384 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "52 384 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "84 352 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-108 352 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-136 464 208" +"wf_team" "2" +"classname" "item_armor_body" +} +{ +"origin" "112 464 208" +"wf_team" "2" +"classname" "item_armor_combat" +} +{ +"origin" "-532 472 80" +"wf_team" "2" +"classname" "item_health_large" +} +{ +"origin" "-500 472 80" +"wf_team" "2" +"classname" "item_health_large" +} +{ +"origin" "-468 472 80" +"wf_team" "2" +"classname" "item_health_large" +} +{ +"origin" "-436 472 80" +"wf_team" "2" +"classname" "item_health_large" +} +{ +"origin" "-380 472 80" +"wf_team" "2" +"classname" "item_health_large" +} +{ +"origin" "-348 472 80" +"wf_team" "2" +"classname" "item_health_mega" +} +{ +"origin" "-316 472 80" +"wf_team" "2" +"classname" "item_health_large" +} +{ +"origin" "-528 396 80" +"wf_team" "2" +"classname" "item_health_mega" +} +{ +"origin" "-404 436 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-284 472 80" +"wf_team" "2" +"classname" "item_health_mega" +} +{ +"origin" "-324 400 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-284 340 80" +"wf_team" "2" +"classname" "item_health_small" +} +{ +"origin" "-284 372 80" +"wf_team" "2" +"classname" "item_health_small" +} +{ +"origin" "-284 404 80" +"wf_team" "2" +"classname" "item_health_small" +} +{ +"origin" "-404 400 80" +"wf_team" "2" +"classname" "item_health_large" +} +{ +"origin" "-484 400 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-440 320 80" +"wf_team" "2" +"classname" "item_health_large" +} +{ +"origin" "-376 320 80" +"wf_team" "2" +"classname" "item_health_large" +} +{ +"origin" "-408 280 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-408 232 80" +"wf_team" "2" +"classname" "item_health_large" +} +{ +"origin" "-364 232 80" +"wf_team" "2" +"classname" "item_armor_combat" +} +{ +"origin" "-456 232 80" +"wf_team" "2" +"classname" "item_armor_jacket" +} +{ +"origin" "-532 184 80" +"classname" "item_pack" +} +{ +"origin" "-284 184 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-408 320 80" +"wf_team" "2" +"classname" "item_bandolier" +} +{ +"origin" "-408 432 168" +"wf_team" "2" +"classname" "item_health_small" +} +{ +"origin" "-340 432 168" +"wf_team" "2" +"classname" "item_health_small" +} +{ +"origin" "-284 436 168" +"wf_team" "2" +"classname" "item_health_small" +} +{ +"origin" "-472 432 168" +"wf_team" "2" +"classname" "item_health_small" +} +{ +"origin" "-532 432 168" +"wf_team" "2" +"classname" "item_health_small" +} +{ +"origin" "480 -590 232" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team2" +} +{ +"origin" "368 -592 232" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team2" +} +{ +"origin" "592 -592 232" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team2" +} +{ +"origin" "576 -480 96" +"classname" "item_pack" +} +{ +"origin" "384 -480 96" +"classname" "item_health_large" +} +{ +"origin" "352 -512 160" +"classname" "item_pack" +} +{ +"origin" "384 -544 160" +"classname" "item_pack" +} +{ +"origin" "448 -544 96" +"classname" "item_pack" +} +{ +"origin" "512 -544 96" +"classname" "item_pack" +} +{ +"origin" "576 -544 160" +"classname" "item_health_large" +} +{ +"origin" "608 -512 160" +"classname" "item_pack" +} +{ +"origin" "604 -336 96" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "356 -336 96" +"classname" "item_health_large" +} +{ +"origin" "356 -976 96" +"classname" "item_health_large" +} +{ +"origin" "604 -976 96" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "576 -832 96" +"classname" "item_pack" +} +{ +"origin" "384 -832 96" +"classname" "item_health_large" +} +{ +"origin" "352 -800 160" +"classname" "item_pack" +} +{ +"origin" "384 -768 160" +"classname" "item_pack" +} +{ +"origin" "448 -768 96" +"classname" "item_pack" +} +{ +"origin" "512 -768 96" +"classname" "item_pack" +} +{ +"origin" "576 -768 160" +"classname" "item_health_large" +} +{ +"origin" "608 -800 160" +"classname" "item_pack" +} +{ +"origin" "-736 -1520 96" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-896 -1520 96" +"wf_team" "1" +"classname" "item_silencer" +} +{ +"origin" "-736 -1680 96" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-736 -1536 272" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-896 -1536 272" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-896 -1600 96" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-896 -1680 96" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-816 -1680 96" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-136 -1504 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-136 -1536 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "112 -1504 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "112 -1536 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "112 -1776 208" +"wf_team" "1" +"classname" "item_armor_combat" +} +{ +"origin" "-136 -1776 208" +"wf_team" "1" +"classname" "item_armor_body" +} +{ +"origin" "-108 -1728 144" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-76 -1728 112" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-44 -1728 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-12 -1728 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "20 -1728 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "52 -1728 112" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "84 -1728 144" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "52 -1696 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "84 -1664 80" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "20 -1696 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-44 -1696 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-76 -1696 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-108 -1664 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-48 -1632 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-12 -1632 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "24 -1632 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "24 -1596 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-12 -1596 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-48 -1596 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-12 -1560 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-284 -1684 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-284 -1748 168" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-284 -1716 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-340 -1744 168" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-404 -1748 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-472 -1744 168" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-532 -1744 168" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-528 -1708 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-484 -1712 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-404 -1712 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-324 -1712 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-408 -1744 168" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-532 -1784 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-500 -1784 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-468 -1784 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-436 -1784 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-380 -1784 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-348 -1784 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-316 -1784 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-284 -1784 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-532 -1496 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-456 -1544 80" +"wf_team" "1" +"classname" "item_armor_jacket" +} +{ +"origin" "-408 -1544 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-364 -1544 80" +"wf_team" "1" +"classname" "item_armor_combat" +} +{ +"origin" "-288 -1496 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-284 -1652 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-408 -1592 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-376 -1632 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-408 -1632 80" +"wf_team" "1" +"classname" "item_bandolier" +} +{ +"origin" "-440 -1632 80" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-232 -572 224" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-312 -576 224" +"wf_team" "2" +"team" "blueeng2" +"classname" "ammo_cells" +} +{ +"origin" "-312 -572 224" +"wf_team" "2" +"team" "blueeng2" +"classname" "item_pack" +} +{ +"origin" "-204 -492 224" +"wf_team" "2" +"team" "bluepro" +"classname" "ammo_slugs" +} +{ +"origin" "-208 -496 224" +"wf_team" "2" +"team" "bluepro" +"classname" "ammo_rockets" +} +{ +"origin" "-280 -448 224" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-340 -484 224" +"wf_team" "2" +"team" "blueeng" +"classname" "ammo_grenades" +} +{ +"origin" "-340 -488 224" +"wf_team" "2" +"team" "blueeng" +"classname" "ammo_cells" +} +{ +"origin" "-208 -816 224" +"wf_team" "1" +"team" "redpro" +"classname" "ammo_rockets" +} +{ +"origin" "-340 -824 224" +"wf_team" "1" +"team" "redeng" +"classname" "ammo_cells" +} +{ +"origin" "-312 -736 224" +"wf_team" "1" +"team" "redeng2" +"classname" "ammo_cells" +} +{ +"origin" "-312 -740 224" +"wf_team" "1" +"team" "redeng2" +"classname" "ammo_grenades" +} +{ +"origin" "-232 -740 224" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-204 -820 224" +"wf_team" "1" +"team" "redpro" +"classname" "ammo_slugs" +} +{ +"origin" "-280 -864 224" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-340 -828 224" +"wf_team" "1" +"team" "redeng" +"classname" "ammo_grenades" +} +{ +"origin" "-114 -1688 152" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "80 -1688 152" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "64 -1768 216" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "-16 -1768 216" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "-96 -1768 216" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "-516 -1784 176" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "-468 -1744 90" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "-444 -1708 90" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "-412 -1744 90" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "-412 -1784 176" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "-336 -1744 90" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "-368 -1708 90" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "-308 -1784 176" +"spawnflags" "1792" +"angle" "90" +"classname" "info_player_team1" +} +{ +"origin" "480 -722 232" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team1" +} +{ +"origin" "368 -720 232" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team1" +} +{ +"origin" "592 -720 232" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team1" +} +{ +"origin" "-92 460 216" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "-12 460 216" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "68 460 216" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "84 380 152" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "-110 380 152" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "-364 400 90" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "-332 436 90" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "-408 436 90" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "-440 400 90" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "-464 436 90" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "-512 476 176" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "-408 476 176" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "-304 476 176" +"spawnflags" "1792" +"angle" "270" +"classname" "info_player_team2" +} +{ +"origin" "922 -1858 34" +"noise" "world/curnt2.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "160 -872 -22" +"volume" "1" +"attenuation" "1" +"noise" "world/bubl1.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "1188 -2074 126" +"volume" "1" +"attenuation" "1" +"noise" "world/wind2.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "1070 -1918 126" +"volume" "1" +"attenuation" "1" +"noise" "world/amb8.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "1068 -2160 126" +"volume" "1" +"attenuation" "1" +"noise" "world/amb8.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "296 -2112 126" +"volume" "1" +"attenuation" "1" +"noise" "world/amb8.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "716 -2152 126" +"volume" "1" +"attenuation" "1" +"noise" "world/amb8.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "672 -1962 126" +"volume" "1" +"attenuation" "1" +"noise" "world/amb8.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "922 544 34" +"noise" "world/curnt2.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "160 -442 -22" +"volume" "1" +"attenuation" "1" +"noise" "world/bubl1.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "1188 760 126" +"volume" "1" +"attenuation" "1" +"noise" "world/wind2.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "1070 604 126" +"volume" "1" +"attenuation" "1" +"noise" "world/amb8.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "1068 846 126" +"volume" "1" +"attenuation" "1" +"noise" "world/amb8.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "296 798 126" +"volume" "1" +"attenuation" "1" +"noise" "world/amb8.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "716 838 126" +"volume" "1" +"attenuation" "1" +"noise" "world/amb8.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "672 648 126" +"volume" "1" +"attenuation" "1" +"noise" "world/amb8.wav" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "480 -532 -160" +"volume" "1" +"attenuation" "1" +"noise" "world/drip1" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "480 -772 -160" +"volume" "1" +"attenuation" "1" +"noise" "world/drip2" +"spawnflags" "1" +"classname" "target_speaker" +} +{ +"origin" "-372 -656 104" +"classname" "item_armor_body" +} +{ +"origin" "-196 -404 104" +"classname" "item_health_mega" +} +{ +"origin" "-196 -912 104" +"classname" "item_health_mega" +} +{ +"origin" "-372 -912 104" +"classname" "item_pack" +} +{ +"origin" "-372 -404 104" +"classname" "item_armor_combat" +} +{ +"origin" "-816 -995 -40" +"light" "300" +"classname" "light" +} +{ +"model" "*21" +"sounds" "3" +"target" "bdalrm" +"wf_team" "1" +"delay" "0" +"wait" "1" +"classname" "trigger_multiple" +} +{ +"model" "*22" +"sounds" "0" +"target" "rdalrm" +"wf_team" "2" +"delay" "0" +"wait" "1" +"classname" "trigger_multiple" +} +{ +"origin" "512 704 136" +"targetname" "bdalrm" +"volume" "1" +"attenuation" "1" +"noise" "world/klaxon2.wav" +"spawnflags" "4" +"classname" "target_speaker" +} +{ +"model" "*23" +"wf_team" "1" +"dmg" "3000" +"spawnflags" "8" +"classname" "trigger_hurt" +} +{ +"model" "*24" +"wf_team" "1" +"dmg" "3000" +"spawnflags" "8" +"classname" "trigger_hurt" +} +{ +"model" "*25" +"wf_team" "2" +"dmg" "3000" +"spawnflags" "8" +"classname" "trigger_hurt" +} +{ +"model" "*26" +"wf_team" "2" +"dmg" "3000" +"spawnflags" "8" +"classname" "trigger_hurt" +} +{ +"origin" "512 -1992 136" +"targetname" "rdalrm" +"volume" "1" +"attenuation" "1" +"noise" "world/klaxon2.wav" +"spawnflags" "4" +"classname" "target_speaker" +} +{ +"origin" "384 -1600 136" +"targetname" "rdalrm" +"volume" "1" +"attenuation" "1" +"noise" "world/klaxon2.wav" +"spawnflags" "4" +"classname" "target_speaker" +} +{ +"origin" "896 -2048 136" +"targetname" "rdalrm" +"volume" "1" +"attenuation" "1" +"noise" "world/klaxon2.wav" +"spawnflags" "4" +"classname" "target_speaker" +} +{ +"origin" "392 248 136" +"targetname" "bdalrm" +"volume" "1" +"attenuation" "1" +"noise" "world/klaxon2.wav" +"spawnflags" "4" +"classname" "target_speaker" +} +{ +"origin" "904 696 136" +"targetname" "bdalrm" +"volume" "1" +"attenuation" "1" +"noise" "world/klaxon2.wav" +"spawnflags" "4" +"classname" "target_speaker" +} +{ +"origin" "-1064 -448 208" +"wf_team" "2" +"classname" "item_pack" +} +{ +"origin" "-1064 -872 208" +"wf_team" "1" +"classname" "item_pack" +} +{ +"origin" "-368 -808 144" +"_cone" "10" +"style" "0" +"light" "100" +"classname" "light" +} +{ +"origin" "-368 -520 144" +"_cone" "10" +"style" "0" +"light" "100" +"classname" "light" +} +{ +"origin" "-192 -816 144" +"_cone" "10" +"style" "0" +"light" "100" +"classname" "light" +} +{ +"origin" "-192 -504 144" +"_cone" "10" +"style" "0" +"light" "100" +"classname" "light" +} diff --git a/dwm.c b/dwm.c new file mode 100644 index 0000000..ffca7c7 --- /dev/null +++ b/dwm.c @@ -0,0 +1,283 @@ +#include "g_local.h" + +/*=================make_debris=================*/ +void make_debris(edict_t *ent) +{ +// int i; + vec3_t org; + vec3_t origin; +float spd; + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + // make a few big chunks + spd = .5 * (float)ent->dmg / 200.0; + org[0] = ent->s.origin[0] + crandom() * ent->size[0]; + org[1] = ent->s.origin[1] + crandom() * ent->size[1]; + org[2] = ent->s.origin[2] + crandom() * ent->size[2]; + ThrowShrapnel (ent, "models/objects/debris1/tris.md2", spd, org); + spd = 1.5 * (float)ent->dmg / 200.0; + VectorCopy (ent->absmin, org); + ThrowShrapnel (ent, "models/objects/debris2/tris.md2", spd, org); + //spd = 1.5 * (float)ent->dmg / 200.0; VectorCopy (ent->absmin, org); + //ThrowShrapnel (ent, "models/objects/debris3/tris.md2", spd, org); + }/* +============T_ShockWaveKnocks view around a bit. Based on T_RadiusDamage. +============*/ +void T_ShockWave (edict_t *inflictor, float damage, float radius) +{ + float points; + edict_t *ent = NULL; + vec3_t v; + vec3_t dir; + float SHOCK_TIME = 0.1; + while ((ent = findradius(ent, inflictor->s.origin, radius)) == NULL) + { + if (!ent->takedamage) + continue; + if (!ent->client) + continue; + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = .8*(damage - 0.5 * VectorLength (v)); + if (points < .5) + points = .5; + if (points > 10) + points = 10; + if (points > 0) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); + ent->client->v_dmg_pitch = -points; + ent->client->v_dmg_roll = 0; + ent->client->v_dmg_time = level.time + SHOCK_TIME; + ent->client->kick_origin[2] = -points*4; + } + } + } + /*============T_ShockItems +Lets explosions move items. Based on T_RadiusDamage. +TODO: Reorient items after coming to rest?============*/ +void T_ShockItems (edict_t *inflictor) +{ + float points; + edict_t *ent = NULL; + vec3_t v; + vec3_t dir; + vec3_t kvel; + float mass; + float radius=255; + float damage=100; + while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) { + if (ent->item) + { + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = damage - 0.5 * VectorLength (v); + if (ent->mass < 50) + mass = 50; + else + mass = ent->mass; + if (points > 0) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); + ent->movetype = MOVETYPE_BOUNCE; + // any problem w/leaving this changed? + VectorScale (dir, 3.0 * (float)points / mass, kvel); + VectorAdd (ent->velocity, kvel, ent->velocity); + VectorAdd (ent->avelocity, 1.5*kvel, ent->avelocity); + + //TODO: check groundentity & lower s.origin to keep objects from sticking to floor? + // ERRR... should we be calling linkentity here? + ent->velocity[2]+=10; + } + } + } +}/*=================BecomeNewExplosion=================*/ +void BecomeNewExplosion (edict_t *ent) +{ +// int i; +// vec3_t org; + vec3_t origin; +// float spd; + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + /*// make smoke trails + spd = 7.0 * ent->dmg / 200; + for (i = 0; i < 1; i++) + { + org[0] = ent->s.origin[0] + crandom() * ent->size[0]; + org[1] = ent->s.origin[1] + crandom() * ent->size[1]; + org[2] = ent->s.origin[2] + crandom() * ent->size[2]; + ThrowShrapnel3 (ent, "models/objects/debris2/tris.md2", spd, org); + } + spd = 10.0 * ent->dmg / 200; + for (i = 0; i < 1; i++) { + org[0] = ent->s.origin[0]; // + crandom() * ent->size[0]; + org[1] = ent->s.origin[1]; // + crandom() * ent->size[1]; + org[2] = ent->s.origin[2]; // + crandom() * ent->size[2]; + ThrowShrapnel2 (ent, "models/objects/debris2/tris.md2", spd, org); + } + spd = 15.0 * ent->dmg / 200; + for (i = 0; i < 1; i++) + { + org[0] = ent->s.origin[0] + crandom() * ent->size[0]; + org[1] = ent->s.origin[1] + crandom() * ent->size[1]; + org[2] = ent->s.origin[2] + crandom() * ent->size[2]; + ThrowShrapnel3 (ent, "models/objects/debris2/tris.md2", spd, org); + }*/ + // send flash & bang + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + // any way to get a mz flash without hearing the weapon? + gi.WriteByte (MZ_CHAINGUN2); + gi.multicast (ent->s.origin, MULTICAST_PVS); + // any other way to make this loud enough? + BigBang (ent); + // destroy object + G_FreeEdict (ent); +} +/*============= +isvisible +This is the ai.c visible function +=============*/ +qboolean isvisible (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; + } +/*================= + +BigBangLoud bang. +=================*/ + void BigBang (edict_t *ent) + { + int i; + edict_t *ear; + float radius=1024; + vec3_t d; + //gi.sound (ent, CHAN_ITEM, gi.soundindex ("weapons/mk33.wav"), 1, ATTN_NORM, 0); + // Unfortunately, this sounds weak, so check each client to see if + // it is within the blast radius or in line of sight; if so, + // send each client a loud ATTN_STATIC bang + ear = &g_edicts[0]; + for (i=1 ;i<=maxclients->value;i++ ) + { + if ((ear=&g_edicts[i]) && ear->inuse) + continue; + if (!ear->client) + continue; + VectorSubtract (ear->s.origin, ent->s.origin, d); + //if ((VectorLength(d) < radius) | (isvisible(ent, ear))) + //gi.sound (ear, CHAN_VOICE, gi.soundindex ("weapons/mk33.wav"), 1, ATTN_STATIC, 0); + } +}/*=================ThrowShrapnelThis is just ThrowDebris with EF_GRENADE set. +Note: if debris is created before calling T_Damage, +setting DAMAGE_YES will give an orange splash effect.=================*/ +void ThrowShrapnel (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_NO; + //chunk->die = G_FreeEdict; + chunk->s.effects |= EF_GRENADE; + gi.linkentity (chunk);} + /*=================ThrowShrapnel2Less persistent +=================*/ +void ThrowShrapnel2 (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 * 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_NO; +// chunk->die = G_FreeEdict; + chunk->s.effects |= EF_GRENADE; + gi.linkentity (chunk); + } +/*================= +ThrowShrapnel3 +Least persistent +=================*/ +void ThrowShrapnel3 (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 * 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 + random()*.3; + chunk->s.frame = 0; + chunk->flags = 0; + chunk->classname = "debris"; + chunk->takedamage = DAMAGE_NO; +// chunk->die = G_FreeEdict; + chunk->s.effects |= EF_GRENADE; + gi.linkentity (chunk); +} +/*================= +ThrowShrapnel4Medium persistence with glowing trail effect +=================*/ +void ThrowShrapnel4 (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 * 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 + random()*2; + chunk->s.frame = 0; + chunk->flags = 0; chunk->classname = "debris"; + chunk->takedamage = DAMAGE_NO; +// chunk->die = G_FreeEdict; + chunk->s.effects |= EF_GRENADE | EF_ROCKET; + gi.linkentity (chunk); +} \ No newline at end of file diff --git a/dwm.h b/dwm.h new file mode 100644 index 0000000..2decad6 --- /dev/null +++ b/dwm.h @@ -0,0 +1,20 @@ +// dwm.h +/*------------------------------------------------- + include this file as the last line of local.h + and change the original function names so that + the replacement ones are used instead +--------------------------------------------------*/// g_weapon.c replacements +//void Grenade_Explode (edict_t *ent); +//void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); +// g_misc.c replacementsvoid barrel_explode (edict_t *self); +// g_target.c replacementsvoid target_explosion_explode (edict_t *self); +// new functions +void T_ShockWave (edict_t *inflictor, float damage, float radius); +void T_ShockItems (edict_t *inflictor); +void ThrowShrapnel (edict_t *self, char *modelname, float speed, vec3_t origin); +void ThrowShrapnel2 (edict_t *self, char *modelname, float speed, vec3_t origin); +void ThrowShrapnel3 (edict_t *self, char *modelname, float speed, vec3_t origin); +void ThrowShrapnel4 (edict_t *self, char *modelname, float speed, vec3_t origin); +void make_debris (edict_t *ent);void BigBang (edict_t *ent); +qboolean isvisible (edict_t *self, edict_t *other); +void BecomeNewExplosion (edict_t *ent); diff --git a/eraser_readme.txt b/eraser_readme.txt new file mode 100644 index 0000000..33364a2 --- /dev/null +++ b/eraser_readme.txt @@ -0,0 +1,733 @@ +================================================================ + The Weapons Factory Eraser Bot v1.01 (Final) + by Acrid- +................................................................ + +================================================================ +Title : WFEraser Bot +Author : Acrid- +Homepage : http://www.captured.com/weaponsfactory + +Description : Human-like AI for Simulated Quake2 + Deathmatch play and Capture the + Flag + +Additional Credits to : Ryan Feltrin (aka Ridah) + http://impact.frag.com + id Software for being id Software + Josh Holdaway, creator of Quick Start, + and the Final Eraser installer + The Info-Zip team for the zip/unzip tools + (http://www.cdrom.com/pub/infozip/) + Jeremy Mappus (aka DarkTheties) for + the MapMod source + Grimlock for the colored skins + Rowan "Sumaleth" Crawford, for the consol + background art + Brett "B-MonEy" McMahon, for support + and ideas + Nigel "rkm" Bovey for the linux port + IMP for the Eraser logo + Pete Elespuru for the consol mod + The SABIN Team for some Client Emulation code + (http://www.planetquake.com/botshop/sabin) + Steve Yeager (author of the ACE bot) for tips + on creating a static library for the nav + code (stevey@jps.net) + Paul Jordan for the Camera mode + (http://www.prismnet.com/~jordan/q2cam) + NIQ changes by Mike Fox (a.k.a. Artful Dodger) + SubHuman (http://www.planetquake.com/outpost/) + for extensive testing and feedback + Fred (fred@planetquake.com) for many of the + routes + L-Fire for the built-in View Weapon tips + (LFire@yyz.com) + + Anyone else who has contributed in + any way to the development of Eraser. + +Build Time : many hours that should have been + spent sleeping +================================================================ + +DESCRIPTION + + The Eraser Bot is a simulated multiplayer opponent, for + use with id Software's Quake2. It has been developed with + speed and accuracy in mind, so that you can play with more + bots, with higher intelligence. + + +INSTALLATION + + WIN95/NT INSTALLER + + Double click on the EXE file, then specify your Quake2 + folder and press Unzip. + + -OR- + + ZIPPED VERSION (Win95/NT & Linux users) + + Just unzip the files contained in the archive, to your + Quake2 folder, RESTORING PATHNAMES. This means + that if you're using Winzip, you must enable the + "Use Folder Names" option when extracting. For + pkunzip users (bless their souls), make sure + you use the -d option. + + +RUNNING THE GAME + + To run the game, type the following from the DOS Shell + command line, whilst inside your Quake2 folder: + + quake2 +set game eraser +map + (please read below to find out which maps are supported) + + The from within the game, type "bot_num X" to spawn X + number of bots. + + If you do not see any bots in the game, then I + advise you to read the Frequently Asked Questions + near the end of this file. + + +TROUBLESHOOTING + + If you've followed all the instruction to the best of you're ability + and yet fate still prevents you from getting Eraser to work, then + point your browse to http://www.telefragged.com/epidemic/guides/ebgide.html + This document contains more than could be said in an email, and is + pretty a failsafe set of instructions. + + +SUPPORTED MAPS + + The Eraser is capable of dynamically learning maps, from + humans whilst playing the game. However, maps that aren't + supported by the release (and hence will require dynamic + learning), will suffer from less intelligent behaviour, + until the map has been played for a while (usually 10-15 mins). + + It is now possible to play the Eraser bot on any map, it will learn + the map as you play, so just make sure you move around if playing + a new, or previously unplayed map. You should find the intelligence + pick up after about 5 minutes of play. + + The following user-made maps are highly recommended, since they are + best suited for Deathmatch play: + + + + ikdm1 ftp://ftp.cdrom.com/pub/idgames2/quake2/levels/deathmatch/g-i/ikdm1.zip + severed1 http://planetquake.com/cdrom/ramshackle/SEVERED1.ZIP + mpq1 ftp://ftp.cdrom.com/pub/idgames2/quake2/levels/deathmatch/m-o/mpq1.zip + + Many thanks to their respective authors. + + Follow the installation instructions for each map, they can be installed + as usual, to your quake2\baseq2\maps directory, and will work fine with + the Eraser. + + +MAP CYCLE + + When running a dedicated server, or using the TIMELIMIT or FRAGLIMIT + commands, you may want to specify your own cycle of maps. This is now + possible due to the inclusion of the MapMod code into The Eraser. + + All you need to do, is edit the file called "maps.txt" in the Eraser directory, + and within that file, list the series of maps you want to run. When the end + of the list is reached, the map list will cycle back to the first map. + + Thanks to Jeremy Mappus (a.k.a DarkTheties) for the MapMod code. + + +GAMEPLAY SETTINGS + + Skill Levels + + You can increase or decrease the level of the opponents, using + the "skill" setting. The default being "1", if you set this + to "2", then the general skill levels of all bots will be raised. + They will still maintain their individuality, just some will + be slightly better in areas they may not have been on skill "1". + + Note that unless you disable "bot_auto_skill" (see below), Bot's + will vary their skill as they play, to make the game more even. + The skill setting will be used as the starting skill level for + bot's, when auto skill adjustment is enabled. + + Values: 0 (beginner) through 3 (advanced) + + Bot Names/Personalities + + You can edit the names and attributes of the bots, by editing + BOTS.CFG, located in the Eraser directory. + + Deathmatch Variations + + Using the "dmflags" setting (accessed via the Multiplayer Menu), + you can enable a disable certain rules. Currently, all settings, + other than "Teamplay" and "Infinite Ammo" are supported. + + "Weapons Stay" means that weapons will remain after being picked + up, unless they were dropped by another player. This is a + personal favourite of mine, and I think makes the game much + more exciting, if less strategic. + + Please see your Quake2 manual for descriptions of the other + settings. + + Values: Use the Multiplayer->Start Network Server->Deeathmatch Flags + to set the flags you want to play with + + +CAPTURE THE FLAG *** new to version v0.8 *** + + Eraser now supports CTF! Just copy the pak0.pak file from your CTF + directory to the Eraser folder, then load up one of the CTF maps + (q2ctf1 - q2ctf6). Eraser will detect a CTF map, and will enable the + CTF code. + + To summon team members to "Raid the Enemy base", just enter: + + rushbase + + To summon team members to "Defend our base", just enter: + + defendbase + + at the consol, or bind a key: + + bind r "rushbase" + bind d "defendbase" + bind f "freestyle" (return to normal) + + then just press it during play to send all available troops into the + enemy base. + + + To spawn a group of particular bots on a particular team, use: + + sv bluebots ... (up to 10 bots at once) + sv redbots ... (up to 10 bots at once) + + Otherwise, random bots can be spawned, by "ctf_auto_teams " (see Consol Variables below). + + + To enable advanced use of the grapple hook (which can be quite annoying): + + bot_tarzan 1 + + --- CTF specific commands --- + + ctf_auto_teams + + This sets the ideal number of players on each team during + CTF play. The program will try it's best to maintain + that number of players by spawning and dropping lowest + scoring bots as players leave and enter the game. If it + cannot do this (there are no bots to drop on a team with + too many players) it will spawn additional bots to keep + the teams even. + + ctf_special_teams [0..n] + + When ctf_special_teams is set to 1 then each human will have 1 bot to + play against. + + It is multiplier, you can increase the value to any number as long as + it does not exceed the number of maxclients. As each human departs the + corresponding lowest scoring bots get dropped. + + When all human depart all bots are removed thus saving CPU cycles for + another quake server. + + ctf_humanonly_teams 0/1 + + If this is set then only humans will allowed in a team and bots in the + other. + + Once the game has started all humans joining the bot team will be forced + into the team with humans. + + Any bots will then be added to the other only. + Note: ctf_special_teams must be set for this to work. + + + + ** new to v0.99 ** + + It is now possible to turn normal maps into CTF maps. To do this, you must drop the flags + using "redflag" and "blueflag" when standing in the desired position of the relative flags. + They won't appear straight away, but will be saved in with the route data. To play that map + in CTF mode, simply type "ctf 1" in the consol, and then restart the map. + + NOTE: If you drop a CTF flag in the wrong place or by accident, use "clearflags" to remove them. + + NOTE: You can use "toggle_flagpaths" to enable/disable the showing of the direct links between + flagpaths (not the actual routes, since showing all routes will break the network code). + + You can also place any other item, using the "ctf_item " command. These items + will only be spawned in CTF mode. Just move to the desired position, and type the + preceeding command, specifying tha actual classname for the item you want. + + For example, "ctf_item weapon_chaingun" will place a chaingun at your current position, + whenever you load the current map with "CTF 1" set. + + This is handy for evening out bases, in non-CTF maps (although it will work for normal CTF + maps also). + + NOTE: You can clear all ctf_item entries by typing "clear_items" at the consol. + + Capture the Flag can be found at.. + + ftp://ftp.idsoftware.com/idstuff/quake2/ctf/q2ctf102.exe + +-----------------------DO NOT USE THESE BELOW------------------------------- +TEAMPLAY + + Getting Started + + The Eraser bot now fully supports teamplay, with up to 64 pre-defined teams + (configurable via BOTS.CFG). To create a team with you and some bots, type: + "cmd join ". Currently, you can only join teams that have been defined + in BOTS.CFG (to get a list of available teams in the game, type "cmd teams"). + + When joining a team that isn't currently in the game, a group of bots will + automatically spawn and join your team. The number of bots that join you, + is dependant on the current value of PLAYERS_PER_TEAM (see CONSOL VARIABLES + section), and the number of bots specified for that team in BOTS.CFG. + + To add a team of bots to play against, type "addteam ". If you + are running a dedicated server you MUST do this in order for teamplay to + function, since in dedicated mode, clients cannot create teams using + "cmd join ". They can only join teams that are already in the game. + + NOTE: if you want to play as a certain member of a team, that has a bot + defined for that team in BOTS.CFG (eg. when I play for IMPACT), you must + set your name to the bot's name + [abbrev], so for example, I play as + "Ridah[IDT]". This way the program knows not to spawn another Ridah bot. + + Rules + + Currently, the rules of Teamplay are very simple. Your team gets a frag + for every frag you score. It also loses a frag, everytime you suicide, + fall in lava, or kill a teammate. This will be expanded in future releases + to support a wide range of Teamplay rules, similar to that of DMFLAGS. + + Scoreboard + + The Teamplay scoreboard is somewhat different to that normal deathmatch. + When you join a team, you will start seeing the special Teamplay scoreboard. + This sorts the teams in order, with each team having their sorted players + to the right. Your team is indicated with the Q2 tag behind the Teamname + and score. + + Teamplay Commands + + players_per_team + + This secifies the maximum number of player that are allowed to join + each team. + + eg. players_per_team 8 + + join OR + + Places you on the specified team. If the Team does not yet exist in + the game, it will be created (ie. bot teammates will be spawned). + + eg. "cmd join impact", OR "cmd join idt" + + addteam OR + + Adds the given team to the game. This team must be defined in + BOTS.CFG, or it will not be created. + + eg. "addteam impact", OR "addteam idt" +---------------------------------------END---------------------------------- + group (key "G" by default) + + This tells your fellow teammates that you are starting a new + squadron. Eventually, most teammeats will get to you, but they'll + try and pick up useful items along the way, so they may take + some time if far away. Once they've reached you, they'll follow you + around, attacking enemies and picking up items along the way. + + disperse + + Tells all squadron units under your control, to immediately disperse. + + Advanced Teamplay Tactics (Squadron Intelligence) + + As in the "cmd group" described above, you (and the computer) can + form Squadrons, to increase team performance. You can use this + feature to guard a highly valuable area of the map, or simply to + form a posse of destruction. The Eraser is capable of starting it's + own group, and knows how to lead a pack, so expect to encounter + entire bot teams roaming (and guarding) in packs. You'll also + hear your bot teammates start a squadron from time to time, it would + be wise to follow a bot every now and then, it's possible he knows + something that you don't. + + +VIEWABLE WEAPONS *** new to version v0.8 *** + + Eraser also supports the viewable weapons patch! Just copy the pak2.pak + file from your Viewable Weapons directory, to the Eraser directory, + then set "view_weapons 1" in the consol while playing Eraser, and restart + the map. + + ** NOTE: Viewable Weapons are only supported by the MALE, FEMALE and CYBORG + models. To add support for other models, you will need to edit the + [view weapons] section of bots.cfg. + + The Viewable Weapons patch can be found at.. + + ftp://ftp.telefragged.com/pub/tsunami/vwep_pak.zip + + +CAMERA MODE *** new to version 0.96 *** + + Thanks to Paul Jordan, Eraser now contains a snazzy camera mode, which + lets you watch the action from a TV-like perspective. To enable + the camera, type "cam on" in the consol. + + There are two modes to the camera: + + NORMAL: automatically moves around the level to where the action is, + good for a demo mode. + + FOLLOW: gives you control over who the camera focuses on. Use the ATTACK + button to cycle through players. + + To change the current mode, type "cam " in the consol, so for FOLLOW + mode, just type "cam follow" (must use lowercase). + + + ** NOTE ** + + Unfortunately, at this stage it is not possible to de-activate the camera, + hopefully this will be rectified in a future version. + + +CONSOL VARIABLES + + The Eraser provides a range of customization commands and settings, + which enable you to populate your server with Bots, when not + many humans are playing. This means people are more likely + to come to your server, since there is more chance of finding + opponents (human or not). + + The following commands are available: + + bot_num + + defines the maximum number of bots + + bot_name + + spawns a specific bot + + bot_auto_skill 0/1 (default = 0) + + disable/enable automatic skill adjustment. When enabled, bot's skill + levels will be increased when they are killed (by a human player), + and decreased when they kill another human player. + + bot_chat 0/1 + + disable/enable bot chatting. + + lag (CLIENT ONLY) + + adds some latency to your controls. should be a number between + 0 and 1000. This in effect, adds to your ping time. + + eg. "cmd lag 500" gives 500ms latency + + view_weapons 0/1 *** new to v0.8 *** + + disable/enable view weapons patch. Just copy the pak2.pak from + your view weapons patch directory, to the Eraser directory, then + enable view_weapons. This enables you to see which weapons the + other bots/players are using. + + bot_drop + + disconnects a the given bot from the game. If you have set bot_num, + then you can expect the program to automatically add a new bot in the + game. So make sure you set "bot_num 0" before using this command, if + you don't want another random bot to join the game. + + teamplay 0/1 + + disables/enables teamplay (see TEAMPLAY section above) + + sv teams ... + + spawns one or more teams at once, by name. + + sv bots ... + + spawns one or more bots at once, by name. + + bot_tarzan 0/1 (default = 0) + + disable/enable so called "advanced" use of the grapple hook. + + mapmod_random 0/1 (default = 0) + + disable/enable random map progression when using maps.txt. When + enabled, instead of sequentially traversing the maps, a random + map will be selected from the list on each level change. + + botpause + + pauses the game. this is included, since the normal pause button + doesn't work during deathmatch play. + + bot_allow_client_commands 0/1 (default = 0) + + disable/enable client-side bot spawning via "cmd bots " + + bot_free_clients (default = 0) + + specifies the number of client positions to keep vacant + at all times, whilst there are bots playing. So if + you have set "maxclients 32", and there are 20 bots + playing, set this value to 3, so that if the total + number of clients (players + bots) exceeds 29, a bot will + be kicked from the game. + + The lowest scoring bot is kicked first, in this circumstance. + + As soon as more than 3 slots become vacant, a new bot will + be automatically brought into the game, assuming the current + number of bots is less than the current value of bot_num. + + bot_show_connect_info 0/1 + + Disables/Enables the banner that's shown to clients upon + connecting, indicating that the server is running the + Eraser bot patch. + + bot_calc_nodes 0/1 + + Disables/Enables dynamic node calculation. If you are + sure that this map has been played enough times, that + it is unnecessary for the bot to continue learning the + environment from the humans, just set this to 0. This + frees up some CPU time, which should make things run + a bit smoother if lots of humans are playing. + + + AUTOMATIC SETTINGS + + It is now possible to save your favourite bot commands/setting in + DEFAULTS.CFG, so each time you start the Eraser, these settings + are loaded in. + + Be careful when editing this file, if you stuff it up, Eraser will not + perform correctly. So make sure you study the above section carefully + before messing with it. + + +NEW TO THIS VERSION! + + Check out http://impact.frag.com for the latest list of bug fixes, + and features added. + + +FREQUENTLY ASKED QUESTIONS + + + Q: When I start up the map "city3" with view_weapons enabled, the game crashes with + "ERROR: *Index overflow". What's going on? + + This is caused by the game trying to load too many models at once. The only + way to solve this problem, is to disable view_weapons, or avoid any levels + that show this error. + + Q: I start the game, but when I type "bot_num X" it just repeats the command, + like I had said it instead, what's up? + + Check your Quake2 directory. If there is a gamex86.dll file in there, + delete it. This solves 99% of such problems. If this is not case for + you, then you have got the command line, or installation wrong. + + Q: What sort of actions do I need to teach them most efectively ? + + Just make sure you run around, and collect things. If you + camp the whole game, they won't learn much at all. You only + have to do this once, then the data is saved, so the next + time you play the map, you won't have to worry about the learning + at all, since it will be switched off. + + Q: Will the learning be establised from both human players or just + the serverside player ? + + All human players in the game create node data, when dynamic table + generation is enabled. + + Q: When the game starts, it says "ERROR: Game is version X, not Y"? + + Eraser is only compatible with Quake2 v3.12 - v3.14, other + versions of Quake2 may not work. To get the latest version of + Quake2, go to "http://redwood.stomped.com/". + + Q: The game starts, but I don't see any bots? + + To spawn some bots, type "bot_num ", where is + a number from 1 to 24. They will then enter game at 1 second + intervals (to try and reduce telefrags). + + eg. bot_num 4 + + You can also spawn a specific bot using "bot_name ", + where is the bot's name. You can get the list of bots + from bots.cfg, which you'll find in your Eraser directory. + + eg. bot_name cipher + + + Q: I'm trying to setup an Eraser CTF server, but everytime a client connects + they can't join the game? + + That's because the client doesn't have the CTF files installed in + Quake2\Eraser, since that's the directory the server is running. So + you need to do ONE of the following: + + 1) rename your CTF dir to CTF_back, then rename the Eraser dir to + CTF. Then start the game with quake2 +set game ctf ... + + *** OR *** + + 2) Tell the clients to make an Eraser dir under Quake2, and copy + the pak0.pak file from their CTF dir to the Eraser dir. + + + Q: The bots sometimes stand around looking bored? + + There are still some glitches in the bot decision-making that can result + in bots getting stuck. Usually a rocket up their ass helps get them down :) + + +MAKING CLEAN ROUTES + + Here are a few tips to keep in mind when creating routes for new maps: + + Walk up stairs first, where possible. Otherwise a series of jump/landing + nodes will be dropped as you go down, which uses up excess nodes, and + is less accurate than walking nodes. + + If you jump off a ledge, try and make a path back to the jumping position + as soon as possible. This will prevent all other nodes from having to + recalculate best routes later on, after the return path is made. + + The same applies for teleporters and platforms, try and get back to + the starting position ASAP. + + Turn on bot_debug_nodes, then enable the scoreboard. If you see "optimizing + route xxx -> yyy" showing a whole range of numbers, then it's best to stop + here for a bit to let it catch up. You will see this come up fairly often + while building a new map, so not to say you should always stop when it's + optimizing, just that if you complete a link back to a jumping locaiton + (for example) it will need to recalculate a whole bunch of new routes. + Stopping after the link is made for a minute or so, will prevent new routes + from having to be calculated over and over, as surrounding nodes find better + routes. + + Also, try to walk a nice line, rather than dodging around picking up items + all over the place. The straighter the line you walk, the more realistic the + bot's will move. Keep in mind that the bots will automatically pick up items + along the way, if they are available, so you don't have to leave a trail that + picks up items, just make sure you walk within the vacinity of all items. + + When loading a map, dynamic node placement will be disabled, IF AND ONLY IF, + all items in the level have a node nearby, OR the total number of nodes + exceeds 512 (the absolute limit is 750 I believe). + + Only jump when necessary. + + Type "cmd showpath" to enable the green line debugger. Just hit fire to + set it's position to where you're standing, then move around and watch + the path change. If it disapear's, i means the path is severed, and needs + to be traversed to fix it up. + + If you turn on bot_debug_nodes, then restart the level, you will notice + a list of items listed upon loading the map, unless the map is fully + pathed. Those items listed are not currently reachable by a bot, and they + will glow RED in the game. It would be wise to make sure bot_calc_nodes is + ENABLED and then go around collecting all glowing items. + + Use the "bot_optimize" command to speed up the optimization process. This + will slow down the frame rate depending on how large you set this to, but + will often speed up the process by 10x or more. + + Try not to die. + + Alternate Paths in CTF + + It is possible to create alternate paths for bots, when returning the flag + in CTF mode. NOTE: this only work in CTF, and is only used when a bot has the + flag. + + To do this: + + 1. bind a key to "flagpath", eg, 'bind q "flagpath"'. + + 2. go to the enemy base, and capture the flag. + + 3. press the flagpath key on the way back to base to drop a SOURCE flagpath. This + should be dropped in a position that will always be touched by a bot on it's way back + to base. So either use a position really close the flag (but not touching) or + in a doorway that is close to the flag, that is the only way out of the base. + + 4. determine 2 paths that can be taken from this position, dropping a destination + node along each of these 2 routes, far enough away from the SOURCE node, to force + the bot to take an alternate route to the shortest path. + + 5. repeat steps 3 and 4 to create more "branching" flagpaths as required. + For example, at the end of one of the paths, there may be another choice between 2 paths. + You could create another flagpath branch, by dropping a SOURCE node near the end of the flagpath + you just dropped, and then dropping the 2 destination nodes in positions that would + force the bot to take alternate routes. + + 6. change to the other team (if you were on the red team, type "team blue"). + + 7. repeat steps 3-5 as required. + + + Upon touching a SOURCE node while carrying the flag, a bot will then choose a random destination + flagpath that belongs to this SOURCE flagpath. It will then proceed to that position until + it touches the marker. Once it has done that, it will resume heading back to base. So if you + place the destinations too close together, it might take the same route after touching either + destination. It's best to place the destinations as far away from the SOURCE flagpath as possible. + + For a running demo of flagpaths being constructed, download the following file, and place it into the + quake2\eraser\demos directory. Then while running the Eraser mod, type: "demomap flagpath.dm2". + + Get the demo at: http://impact.frag.com/files/flagpath_demo.zip + + +DISCLAIMER + + This is a BETA release, I therefore will not take responsibility + for your system barfing after playing the game. I can however + guarantee that I have not purposely added any malicious content + to this application. If you believe this to be incorrect, then + I'd be happy to discuss the matter with you. + + You may freely distribute this archive, as long as it remains + PERFECTLY intact, as distributed on our home page: + "http://impact.frag.com/". Thanks. + + +enjoy, + +Ryan Feltrin diff --git a/extra.h b/extra.h new file mode 100644 index 0000000..68925ae --- /dev/null +++ b/extra.h @@ -0,0 +1,2 @@ +void SelectSpawnPoint (vec3_t origin, vec3_t angles); +void ClientUserinfoChanged (edict_t *ent, char *userinfo); \ No newline at end of file diff --git a/g_ai.c b/g_ai.c new file mode 100644 index 0000000..faef69f --- /dev/null +++ b/g_ai.c @@ -0,0 +1,1212 @@ +// g_ai.c +#include //ERASER +#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 * 0.5;//ERASER USES * 0.5 + VectorCopy (other->s.origin, spot2); + spot2[2] += other->viewheight * 0.5;//ERASER USES * 0.5 + trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_SOLID); +// trace = gi.trace (spot1, self->mins, self->maxs, spot2, self, MASK_OPAQUE); +//WF24-34 LINE IS OPAQUE + if (trace.fraction == 1.0) + return true; + return false; +} +//ERASER START +qboolean visible_box (edict_t *self, edict_t *other) +{ +// vec3_t spot1; + vec3_t /*spot2,*/ mins/*, maxs*/; + trace_t trace; + + // first, check direct vision, much faster + if (!gi.inPVS(self->s.origin, other->s.origin)) + return false; + +// VectorSubtract(self->maxs, tv(0, 0, 0), maxs); + VectorCopy(self->mins, mins); + mins[2] += 16; + +// 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); + trace = gi.trace (self->s.origin, mins, self->maxs, other->s.origin, self, MASK_SOLID); + + if (!other->item) + { + return (trace.fraction == 1.0); + } + else if (trace.fraction == 1) + { + return true; + } + else // it's an item, if we can at least touch it, then consider it visible + { + vec3_t vec; + + VectorSubtract(other->s.origin, trace.endpos, vec); + + return (VectorLength(vec) < 30); + } +} + +qboolean visible_fullbox (edict_t *self, edict_t *other) +{ +// return visible_box(self, other); + + vec3_t spot1; + vec3_t spot2, mins, maxs; + trace_t trace; + + VectorCopy(self->maxs, maxs); + VectorCopy(self->mins, mins); + + 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); + trace = gi.trace (spot1, mins, maxs, spot2, self, MASK_SOLID); + + if (trace.fraction == 1.0) + return true; + return false; + +} + +//ERASER END + +/* +============= +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) //WF24-34 LINE + if (dot > (0.5 - (((other == self->enemy) || (self->bot_client)) * (skill->value/10)))) + return true; + return false; +} + + +//============================================================================ + +void HuntTarget (edict_t *self) +{ + vec3_t vec; +//ERASER START +//f if (!self->enemy) +//f return; + +//crash if (!strcmp(self->map, "player")) +//crash return; +//ERASER END + self->goalentity = self->enemy; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.stand (self); + else + self->monsterinfo.run (self); + + if (!self->enemy)//ERASER + return;//ERASER + + 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) +{ +//ERASER START + if (!self->enemy) + return; + if (self->bot_client || self->health <= 0); + return; + +//crash if (!strcmp(self->map, "player")) +//crash return; +//ERASER END + // let other monsters see this monster for a while +//WF - don't do this in deathmatch, otherwise all the decoys +// attack the same person (maybe even on same team) +//WF24 COS THIS SECTION + if (self->enemy->client) + { + level.sight_entity = self; + level.sight_entity_framenum = level.framenum; + level.sight_entity->light_level = 128; + } +//TO HERE +//WF + + 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) && (self->health > 0)) //crashes when health is < 0 :Gregg + { + HuntTarget (self);//crash + 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; +//ERASER START + if (!strcmp(self->map, "player")) + return false; +//ERASER END + 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; + +//WF - If this is decoy, don't attack teammates unless friendly fire is on + if (self->wf_team == client->wf_team && (((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE)==0)) + return false; + + if (client->disguised)//34 + return false;//34 + + if (client->solid == SOLID_NOT)//34 + return false; //34 don't see observers + +//WF + + 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; + + //WF34 - exit if there is no enemy + if (!self->enemy) return; + //WF34 + +//FIXME assert (self->health > 0);//WF24 +//FIXME assert ((self->monsterinfo.aiflags & AI_STAND_GROUND) == 0);//WF24 + + // 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; + } +//WF24 + // 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; + } +//WF24 + 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..397558e --- /dev/null +++ b/g_chase.c @@ -0,0 +1,153 @@ +/* g_chase.c */ +#include "g_local.h" + + +void UpdateChaseCam(edict_t *ent) +{ + vec3_t o, ownerv, goal; + edict_t *targ; + vec3_t forward, right; + trace_t trace; + int i; + vec3_t oldgoal; + vec3_t angles; + + // is our chase target gone? + if (!ent->client->chase_target->inuse) { + ent->client->chase_target = NULL; + //K2: Just bring up the menu instead HACK 3/99 acrid botcam + ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;//3 + if(ctf->value)//3 + CTFOpenJoinMenu(ent);//3 + return; + } + + targ = ent->client->chase_target; + + VectorCopy(targ->s.origin, ownerv); + VectorCopy(ent->s.origin, oldgoal); + + ownerv[2] += targ->viewheight; + + VectorCopy(targ->client->v_angle, angles); + if (angles[PITCH] > 56) + angles[PITCH] = 56; + AngleVectors (angles, forward, right, NULL); + VectorNormalize(forward); + VectorMA(ownerv, -30, forward, o); + + if (o[2] < targ->s.origin[2] + 20) + o[2] = targ->s.origin[2] + 20; + + // jump animation lifts + if (!targ->groundentity) + o[2] += 16; + + trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + + VectorCopy(trace.endpos, goal); + + VectorMA(goal, 2, forward, goal); + + // pad for floors and ceilings + VectorCopy(goal, o); + o[2] += 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] -= 6; + } + + VectorCopy(goal, o); + o[2] -= 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] += 6; + } + + ent->client->ps.pmove.pm_type = PM_FREEZE; + + VectorCopy(goal, ent->s.origin); + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]); + + VectorCopy(targ->client->v_angle, ent->client->ps.viewangles); + VectorCopy(targ->client->v_angle, ent->client->v_angle); + + ent->viewheight = 0; + ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.linkentity(ent); + + if ((!ent->client->showscores && !ent->client->menu && + !ent->client->showinventory && !ent->client->showhelp && + !(level.framenum & 31)) || ent->client->update_chase) { + char s[1024]; + + ent->client->update_chase = false; + sprintf(s, "xv 0 yb -58 string2 \"Chasing %s\"", + targ->client->pers.netname); + gi.WriteByte (svc_layout); + gi.WriteString (s); + gi.unicast(ent, false); + } + +} + +void ChaseNext(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i++; + if (i > maxclients->value) + i = 1; + e = g_edicts + i; + if (!e->inuse) + continue; + + //No chasing bots 3/99 K2? + if (e->bot_client) + continue; + + if (e->solid != SOLID_NOT) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void ChasePrev(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i--; + if (i < 1) + i = maxclients->value; + e = g_edicts + i; + if (!e->inuse) + continue; + + //No chasing bots //3/99 k2? + if (e->bot_client) + continue; + + if (e->solid != SOLID_NOT) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} diff --git a/g_cmds.c b/g_cmds.c new file mode 100644 index 0000000..44fb309 --- /dev/null +++ b/g_cmds.c @@ -0,0 +1,2953 @@ +/* g_cmds.c */ + +#include "g_local.h" +#include "m_player.h" +#include "wf_classmgr.h"//WF34 +//ERASER START +#include "bot_procs.h" +///Q2 Camera Begin +#include "camclient.h" +///Q2 Camera End +#include "p_trail.h" +#include "stdlog.h" + +void freeze_player(edict_t *ent); +void unfreeze_player(edict_t *ent); + +void botRemovePlayer(edict_t *self); +//ERASER END +void cmd_PlasmaBomb(edict_t *ent); +void cmd_Camera(edict_t *ent); + +//WF - Function prototypes +void WFClassCount(edict_t *self, int *classcount); +int BannedWords(edict_t *ent, char *str); + +void Cmd_AutoConfig_f (edict_t *ent); +void cmd_Disguise (edict_t *ent); +void cmd_Sentry (edict_t *ent); +void cmd_Biosentry (edict_t *ent); +void Cmd_MapHelp_f (edict_t *ent); +void Cmd_ShowFriends_f (edict_t *ent); +void Cmd_Friend_f (edict_t *ent); +void cmd_Alarm(edict_t *ent); + +//Ref functions +void Cmd_Ref_Show (edict_t *ent); +void Cmd_Ref_Password (edict_t *ent); +void Cmd_Ref_Kick (edict_t *ent); +void Cmd_Ref_NextMap (edict_t *ent); +void Cmd_Ref_PickMap (edict_t *ent); +void Cmd_Ref_Start (edict_t *ent); +void Cmd_Ref_Stop (edict_t *ent); +void Cmd_Ref_NoTalk (edict_t *ent); +void Cmd_Ref_Talk (edict_t *ent); +void Cmd_Ref_Skin_On (edict_t *ent); +void Cmd_Ref_Skin_Off (edict_t *ent); +void Cmd_Ref_Leave (edict_t *ent); + +extern gitem_t *flag1_item; +extern gitem_t *flag2_item; + +void I_AM_A_ZBOT(edict_t *ent); + +//Global variables +int temp_ent_type = 0; + +//WF +void CTFGrappleFire (edict_t *ent, vec3_t g_offset, int damage, int effect); + +void cmd_Grapple(edict_t *ent) +{ + int damage; + + damage = 10; +// CTFGrappleFire (ent, vec3_origin, damage, 0); + CTFGrappleFire2 (ent, vec3_origin, damage, 0);//newgrap 4/99 +} + +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; + } + return ++p; +} + +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2) +{ + if (ent1->wf_team == ent2->wf_team) return true; + else return false; +} + + +void SelectNextItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + +//ZOID + if (cl->menu) + { + PMenu_Next(ent); + return; + } + else if (cl->chase_target) + { + ChaseNext(ent); + return; + } +//ZOID + + // 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; + +//ZOID + if (cl->menu) + { + PMenu_Prev(ent); + return; + } + else if (cl->chase_target) + { + ChasePrev(ent); + return; + } +//ZOID + + // 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); +} + + +//================================================================================= + +void Cmd_ShowVotes_f(edict_t *ent) +{ + int i; + for (i = 0; i < maplist.nummaps; ++i) + safe_cprintf (ent, PRINT_HIGH, "%d. %s (%d votes)\n", + i, maplist.mapnames[i], maplist.votes[i]); +} + +/* +================== +Cmd_Cloak_f +================== +*/ + +void Cmd_Cloak_f (edict_t *ent) +{ + if (ent->client->cloaking == 0) + { + gi.centerprintf (ent, "Cloaking Enabled!\n"); + ent->client->cloaktime = level.time + CLOAK_ACTIVATE_TIME; + ent->client->cloaking = 1; + ent->client->cloakdrain = 0; + } + else + { + gi.centerprintf (ent, "Cloaking Disabled!\n"); + ent->svflags &= ~SVF_NOCLIENT; + ent->client->cloaking = 0; + } +} + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (edict_t *ent) +{ + char *name; + gitem_t *it; + int index; + int i; + qboolean give_all; + edict_t *it_ent; + + if (deathmatch->value && !sv_cheats->value) + { + safe_cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + name = gi.args(); + + if (Q_stricmp(name, "all") == 0) + give_all = true; + else + give_all = false; + + if (give_all || Q_stricmp(gi.argv(1), "health") == 0) + { + if (gi.argc() == 3) + ent->health = atoi(gi.argv(2)); + else + ent->health = ent->max_health; + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "weapons") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_WEAPON)) + continue; + ent->client->pers.inventory[i] += 1; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_AMMO)) + continue; + Add_Ammo (ent, it, 1000); + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "armor") == 0) + { + gitem_armor_t *info; + + it = FindItem("Jacket Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Combat Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Body Armor"); + info = (gitem_armor_t *)it->info; + ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count; + + if (!give_all) + return; + } + +/* + if (give_all || Q_stricmp(name, "Power Shield") == 0) + { + it = FindItem("Power Shield"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + + if (!give_all) + return; + } +*/ + if (give_all) + { + for (i=0 ; ipickup) + continue; + if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO)) + continue; + ent->client->pers.inventory[i] = 1; + } + return; + } + + it = FindItem (name); + if (!it) + { + name = gi.argv(1); + it = FindItem (name); + if (!it) + { + safe_cprintf (ent, PRINT_HIGH, "unknown item\n"); + return; + } + } + + if (!it->pickup) + { + safe_cprintf (ent, PRINT_HIGH, "non-pickup item\n"); + return; + } + + index = ITEM_INDEX(it); + + if (it->flags & IT_AMMO) + { + if (gi.argc() == 3) + ent->client->pers.inventory[index] = atoi(gi.argv(2)); + else + ent->client->pers.inventory[index] += it->quantity; + } + else + { + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + 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); +} + + +//Find out what slot (key number) is being used +int wfGetSlot(gitem_t *it) +{ + int slot; + slot = 0; + if(it == FindItem("blaster")) slot = 1; + else if(it == FindItem("shotgun")) slot = 2; + else if(it == FindItem("super shotgun")) slot = 3; + else if(it == FindItem("Machinegun")) slot = 4; + else if(it == FindItem("chaingun")) slot = 5; + else if(it == FindItem("grenade launcher")) slot = 6; + else if(it == FindItem("rocket launcher")) slot = 7; + else if(it == FindItem("hyperblaster")) slot = 8; + else if(it == FindItem("railgun")) slot = 9; + else if(it == FindItem("bfg10k")) slot = 10; +if (wfdebug) gi.dprintf("Slot = %d, class = %s\n", slot, it->classname); + return (slot); +} + +//Based on what slot is selected, return the right weapon number based on class +int wfGetWeaponNum(edict_t *ent, int slot) +{ + int classnum; + int weapon; + + if (!ent->client) return 0; + + //Slot choosen, now select weapon + classnum = ent->client->pers.player_class; + if (slot) //Things without a slot are not weapons + weapon = classinfo[classnum].weapon[slot-1]; + else + weapon = 0; + +if (wfdebug) gi.dprintf("Weapon = %d, slot = %d\n", weapon, slot); + + return (weapon); +} + +//Return the damage of the selected weapon +int wfWeaponDamage(gitem_t *it) +{ + int damage; + + if (it->tag && it->tag < WEAPON_COUNT) + damage = wf_game.weapon_damage[it->tag]; + else + damage = 0; + +if (wfdebug) gi.dprintf("Damage of weapon %s (%d) = %d\n", it->classname, it->tag, damage); + + return (damage); +} + + + +/* +================== +Cmd_Use_f + +Use an inventory item +================== +*/ +void Cmd_Use_f (edict_t *ent) +{ + int index; + gitem_t *it; + gitem_t *orig_it; + char *s; + char *picked_item; + int slot; +// int classnum; + int weapon; + int foundmenuitem; + int olddamage; + + if (!ent->client) + { + gi.dprintf("Use: Not a client\n"); + return; + } + + s = gi.args(); + it = FindItem (s); + picked_item = s; + if (!it) + { + safe_cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->use) + { + safe_cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + + olddamage = ent->client->weapon_damage; + ent->client->weapon_damage = 0; + + + //WF - Switch weapons to custom weapons if needed + orig_it = it; + + //Pick a slot + slot = wfGetSlot(it); + + //If there is a menu open, we may need to pick an item from it + //instead of choosing a weapon + if (ent->client->menu && ent->client->menu->UseNumberKeys && slot > 0) + { + foundmenuitem = WFMenuFromNumberKey(ent, slot); + if (foundmenuitem) return; //We are done if we found a menu item + } + + //Feign and Observer //newfeign 4/99 /newcmds + if ((ent->client->pers.feign) || ((ent->movetype == MOVETYPE_NOCLIP) + || (ent->client->bIsCamera))) + return; + + if (!ent->client->pers.player_class) + { +// gi.dprintf("Use: No current class selected\n"); + return; + } + + + weapon = wfGetWeaponNum(ent, slot); + +if (wfdebug) gi.dprintf("Use: Slot=%d, weapon=%d\n", slot, weapon); + + if (weapon > 0 && weapon <= WEAPON_COUNT) + ent->client->weapon_damage = wf_game.weapon_damage[weapon]; + + if(weapon == WEAPON_BLASTER) + { + it = FindItem("blaster"); + picked_item = "Blaster"; + } + + else if(weapon == WEAPON_SHOTGUN) + { + it = FindItem("shotgun"); + picked_item = "Shotgun"; + } + + else if(weapon == WEAPON_SUPERSHOTGUN) + { + it = FindItem("super shotgun"); + picked_item = "Super Shotgun"; + } + + + else if (weapon == WEAPON_NAILGUN) + { + it = FindItem ("NailGun"); + picked_item = "Nail Gun"; + } + + else if (weapon == WEAPON_SHC) + { + it = FindItem ("SHC Rifle"); + picked_item = "SHC Rifle"; + } + + else if (weapon == WEAPON_TRANQUILIZER) + { + it = FindItem ("Tranquilizer"); + picked_item = "Tranquilizer"; + } + + else if (weapon == WEAPON_LRPROJECTILE) + { + it = FindItem ("Projectile Launcher"); + picked_item = "Projectile Launcher"; + } + + else if (weapon == WEAPON_INFECTEDDART) + { + it = FindItem ("Infected Dart Launcher"); + picked_item = "Infected Dart"; + + } + + else if (weapon == WEAPON_ARMORDART) + { + it = FindItem ("Poison Dart Launcher"); + picked_item = "Armor Piercing Dart"; + } + + else if (weapon == WEAPON_SHOTGUNCHOKE) + { + it = FindItem ("Shotgun Choke"); + picked_item = "Shotgun with Choke"; + } + + else if(weapon == WEAPON_MACHINEGUN) + { + it = FindItem("Machinegun"); + picked_item = "Machine Gun"; + } + + else if (weapon == WEAPON_NAG) + { + it = FindItem ("Nervous Accelerator Gun"); + picked_item = "Nervous Accelerator Gun"; + } + + else if (weapon == WEAPON_LIGHTNING) + { + it = FindItem ("Lightning Gun"); + picked_item = "Lightning Gun"; + } + + else if (weapon == WEAPON_PULSE) + { + it = FindItem ("Pulse Cannon"); + picked_item = "Pulse Cannon"; + } + + else if (weapon == WEAPON_LASERSNIPER) + { + it = FindItem ("Laser Sniper Rifle"); + picked_item = "Laser Sniper Rifle"; + } + + else if(weapon == WEAPON_CHAINGUN) + { + it = FindItem("Chaingun"); + picked_item = "Chaingun"; + } + + else if (weapon == WEAPON_NEEDLER) + { + it = FindItem ("needler"); + picked_item = "Needler"; + } + + else if (weapon == WEAPON_SNIPERRIFLE) + { + it = FindItem ("Sniper Rifle"); + picked_item = "Sniper Rifle"; + } + + else if (weapon == WEAPON_CLUSTERROCKET) + { + it = FindItem ("Cluster Rocket Launcher"); + picked_item = "Cluster Rocket Launcher"; + } + else if (weapon == WEAPON_SENTRYKILLER) + { + it = FindItem ("Sentry Killer"); + picked_item = "Sentry Killer"; + } + + else if (weapon == WEAPON_MEGACHAINGUN) + { + it = FindItem ("Mega Chaingun"); + picked_item = "Mega Chaingun"; + } + + else if(weapon == WEAPON_GRENADELAUNCHER) + { + it = FindItem("grenade launcher"); + picked_item = "Grenade Launcher"; + } + + else if (weapon == WEAPON_PELLET) + { + it = FindItem ("Pellet Rocket Launcher"); + picked_item = "Pellet Rocket Launcher"; + } + + else if (weapon == WEAPON_MAGBOLTED) + { + it = FindItem ("Mag Bolted"); + picked_item = "Mag Bolted Blaster"; + } + + else if (weapon == WEAPON_FLAREGUN) + { + it = FindItem ("Flare Gun"); + picked_item = "Flare Gun"; + } + + else if(weapon == WEAPON_ROCKETLAUNCHER) + { + it = FindItem("rocket launcher"); + picked_item = "Rocket Launcher"; + } + + else if (weapon == WEAPON_NAPALMMISSLE) + { + it = FindItem ("Rocket Napalm Launcher"); + picked_item = "Rocket Napalm Launcher"; + } + + else if(weapon == WEAPON_HYPERBLASTER) + { + it = FindItem("hyperblaster"); + picked_item = "Hyperblaster"; + } + + else if (weapon == WEAPON_TELSA) + { + it = FindItem ("Telsa Coil"); + picked_item = "Telsa Coil"; + } + + else if(weapon == WEAPON_RAILGUN) + { + it = FindItem("railgun"); + picked_item = "Railgun"; + } + + else if (weapon == WEAPON_BFG) + { + it = FindItem("bfg10k"); + picked_item = "BFG"; + } + + else if (weapon == WEAPON_FLAMETHROWER) + { + it = FindItem ("FlameThrower"); + picked_item = "FlameThrower"; + } + else if (weapon == WEAPON_TRANQUILDART) + { + it = FindItem ("Tranquilizer Dart Launcher"); + picked_item = "Tranquilizer Dart"; + } + else if (weapon == WEAPON_KNIFE) + { + it = FindItem ("Knife"); + picked_item = "Knife"; + } + else if (weapon == WEAPON_FREEZER)//acrid 3/99 + { + it = FindItem ("Freezer"); + picked_item = "Freezer"; + } + else if (weapon == WEAPON_AK47) + { + it = FindItem ("AK47"); + picked_item = "AK47"; + } + else if (weapon == WEAPON_PISTOL) + { + it = FindItem ("Pistol"); + picked_item = "Pistol"; + } + + else if (weapon == WEAPON_STINGER) + { + it = FindItem ("Stinger Launcher"); + picked_item = "Stinger Launcher"; + } + else if (weapon == WEAPON_DISRUPTOR) + { + it = FindItem ("Disruptor"); + picked_item = "Disruptor"; + } + else if (weapon == WEAPON_ETF_RIFLE) + { + it = FindItem ("ETF Rifle"); + picked_item = "ETF Rifle"; + } +/* + else if (weapon == WEAPON_PLASMA_BEAM) + { + it = FindItem ("Plasma Beam"); + picked_item = "Plasma Beam"; + } +*/ + else if (weapon == WEAPON_ION_RIPPER) + { + it = FindItem ("Ionripper"); + picked_item = "Ion Ripper"; + } + else if (weapon == WEAPON_PHALANX) + { + it = FindItem ("Phalanx"); + picked_item = "Phalanx"; + } + if (it == NULL) + { + it = orig_it; + safe_cprintf(ent, PRINT_HIGH, "Coudn't find that weapon!\n"); + } + + //Is this weapon banned? +/* if ((banweapon->string) && (it == FindItem(banweapon->string))) + { + safe_cprintf (ent, PRINT_HIGH, "Sorry - the %s is currently disabled.\n",banweapon->string); + ent->client->weapon_damage = olddamage; + return; + } +*/ + + if (slot != 0 && weapon == 0) + { + safe_cprintf (ent, PRINT_HIGH, "Nothing There.\n"); + ent->client->weapon_damage = olddamage; + return; + } + + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + safe_cprintf (ent, PRINT_HIGH, "Not In Inventory.\n"); + ent->client->weapon_damage = olddamage; + return; + } + safe_cprintf(ent, PRINT_HIGH, "%s Selected\n",picked_item); + + it->use (ent, it); + + //They may have done a "use " directly instead of + // the number keys + if(it == FindItem("Rocket Napalm Launcher")) weapon = wf_game.weapon_damage[WEAPON_NAPALMMISSLE]; + + //Turn on/off laser sight if needed. Not on if homing is not on + if ((weapon == WEAPON_NAPALMMISSLE) && ( ent->client->pers.homing_state) ) + lasersight_on (ent); + else + lasersight_off (ent); + +} + + +/* +================== +Cmd_Drop_f + +Drop an inventory item +================== +*/ +void Cmd_Drop_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + +//ZOID--special case for tech powerups + if (Q_stricmp(gi.args(), "tech") == 0 && (it = CTFWhat_Tech(ent)) != NULL) { + it->drop (ent, it); + return; + } +//ZOID + +//WF - Drop the flag + if (Q_stricmp(gi.args(), "flag") == 0 ) + { + CTFDeadDropFlag(ent); + return; + } +//WF + s = gi.args(); + 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; + +//ZOID + if (ent->client->menu) + { + PMenu_Close(ent); + ent->client->update_chase = true; + return; + } +//ZOID + + if (cl->showinventory) + { + cl->showinventory = false; + return; + } + +//ZOID + //has team been picked? + if (ctf->value && cl->resp.ctf_team == CTF_NOTEAM) + { + CTFOpenJoinMenu(ent); + return; + } +//ZOID +//WF + //has class been picked? + if (ent->client->pers.player_class == 0) + { + WFOpenClassMenu(ent); + return; + } +//WF + + cl->showinventory = true; + cl->showscores = false; + + 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; + + +//ZOID + if (ent->client->menu) + { + PMenu_Select(ent); + return; + } +//ZOID + + 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); + +} + +//ZOID +/* +================= +Cmd_LastWeap_f +================= +*/ +void Cmd_LastWeap_f (edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + cl->pers.lastweapon->use (ent, cl->pers.lastweapon); +} +//ZOID + +/* +================= +Cmd_WeapPrev_f +================= +*/ +void Cmd_WeapPrev_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + int slot = 0; + int weapon = 0; + + 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; + int slot = 0; + int weapon = 0; + + 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; + + int slot = 0; + int weapon = 0; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + index = ITEM_INDEX(cl->pers.lastweapon); + if (!cl->pers.inventory[index]) + return; + it = &itemlist[index]; + if (!it->use) + return; + if (! (it->flags & IT_WEAPON) ) + return; + it->use (ent, it); + +} + +/* +================= +Cmd_InvDrop_f +================= +*/ +void Cmd_InvDrop_f (edict_t *ent) +{ + gitem_t *it; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + 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) +{ +//ZOID + if (ent->solid == SOLID_NOT) + return; +//ZOID + //No kill cmd while disease 3/99 acrid + //Acrid - I'm allowing you to kill yourself, but the nurse gets a frag for it. See + // p_client.c +// if (ent->disease) +// { +// if (((int)wfflags->value & WF_NO_PLAYER_CLASSES) == 0) +// safe_cprintf (ent, PRINT_HIGH, "Take it like a %s\n", classinfo[ent->client->pers.player_class].name); +// return; +// } + if((level.time - ent->client->respawn_time) < 5) + return; + ent->flags &= ~FL_GODMODE; + ent->health = 0; + meansOfDeath = MOD_SUICIDE; + + //WF - Treat this like a change class. Clean up all their toys + WFPlayer_ChangeClassTeam(ent); + + player_die (ent, ent, ent, 100000, vec3_origin); + + // don't even bother waiting for death frames +//3.20 ent->deadflag = DEAD_DEAD; +//3.20 respawn (ent); +} + +/* +================= +Cmd_PutAway_f +================= +*/ +void Cmd_PutAway_f (edict_t *ent) +{ + ent->client->showscores = false; + ent->client->showhelp = false; + ent->client->showinventory = false; +//ZOID + if (ent->client->menu) + PMenu_Close(ent); + ent->client->update_chase = true; +//ZOID +} + + +int PlayerSort (void const *a, void const *b) +{ + int anum, bnum; + + anum = *(int *)a; + bnum = *(int *)b; + + anum = game.clients[anum].ps.stats[STAT_FRAGS]; + bnum = game.clients[bnum].ps.stats[STAT_FRAGS]; + + if (anum < bnum) + return -1; + if (anum > bnum) + return 1; + return 0; +} + +/* +================= +Cmd_Players_f +================= +*/ +void Cmd_Players_f (edict_t *ent) +{ + int i; + int count; + char small[64]; + char large[1280]; + int index[256]; + + count = 0; + for (i = 0 ; i < maxclients->value ; i++) + if (game.clients[i].pers.connected) + { + index[count] = i; + count++; + } + + // sort by frags + qsort (index, count, sizeof(index[0]), PlayerSort); + + // print information + large[0] = 0; + + for (i = 0 ; i < count ; i++) + { + Com_sprintf (small, sizeof(small), "%3i %s\n", + game.clients[index[i]].ps.stats[STAT_FRAGS], + game.clients[index[i]].pers.netname); + if (strlen (small) + strlen(large) > sizeof(large) - 100 ) + { // can't print all of them in one packet + strcat (large, "...\n"); + break; + } + strcat (large, small); + } + + safe_cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count); +} + +//WF - prototype from wf_decoy.c +void decoy_taunt (edict_t *self); +void decoy_flip (edict_t *self); +void decoy_salute (edict_t *self); +void decoy_wave (edict_t *self); +void decoy_point (edict_t *self); + + +/* +================= +Cmd_NoSpam_f +================= +*/ +void Cmd_NoSpam_f (edict_t *ent) +{ + int i; + if (!ent->client) return; + + i = atoi (gi.argv(1)); + ent->client->pers.nospam_level = i; + safe_cprintf (ent, PRINT_HIGH, "NOSPAM set to %d\n", i); +} + + +/* +================= +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; + + //WF - if we have a decoy, make it flipoff too + if (ent->decoy) decoy_flip(ent->decoy); + break; + case 1: + safe_cprintf (ent, PRINT_HIGH, "salute\n"); + ent->s.frame = FRAME_salute01-1; + ent->client->anim_end = FRAME_salute11; + + //WF - if we have a decoy, make it salute too + if (ent->decoy) decoy_salute(ent->decoy); + break; + case 2: + safe_cprintf (ent, PRINT_HIGH, "taunt\n"); + ent->s.frame = FRAME_taunt01-1; + ent->client->anim_end = FRAME_taunt17; + + //WF - if we have a decoy, make it taunt too + if (ent->decoy) decoy_taunt(ent->decoy); + break; + case 3: + safe_cprintf (ent, PRINT_HIGH, "wave\n"); + ent->s.frame = FRAME_wave01-1; + ent->client->anim_end = FRAME_wave11; + + //WF - if we have a decoy, make it wave too + if (ent->decoy) decoy_wave(ent->decoy); + break; + case 4: + default: + safe_cprintf (ent, PRINT_HIGH, "point\n"); + ent->s.frame = FRAME_point01-1; + ent->client->anim_end = FRAME_point12; + + //WF - if we have a decoy, make it point too + if (ent->decoy) decoy_point(ent->decoy); + break; + } +} + +/* +================= +Cmd_Join_f +================= +*/ +//ERASER START +void ClientUserinfoChanged (edict_t *ent, char *userinfo); +void Cmd_Join_f(edict_t *ent, char *teamname) +{ + int i; + char userinfo[MAX_INFO_STRING]; + + if (ctf->value) + { + safe_cprintf(ent, PRINT_HIGH, "\nTeams not available in CTF mode.\nUse \"sv bluebots ..\" and \"sv bluebots ..\" to spawn groups of bots in CTF.\n"); + return; + } + + if (ent->client->team) + { + safe_cprintf(ent, PRINT_HIGH, "\nYou are already a member of a team.\nYou must disconnect to change teams.\n\n"); + return; + } + + for (i=0; iingame && dedicated->value) + continue; // only allow joining a team that has been created on the server + +#ifdef _WIN32 + if (!_stricmp(bot_teams[i]->teamname, teamname) || !_stricmp(bot_teams[i]->abbrev, teamname)) +#else + if (!strcasecmp(bot_teams[i]->teamname, teamname) || !strcasecmp(bot_teams[i]->abbrev, teamname)) +#endif + { // match found + + // check team isn't already full + if ((bot_teams[i]->num_players >= players_per_team->value) && (bot_teams[i]->num_bots == 0)) + { + safe_cprintf(ent, PRINT_HIGH, "Team \"%s\" is full.\n", bot_teams[i]->teamname); + return; + } + + bot_teams[i]->num_players++; + + if (strlen(bot_teams[i]->default_skin) > 0) // set the team's skin + {//botDebugPrint("SKIN 3 (ACRID)\n"); + // copy userinfo + strcpy(userinfo, ent->client->pers.userinfo); + + // set skin + Info_SetValueForKey (userinfo, "skin", bot_teams[i]->default_skin); + //botDebugPrint("SKIN 1 (ACRID)\n"); + // record change + ClientUserinfoChanged(ent, userinfo); + } + + // must set this after skin! + ent->client->team = bot_teams[i]; + bot_teams[i]->ingame = true; // make sure we enable the team + + my_bprintf(PRINT_HIGH, "%s has joined team %s\n", ent->client->pers.netname, bot_teams[i]->teamname); + + sl_LogPlayerTeamChange( &gi, + ent->client->pers.netname, + CTFTeamName(ent->client->resp.ctf_team)); + return; + } + } +} + +void Cmd_Lag_f (edict_t *ent, char *val) +{ + int i; + + i = atoi(val); + + if (i > 0) + { + if (i < 1000) + { + ent->client->latency = i; + safe_cprintf(ent, PRINT_HIGH, "Latency set to %i\n", i); + } + else + { + safe_cprintf(ent, PRINT_HIGH, "lag must be lower than 1000\n"); + } + } + else + { + safe_cprintf(ent, PRINT_HIGH, "lag must be higher than 0\n"); + } +} + +void Cmd_Teams_f (edict_t *ent) +{ + char str[256]; + int i, j; + + if (ctf->value) + { + safe_cprintf(ent, PRINT_HIGH, "\nTeams not available in CTF mode.\nUse \"sv bluebots ..\" and \"sv redbots ..\" to spawn groups of bots in CTF.\n\n"); + return; + } + + safe_cprintf(ent, PRINT_HIGH, "\n=====================================\nAvailable teams:\n\n"); + + // list all available teams + for (i=0; iingame && dedicated->value) + continue; // don't show teams that haven't been created, when in dedicated server mode + + // print the team name + safe_cprintf(ent, PRINT_HIGH, "%s ", bot_teams[i]->teamname); + + for (j=0; j<(15-strlen(bot_teams[i]->teamname)); j++) + str[j] = ' '; + str[j] = 0; + + safe_cprintf(ent, PRINT_HIGH, "%s(%s)", str, bot_teams[i]->abbrev); + + for (j=0; j<(4-strlen(bot_teams[i]->abbrev)); j++) + str[j] = ' '; + str[j] = 0; + + safe_cprintf(ent, PRINT_HIGH, str); + + if (bot_teams[i]->ingame) + { + safe_cprintf(ent, PRINT_HIGH, "%i plyrs", bot_teams[i]->num_players); + if (bot_teams[i]->num_bots) + safe_cprintf(ent, PRINT_HIGH, " (%i bots)\n", bot_teams[i]->num_bots); + + safe_cprintf(ent, PRINT_HIGH, "\n"); + } + else + safe_cprintf(ent, PRINT_HIGH, "[none]\n"); + + } + + safe_cprintf(ent, PRINT_HIGH, "\n=====================================\n"); +} + +void Cmd_BotCommands_f (edict_t *ent) +{ // show bot info + gi.dprintf("\n=================================\nSERVER ONLY COMMANDS:\n\n \"bot_num \" - sets the maximum number of bots at once to \n\n \"bot_name \" - spawns a specific bot\n\n \"bot_free_clients \" - makes sure there are always free client spots\n\n \"bot_calc_nodes 0/1\" - Enable/Disable dynamic node-table calculation\n\n \"bot_allow_client_commands \" - set to 1 to allow clients to spawn bots via \"cmd bots \"\n=================================\n\n"); +} + +void Cmd_Tips_f (edict_t *ent) +{ + safe_cprintf(ent, PRINT_HIGH, "\nERASER TIPS:\n\n * Set \"skill 0-3\" to vary the difficulty of your opponents (1 is default)\n\n * You can create your own bots by editing the file BOTS.CFG in the Eraser directory\n\n * Set \"maxclients 32\" to allow play against more bots\n\n"); +} + +void Cmd_Botpath_f (edict_t *ent) +{ + trace_t tr; + vec3_t dest; + + AngleVectors(ent->client->v_angle, dest, NULL, NULL); + VectorScale(dest, 600, dest); + VectorAdd(ent->s.origin, dest, dest); + + tr = gi.trace(ent->s.origin, VEC_ORIGIN, VEC_ORIGIN, dest, ent, MASK_PLAYERSOLID); + + if (tr.ent && tr.ent->bot_client) + { + tr.ent->flags |= FL_SHOWPATH; + + gi.dprintf("Showing path for %s\n", tr.ent->client->pers.netname); + } +} + +// toggles the debug path for this client +void Cmd_Showpath_f (edict_t *ent) +{ + if (ent->flags & FL_SHOWPATH) + ent->flags -= FL_SHOWPATH; + else + ent->flags |= FL_SHOWPATH; +} +//ERASER END +//WF34 START +/* +Flood Protection (anti-spam) code by John. Moved to function by Gregg +*/ +int FloodProtect(edict_t *ent) +{ + int floodtime; + + //See if flood protection is turned off + if (wf_game.floodertime) return 0;//dont worry about it + + if (ent->client) + { + + //WF John flood protection code + if(ent->client->floodtime2client->floodtime1>level.time) + ent->client->floodtime2 = level.time + wf_game.floodertime; + } + else + { + floodtime=(int)(ent->client->floodtime2-level.time); + safe_cprintf(ent, PRINT_HIGH, "Cannot talk for %i more seconds\n",floodtime); + return 1; + } + + ent->client->floodtime1 = level.time + wf_game.floodertime; + } + + return 0; +} +//WF34 END +/* +================== +Cmd_Say_f +================== +*/ +void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0) +{ + int j; + edict_t *other; + char *p; + char text[2048]; + + + + if (FloodProtect(ent)) + return; + + if (gi.argc () < 2 && !arg0) + return; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + team = false; + + if (team) + Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname); + else + Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname); + + if (arg0) + { + strcat (text, gi.argv(0)); + strcat (text, " "); + strcat (text, gi.args()); + } + else + { + p = gi.args(); + + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + strcat(text, p); + } + + // don't let text be too long for malicious reasons + if (strlen(text) > 150) + text[150] = 0; + + strcat(text, "\n"); + + if (BannedWords(ent, text)) + return; + + j = 0; + while (j < 150 && text[j] != 0) + { + if (text[j] == '%') text[j] = '_'; // don't allow formated characters in text + ++j; + } + + + 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 || other->bot_client) + continue; + if (team) + { + if (!OnSameTeam(ent, other)) + continue; + } + safe_cprintf(other, PRINT_CHAT, "%s", text); + } +} +//ERASER START +//========================================================== +// CTF flagpath hack, provide alternate routes for bots to return +// the flag to base +void FlagPathTouch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->bot_client) + return; + + if (ent->last_goal) // this is a source flagpath + { + if (other->client->resp.ctf_team != ent->skill_level) + return; + if (other->flagpath_goal) // already heading for a destination + return; + } + else // this is a destination + { + if (other->flagpath_goal == ent) // reached destination, so clear it + other->flagpath_goal = NULL; + + return; + } + + if (!CarryingFlag(other)) + return; + + // carring flag, if this is a src path, send them on an alternate (safest) route + if (ent->last_goal) + { + float count1, count2; + int i; + + if (!other->flagpath_goal) + { + count1 = count2 = 0; + + for (i=0; iclient->resp.ctf_team && (players[i]->client->resp.ctf_team != other->client->resp.ctf_team)) + { + count1 += entdist(ent->last_goal, players[i]); + count2 += entdist(ent->target_ent, players[i]); + } + } + + if (count1 > count2) + other->flagpath_goal = ent->last_goal; + else + other->flagpath_goal = ent->target_ent; + } + + } + else // this is a destination path + { + other->flagpath_goal = NULL; + } +} + +extern int dropped_trail; + +edict_t *flagpaths[3] = {NULL, NULL, NULL}; +void FlagPath(edict_t *ent, int ctf_team) +{ + int i; + static int flagpath_type=0; + + if (flagpath_type == 0) + { + // new source + if (flagpaths[flagpath_type]) + { + if (ent->client) + { + safe_cprintf(ent, PRINT_HIGH, "Incomplete FlagPath, starting a new path.\n"); + + for (i=0; i<3; i++) + { + G_FreeEdict(flagpaths[i]); + flagpaths[i] = NULL; + } + } + } + + flagpaths[flagpath_type] = G_Spawn(); + flagpaths[flagpath_type]->classname = "flag_path_src"; + VectorCopy(ent->s.origin, flagpaths[flagpath_type]->s.origin); + flagpaths[flagpath_type]->last_goal = NULL; + flagpaths[flagpath_type]->target_ent = NULL; + flagpaths[flagpath_type]->skill_level = ctf_team; + + safe_cprintf(ent, PRINT_HIGH, "Flagpath SOURCE dropped.\n"); + } + else if (flagpath_type == 1) + { + + flagpaths[flagpath_type] = G_Spawn(); + flagpaths[flagpath_type]->classname = "flag_path_dest"; + VectorCopy(ent->s.origin, flagpaths[flagpath_type]->s.origin); + flagpaths[flagpath_type]->last_goal = NULL; + flagpaths[flagpath_type]->target_ent = NULL; + flagpaths[flagpath_type]->paths[0] = -1; + + flagpaths[0]->last_goal = flagpaths[flagpath_type]; + + safe_cprintf(ent, PRINT_HIGH, "Flagpath DEST 1 dropped.\n"); + } + else if (flagpath_type == 2) + { + flagpaths[flagpath_type] = G_Spawn(); + flagpaths[flagpath_type]->classname = "flag_path_dest"; + VectorCopy(ent->s.origin, flagpaths[flagpath_type]->s.origin); + flagpaths[flagpath_type]->last_goal = NULL; + flagpaths[flagpath_type]->target_ent = NULL; + flagpaths[flagpath_type]->paths[0] = -1; + + flagpaths[0]->target_ent = flagpaths[flagpath_type]; + + // completed paths, so make them triggers, and clear + for (i=0; i<3; i++) + { + flagpaths[i]->solid = SOLID_TRIGGER; + VectorSet(flagpaths[i]->mins, -16, -16, -16); + VectorSet(flagpaths[i]->maxs, 16, 16, 4); + flagpaths[i]->touch = FlagPathTouch; + gi.linkentity(flagpaths[i]); + + flagpaths[i] = NULL; + } + + flagpath_type = -1; + + if (ent->client) + { + safe_cprintf(ent, PRINT_HIGH, "Flagpath DEST 2 dropped.\nSequence complete.\n\n"); + dropped_trail = true; + } + } + + flagpath_type++; +} +extern float team1_rushbase_time, team2_rushbase_time; // used by RUSHBASE command +extern float team1_defendbase_time, team2_defendbase_time; +#define RUSHBASE_OVERRIDE_TIME 180.0 +//ERASER END +/*/newcmds acrid +==================== +ClientCommandModeUse +==================== +*/ +void ClientCommandModeUse (edict_t *ent,char *cmd) +{ + cmd = gi.argv(0); + +//GREGG havent looked at zbot code so I added these to be safe, because this bypasses the cmds list + // -- ANTI-ZBOT STARTS -- + /* + if (Q_stricmp(cmd, "!zbot") == 0) + ent->client->resp.bot_end = 0; // not a bot - test 1 + else if (Q_stricmp(cmd, "@zbot") == 0) + ent->client->resp.bot_end3 = 0; // not a bot - test 2 + else if (Q_stricmp(cmd, "#zbot") == 0) + ent->client->resp.bot_end3 = 0; // not a bot - test3 + */ + // -- ANTI-ZBOT CODE ENDS -- // + + //Feign can use the special menu to live again newfeign 4/99 acrid + if (ent->client->pers.feign) + { + if (Q_stricmp (cmd, "special") == 0) + { + if (ent->health > 0 && ent->wf_team >= 1) + WFSpecialMenu(ent); + } + else if (Q_stricmp (cmd, "vote") == 0) + { + if (ent->client->pers.HasVoted == false) + { + WFMapVote(ent); + } + else + safe_cprintf (ent, PRINT_HIGH, "You have already voted for a map.\n"); + } + //commands you can only use if menu open + else if (ent->client->menu && ent->client->menu->UseNumberKeys) + { + if (Q_stricmp (cmd, "use") == 0) + Cmd_Use_f (ent); + else if (Q_stricmp (cmd, "invuse") == 0) + Cmd_InvUse_f (ent); + } + //else print you can't you + else + safe_cprintf (ent, PRINT_HIGH, "You can't use the %s command in Feign mode.\n",cmd); + } + + //Observer & Chasecam Commands 4/99 + if (ent->movetype == MOVETYPE_NOCLIP) + { + + 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); + + //Commands you can only use if menu open + else if (ent->client->menu && ent->client->menu->UseNumberKeys) + { + if (Q_stricmp (cmd, "use") == 0) + Cmd_Use_f (ent); + else if (Q_stricmp (cmd, "invuse") == 0) + Cmd_InvUse_f (ent); + } + //else print you can't you + else + safe_cprintf (ent, PRINT_HIGH, "You can't use the %s command in Observer mode. Use TAB key to join team\n",cmd); + } + + + //Bot CAM Commands + if (ent->client->bIsCamera) + { + //BotCam + if (Q_stricmp (cmd, "cam") == 0) + { + //Acrid 3/99 prevent cam while using chasecam + if(ent->client->chase_target) + safe_cprintf (ent, PRINT_HIGH, "You must leave chasecam first.\n"); + else + CameraCmd(ent,gi.argv(1)); + } + //else print you can't you + else + safe_cprintf (ent, PRINT_HIGH, "You can't use the %s command in CAM mode.\n",cmd); + } +} +/* +================= +ClientCommand +================= +*/ + + +void ClientCommand (edict_t *ent) +{ + + int classcount[MAX_CLASSES + 1]; + + char *cmd; + char *args; + int i; + int class_picked; + + if (!ent->client /*|| ent->bot_client*/) + return; // not fully in game yet + + if( ent->frozen ) //acrid 3/19 no cmds if frozen + return; + + if (level.intermissiontime) + return; + + cmd = gi.argv(0); + +/////////////////General Commands you can use in any mode////////////////// +///////////////////////////Acrid 4/99////////////////////////////////////// + //Feign + if (Q_stricmp (cmd, "feign") == 0) + { + if ((ent->client->player_special & SPECIAL_FEIGN) + && (ent->wf_team >= 1) + && (ent->health > 0)) + Cmd_Feign_f(ent); + else + safe_cprintf (ent, PRINT_HIGH, "Sorry, you are not allowed to feign death.\n"); + return; + } + else if (Q_stricmp (cmd, "players") == 0) + { + Cmd_Players_f (ent); + return; + } + else if (Q_stricmp (cmd, "showplayers") == 0) + { + Cmd_ShowPlayers(ent); + return;//4/99 + } + else if (Q_stricmp (cmd, "showclass") == 0) + { + Cmd_ShowClass(ent); + return;//4/99 + } + else if (Q_stricmp (cmd, "showvotes") == 0) + { + Cmd_ShowVotes_f (ent); + return;//4/99 + } + else if (Q_stricmp (cmd, "score") == 0) + { + Cmd_Score_f (ent); + return; + } + else if (Q_stricmp (cmd, "help") == 0) + { + Cmd_Help_f (ent); + return; + } + + else if (Q_stricmp (cmd, "maphelp") == 0) + { + Cmd_MapHelp_f (ent); + return; + } + else if (Q_stricmp (cmd, "say") == 0) + { + if (ent->client->silenced) + { + safe_cprintf(ent, PRINT_HIGH, "The REF says you can't talk!\n"); + return; + } + + Cmd_Say_f (ent, false, false); + return; + } + //============== START REF COMMANDS ================= + else if (Q_stricmp (cmd, "ref_password") == 0) + { + Cmd_Ref_Password(ent); + return; + } + else if (Q_stricmp (cmd, "ref") == 0) + { + Cmd_Ref_Show(ent); + return; + } + else if (Q_stricmp (cmd, "ref_kick") == 0) + { + Cmd_Ref_Kick(ent); + return; + } + else if (Q_stricmp (cmd, "ref_nextmap") == 0) + { + Cmd_Ref_NextMap(ent); + return; + } + else if (Q_stricmp (cmd, "ref_pickmap") == 0) + { + Cmd_Ref_PickMap(ent); + return; + } + else if (Q_stricmp (cmd, "ref_start") == 0) + { + Cmd_Ref_Start(ent); + return; + } + else if (Q_stricmp (cmd, "ref_stop") == 0) + { + Cmd_Ref_Stop(ent); + return; + } + else if (Q_stricmp (cmd, "ref_talk") == 0) + { + Cmd_Ref_Talk(ent); + return; + } + else if (Q_stricmp (cmd, "ref_notalk") == 0) + { + Cmd_Ref_NoTalk(ent); + return; + } + else if (Q_stricmp (cmd, "ref_skin_on") == 0) + { + Cmd_Ref_Skin_On(ent); + return; + } + else if (Q_stricmp (cmd, "ref_skin_off") == 0) + { + Cmd_Ref_Skin_Off(ent); + return; + } + else if (Q_stricmp (cmd, "ref_leave") == 0) + { + Cmd_Ref_Leave(ent); + return; + } + //same as team play, but goes out to everyone + else if (Q_stricmp (cmd, "ref_play") == 0) + { + if (FloodProtect(ent)) + return; + + args=gi.args(); + if (strlen(args) > 50) + { + return; + } + + //Dont allow ref sounds to play + if (wf_game.ref_ent != ent) + { + safe_cprintf(ent, PRINT_HIGH, "Only the REF can use this command!\n"); + return; + } + + + Cmd_WFPlayTeam(ent, args, 1); + return; + } + //============== END REF COMMANDS ================= + + else if (Q_stricmp (cmd, "say_team") == 0 || Q_stricmp (cmd, "steam") == 0) + { + if (ent->client->silenced) + { + safe_cprintf(ent, PRINT_HIGH, "The REF says you can't talk!\n"); + return; + } + + if (ctf->value) + CTFSay_Team(ent, gi.args()); + else//ERASER + Cmd_Say_f (ent, true, false); + return; + } + else if (Q_stricmp (cmd, "team") == 0) + { + CTFTeam_f (ent); + return; + } + else if (Q_stricmp(cmd, "id") == 0) + { + CTFID_f (ent); + return; + } + // Help command + else if (Q_stricmp (cmd, "wfhelp") == 0) + { + WFShowHelp(ent, NULL); + return; + } + + // wfflags command + else if (Q_stricmp (cmd, "wfflags") == 0) + { + Cmd_WFFlags_f(ent); + return; + } + else if (Q_stricmp (cmd, "vote") == 0) + { + if (ent->client->pers.HasVoted == false) + { + WFMapVote(ent); + } + else + safe_cprintf (ent, PRINT_HIGH, "You have already voted for a map.\n"); + return; + } + + //play the requested sound + else if (Q_stricmp (cmd, "wfplay") == 0) + { + if (ent->client->silenced) + { + safe_cprintf(ent, PRINT_HIGH, "The REF says you can't play sounds!\n"); + return; + } + + if (FloodProtect(ent)) + return; + + args = gi.args(); + if (strlen(args) > 50) + { + return; + } + + //Dont allow ref sounds to play + if (wf_game.ref_ent != ent) + { + if ((strstr(args, "ref\\")) || (strstr(args, "ref/"))) + { + safe_cprintf(ent, PRINT_HIGH, "Only the REF can play those sounds!\n"); + return; + } + } + + +// gi.sound (ent, CHAN_VOICE, gi.soundindex (args), 1, ATTN_NORM, 0); + gi.sound (ent, CHAN_AUTO, gi.soundindex (args), 1, ATTN_NORM, 0); + return; + } + //play the requested sound + else if (Q_stricmp (cmd, "wfteamplay") == 0) + { + if (ent->client->silenced) + { + safe_cprintf(ent, PRINT_HIGH, "The REF says you can't play sounds!\n"); + return; + } + + if (FloodProtect(ent)) + return; + + args=gi.args(); + if (strlen(args) > 50) + { + return; + } + + //Dont allow ref sounds to play + if (wf_game.ref_ent != ent) + { + if ((strstr(args, "ref\\")) || (strstr(args, "ref/"))) + { + safe_cprintf(ent, PRINT_HIGH, "Only the REF can play those sounds!\n"); + return; + } + } + + + Cmd_WFPlayTeam(ent, args, 0); + return; + } + //no spam setting. arg = number + else if (Q_stricmp (cmd, "nospam") == 0) + { + Cmd_NoSpam_f (ent); + return; + } + + // 'autoconfig' command + // arg = none, "on" or "off" + else if (Q_stricmp (cmd, "autoconfig") == 0) + { + Cmd_AutoConfig_f (ent); + return; + } + + else if (Q_stricmp (cmd, "autozoom") == 0) + { + if (ent->client->pers.autozoom) + { + ent->client->pers.autozoom=0; + ent->client->ps.fov = 90; + } + else + ent->client->pers.autozoom=1; + return; + } + + //If in feign or observer mode use these /newcmds + if ((!sv_cheats->value) && ( (ent->client->pers.feign) || + (ent->movetype == MOVETYPE_NOCLIP) || (ent->client->bIsCamera))) + { + ClientCommandModeUse (ent ,cmd); + return; + } +////////////////////////END of General Commands//////////////////////////// +/////////////////////////////////////////////////////////////////////////// + if (Q_stricmp (cmd, "use") == 0) + Cmd_Use_f (ent); + else if (Q_stricmp (cmd, "invuse") == 0) + Cmd_InvUse_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, "invdrop") == 0) + Cmd_InvDrop_f (ent); + else if (Q_stricmp (cmd, "weapprev") == 0) + Cmd_WeapPrev_f (ent); + else if (Q_stricmp (cmd, "weapnext") == 0) + Cmd_WeapNext_f (ent); + else if (Q_stricmp (cmd, "weaplast") == 0) + Cmd_WeapLast_f (ent); + else if (Q_stricmp (cmd, "kill") == 0) + Cmd_Kill_f (ent); + else if (Q_stricmp (cmd, "putaway") == 0) + Cmd_PutAway_f (ent); + else if (Q_stricmp (cmd, "wave") == 0) + Cmd_Wave_f (ent); + else if (Q_stricmp (cmd, "changeclass") == 0) + { + if (ent->wf_team >= 1) + WFOpenClassMenu(ent); + } + + // -- ANTI-ZBOT STARTS -- //WF34 + /* + else if (Q_stricmp(cmd, "!zbot") == 0) + ent->client->resp.bot_end = 0; // not a bot - test 1 + else if (Q_stricmp(cmd, "@zbot") == 0) + ent->client->resp.bot_end3 = 0; // not a bot - test 2 + else if (Q_stricmp(cmd, "#zbot") == 0) + ent->client->resp.bot_end3 = 0; // not a bot - test3 + */ + // -- ANTI-ZBOT CODE ENDS -- // + + ///Q2 Camera Begin //acrid 3/99 botcam + else if (Q_stricmp (cmd, "cam") == 0) + { //Acrid 3/99 prevent cam while using chasecam + if(ent->client->chase_target) + safe_cprintf (ent, PRINT_HIGH, "You must leave chasecam first.\n"); + else + CameraCmd(ent,gi.argv(1)); +//orig 3/99 if (CameraCmd(ent)) +// orig 3/99 botRemovePlayer(ent); // Ridah, must remove from list first (make sure to add to list, if ability to change back to player is added) + } + ///Q2 Camera End + +/*=========== +WF Commands +=============*/ + // 'homing' command + else if (Q_stricmp (cmd, "homing") == 0) + Cmd_Homing_f (ent); + + // 'grenade' command + else if (Q_stricmp (cmd, "grenade") == 0) + { + if (ent->health > 0 && ent->wf_team >= 1) + Cmd_Grenade_f (ent); + } + + // 'grenade1' command + else if (Q_stricmp (cmd, "grenade1") == 0) + { + if (ent->health > 0 && ent->wf_team >= 1) + { + Cmd_Grenade1 (ent); + } + } + + // 'grenade2' command + else if (Q_stricmp (cmd, "grenade2") == 0) + { + if (ent->health > 0 && ent->wf_team >= 1) + Cmd_Grenade2 (ent); + } + + // 'grenade3' command + else if (Q_stricmp (cmd, "grenade3") == 0) + { + if (ent->health > 0 && ent->wf_team >= 1) + Cmd_Grenade3 (ent); + } + + // 'decoy' command + else if (Q_stricmp (cmd, "decoy") == 0) + { +if (wfdebug) gi.dprintf("team = %d, resp team = %d\n", ent->wf_team, ent->client->resp.ctf_team); + + if (ent->health > 0 && ent->wf_team >= 1) + SP_Decoy (ent); + } + + // jetpack thrust + else if (Q_stricmp(cmd, "thrust") == 0 ) + Cmd_Thrust_f (ent); + + // Scanner +// else if (Q_stricmp (cmd, "scanner") == 0) +// Toggle_Scanner(ent); + + // Compass 5/99 +// else if (Q_stricmp (cmd, "compass") == 0) + // Toggle_Compass(ent,gi.argv(1)); + + // detpipes command + else if (Q_stricmp (cmd, "detpipes") == 0) + Cmd_DetPipes_f (ent); + + // reno commands + else if (Q_stricmp (cmd, "reno") == 0) + Cmd_Reno_f (ent); + + // decoy skins commands + //else if (Q_stricmp (cmd, "dskin") == 0) + // Cmd_DSkin_f (ent); + + //Grenade turrets +// else if (Q_stricmp (cmd, "turret") == 0) +// Cmd_Turret_f (ent); + +//showclass 4/99 +//showplayers 4/99 + + + + else if (Q_stricmp (cmd, "special") == 0) + { + if (ent->health > 0 && ent->wf_team >= 1) + WFSpecialMenu(ent); + } + //grenade dump (show active grenades) + else if (Q_stricmp (cmd, "gdump") == 0) + { + for (i = 1; i <= GRENADE_TYPE_COUNT; ++i) + gi.dprintf("%d. %d\n",i,ent->client->pers.active_grenades[i]); + } + else if (Q_stricmp (cmd, "snipezoom") == 0) + { + + ent->PlayerSnipingZoom= atoi(gi.argv(1)); + if (ent->PlayerSnipingZoom > 35) + ent->PlayerSnipingZoom =35; + else if (ent->PlayerSnipingZoom <25) + ent->PlayerSnipingZoom = 25; + } + //simulate the removal of flags from a map +// else if (Q_stricmp (cmd, "debug-killflags") == 0) +// { +// ent->client->pers.inventory[ITEM_INDEX(flag1_item)] = 0; +// ent->client->pers.inventory[ITEM_INDEX(flag2_item)] = 0; +// } + + //zbot debugging command + else if (Q_stricmp (cmd, "iamazbot") == 0) + { + I_AM_A_ZBOT(ent); + } + + +// else if (Q_stricmp (cmd, "tripbomb") == 0) +// { +// cmd_TripBomb(ent); +// } +// cant track down the other grapple, cmd grapple is 1 at console + else if (Q_stricmp (cmd, "grapple") == 0) + { + //jR The great bug fix + + if (ent->health <= 0)//acrid added = 4/99 + { + return; + } + if (ent->movetype == MOVETYPE_NOCLIP) + return; + + //Has server turned off grappling on server? WF34 + if ((int)wfflags->value & WF_NO_GRAPPLE) + { + safe_cprintf(ent, PRINT_HIGH, "Sorry - The grapple is turned off on this server.\n"); + return; + } + //Is this class capable of using the grapple? + if ((ent->client) && ((ent->client->player_special & SPECIAL_GRAPPLE) == 0)) + { + safe_cprintf(ent, PRINT_HIGH, "Sorry - This class cannot use the grapple.\n"); + return; + } + + if (ent->wf_team <= 0) + return; +//WF34 END + if (ent->client->ctf_grapple) + { +// CTFResetGrapple(ent->client->ctf_grapple); + CTFResetGrapple2(ent->client->ctf_grapple);//newgrap 4/99 + } + else + { + cmd_Grapple(ent); + } + } + //New commands in 3.3, 3.4 + + //toggle on/off + else if (Q_stricmp (cmd, "lasersight") == 0) + { + if(ent->client->pers.laseron) + ent->client->pers.laseron=0; + else + ent->client->pers.laseron=1; + } + //toggle on/off + else if (Q_stricmp (cmd, "supplydepot") == 0) + { + if ((ent->client->player_special & SPECIAL_SUPPLY_DEPOT) + && (ent->wf_team >= 1) + && (ent->health > 0)) + SP_SupplyDepot(ent); + else + safe_cprintf (ent, PRINT_HIGH, "Sorry, you can't build a supply depot.\n"); + } + + //toggle on/off + else if (Q_stricmp (cmd, "healingdepot") == 0) + { + if ((ent->client->player_special & SPECIAL_HEALING) + && (ent->wf_team >= 1) + && (ent->health > 0)) + SP_HealingDepot(ent); + else + safe_cprintf (ent, PRINT_HIGH, "Sorry, you can't build a healing depot\n"); + } + + //argument = "short", "medium" or "long" + else if (Q_stricmp (cmd, "plasmabomb") == 0) + { + if (ent->client->player_special & SPECIAL_PLASMA_BOMB) + cmd_PlasmaBomb(ent); + else + safe_cprintf (ent, PRINT_HIGH, "Sorry, you can't create a plasma bomb.\n"); + } + + //argument = "build", "detonate", "remove", "view" + else if (Q_stricmp (cmd, "camera") == 0) + { + if (ent->client->player_special & SPECIAL_REMOTE_CAMERA) + cmd_Camera(ent); + else + safe_cprintf (ent, PRINT_HIGH, "Sorry, you can't use the camera.\n"); + } + + //argument = "build", "remove", "upgrade", "repair" and "reload" + else if (Q_stricmp (cmd, "sentry") == 0) + { + if ((ent->client->player_special & SPECIAL_SENTRY_GUN) + && (ent->wf_team >= 1) + && (ent->health > 0)) + { + cmd_Sentry(ent); + } + else + safe_cprintf (ent, PRINT_HIGH, "Sorry, you can't create a sentry gun.\n"); + } + + //argument = "build", "remove", "upgrade", "repair" and "reload" + else if (Q_stricmp (cmd, "biosentry") == 0) + { + if ((ent->client->player_special & SPECIAL_BIOSENTRY) + && (ent->wf_team >= 1) + && (ent->health > 0)) + cmd_Biosentry(ent); + else + safe_cprintf (ent, PRINT_HIGH, "Sorry, you can't create a biosentry.\n"); + } + + //argument = class number. default to class # 1 + else if (Q_stricmp (cmd, "disguise") == 0) + { + if (ent->client->player_special & SPECIAL_DISGUISE) + cmd_Disguise(ent); + else + safe_cprintf (ent, PRINT_HIGH, "Sorry, you are not allowed to disguise.\n"); + } + + else if (Q_stricmp (cmd, "cloak") == 0) + { + if (ent->client->player_special & SPECIAL_CLOAK) + Cmd_Cloak_f (ent); + else + safe_cprintf (ent, PRINT_HIGH, "Sorry, you are not allowed to cloak.\n"); + } + +//WF + else if (Q_stricmp (cmd, "alarm") == 0) + cmd_Alarm (ent); + + + // find friend commands + else if (Q_stricmp (cmd, "friend") == 0) + Cmd_Friend_f (ent); + else if (Q_stricmp (cmd, "showfriends") == 0) + Cmd_ShowFriends_f (ent); + else if (Q_stricmp (cmd, "ihavenofriends") == 0) + { + ent->client->pers.hasfriends = 0; + safe_cprintf (ent, PRINT_HIGH, "Friends can no longer find you.\n"); + } + else if (Q_stricmp (cmd, "ihavefriends") == 0) + { + ent->client->pers.hasfriends = 1; + safe_cprintf (ent, PRINT_HIGH, "Friends can now find you.\n"); + } + else if (Q_stricmp (cmd, "motd") == 0) + { + gi.centerprintf(ent, "\n\n=====================================\n%s\n%s\n%s\n\n-------------------------------------\n", + wf_game.motd[0], wf_game.motd[1], wf_game.motd[2]); + } + +/////////////////////////////ERASER START////////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + + //acrid 3/7/99 added && bot_allow_client_commands->value to all bot commands + else if (Q_stricmp (cmd, "botname") == 0) + { + if (!bot_allow_client_commands->value) + { + gi.dprintf("Server has disabled bot commands\n"); + return; + } + spawn_bot (gi.argv(1)); + } + else if (Q_stricmp (cmd, "bots") == 0) + { + safe_cprintf(ent, PRINT_HIGH, "\nThis command is not used anymore.\nUse bot_num # to spawn some bots.\n\n"); + } + else if (Q_stricmp (cmd, "servcmd") == 0) + { + Cmd_BotCommands_f(ent); + } + else if (Q_stricmp (cmd, "tips") == 0) + { + Cmd_Tips_f(ent); + } + else if (Q_stricmp (cmd, "addmd2skin") == 0 && bot_allow_client_commands->value) + { + AddModelSkin(gi.argv(1), gi.argv(2)); + } + else if (Q_stricmp (cmd, "join") == 0 && bot_allow_client_commands->value) + { + Cmd_Join_f(ent, gi.argv(1)); + } + else if (Q_stricmp (cmd, "lag") == 0 && bot_allow_client_commands->value) + { + Cmd_Lag_f(ent, gi.argv(1)); + } + else if (Q_stricmp (cmd, "teams") == 0 && bot_allow_client_commands->value) + { + Cmd_Teams_f(ent); + } + else if (Q_stricmp (cmd, "botpath") == 0 && bot_allow_client_commands->value) + { + Cmd_Botpath_f(ent); + } + else if (Q_stricmp (cmd, "showpath") == 0 && bot_allow_client_commands->value) + { + Cmd_Showpath_f(ent); + } + else if (Q_stricmp (cmd, "group") == 0 && bot_allow_client_commands->value) + { + TeamGroup(ent); + } + else if (Q_stricmp (cmd, "disperse") == 0 && bot_allow_client_commands->value) + { + TeamDisperse(ent); + } + else if (Q_stricmp (cmd, "rushbase") == 0 && bot_allow_client_commands->value) + { + + edict_t *plyr; + int i; + edict_t *flag, *enemy_flag; + + if (!ctf->value) + { + safe_cprintf(ent, PRINT_HIGH, "Command only available during CTF play\n"); + return; + } + + if (ent->client->resp.ctf_team == CTF_TEAM1) + { + flag = flag1_ent; + enemy_flag = flag2_ent; + team1_rushbase_time = RUSHBASE_OVERRIDE_TIME; + team1_defendbase_time = 0; + } + else + { + flag = flag2_ent; + enemy_flag = flag1_ent; + team2_rushbase_time = RUSHBASE_OVERRIDE_TIME; + team2_defendbase_time = 0; + } + + gi.centerprintf(ent, "All available units RUSH BASE!\n\n(Type \"freestyle\" to return to normal)\n", ent->client->pers.netname); + + for (i=0; iclient->resp.ctf_team != ent->client->resp.ctf_team) + continue; +// if (plyr->target_ent) +// continue; + if (plyr->bot_client) + { + plyr->movetarget = enemy_flag; + plyr->movetarget_want = 99; + } + else if (plyr != ent) + { + safe_cprintf(plyr, PRINT_CHAT, "<%s> Rushing base!\n", ent->client->pers.netname); + } + } + } + + else if (Q_stricmp (cmd, "defendbase") == 0 && bot_allow_client_commands->value) + { + + edict_t *plyr; + int i; + edict_t *flag, *enemy_flag; + + if (!ctf->value) + { + safe_cprintf(ent, PRINT_HIGH, "Command only available during CTF play\n"); + return; + } + + if (ent->client->resp.ctf_team == CTF_TEAM1) + { + flag = flag1_ent; + enemy_flag = flag2_ent; + team1_rushbase_time = 0; + team1_defendbase_time = RUSHBASE_OVERRIDE_TIME; + } + else + { + flag = flag2_ent; + enemy_flag = flag1_ent; + team2_rushbase_time = 0; + team2_defendbase_time = RUSHBASE_OVERRIDE_TIME; + } + + gi.centerprintf(ent, "All available units DEFEND BASE!\n\n(Type \"freestyle\" to return to normal)\n", ent->client->pers.netname); + + for (i=0; iclient->resp.ctf_team != ent->client->resp.ctf_team) + continue; +// if (plyr->target_ent) +// continue; + if (plyr->bot_client) + { + plyr->movetarget = flag; + plyr->target_ent = flag; + plyr->movetarget_want = WANT_SHITYEAH; + } + else if (plyr != ent) + { + safe_cprintf(plyr, PRINT_CHAT, "<%s> Defending base!\n", ent->client->pers.netname); + } + } + } + + else if (Q_stricmp (cmd, "freestyle") == 0 && bot_allow_client_commands->value) + { + if (ent->client->resp.ctf_team == CTF_TEAM1) + { + team1_rushbase_time = 0; + team1_defendbase_time = 0; + } + else + { + team2_rushbase_time = 0; + team2_defendbase_time = 0; + } + + safe_cprintf(ent, PRINT_HIGH, "Returning bots to Freestyle mode.\n"); + } + + else if (Q_stricmp (cmd, "flagpath") == 0 && bot_allow_client_commands->value) + { + FlagPath(ent, ent->client->resp.ctf_team); + } + else if (Q_stricmp (cmd, "clear_flagpaths") == 0 && bot_allow_client_commands->value) + { + edict_t *trav=NULL, *last=NULL; + int count=0; + + while (trav = G_Find(last, FOFS(classname), "flag_path_src")) + { + last = trav; + G_FreeEdict(trav); + count++; + } + + last = NULL; + while (trav = G_Find(last, FOFS(classname), "flag_path_dest")) + { + last = trav; + G_FreeEdict(trav); + count++; + } + + if (count) + safe_cprintf(ent, PRINT_HIGH, "\nSuccessfully cleared all flagpaths\n\n"); + } + else if (Q_stricmp (cmd, "botpause") == 0 && bot_allow_client_commands->value) + { + paused = !paused; + + if (!paused) + { // just resumed play + int i; + + for (i=0; ibot_client && !ent->client->ctf_grapple) + { + players[i]->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + } + } + + safe_bprintf(PRINT_HIGH, "%s unpaused the game\n", ent->client->pers.netname); + } + } + else if ((Q_stricmp (cmd, "redflag") == 0) && bot_allow_client_commands->value || (Q_stricmp (cmd, "blueflag") == 0) && bot_allow_client_commands->value) + { // spawn a red flag, which gets saved as node data to make DM levels into CTF levels + edict_t *flag; + + flag = G_Spawn(); + flag->classname = gi.TagMalloc(16, TAG_LEVEL); + strcpy(flag->classname, cmd); + VectorCopy(ent->s.origin, flag->s.origin); + VectorCopy(ent->s.angles, flag->s.angles); + + safe_cprintf(ent, PRINT_HIGH, "Dropped \"%s\" node\n", cmd); + dropped_trail = true; + } + else if (Q_stricmp (cmd, "clearflags") == 0 && bot_allow_client_commands->value) + { + edict_t *flag; + + flag = NULL; + while (flag = G_Find( flag, FOFS(classname), "blueflag" ) ) + { + G_FreeEdict( flag ); + } + + flag = NULL; + while (flag = G_Find( flag, FOFS(classname), "redflag" ) ) + { + G_FreeEdict( flag ); + } + + safe_cprintf( ent, PRINT_HIGH, "Cleared user created CTF flags.\n"); + } + else if (Q_stricmp (cmd, "ctf_item") == 0 && bot_allow_client_commands->value) + { + ctf_item_t *old_ctf_item_head; + + cmd = gi.argv(1); + + if (strlen(cmd) == 0) + { + safe_cprintf(ent, PRINT_HIGH, "No classname specified, ignored.\n"); + return; + } + + old_ctf_item_head = ctf_item_head; + + ctf_item_head = gi.TagMalloc(sizeof(ctf_item_t), TAG_LEVEL); + memset(ctf_item_head, 0, sizeof(ctf_item_t)); + + strcpy(ctf_item_head->classname, cmd); + VectorCopy(ent->s.origin, ctf_item_head->origin); + VectorCopy(ent->s.angles, ctf_item_head->angles); + ctf_item_head->next = old_ctf_item_head; + + safe_cprintf(ent, PRINT_HIGH, "Successfully placed %s at (%i %i %i)\nThis item will appear upon reloading the current map\n", cmd, (int)ent->s.origin[0], (int)ent->s.origin[1], (int)ent->s.origin[2]); + dropped_trail = true; + } + else if (Q_stricmp (cmd, "clear_items") == 0 && bot_allow_client_commands->value) + { + ctf_item_head = NULL; + safe_cprintf(ent, PRINT_HIGH, "Cleared CTF_ITEM data\n"); + dropped_trail = true; + } + + else if (Q_stricmp (cmd, "toggle_flagpaths") == 0 && bot_allow_client_commands->value) + { + if (ent->flags & FL_SHOW_FLAGPATHS) + { + ent->flags &= ~FL_SHOW_FLAGPATHS; + + // turn off the models + { + edict_t *trav; + + // show lines between alternate routes + trav = NULL; + while (trav = G_Find(trav, FOFS(classname), "flag_path_src")) + trav->s.modelindex = 0; + } + } + else + ent->flags |= FL_SHOW_FLAGPATHS; + } +///////////////////////////ERASER END////////////////////////////////////// + + else + { + //See if this command is the name of a player class + class_picked = 0; + for (i = 1; i <= numclasses; ++i) + { + if ((Q_stricmp (cmd, classinfo[i].name) == 0) && (class_picked == 0)) + class_picked = i; + } + + if (class_picked) + { + + //See if there are any class limits defined + WFClassCount(ent, classcount); + + if (classcount[class_picked] >= classinfo[class_picked].limit) + { + safe_cprintf (ent, PRINT_HIGH, "Sorry, class limit on %s is %d. Pick another class.\n", + classinfo[class_picked].name,classinfo[class_picked].limit); + return; + } + + ent->client->pers.next_player_class = class_picked; + safe_cprintf (ent, PRINT_HIGH, "You will become a %s the next time you respawn. \n", + classinfo[class_picked].name); + } + else + { + // anything that doesn't match a command will be a chat + Cmd_Say_f (ent, false, true); + } + } +} diff --git a/g_combat.c b/g_combat.c new file mode 100644 index 0000000..17118d5 --- /dev/null +++ b/g_combat.c @@ -0,0 +1,951 @@ +// g_combat.c + +#include "g_local.h" +//ERASER START +#include "bot_procs.h"//ERASER +///Q2 Camera Begin +#include "camclient.h" +///Q2 Camera End +//ERASER END +//WF34 START +extern ctfgame_t ctfgame; + +void VectorUnrotate(vec3_t in, vec3_t angles, vec3_t out); +//WF34 END +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean CanDamage (edict_t *targ, edict_t *inflictor) +{ + vec3_t dest; + trace_t trace; + +// bmodels need special checking because their origin is 0,0,0 + if (targ->movetype == MOVETYPE_PUSH) + { + VectorAdd (targ->absmin, targ->absmax, dest); + VectorScale (dest, 0.5, dest); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + if (trace.ent == targ) + return true; + return false; + } + + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + + return false; +} + + +/* +============ +Killed +============ +*/ +void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + if (targ->health < -999) + targ->health = -999; + + targ->enemy = attacker; + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { +// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type + if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) + { + level.killed_monsters++; + if (coop->value && attacker->client) + { attacker->client->resp.score++; + botDebugPrint("scored\n");} + // 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); + } +//ERASER START +///Q2 Camera Begin + PlayerDied(targ); +///Q2 Camera End +//ERASER END + 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 = 1; // power armor is weaker in CTF + pa_te_type = TE_SHIELD_SPARKS; + damage = (2 * damage) / 3; + } + + save = power * damagePerCell; + if (!save) + return 0; + if (save > damage) + save = damage; + + SpawnDamage (pa_te_type, point, normal, save); + ent->powerarmor_time = level.time + 0.2; + + power_used = save / damagePerCell; + + if (client) + client->pers.inventory[index] -= power_used; + else + ent->monsterinfo.power_armor_power -= power_used; + return save; +} + +static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags) +{ + gclient_t *client; + int save; + int index; + gitem_t *armor; + + if (!damage) + return 0; + + client = ent->client; + + if (!client) + return 0; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + index = ArmorIndex (ent); + if (!index) + return 0; + + armor = GetItemByIndex (index); + + if (dflags & DAMAGE_ENERGY) + save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage); + else + save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage); + if (save >= client->pers.inventory[index]) + save = client->pers.inventory[index]; + + if (!save) + return 0; + + client->pers.inventory[index] -= save; + SpawnDamage (te_sparks, point, normal, save); + + return save; +} + +void M_ReactToDamage (edict_t *targ, edict_t *attacker) +{ + botDebugPrint("M_reactTo\n"); + if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER)) + return; + + if (attacker == targ || attacker == targ->enemy) + return; + + // if we are a good guy monster and our attacker is a player + // or another good guy, do not get mad at them + if (targ->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + } + + // we now know that we are not both good guys + + // if attacker is a client, get mad at them because he's good and we're not + if (attacker->client) + { + targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + // this can only happen in coop (both new and old enemies are clients) + // only switch if can't see the current enemy + if (targ->enemy && targ->enemy->client) + { + if (visible(targ, targ->enemy)) + { + targ->oldenemy = attacker; + return; + } + targ->oldenemy = targ->enemy; + } + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + { botDebugPrint("!aiflags &&&&&&"); + FoundTarget (targ);} + return; + } + + // it's the same base (walk/swim/fly) type and a different classname and it's not a tank + // (they spray too much), get mad at them + if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) && + (strcmp (targ->classname, attacker->classname) != 0) && + (strcmp(attacker->classname, "monster_tank") != 0) && + (strcmp(attacker->classname, "monster_supertank") != 0) && + (strcmp(attacker->classname, "monster_makron") != 0) && + (strcmp(attacker->classname, "monster_jorg") != 0) ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + // if they *meant* to shoot us, then shoot back + else if (attacker->enemy == targ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + // otherwise get mad at whoever they are mad at (help our buddy) unless it is us! + else if (attacker->enemy && attacker->enemy != targ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker->enemy; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } +} + +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker) +{ +//ZOID + if (ctf->value && targ->client && attacker->client) + if (targ->client->resp.ctf_team == attacker->client->resp.ctf_team && + targ != attacker) + return true; +//ZOID + + //FIXME make the next line real and uncomment this block + // if ((ability to damage a teammate == OFF) && (targ's team == attacker's team)) + return false; +} + +//WF34 S JR Stuff for artieral blood spray code +void Calc_Offset1(edict_t *self,vec3_t point,vec3_t normal) +{ + vec3_t forward; + vec3_t pos1,pos2; +// int i; + + VectorSubtract(point, self->s.origin, forward); + VectorUnrotate(forward, self->s.angles, pos1); + + AngleVectors(normal, forward, NULL, NULL); + VectorMA(point, 64, forward, forward); + VectorSubtract(forward, self->s.origin, forward); + VectorUnrotate(forward, self->s.angles, pos2); +/*///34 used this + for(i=0;i<3;i++) + { + self->client->blood1_pos1[i] = pos1[i]; + self->client->blood1_pos2[i] = pos2[i]; + } +*/ +} +void Calc_Offset2(edict_t *self,vec3_t point,vec3_t normal) +{ + vec3_t forward; + vec3_t pos1,pos2; +// int i; + + VectorSubtract(point, self->s.origin, forward); + VectorUnrotate(forward, self->s.angles, pos1); + + AngleVectors(normal, forward, NULL, NULL); + VectorMA(point, 64, forward, forward); + VectorSubtract(forward, self->s.origin, forward); + VectorUnrotate(forward, self->s.angles, pos2); +/*//34 used this + for(i=0;i<3;i++) + { + self->client->blood2_pos1[i] = pos1[i]; + self->client->blood2_pos2[i] = pos2[i]; + } +*/ +} + +void Calc_Offset3(edict_t *self,vec3_t point,vec3_t normal) +{ + vec3_t forward; + vec3_t pos1,pos2; +// int i; + + VectorSubtract(point, self->s.origin, forward); + VectorUnrotate(forward, self->s.angles, pos1); + + AngleVectors(normal, forward, NULL, NULL); + VectorMA(point, 64, forward, forward); + VectorSubtract(forward, self->s.origin, forward); + VectorUnrotate(forward, self->s.angles, pos2); +/*//34 used this + for(i=0;i<3;i++) + { + self->client->sblood1_pos1[i] = pos1[i]; + self->client->sblood1_pos2[i] = pos2[i]; + } +*/ +} +void Calc_Offset4(edict_t *self,vec3_t point,vec3_t normal) +{ + vec3_t forward; + vec3_t pos1,pos2; +// int i; + + VectorSubtract(point, self->s.origin, forward); + VectorUnrotate(forward, self->s.angles, pos1); + + AngleVectors(normal, forward, NULL, NULL); + VectorMA(point, 64, forward, forward); + VectorSubtract(forward, self->s.origin, forward); + VectorUnrotate(forward, self->s.angles, pos2); +/*//34 used this + for(i=0;i<3;i++) + { + self->client->sblood2_pos1[i] = pos1[i]; + self->client->sblood2_pos2[i] = pos2[i]; + } +*/ +} +//WF34 E JR end +//Gregg This should fix some problems if it screws other stuff up uncomment this t_damage and recomment the next one +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; + float factor;//WF34 + int delta;//WF34 +// float i;//WF34 +//WF34 START +if (wfdebug) +{ +gi.dprintf("T_Damage: dmg = %d\n", damage); +if (targ) gi.dprintf("Target=%s\n ",targ->classname); +if (targ) gi.dprintf("Targets health=%i\n ",targ->health); +if (targ && targ->creator) gi.dprintf("Targets owner=%s \n",targ->creator->classname); +if (targ && targ->creator) gi.dprintf("Target owner health=%i \n",targ->creator->health); +if (inflictor) gi.dprintf("Inflictor=%s \n",inflictor->classname); +if (attacker) gi.dprintf("Attacker=%s \n",attacker->classname); +gi.dprintf(",mod = %d\n", mod); +} + +//WF34 END +//WF24 S + //Don't allow negative damage + if (damage < 0) + { + damage = -damage; + } +//WF24 E + + if (!targ->takedamage) + return; +//ERASER START + if (!attacker) + return; + + if (level.intermissiontime) + return; + + //acrid 3/99 attackers with a freeze gun in inventory cant damage + // frozen targ + if(targ->frozen && attacker->client && + attacker->client->pers.inventory[ITEM_INDEX(FindItem("Freezer"))]) + return; + +//ERASER END + // 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 these are both clients, see if they are on the same team + if (OnSameTeam (targ, attacker)) + { + if (((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)) + damage = 0; + else + mod |= MOD_FRIENDLY_FIRE; + } + } + +//WF24 S See if the entity allows team damage + if ((targ->noteamdamage == true) && (attacker->wf_team == targ->wf_team)) + { + //If this is an created entity (like sentry gun), allow + //the creator to damage the entity. Teammates can't. + if (targ->creator != attacker) + damage = 0; + } +//WF24 E + meansOfDeath = mod; + + // easy mode takes half damage + if (skill->value == 0 && deathmatch->value == 0 && targ->client) + { + damage *= 0.5; + if (!damage) + damage = 1; + } + + client = targ->client; + + if (dflags & DAMAGE_BULLET) + te_sparks = TE_BULLET_SPARKS; + else + te_sparks = TE_SPARKS; + + VectorNormalize(dir); + +//WF24 S bonus damage for suprising a monster + if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0)) + damage *= 2; +//WF24 E +//ZOID +//strength tech + damage = CTFApplyStrength(attacker, damage); +//ZOID + + if (targ->flags & FL_NO_KNOCKBACK) + knockback = 0; + + +// figure momentum add + if (!(dflags & DAMAGE_NO_KNOCKBACK)) + { + if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) + { + vec3_t kvel; + float mass; + + if (targ->mass < 50) + mass = 50; + else + mass = targ->mass; + + if (targ->client && attacker == targ) + VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack... + else + VectorScale (dir, 500.0 * (float)knockback / mass, kvel); + + VectorAdd (targ->velocity, kvel, targ->velocity); + } + } + + take = damage; + save = 0; + + + // check for godmode + if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) ) + { + take = 0; + save = damage; + SpawnDamage (te_sparks, point, normal, save); + } + + // check for invincibility + if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION)) + { + if (targ->pain_debounce_time < level.time) + { + gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0); + targ->pain_debounce_time = level.time + 2; + } + take = 0; + save = damage; + } + + +//WF24 S + if (targ->client) //Make sure it's a client before we check protecttime + { + + //If client has spawn protect and the means of death is *not* MOD_TELEFRAG, return + //If the mod type is MOD_TELEFRAG, and the means of death *is* MOD_TELEFRAG, + //we've gotten here because both the telefragger and telefragge have spawn protection + //If both have spawn protection, we kill the telefraggee anyway + if (K2_IsProtected(targ) && mod != MOD_TELEFRAG && mod != MOD_REVERSE_TELEFRAG) + { + gi.sound(targ,CHAN_ITEM,gi.soundindex("items/protect3.wav"),1, ATTN_NORM, 0); + return; + } + + } +//WF24 E + +//ZOID +//team armor protect//ERASER ADDED ATTACKER + if (ctf->value && targ->client && attacker && attacker->client && + targ->client->resp.ctf_team == attacker->client->resp.ctf_team && + targ != attacker && ((int)dmflags->value & DF_ARMOR_PROTECT)) { + psave = asave = 0; + } + else if (mod != WEAPON_ARMORDART) //ignore armor for this one + { +//ZOID + psave = CheckPowerArmor (targ, point, normal, take, dflags); + take -= psave; + + asave = CheckArmor (targ, point, normal, take, te_sparks, dflags); + take -= asave; + } + else//WF24 S + { + psave = asave = 0; + }//WF24 E + + //treat cheat/powerup savings the same as armor + asave += save; + + +//ZOID +//resistance tech + take = CTFApplyResistance(targ, take); +//ZOID + + // team damage avoidance + if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker) ) + return; + + +//ZOID + CTFCheckHurtCarrier(targ, attacker); +//ZOID + +//WF24 S + + if (take < 0) + { + take = -take; + } +//WF24 E + +// do the damage + if (take) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + SpawnDamage (TE_BLOOD, point, normal, take); + else + SpawnDamage (te_sparks, point, normal, take); + + //Artieral Blood spray code JR 9/25/98 +/* + if(targ->client) + { + //A large hit somewhere maybe + if(take>50) + { + //Check to seee if the first major blood spot is free + if(targ->client->blood1_amount=0) + { + i=random(); + //Did they hit a major blood vessel + if(i>0.45) + { + //Yes they did!!! =-) + //Set it up here for the spraying + VectorCopy(point,targ->client->blood1_point); + VectorCopy(normal,targ->client->blood1_normal); + Calc_Offset1(targ,targ->client->blood1_point,targ->client->blood1_normal); + + targ->client->blood1_amount=(take-20-take%5); + + } + } + //Check to see if the second major blood spot is free + //if it isn't there won't be any blood pouring out + //most likely they are dead anyways unless they had + //a lot of health. Also for lag reasons + else if(targ->client->blood2_amount=0) + { + i=random(); + //Chances are a little harder + if(i>0.3333) + { + //Oh my gosh its another hit + targ->client->blood2_amount=(take-20-take%5); + VectorCopy(point,targ->client->blood2_point); + VectorCopy(normal,targ->client->blood2_normal); + Calc_Offset2(targ,targ->client->blood2_point,targ->client->blood2_normal); + } + } + } + //For the smaller blood vessels I guess + //The amount sprayed out is half the amount of a major hit + //per spray for the equal amount number + else if(take>25) + { + //is the first small blood point being used + if(targ->client->sblood1_amount=0) + { + //It isn't time to see if they are lucky + i=random(); + if(i>0.15) + { + //Yes they are + targ->client->sblood1_amount=(take-10-take%5); + VectorCopy(point,targ->client->sblood1_point); + VectorCopy(normal,targ->client->sblood1_normal); + Calc_Offset3(targ,targ->client->sblood1_point,targ->client->sblood1_normal); + } + } + //checking the second blood point + else if(targ->client->sblood2_amount=0) + { + //Random numbers blah blah blah... + i=random(); + if(i>0.1) + { + //Imagine if all the blood spots are taken + //How gruesome + targ->client->sblood2_amount=(take-10-take%5); + VectorCopy(point,targ->client->sblood2_point); + VectorCopy(normal,targ->client->sblood2_normal); + Calc_Offset4(targ,targ->client->sblood2_point,targ->client->sblood2_normal); + } + } + } + } +*/ + //end artieral blood spray code JR 9/25/98 + +//WF34 ++TeT start team balancing code + if (((int)wfflags->value & WF_AUTO_TEAM_BALANCE) + && ((ctfgame.team1 > 2) || (ctfgame.team2 > 2))) + { + if (targ->wf_team == CTF_TEAM1) + { + delta = (ctfgame.team1 - ctfgame.team2); + } + else + { + delta = (ctfgame.team2 - ctfgame.team1); + } + // never let them take less then 20% + if (delta < -8) + { + delta = -8; + } + factor = (10.0 + (float)delta) / 10.0; + take = (float)take * factor; + } +//WF34 E --TeT end team balancing code + + 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) && (!targ->bot_client))//ERASER ADDED && TBC + { + M_ReactToDamage (targ, attacker); + if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) + { + targ->pain (targ, attacker, knockback, take); + // nightmare mode monsters don't go into pain frames often + if (skill->value == 3) + targ->pain_debounce_time = level.time + 5; + } + } + else if (client) + { + if (!(targ->flags & FL_GODMODE) && (take)) + targ->pain (targ, attacker, knockback, take); + } + else if (take) + { + if (targ->pain) + targ->pain (targ, attacker, knockback, take); + } + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if (client) + { + client->damage_parmor += psave; + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + VectorCopy (point, client->damage_from); + } +} + +/* +============ +T_RadiusDamage +============ +*/ +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod) +{ + float points; + edict_t *ent = NULL; + edict_t *prev_ent = NULL;//WF34 + vec3_t v; + vec3_t dir; + int i = 0;//WF34 + +if (wfdebug) +{ + gi.dprintf("T_RadiusDamage: dmg = %d; inflictor = %s\n", (int)damage, inflictor->classname); +} + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) + { + ++i;//WF34 + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = damage - 0.5 * VectorLength (v); + if (ent == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (ent, inflictor)) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); +if (wfdebug) +{ + gi.dprintf("Calling t_damage. Damage = %d, Points = %d, vlen = %d\n", (int)damage, (int)points, (int) VectorLength (v)); +} + T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); +//ERASER START, don't drop jump nodes if rocket jumping + if (ent->jump_ent && ent->client && !ent->bot_client && ent->velocity[2] > 0) + { + G_FreeEdict(ent->jump_ent); + ent->jump_ent = NULL; + } +//ERASER END + } + } + prev_ent = ent;//WF34 + } +} + + +/* +=========================================================== +T_Radius2Damage - modified to include kickback flags +=========================================================== +*/ +void T_Radius2Damage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int kickback, int mod) +{ + float points; + edict_t *ent = NULL; + edict_t *prev_ent = NULL;//WF34 + vec3_t v; + vec3_t dir; + int i = 0;//WF34 + +if (wfdebug) +{ + gi.dprintf("T_RadiusDamage: dmg = %d\n", (int)damage); +} + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) + { + ++i;//WF34 + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = damage - 0.5 * VectorLength (v); + if (ent == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (ent, inflictor)) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); +if (wfdebug) gi.dprintf("Calling t_damage. Damage = %d, Points = %d, vlen = %d\n", (int)damage, (int)points, (int) VectorLength (v)); + T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, kickback, DAMAGE_RADIUS, mod); +//ERASER START, don't drop jump nodes if rocket jumping + if (ent->jump_ent && ent->client && !ent->bot_client && ent->velocity[2] > 0) + { + G_FreeEdict(ent->jump_ent); + ent->jump_ent = NULL; + } +//ERASER END + } + } + prev_ent = ent;//WF34 + } +} \ No newline at end of file diff --git a/g_ctf.c b/g_ctf.c new file mode 100644 index 0000000..67129b2 --- /dev/null +++ b/g_ctf.c @@ -0,0 +1,5769 @@ +/* g_ctf.c */ + +#include "g_local.h" +#include "wf_classmgr.h" +#include "stdlog.h" // StdLog - Mark Davies +//ERASER START +#include "p_trail.h" +#include "bot_procs.h" + +void cmd_RemoveLaserDefense(edict_t *ent); +void readline(FILE *file, char *str, int max); +int PlayerChangeScore(edict_t *self, int points); +void WFClassCount(edict_t *self, int *classcount); +void WFEndDMLevel (char *mapname); + +char classmenustring[MAX_CLASSES + 1][64]; +char disguisemenustring[MAX_CLASSES + 1][64]; +char menustring[24][64]; +char menutitle[32]; +int BannedWords(edict_t *ent, char *str); +void HealPlayer(edict_t *ent); +void Cmd_Cloak_f (edict_t *ent); + +#define MAX_MENU_MAPS 25 + +extern int modID; + +edict_t *AddToItemList(edict_t *ent, edict_t *head); +void RemoveFromItemList(edict_t *ent); + +//ERASER END + +//WF - function prototypes +void cmd_PlasmaBombMenu(edict_t *ent); +void cmd_Alarm(edict_t *ent); +void Cmd_Feign_f (edict_t *ent); + +ctfgame_t ctfgame; +qboolean techspawn = false; + +//ERASER START //ACRID +edict_t *flag1_ent=NULL; +edict_t *flag2_ent=NULL; + +edict_t *flagreturn1_ent=NULL;//$ +edict_t *flagreturn2_ent=NULL;//$ +//ERASER END + +cvar_t *ctf; +cvar_t *ctf_forcejoin; + +gitem_t *item_tech1, *item_tech2, *item_tech3, *item_tech4;//ERASER + +char *ctf_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 " + +// timeout icon +"if 30 " + "xv 148 " + "yt 100 " + "pic 30 " +"endif " + +// frags +"xr -50 " +"yt 2 " +"num 3 14 " + +//tech +"yb -129 " +"if 26 " + "xr -26 " + "pic 26 " +"endif " + +// red team +"yb -102 " +"if 17 " + "xr -26 " + "pic 17 " +"endif " +"xr -62 " +"num 2 18 " +//joined overlay +"if 22 " + "yb -104 " + "xr -28 " + "pic 22 " +"endif " + +// blue team +"yb -75 " +"if 19 " + "xr -26 " + "pic 19 " +"endif " +"xr -62 " +"num 2 20 " +"if 23 " + "yb -77 " + "xr -28 " + "pic 23 " +"endif " + +// have flag graph +"if 21 " + "yt 26 " + "xr -24 " + "pic 21 " +"endif " + +// weapon damage (was 75,150) +"if 28 " + "yt 52 " + "xr -50 " + "num 3 28 " + "yt 78 " + "xr -24 " + "pic 29 " +"endif " + +// id view state +"if 27 " + "xv 0 " + "yb -58 " + "string \"Viewing\" " + "xv 64 " + "stat_string 27 " +"endif " +; + +static char *tnames[] = { + "item_tech1", "item_tech2", "item_tech3", "item_tech4", + NULL +}; + +gitem_t *titems[4] = {NULL, NULL, NULL, NULL};//ERASER + +void stuffcmd(edict_t *ent, char *s) +{ + gi.WriteByte (11); + gi.WriteString (s); + gi.unicast (ent, true); +} + +/*--------------------------------------------------------------------------*/ + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +static edict_t *loc_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 0 + if (from->solid == SOLID_NOT) + continue; +#endif + 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; +} + +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]; +} + +static 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++) { + trace = gi.trace (viewpoint, vec3_origin, vec3_origin, targpoints[i], inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + } + + return false; +} + +/*--------------------------------------------------------------------------*/ + +//static gitem_t *flag1_item; +//static gitem_t *flag2_item; +gitem_t *flag1_item; +gitem_t *flag2_item; + +gitem_t *GetEnemyFlag(int teamno) +{ + if (teamno <= 0 || teamno > 2) return NULL; + else if (teamno == 1) return flag2_item; + else return flag1_item; +} + +void botGetFlag(edict_t *ent);//ERASER + +void CTFInit(void) +{ + ctf = gi.cvar("ctf", "1", CVAR_SERVERINFO | CVAR_LATCH);//ERASER ADDED | CVAR_LATCH//WF USES 1 + ctf_forcejoin = gi.cvar("ctf_forcejoin", "", 0); + + if (!flag1_item) + flag1_item = FindItemByClassname("item_flag_team1"); + if (!flag2_item) + flag2_item = FindItemByClassname("item_flag_team2"); + memset(&ctfgame, 0, sizeof(ctfgame)); + techspawn = false; + +//ERASER START, precache Tech powerups + item_tech1 = FindItemByClassname("item_tech1"); + item_tech2 = FindItemByClassname("item_tech2"); + item_tech3 = FindItemByClassname("item_tech3"); + item_tech4 = FindItemByClassname("item_tech4"); +//ERASER END +} + +/*--------------------------------------------------------------------------*/ + +char *CTFTeamName(int team) +{ + switch (team) { + case CTF_TEAM1: + return "RED"; + case CTF_TEAM2: + return "BLUE"; + } + return "UNKNOWN"; +} + +char *CTFOtherTeamName(int team) +{ + switch (team) { + case CTF_TEAM1: + return "BLUE"; + case CTF_TEAM2: + return "RED"; + } + return "UNKNOWN"; +} + +int CTFOtherTeam(int team) +{ + switch (team) { + case CTF_TEAM1: + return CTF_TEAM2; + case CTF_TEAM2: + return CTF_TEAM1; + } + return -1; // invalid value +} + +/*--------------------------------------------------------------------------*/ + +edict_t *SelectRandomDeathmatchSpawnPoint (void); +edict_t *SelectFarthestDeathmatchSpawnPoint (void); +float PlayersRangeFromSpot (edict_t *spot); + +void CTFAssignSkin(edict_t *ent, char *s) +{ + int playernum = ent-g_edicts-1; + char modelname[64]; + char skinname[64]; + int team_no = 0; + + + //If not running CTF, always pick team1 + if (!ctf->value) +// return; + team_no = CTF_TEAM1; + + // Set model name + if (ent->disguised) + sprintf(modelname, "%s/",classinfo[ent->disguised].model_name); + else + sprintf(modelname, "%s/",classinfo[ent->client->pers.player_class].model_name); + + + // Set skin name and check for disguise + if (!team_no) team_no = ent->client->resp.ctf_team; + +//gi.dprintf("Teamno = %d\n", team_no); + switch (team_no) + { + case CTF_TEAM1: + if (ent->disguised) + strcpy(skinname, classinfo[ent->disguised].skin2); + else + strcpy(skinname, classinfo[ent->client->pers.player_class].skin1); + break; + case CTF_TEAM2: + if (ent->disguised) + strcpy(skinname, classinfo[ent->disguised].skin1); + else + strcpy(skinname, classinfo[ent->client->pers.player_class].skin2); + break; + default: + //no skin? + ; + } + + //Special case for the ref if he is using the ref skin + if ((wf_game.show_ref_skin == 1) && (wf_game.ref_ent == ent)) + { + sprintf(modelname, "%s/",classinfo[ent->disguised].model_name); + switch (team_no) + { + case CTF_TEAM1: + strcpy(skinname, "xref_r"); + break; + default: + strcpy(skinname, "xref_b"); + } + } + + if (ent->client->player_special & SPECIAL_MERCENARY) + { + safe_cprintf(ent, PRINT_HIGH, "Merc - You are on the %s team\n",CTFTeamName(ent->client->resp.ctf_team)); + } + + //Now do the model/skin assignment + switch (team_no) { + case CTF_TEAM1: + case CTF_TEAM2: + gi.configstring (CS_PLAYERSKINS+playernum, + va("%s\\%s%s", ent->client->pers.netname, modelname, skinname) ); +// gi.dprintf(": %s\\%s%s\n", ent->client->pers.netname, modelname, skinname); + break; + default: + gi.configstring (CS_PLAYERSKINS+playernum, + va("%s\\%s", ent->client->pers.netname, s) ); + botDebugPrint("SKIN 9 (ACRID)\n"); + break; + } + +} +extern int force_team;//ERASER + +int FindPlayerID(gclient_t *client) +{ + edict_t *e; + int i; + + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->client) + continue; + if (e ->client == client) + return i; + } + + return (0); + +} + +// ERASER, added "is_bot" flag to force assigning bot's to a team +void CTFAssignTeam(gclient_t *who, qboolean is_bot) +{ + edict_t *player; + int i; + int team1count = 0, team2count = 0; + + if (!ctf->value) + { + // If not running CTF, then use the player's id number as team number + who->resp.ctf_team = FindPlayerID(who); + who->resp.next_ctf_team = who->resp.ctf_team; + return; + } + + botDebugPrint("CTF ASSIGN TEAM(ACRID)\n"); + who->resp.ctf_state = CTF_STATE_START; +//ERASER ADDED IS BOT + if (!is_bot && !((int)dmflags->value & DF_CTF_FORCEJOIN)) { + who->resp.ctf_team = CTF_NOTEAM; + return; + } +//ERASER START + if (force_team == CTF_TEAM1) + { + who->resp.ctf_team = CTF_TEAM1; + return; + } + else if (force_team == CTF_TEAM2) + { + who->resp.ctf_team = CTF_TEAM2; + return; + } +//ERASER END + for (i = 1; i <= maxclients->value; i++) { + player = &g_edicts[i]; + + if (!player->inuse || player->client == who) + continue; + + switch (player->client->resp.ctf_team) { + case CTF_TEAM1: + team1count++; + break; + case CTF_TEAM2: + team2count++; + } + } + if (team1count < team2count) + who->resp.ctf_team = CTF_TEAM1; + else if (team2count < team1count) + who->resp.ctf_team = CTF_TEAM2; + else if (rand() & 1) + who->resp.ctf_team = CTF_TEAM1; + else + who->resp.ctf_team = CTF_TEAM2; +} + +/* +================ +SelectCTFSpawnPoint + +go to a ctf point, but NOT the two points closest +to other players +================ +*/ +edict_t *SelectCTFSpawnPoint (edict_t *ent) +{ + edict_t *spot, *spot1, *spot2; + int count = 0; + int selection; + float range, range1, range2; + char *cname; + +//WF - Only spawn on your own team's spawn points unless that is turned off + + if ((int)wfflags->value & WF_NO_FORT_RESPAWN) + { + if (ent->client->resp.ctf_state != CTF_STATE_START) + if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST) + return SelectFarthestDeathmatchSpawnPoint (); + else + return SelectRandomDeathmatchSpawnPoint (); + } +//WF + + ent->client->resp.ctf_state = CTF_STATE_PLAYING; + +// if (ent->client->resp.ctf_team == 0) +// gi.dprintf("DEBUG: Client has no team selected!\n"); + + switch (ent->client->resp.ctf_team) { + case CTF_TEAM1: + cname = "info_player_team1"; + break; + case CTF_TEAM2: + cname = "info_player_team2"; + break; + default: + return SelectRandomDeathmatchSpawnPoint(); + } + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + while ((spot = G_Find (spot, FOFS(classname), cname)) != NULL) + { + count++; + range = PlayersRangeFromSpot(spot); + if (range < range1) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if (!count) + return SelectRandomDeathmatchSpawnPoint(); + + if (count <= 2) + { + spot1 = spot2 = NULL; + } + else + count -= 2; + + selection = rand() % count; + + spot = NULL; + do + { + spot = G_Find (spot, FOFS(classname), cname); + if (spot == spot1 || spot == spot2) + selection++; + } while(selection--); + + return spot; +} + +/*------------------------------------------------------------------------*/ +/* +CTFFragBonuses + +Calculate the bonuses for flag defense, flag carrier defense, etc. +Note that bonuses are not cumaltive. You get one, they are in importance +order. +*/ +void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker) +{ + int i; + edict_t *ent; + gitem_t *flag_item, *enemy_flag_item; + int otherteam; + edict_t *flag; + edict_t *carrier = NULL; + char *c; + vec3_t v1, v2; + + // no bonus for fragging yourself + if (!targ->client || !attacker->client || targ == attacker) + return; + + otherteam = CTFOtherTeam(targ->client->resp.ctf_team); + if (otherteam < 0) + return; // whoever died isn't on a team + + // same team, if the flag at base, check to he has the enemy flag + if (targ->client->resp.ctf_team == CTF_TEAM1) { + flag_item = flag1_item; + enemy_flag_item = flag2_item; + } else { + flag_item = flag2_item; + enemy_flag_item = flag1_item; + } + + // did the attacker frag the flag carrier? + if (targ->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) { + attacker->client->resp.ctf_lastfraggedcarrier = level.time; + if (PlayerChangeScore(attacker,CTF_FRAG_CARRIER_BONUS)) + { + safe_cprintf(attacker, PRINT_MEDIUM, "BONUS: %d points for fragging enemy flag carrier.\n", + CTF_FRAG_CARRIER_BONUS); + + + // Log Flag Carrier Frag - MarkDavies + sl_LogScore( &gi, + attacker->client->pers.netname, + NULL, + "FC Frag", + -1, + CTF_FRAG_CARRIER_BONUS); + } + + // the the target had the flag, clear the hurt carrier + // field on the other team + for (i = 1; i <= maxclients->value; i++) { + ent = g_edicts + i; + if (ent->inuse && ent->client->resp.ctf_team == otherteam) + ent->client->resp.ctf_lasthurtcarrier = 0; + } + return; + } + + if (targ->client->resp.ctf_lasthurtcarrier && + level.time - targ->client->resp.ctf_lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT && + !attacker->client->pers.inventory[ITEM_INDEX(flag_item)]) { + // attacker is on the same team as the flag carrier and + // fragged a guy who hurt our flag carrier + if (PlayerChangeScore(attacker,CTF_CARRIER_DANGER_PROTECT_BONUS)) + { + my_bprintf(PRINT_MEDIUM, "%s defends %s's flag carrier against an agressive enemy\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + + // Log Flag Danger Carrier Protect Frag - MarkDavies + sl_LogScore( &gi, + attacker->client->pers.netname, + NULL, + "FC Def", + -1, + CTF_CARRIER_DANGER_PROTECT_BONUS); + } + return; + } + + // flag and flag carrier area defense bonuses + + // we have to find the flag and carrier entities + + // find the flag + switch (attacker->client->resp.ctf_team) { + case CTF_TEAM1: + c = "item_flag_team1"; + break; + case CTF_TEAM2: + c = "item_flag_team2"; + break; + default: + return; + } + + flag = NULL; + while ((flag = G_Find (flag, FOFS(classname), c)) != NULL) { + if (!(flag->spawnflags & DROPPED_ITEM)) + break; + } + + if (!flag) + return; // can't find attacker's flag + + // find attacker's team's flag carrier + for (i = 1; i <= maxclients->value; i++) { + carrier = g_edicts + i; + if (carrier->inuse && + carrier->client->pers.inventory[ITEM_INDEX(flag_item)]) + break; + carrier = NULL; + } + + // ok we have the attackers flag and a pointer to the carrier + + // check to see if we are defending the base's flag + VectorSubtract(targ->s.origin, flag->s.origin, v1); + VectorSubtract(attacker->s.origin, flag->s.origin, v2); + + if (VectorLength(v1) < CTF_TARGET_PROTECT_RADIUS || + VectorLength(v2) < CTF_TARGET_PROTECT_RADIUS || + loc_CanSee(flag, targ) || loc_CanSee(flag, attacker)) { + // we defended the base flag + if (PlayerChangeScore(attacker,CTF_FLAG_DEFENSE_BONUS)) + { + if (flag->solid == SOLID_NOT) + my_bprintf(PRINT_MEDIUM, "%s defends the %s base.\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + else + my_bprintf(PRINT_MEDIUM, "%s defends the %s flag.\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + + // Log Flag Defense - MarkDavies + sl_LogScore( &gi, + attacker->client->pers.netname, + NULL, + "F Def", + -1, + CTF_FLAG_DEFENSE_BONUS); + } + return; + } + + if (carrier && carrier != attacker) { + VectorSubtract(targ->s.origin, carrier->s.origin, v1); + VectorSubtract(attacker->s.origin, carrier->s.origin, v1); + + if (VectorLength(v1) < CTF_ATTACKER_PROTECT_RADIUS || + VectorLength(v2) < CTF_ATTACKER_PROTECT_RADIUS || + loc_CanSee(carrier, targ) || loc_CanSee(carrier, attacker)) { + if (PlayerChangeScore(attacker,CTF_CARRIER_PROTECT_BONUS)) + { + my_bprintf(PRINT_MEDIUM, "%s defends the %s's flag carrier.\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + + // Log Flag Defense - MarkDavies + sl_LogScore( &gi, + attacker->client->pers.netname, + NULL, + "C Prot", + -1, + CTF_CARRIER_PROTECT_BONUS); + } + return; + } + } +} + +void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker) +{ + gitem_t *flag_item; + + if (!targ->client || !attacker->client) + return; + + if (targ->client->resp.ctf_team == CTF_TEAM1) + flag_item = flag2_item; + else + flag_item = flag1_item; + + if (targ->client->pers.inventory[ITEM_INDEX(flag_item)] && + targ->client->resp.ctf_team != attacker->client->resp.ctf_team) + attacker->client->resp.ctf_lasthurtcarrier = level.time; +} + + +/*------------------------------------------------------------------------*/ + +void RemoveFromItemList(edict_t *ent); + +void CTFResetFlag(int ctf_team) +{ + char *c; + edict_t *ent; + + switch (ctf_team) { + case CTF_TEAM1: + c = "item_flag_team1"; + flag1dropped = 0; //clear the dropped flag flag + break; + case CTF_TEAM2: + c = "item_flag_team2"; + flag2dropped = 0; + break; + default: + return; + } + + ent = NULL; + while ((ent = G_Find (ent, FOFS(classname), c)) != NULL) { + if (ent->spawnflags & DROPPED_ITEM) + { + RemoveFromItemList(ent);//ERASER + G_FreeEdict(ent); + } + else { + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity(ent); + ent->s.event = EV_ITEM_RESPAWN; + } + } +} + +void CTFResetFlags(void) +{ + CTFResetFlag(CTF_TEAM1); + CTFResetFlag(CTF_TEAM2); +} + +qboolean CTFPickup_Flag(edict_t *ent, edict_t *other) +{ + int ctf_team; + int i; + edict_t *player; + gitem_t *flag_item, *enemy_flag_item; + int count;//ERASER + qboolean ret; + + if (other->client->pers.feign)//5/99 + return false; + + if ((strcmp(ent->classname, "item_flagreturn_team1") == 0)|| + (strcmp(ent->classname, "item_flagreturn_team2") == 0)) + {botDebugPrint("pickup class %s\n",ent->classname); + return false; + } + if (level.time < 30.0) + { + safe_cprintf(other, PRINT_HIGH, "You must wait another %d seconds to pick up flag\n", (int)(31 - level.time)); + return false; + } + + if (wf_game.game_halted) + { + safe_cprintf(ent, PRINT_HIGH, "You can't pick up flag while game is suspended.\n"); + return false; + } + + // WF - check for unbalanced teams. Don't allow pickup if they are + if (WFUnbalanced_Teams(ent, other)) return false; + +//WF24 SIf this is a STWF flag pickup call a different routine + //otherwise, use the same routine + if (((int)wfflags->value & WF_ZOID_FLAGCAP) == 0) + { + ret = WFPickup_Flag(ent, other); + return(ret); + } + + // figure out what team this flag is + if (strcmp(ent->classname, "item_flag_team1") == 0) + ctf_team = CTF_TEAM1; + else if (strcmp(ent->classname, "item_flag_team2") == 0) + ctf_team = CTF_TEAM2; + else { + safe_cprintf(ent, PRINT_HIGH, "Don't know what team the flag is on.\n"); + return false; + } + + // same team, if the flag at base, check to he has the enemy flag + if (ctf_team == CTF_TEAM1) { + flag_item = flag1_item; + enemy_flag_item = flag2_item; + } else { + flag_item = flag2_item; + enemy_flag_item = flag1_item; + } + +//ERASER START + if (other->bot_client && (other->movetarget == ent)) + other->movetarget = NULL; +//ERASER END NOT SURE WHAT THIS DOES + + if (ctf_team == other->client->resp.ctf_team) { + + if (!(ent->spawnflags & DROPPED_ITEM)) { + // the flag is at home base. if the player has the enemy + // flag, he's just won! + + if (other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) + { + my_bprintf(PRINT_HIGH, "%s captured the %s flag!\n", + other->client->pers.netname, CTFOtherTeamName(ctf_team)); + other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)] = 0; + ctfgame.last_flag_capture = level.time; + ctfgame.last_capture_team = ctf_team; + if (ctf_team == CTF_TEAM1) + ctfgame.team1++; + else + ctfgame.team2++; + + gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0); + + // other gets another 10 frag bonus + if (PlayerChangeScore(other,CTF_CAPTURE_BONUS)) + { + // Log Flag Capture - MarkDavies + sl_LogScore( &gi, + other->client->pers.netname, + NULL, + "F Capture", + -1, + CTF_CAPTURE_BONUS); + } + + // Ok, let's do the player loop, hand out the bonuses + for (i = 1; i <= maxclients->value; i++) { + player = &g_edicts[i]; + if (!player->inuse) + continue; + +//ERASER START, if following the player, stop + if (player->bot_client && (player->target_ent == other)) + player->target_ent = NULL; +//ERASER END + + if (player->client->resp.ctf_team != other->client->resp.ctf_team) + player->client->resp.ctf_lasthurtcarrier = -5; + else if (player->client->resp.ctf_team == other->client->resp.ctf_team) { + if (player != other) + {//WF34 + if (PlayerChangeScore(player,CTF_TEAM_BONUS)) + { + // Log Flag Capture Team Score - MarkDavies + sl_LogScore( &gi, + player->client->pers.netname, + NULL, + "Team Score", + -1, + CTF_TEAM_BONUS); + } + }//WF34 END + // award extra points for capture assists + if (player->client->resp.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time) + { + if (PlayerChangeScore(player,CTF_RETURN_FLAG_ASSIST_BONUS)) + { + my_bprintf(PRINT_HIGH, "%s gets an assist for returning the flag!\n", player->client->pers.netname); + + // Log Flag Capture Team Score - MarkDavies + sl_LogScore( &gi, + player->client->pers.netname, + NULL, + "F Return Assist", + -1, + CTF_RETURN_FLAG_ASSIST_BONUS); + } + } + if (player->client->resp.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time) + { + if (PlayerChangeScore(player,CTF_FRAG_CARRIER_ASSIST_BONUS)) + { + my_bprintf(PRINT_HIGH, "%s gets an assist for fragging the flag carrier!\n", player->client->pers.netname); + + // Log Flag Capture Team Score - MarkDavies + sl_LogScore( &gi, + player->client->pers.netname, + NULL, + "FC Frag Assist", + -1, + CTF_FRAG_CARRIER_ASSIST_BONUS); + } + } + } + } + + CTFResetFlags(); + return false; + } + return false; // its at home base already + } + // hey, its not home. return it by teleporting it back +//WF USES GI.BPRINT + if (PlayerChangeScore(other,CTF_RECOVERY_BONUS)) + { + my_bprintf(PRINT_HIGH, "%s returned the %s flag!\n", + other->client->pers.netname, CTFTeamName(ctf_team)); + + // Log Flag Recover - MarkDavies + sl_LogScore( &gi, + other->client->pers.netname, + NULL, + "F Return", + -1, + CTF_RECOVERY_BONUS); + } + + other->client->resp.ctf_lastreturnedflag = level.time; + gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0); + + //CTFResetFlag will remove this entity! We must return false + CTFResetFlag(ctf_team); + return false; + } + + // hey, its not our flag, pick it up + if (PlayerChangeScore(other,CTF_FLAG_BONUS)) + { + my_bprintf(PRINT_HIGH, "%s got the %s flag!\n", + other->client->pers.netname, CTFTeamName(ctf_team)); + // Log Flag Pickup - MarkDavies + sl_LogScore( &gi, + other->client->pers.netname, + NULL, + "F Pickup", + -1, + CTF_FLAG_BONUS); + } + + other->client->pers.inventory[ITEM_INDEX(flag_item)] = 1; + other->client->resp.ctf_flagsince = level.time; + + //Remove player disguise + if (other->disguised) WFRemoveDisguise(other); + +//ERASER START: send this bot back to it's base! + if (other->bot_client) + { +if (CarryingFlag(other) && (((int)wfflags->value & WF_ZOID_FLAGCAP) == 0) + && (flagreturn1_ent != NULL || flagreturn2_ent != NULL) )//$ +{//$ + if (other->client->resp.ctf_team == CTF_TEAM1)//$ + other->movetarget = flagreturn1_ent;//$ + else//$ + other->movetarget = flagreturn2_ent;//$ + + other->movetarget_want = WANT_SHITYEAH;//$ +}//$ +else//$ +{//$ + if (other->client->resp.ctf_team == CTF_TEAM1) + other->movetarget = flag1_ent; + else + other->movetarget = flag2_ent; + + other->movetarget_want = WANT_SHITYEAH; +}//$ + } +//ERASER END + + // pick up the flag + // if it's not a dropped flag, we just make is disappear + // if it's dropped, it will be removed by the pickup caller + if (!(ent->spawnflags & DROPPED_ITEM)) { + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + + +//ERASER START: inform bots of our location and request assistance + count = 0; + for (i=0; iclient->resp.ctf_team == other->client->resp.ctf_team) + { + if (players[i]->target_ent == ent) + players[i]->target_ent = NULL; + + if (players[i]->bot_client && (players[i] != other) && + (!players[i]->target_ent) && + (entdist(players[i], other) < 2000)) + { // set bot to go straight to this player + players[i]->target_ent = other; +// if (++count >= 2) // we have enough guardians +// break; + } + } + } +//ERASER END + + return true; +} +//WF24 USES STATIC +void CTFDropFlagTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //owner (who dropped us) can't touch for two secs + if (other == ent->owner && + ent->nextthink - level.time > CTF_AUTO_FLAG_RETURN_TIMEOUT-2) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void CTFDropFlagThink(edict_t *ent) +{ + +// if (ent->timestamp < (level.time - CTF_AUTO_FLAG_RETURN_TIMEOUT))//ERASER +// {//E //ACRID Fixes flagdrop/returntimeout bug + // auto return the flag + // reset flag will remove ourselves + if (strcmp(ent->classname, "item_flag_team1") == 0) { + CTFResetFlag(CTF_TEAM1); + my_bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + } else if (strcmp(ent->classname, "item_flag_team2") == 0) { + CTFResetFlag(CTF_TEAM2); + my_bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM2)); + } +// } +//ERASER START + else // inform bots! + { + int i; + + for (i=0; i 1500) && !gi.inPVS(ent->s.origin, players[i]->s.origin)) + continue; + + players[i]->movetarget = ent; + } + + ent->nextthink = level.time + 0.2; + } +} +//ERASER END +//WF34 START +int CTFHasFlag(edict_t *self) +{ + int hasflag; + + if (!flag1_item) + { + gi.dprintf("No flag1 item!\n"); + return 0; + } + if (!flag2_item) + { + gi.dprintf("No flag2 item!\n"); + return 0; + } + hasflag = 0; + if (self->client->pers.inventory[ITEM_INDEX(flag1_item)]) + hasflag = 1; + else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)]) + hasflag = 1; + + return (hasflag); +} +//WF34 END +// Called from PlayerDie, to drop the flag from a dying player +void CTFDeadDropFlag(edict_t *self) +{ + edict_t *dropped = NULL; + + if (!ctf->value)//ERASER + return;//E + + if (!flag1_item || !flag2_item) + CTFInit(); + + if (self->client->pers.inventory[ITEM_INDEX(flag1_item)]) + { + dropped = Drop_Item(self, flag1_item); + self->client->pers.inventory[ITEM_INDEX(flag1_item)] = 0; + my_bprintf(PRINT_HIGH, "%s lost the %s flag!\n", + self->client->pers.netname, CTFTeamName(CTF_TEAM1)); + flag1dropped = 1;//WF34 + } + else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)]) + { + dropped = Drop_Item(self, flag2_item); + self->client->pers.inventory[ITEM_INDEX(flag2_item)] = 0; + my_bprintf(PRINT_HIGH, "%s lost the %s flag!\n", + self->client->pers.netname, CTFTeamName(CTF_TEAM2)); + flag2dropped = 1;//WF34 + } + + if (dropped) { + dropped->think = CTFDropFlagThink;//ACRID fixes flagdrop/returntimeout +// dropped->timestamp = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT; + //dropped->nextthink = level.time + 0.2;//ERASER + dropped->nextthink = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT; + dropped->touch = CTFDropFlagTouch; + + CalcItemPaths(dropped);//ERASER + } + + + + + + +} + +qboolean CTFDrop_Flag(edict_t *ent, gitem_t *item) +{ + if (rand() & 1) + { + safe_cprintf(ent, PRINT_HIGH, "Only lusers drop flags.\n"); + } + else + { + safe_cprintf(ent, PRINT_HIGH, "Winners don't drop flags.\n"); + } + return false; +} +//ERASER START +float team1_rushbase_time, team2_rushbase_time; // used by RUSHBASE command +float team1_defendbase_time, team2_defendbase_time; +//ERASER END +//WF USES STATIC +void CTFFlagThink(edict_t *ent) +{ +//ERASER START +#define CTF_DEBUG false + + edict_t *enemy_flag; + float dist; + int this_team_count, enemy_team_count; + int get_defenders=1, get_attackers=1;//problem fixme acrid + + if (ent->solid != SOLID_NOT)//WF24 USES THIS TOO + ent->s.frame = 173 + (((ent->s.frame - 173) + 1) % 16);//WF24 USES THIS TOO + + if (team1_rushbase_time > 0) + team1_rushbase_time -= FRAMETIME; + if (team2_rushbase_time > 0) + team2_rushbase_time -= FRAMETIME; + if (team1_defendbase_time > 0) + team1_defendbase_time -= FRAMETIME; + if (team2_defendbase_time > 0) + team2_defendbase_time -= FRAMETIME; + +//ERASER: make sure the flag is defended +if (!bot_melee->value) + if (ent->last_seek_enemy < (level.time - 1)) + { + int i, ctf_team, count=0, ideal_guarding, ctf_enemy_team; + edict_t *plyr, *closest_guarding=NULL; + + ent->last_seek_enemy = level.time + random(); // so we try and spread out thinks + + // figure out what team this flag is + if (strcmp(ent->classname, "item_flag_team1") == 0) + { + ctf_team = CTF_TEAM1; + ctf_enemy_team = CTF_TEAM2; + enemy_flag = flag2_ent; + + if (team1_rushbase_time > 0) + {//botDebugPrint("rushbase 1\n"); + if (enemy_flag->solid == SOLID_TRIGGER) + { + for (i=0; ibot_client && plyr->client->resp.ctf_team == ctf_team + && !CarryingFlag(plyr)) + { + plyr->target_ent = NULL; // don't stay guarding anything + plyr->movetarget = enemy_flag; + plyr->movetarget_want = 99; + } + + } + } + + get_defenders = false; + } + + if (team2_defendbase_time > 0) + {//botDebugPrint("rushbase 2\n"); + if (enemy_flag->solid == SOLID_TRIGGER) + { + for (i=0; ibot_client && plyr->client->resp.ctf_team == ctf_enemy_team + && !CarryingFlag(plyr)) + { + plyr->target_ent = enemy_flag; + plyr->movetarget = enemy_flag; + plyr->movetarget_want = 99; + } + + } + } + + get_attackers = false; + } + } + else + { + ctf_team = CTF_TEAM2; + ctf_enemy_team = CTF_TEAM1; + enemy_flag = flag1_ent; + + if (team2_rushbase_time > 0) + {//botDebugPrint("rushbase 3\n"); + if (enemy_flag->solid == SOLID_TRIGGER) + { + for (i=0; ibot_client && plyr->client->resp.ctf_team == ctf_team + && !CarryingFlag(plyr)) + { + plyr->target_ent = NULL; // don't stay guarding anything + plyr->movetarget = enemy_flag; + plyr->movetarget_want = 99; + } + + } + } + + get_defenders = false; + } + + if (team1_defendbase_time > 0) + {//botDebugPrint("rushbase 4\n"); + if (enemy_flag->solid == SOLID_TRIGGER) + { + for (i=0; ibot_client && plyr->client->resp.ctf_team == ctf_enemy_team + && !CarryingFlag(plyr)) + { + plyr->target_ent = enemy_flag; + plyr->movetarget = enemy_flag; + plyr->movetarget_want = 99; + } + + } + } + + get_attackers = false; + } + } + + if (!get_defenders && !get_attackers) + goto done; + + // count number of players guarding base + this_team_count = enemy_team_count = 0; + closest_guarding = NULL; + for (i=0; iclient->resp.ctf_team == ctf_team) + {//botDebugPrint("defending check22222\n"); + this_team_count++; + + dist = entdist(plyr, ent); + + if ( ( (plyr->target_ent == ent) + || (!plyr->bot_client))) + { + if (dist < (BOT_GUARDING_RANGE*1.5)) + { +//botDebugPrint("defending check4444444\n"); + closest_guarding = players[i]; + count++; + } + } + } + else if (plyr->client->resp.ctf_team == ctf_enemy_team) + { + enemy_team_count++; + } + } +/* + if (!closest_guarding) + ideal_guarding = 0; + else*/ if (ent->solid != SOLID_TRIGGER) // don't devote as many forces + ideal_guarding = (int)ceil((1.0*(float)this_team_count)/8.0);//was4 + else + ideal_guarding = (int)ceil((1.0 * (float)this_team_count)/7.0);//was2 + + if (get_defenders) + {//botDebugPrint("get defenders111111111\n"); + if (count < ideal_guarding) + { // summon the closest "out of range" bot for defense + edict_t *closest=NULL; + float dist, closest_dist=999999; + + for (i=0; ibot_client) + && (plyr->client->resp.ctf_team == ctf_team) + && (plyr->target_ent != ent) + // && ((dist = entdist(plyr, ent)) > BOT_GUARDING_RANGE) + && (plyr->bot_fire != botBlaster) + && ((dist = entdist(plyr, ent)) < closest_dist)) + { + closest = plyr; + closest_dist = dist; + } + } + + if (closest) // tell this bot to guard the flag! + { + if (CTF_DEBUG) + gi.dprintf( "Sending %s to defend flag\n", closest->client->pers.netname); + closest->target_ent = ent; + closest->movetarget = ent; + closest->movetarget_want = WANT_SHITYEAH; + } + } + else if (count > ideal_guarding /*&& closest_guarding*/) + { // too many guarding flag, release a bot + for (i=0; (iideal_guarding); i++) + { + plyr = players[i]; + + if ( (plyr->bot_client) + && (plyr->client->resp.ctf_team == ctf_team) + && (plyr->target_ent == ent)) + { + if (CTF_DEBUG) + gi.dprintf( "Releasing %s from flag defense.\n", plyr->client->pers.netname); + plyr->target_ent = NULL; + if (plyr->movetarget == ent) + plyr->movetarget = NULL; + + count--; + } + } + } + } + + // check for invaded base + count = 0; + for (i=0; iclient->resp.ctf_team == ctf_enemy_team) + && ((players[i]->movetarget == ent) || !players[i]->bot_client) + && (entdist(players[i], ent) < BOT_GUARDING_RANGE)) + { + //botDebugPrint("defending check3333333\n"); + count++; + } + } + + if (closest_guarding && (ent->solid == SOLID_TRIGGER)) + { + if (count > 1) + { // message all teammates, INVASION! NEED HELP! + if (ent->last_pain < (level.time - 4)) + { + for (i=0; iclient->resp.ctf_team == ctf_team) + { // FIXME: add to custom chat.txt + //if ((!players[i]->bot_client) && (bot_chat->value) && (players[i]->client) && (players[i]->client->pers.nospam_level == 0)) + // safe_cprintf(players[i], PRINT_CHAT, "%s: base under attack!\n", closest_guarding->client->pers.netname); + //else + if (get_defenders && (players[i]->target_ent != ent) && !CarryingFlag(players[i]) && (entdist(players[i], ent) < 1500)) + { + if (CTF_DEBUG) botDebugPrint( "Sending %s to defend flag\n", players[i]->client->pers.netname); + players[i]->target_ent = ent; + players[i]->movetarget = ent; + players[i]->movetarget_want = WANT_SHITYEAH; + } + } + } + + } + + ent->last_pain = level.time; + } + else if ((count == 0) && ent->last_pain && (ent->last_pain > (level.time - 7))) + { + ent->last_pain = 0; + + for (i=0; ivalue) && (!players[i]->bot_client) && (players[i]->client->resp.ctf_team == ctf_team) && (players[i]->client) && (players[i]->client->pers.nospam_level == 0)) + //{ // FIXME: add to custom chat.txt + // safe_cprintf(players[i], PRINT_CHAT, "%s: base secured!\n", closest_guarding->client->pers.netname); + //} + } + } + } + + if (get_attackers) + { + // Send enemy troops to us? + if (count < ((int)ceil((1.0*(float)enemy_team_count)/4.0))) + { + for (i=0; ibot_client) + continue; + if (players[i]->client->resp.ctf_team != ctf_enemy_team) + continue; + if (players[i]->movetarget == ent) + continue; + + dist = entdist(players[i], ent); + + if (dist > 1200) + { + if (players[i]->target_ent && (dist > 2000)) + continue; + //f if ((dist > 1500) && (players[i]->bot_fire == botBlaster)) + //f continue; + } + else if (ent->solid != SOLID_TRIGGER) + { + continue; + } + + if (CTF_DEBUG) + gi.dprintf( "Sending %s to ATTACK flag\n", players[i]->client->pers.netname); + players[i]->movetarget = ent; + players[i]->movetarget_want = WANT_SHITYEAH; + } + } + else if (ent->solid != SOLID_TRIGGER) // release an attacker + { + for (i=0; iclient->resp.ctf_team == ctf_team) + continue; + + if ((players[i]->target_ent == ent) || (players[i]->movetarget == ent)) + { + if (CTF_DEBUG) + gi.dprintf( "Releasing %s from attacking flag\n", players[i]->client->pers.netname); + players[i]->target_ent = NULL; + players[i]->enemy = NULL; + break; + } + } + } + } + + } +//ERASER + +done: +//ERASER END + ent->nextthink = level.time + FRAMETIME;//WF USES THIS TOO +} + +void CTFFlagSetup (edict_t *ent)//ACRID LOOK HERE +{ + trace_t tr; + vec3_t dest; + float *v; +// edict_t *flag_thinker;//ERASER AND IT WAS ALREADY CO + edict_t *spot;//$ + spot = NULL;//$ + + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + 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); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + gi.dprintf ("CTFFlagSetup: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + VectorCopy (tr.endpos, ent->s.origin); + + gi.linkentity (ent); + +//ERASER START + if (strcmp(ent->classname, "item_flag_team1") == 0) + { + //home base (flag return) position + spot = G_Find (spot, FOFS(classname), "item_flagreturn_team1");//$ + if (!spot)//$ + spot = NULL;//$ + flag1_ent = ent; + ent->count = CTF_TEAM1; + flagreturn1_ent = spot;//$ + } + else + { + //home base (flag return) position + spot = G_Find (spot, FOFS(classname), "item_flagreturn_team2");//$ + if (!spot)//$ + spot = NULL;//$ + flag2_ent = ent; + ent->count = CTF_TEAM2; + flagreturn2_ent = spot;//$ + } +if (flagreturn1_ent == NULL) +botDebugPrint("No map placed returns red\n"); +if (flagreturn2_ent == NULL) +botDebugPrint("No map placed returns blue\n"); + CalcItemPaths(ent); +//ERASER END + + ent->nextthink = level.time + FRAMETIME; + ent->think = CTFFlagThink; + +} + +void CTFEffects(edict_t *player) +{ + player->s.effects &= (EF_FLAG1 | EF_FLAG2); + if (player->health > 0) { + if (player->client->pers.inventory[ITEM_INDEX(flag1_item)]) { + player->s.effects |= EF_FLAG1; + } + if (player->client->pers.inventory[ITEM_INDEX(flag2_item)]) { + player->s.effects |= EF_FLAG2; + } + } + + if (player->client->pers.inventory[ITEM_INDEX(flag1_item)]) + player->s.modelindex3 = gi.modelindex("players/male/flag1.md2"); + else if (player->client->pers.inventory[ITEM_INDEX(flag2_item)]) + player->s.modelindex3 = gi.modelindex("players/male/flag2.md2"); + else + player->s.modelindex3 = 0; +} + +// called when we enter the intermission +void CTFCalcScores(void) +{ + int i; + + ctfgame.total1 = ctfgame.total2 = 0; + for (i = 0; i < maxclients->value; i++) { + if (!g_edicts[i+1].inuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + ctfgame.total1 += game.clients[i].resp.score; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + ctfgame.total2 += game.clients[i].resp.score; + } +} + +void CTFID_f (edict_t *ent) +{ + if (ent->client->resp.id_state) { + safe_cprintf(ent, PRINT_HIGH, "Disabling player identication display.\n"); + ent->client->resp.id_state = false; + } else { + safe_cprintf(ent, PRINT_HIGH, "Activating player identication display.\n"); + ent->client->resp.id_state = true; + } +} + +static void CTFSetIDView(edict_t *ent) +{ + vec3_t start,end,endp,offset; + vec3_t forward,right,up; + trace_t tr; + int old_armor_index; + // TeT use a local for id + edict_t* id; + + //Spawn Inviso Bullet + + PMenu_Close(ent); + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(end,100 , 0, 0); + G_ProjectSource (ent->s.origin, end, forward, right, start); + id = G_Spawn (); + id->owner = ent; + id->movetype = MOVETYPE_NOCLIP; + id->solid = SOLID_NOT; + id->classname = "id"; + + //Trace a line to the target + AngleVectors (id->owner->client->v_angle, forward, right, up); + VectorSet(offset,24 , 6, id->owner->viewheight-7); + G_ProjectSource (id->owner->s.origin, offset, forward, right, start); + VectorMA(start,8192,forward,end); + tr = gi.trace (start,NULL,NULL, end,id->owner, + CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + if (tr.fraction != 1) + { + VectorMA(tr.endpos,-4,forward,endp); + VectorCopy(endp,tr.endpos); + } + + while (ent->client->resp.id_state == true) + { + //Client ID + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) //detected client + { + if (tr.ent->deadflag == DEAD_DEAD) break; + // Enemies + if ((tr.ent->takedamage) && (tr.ent != id->owner) && (tr.ent->wf_team != ent->wf_team)) //detected enemy + { + //Decoy? + if (!tr.ent->client) + { + sprintf(ent->client->wfidstr[15], "[Enemy Decoy]\n"); + sprintf(ent->client->wfidstr[16], ""); + ent->client->resp.id_state = false; + break; + } + else if ((strcmp(classinfo[tr.ent->client->pers.player_class].name, "Spy") == 0)) + //detected enemy spy + { + if (tr.ent->disguised) + { + if ((strcmp(classinfo[ent->client->pers.player_class].name, "Spy") == 0)) + //spy vs spy (no more secrets) + { + sprintf(ent->client->wfidstr[15], "[DETECTED! %s - Enemy %s]\n", tr.ent->client->pers.netname, classinfo[tr.ent->client->pers.player_class].name); + sprintf(ent->client->wfidstr[16], ""); + ent->client->resp.id_state = false; + break; + } + break; //he's disguised, don't show + } + } + sprintf(ent->client->wfidstr[15], "[%s - Enemy %s]\n", tr.ent->client->pers.netname, classinfo[tr.ent->client->pers.player_class].name); + sprintf(ent->client->wfidstr[16], ""); + ent->client->resp.id_state = false; + break; + } + + // Friends + if ((tr.ent->takedamage) && (tr.ent != id->owner) && (tr.ent->wf_team == ent->wf_team)) //detected friend + { + //Decoy? + if (!tr.ent->client) + { + sprintf(ent->client->wfidstr[15], "[Friendly Decoy]\n"); + sprintf(ent->client->wfidstr[16], ""); + ent->client->resp.id_state = false; + break; + } + if ((strcmp(classinfo[tr.ent->client->pers.player_class].name, "Spy") == 0)) + { + if (tr.ent->disguised) + { + if ((strcmp(classinfo[ent->client->pers.player_class].name, "Spy") == 0)) + { + sprintf(ent->client->wfidstr[15], "[DETECTED! %s - Friendly %s]\n", tr.ent->client->pers.netname, classinfo[tr.ent->client->pers.player_class].name); + sprintf(ent->client->wfidstr[16], ""); + ent->client->resp.id_state = false; + break; //friendly spy seeing friendly spy.. show ID + } + break; //friendly spy disguised so don't show his id + } + } + if ((strcmp(classinfo[ent->client->pers.player_class].name, "Nurse") == 0)) + { + sprintf(ent->client->wfidstr[15], "[%s - Friendly %s]\n", tr.ent->client->pers.netname, classinfo[tr.ent->client->pers.player_class].name); + sprintf(ent->client->wfidstr[16], "[Health - %d]\n", tr.ent->health); + ent->client->resp.id_state = false; + break; + } + if ((strcmp(classinfo[ent->client->pers.player_class].name, "Engineer") == 0)) + { + old_armor_index = ArmorIndex (tr.ent); + sprintf(ent->client->wfidstr[15], "[%s - Friendly %s]\n", tr.ent->client->pers.netname, classinfo[tr.ent->client->pers.player_class].name); + sprintf(ent->client->wfidstr[16], "[Armor %d/%d]", tr.ent->client->pers.inventory[old_armor_index], tr.ent->client->player_armor); + ent->client->resp.id_state = false; + break; + } + sprintf(ent->client->wfidstr[15], "[%s - Friendly %s]\n", tr.ent->client->pers.netname, classinfo[tr.ent->client->pers.player_class].name); + sprintf(ent->client->wfidstr[16], ""); + ent->client->resp.id_state = false; + break; + } + } + + //Depot ID + if ((strcmp(tr.ent->classname, "depot") == 0)) //detected depot + { + if (tr.ent->wf_team != ent->wf_team) + { + sprintf(ent->client->wfidstr[15], "[%s's Enemy Ammo Depot]\n", tr.ent->owner->client->pers.netname); + sprintf(ent->client->wfidstr[16], ""); + ent->client->resp.id_state = false; + break; + } + if ( tr.ent->wf_team == ent->wf_team) + { + if (tr.ent->owner == ent) + { + sprintf(ent->client->wfidstr[15], "[Your Ammo Depot]\n"); + sprintf(ent->client->wfidstr[16], ""); + ent->client->resp.id_state = false; + break; + } + sprintf(ent->client->wfidstr[15], "[%s's Friendly Ammo Depot]\n", tr.ent->owner->client->pers.netname); + sprintf(ent->client->wfidstr[16], ""); + ent->client->resp.id_state = false; + break; + } + } + + //Sentry ID + if ((strcmp(tr.ent->classname, "SentryGun") == 0)) //detected sentry + { + if (tr.ent->wf_team != ent->wf_team) + { + sprintf(ent->client->wfidstr[15], "[%s's Enemy Sentry]\n", tr.ent->standowner->client->pers.netname); + sprintf(ent->client->wfidstr[16], ""); + if ((strcmp(classinfo[ent->client->pers.player_class].name, "Spy") == 0)) //spy looking at enemy sentry + { + sprintf(ent->client->wfidstr[16], "[%d Bullets]\n", tr.ent->light_level); //spy seeing sentry ammo + ent->client->resp.id_state = false; + break; + } + } + if (tr.ent->wf_team == ent->wf_team) + { + if(tr.ent->standowner == ent) + sprintf(ent->client->wfidstr[15], "[Your Sentry Level:%d]\n", tr.ent->count); + else + sprintf(ent->client->wfidstr[15], "[%s's Friendly Sentry Level:%d]\n", tr.ent->standowner->client->pers.netname, tr.ent->count); + + if ((strcmp(classinfo[ent->client->pers.player_class].name, "Engineer") == 0)) + sprintf(ent->client->wfidstr[16], "[Armor:%d/%d Ammo:%d/%d]", tr.ent->health, tr.ent->max_health, tr.ent->light_level, tr.ent->gib_health); + else + sprintf(ent->client->wfidstr[16], ""); + + ent->client->resp.id_state = false; + break; + } + } + break; + } + + //set up the ID display + if (ent->client->resp.id_state == false) + { + sprintf(ent->client->wfidstr[0], ""); + ent->client->iddisplay[0].text = ent->client->wfidstr[0]; + sprintf(ent->client->wfidstr[1], ""); + ent->client->iddisplay[1].text = ent->client->wfidstr[1]; + sprintf(ent->client->wfidstr[2], ""); + ent->client->iddisplay[2].text = ent->client->wfidstr[2]; + sprintf(ent->client->wfidstr[3], ""); + ent->client->iddisplay[3].text = ent->client->wfidstr[3]; + sprintf(ent->client->wfidstr[4], ""); + ent->client->iddisplay[4].text = ent->client->wfidstr[4]; + sprintf(ent->client->wfidstr[5], ""); + ent->client->iddisplay[5].text = ent->client->wfidstr[5]; + sprintf(ent->client->wfidstr[6], ""); + ent->client->iddisplay[6].text = ent->client->wfidstr[6]; + sprintf(ent->client->wfidstr[7], ""); + ent->client->iddisplay[7].text = ent->client->wfidstr[7]; + sprintf(ent->client->wfidstr[8], ""); + ent->client->iddisplay[8].text = ent->client->wfidstr[8]; + sprintf(ent->client->wfidstr[9], ""); + ent->client->iddisplay[9].text = ent->client->wfidstr[9]; + sprintf(ent->client->wfidstr[10], ""); + ent->client->iddisplay[10].text = ent->client->wfidstr[10]; + sprintf(ent->client->wfidstr[11], ""); + ent->client->iddisplay[11].text = ent->client->wfidstr[11]; + sprintf(ent->client->wfidstr[12], ""); + ent->client->iddisplay[12].text = ent->client->wfidstr[12]; + sprintf(ent->client->wfidstr[13], ""); + ent->client->iddisplay[13].text = ent->client->wfidstr[13]; + sprintf(ent->client->wfidstr[14], ""); + ent->client->iddisplay[14].text = ent->client->wfidstr[14]; //spacers, UGLY but works + + //id specific sprint defines 15 & 16, 16 is special for health or armor + ent->client->iddisplay[15].text = ent->client->wfidstr[15]; + ent->client->iddisplay[15].align = PMENU_ALIGN_CENTER; + ent->client->iddisplay[16].text = ent->client->wfidstr[16]; + ent->client->iddisplay[16].align = PMENU_ALIGN_CENTER; + PMenu_Open(ent, ent->client->iddisplay, -1, sizeof(ent->client->iddisplay) / sizeof(pmenu_t), true, false); + } + + //clear the menus and reset the resp id, kill the "bullet" entity + if (ent->client->menu) ent->client->menu->MenuTimeout = level.time + 2; + ent->client->resp.id_state = false; + vectoangles(tr.plane.normal,id->s.angles); + VectorCopy(tr.endpos,id->s.origin); + gi.linkentity (id); + G_FreeEdict (id); + id = NULL; + +} + +void SetCTFStats(edict_t *ent) +{ + gitem_t *tech; + int i; + int p1, p2; + edict_t *e; + + // logo headers for the frag display + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = gi.imageindex ("ctfsb1"); + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = gi.imageindex ("ctfsb2"); + + // if during intermission, we must blink the team header of the winning team + if (level.intermissiontime && (level.framenum & 8)) + { // blink 1/8th second + // note that ctfgame.total[12] is set when we go to intermission + if (ctfgame.team1 > ctfgame.team2) + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + else if (ctfgame.team2 > ctfgame.team1) + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + else if (ctfgame.total2 > ctfgame.total1) + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + else + { // tie game! + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + } + } + + // tech icon + i = 0; + ent->client->ps.stats[STAT_CTF_TECH] = 0; + + // ++TeT + // only look for tech if they are allowed + if (!((int)dmflags->value & DF_CTF_NO_TECH)) + { + // -- TeT + while (tnames[i]) + { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(tech)]) + { + ent->client->ps.stats[STAT_CTF_TECH] = gi.imageindex(tech->icon); + break; + } + i++; + } + } + + // figure out what icon to display for team logos + // three states: + // flag at base + // flag taken + // flag dropped + p1 = gi.imageindex ("i_ctf1"); + e = G_Find(NULL, FOFS(classname), "item_flag_team1"); + if (e != NULL) + { + if (e->solid == SOLID_NOT) //hidden, so someone must have it + { + int i; + int someonehasit = 0; + + // not at base + // check if on player + p1 = gi.imageindex ("i_ctf1d"); // default to dropped + for (i = 1; i <= maxclients->value; i++) + { + if (g_edicts[i].inuse && + g_edicts[i].client->pers.inventory[ITEM_INDEX(flag1_item)]) + { + // enemy has it + p1 = gi.imageindex ("i_ctf1t"); + someonehasit = 1; + break; + } + } + if ((someonehasit == 0) && (flag1dropped == 0)) + { + gi.dprintf("MISSING FLAG 1 RESET BACK TO BASE\n"); + CTFResetFlag(CTF_TEAM1); + } + } + else if (e->spawnflags & DROPPED_ITEM) + p1 = gi.imageindex ("i_ctf1d"); // must be dropped + } + p2 = gi.imageindex ("i_ctf2"); + e = G_Find(NULL, FOFS(classname), "item_flag_team2"); + if (e != NULL) + { + if (e->solid == SOLID_NOT) + { + int i; + int someonehasit = 0; + + // not at base + // check if on player + p2 = gi.imageindex ("i_ctf2d"); // default to dropped + for (i = 1; i <= maxclients->value; i++) + { + if (g_edicts[i].inuse && + g_edicts[i].client->pers.inventory[ITEM_INDEX(flag2_item)]) + { + // enemy has it + p2 = gi.imageindex ("i_ctf2t"); + someonehasit = 1; + break; + } + } + if ((someonehasit == 0) && (flag2dropped == 0)) + { + gi.dprintf("MISSING FLAG 2 RESET BACK TO BASE\n"); + CTFResetFlag(CTF_TEAM2); + } + + } + else if (e->spawnflags & DROPPED_ITEM) + p2 = gi.imageindex ("i_ctf2d"); // must be dropped + } + + + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1; + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2; + + if (ctfgame.last_flag_capture && level.time - ctfgame.last_flag_capture < 5) { + if (ctfgame.last_capture_team == CTF_TEAM1) + if (level.framenum & 8) + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1; + else + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = 0; + else + if (level.framenum & 8) + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2; + else + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = 0; + } + + ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = ctfgame.team1; + ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = ctfgame.team2; + + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = 0; + if (ent->client->resp.ctf_team == CTF_TEAM1 && + ent->client->pers.inventory[ITEM_INDEX(flag2_item)] && + (level.framenum & 8)) + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = gi.imageindex ("i_ctf2"); + + else if (ent->client->resp.ctf_team == CTF_TEAM2 && + ent->client->pers.inventory[ITEM_INDEX(flag1_item)] && + (level.framenum & 8)) + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = gi.imageindex ("i_ctf1"); + + ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = 0; + ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = 0; + if (ent->client->resp.ctf_team == CTF_TEAM1) + ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = gi.imageindex ("i_ctfj"); + else if (ent->client->resp.ctf_team == CTF_TEAM2) + ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = gi.imageindex ("i_ctfj"); + + ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0; + if (ent->client->resp.id_state) + CTFSetIDView(ent); +} + +/*------------------------------------------------------------------------*/ + +/*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 32) +potential team1 spawning position for ctf games +*/ +void SP_info_player_team1(edict_t *self) +{ +} + +/*QUAKED info_player_team2 (0 0 1) (-16 -16 -24) (16 16 32) +potential team2 spawning position for ctf games +*/ +void SP_info_player_team2(edict_t *self) +{ +} + + +/*------------------------------------------------------------------------*/ +/* GRAPPLE */ +/*------------------------------------------------------------------------*/ +//ERASER START +void KillGrappleSoundKiller(edict_t *owner) +{ +// if there is a grapple sound killer attached to this grapple, kill it + edict_t *trav=NULL; + + while (trav = G_Find(trav, FOFS(classname), "grapple_sound_killer")) + { + if (trav->owner == owner) + { + G_FreeEdict(trav); + break; + } + } +} + +void KillGrappleSound(edict_t *ent) +{ + gi.sound (ent->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), 1, ATTN_NORM, 0); + G_FreeEdict(ent); +} + +void SpawnGrappleSoundKiller(edict_t *owner) +{ +// Fix for continuous sounds + edict_t *ent; + + KillGrappleSoundKiller(owner); + + ent = G_Spawn(); + ent->classname = "grapple_sound_killer"; + ent->owner = owner; + ent->think = KillGrappleSound; + ent->nextthink = level.time + 0.5; // allow some time for them to get pulled +} +//ERASER END + +// ent is player +void CTFPlayerResetGrapple(edict_t *ent) +{ + if (ent->client && ent->client->ctf_grapple) + CTFResetGrapple(ent->client->ctf_grapple); +} + +// self is grapple, not player +void CTFResetGrapple(edict_t *self) +{ + if ((self->owner) && (self->owner->client) && (self->owner->client->ctf_grapple)) { +// if (self->owner->client->ctf_grapple) {//ERASER + float volume = 1.0; + gclient_t *cl; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + KillGrappleSoundKiller(self->owner);//ERASER + + if ((self->owner) && (self->owner->client)) + { + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), volume, ATTN_NORM, 0); + cl = self->owner->client; + cl->ctf_grapple = NULL; + cl->ctf_grapplereleasetime = level.time; + cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + cl->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + } + G_FreeEdict(self); + } + +} + +void CTFGrappleTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + float volume = 1.0; + + if (other == self->owner) + return; + + if (self->owner->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + CTFResetGrapple(self); + return; + } + + VectorCopy(vec3_origin, self->velocity); + + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage || other->client) {//ERASER ADDED || other->client + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, 0, MOD_GRAPPLE); + CTFResetGrapple(self); + return; + } + + self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_PULL; // we're on hook + self->enemy = other; + + self->owner->client->ctf_grapplestart = level.time;//ERASER + + self->solid = SOLID_NOT; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grpull.wav"), volume, ATTN_NORM, 0); + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhit.wav"), volume, ATTN_NORM, 0); + + SpawnGrappleSoundKiller(self->owner);//ERASER + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPARKS); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + +// draw beam between grapple and self +void CTFGrappleDrawCable(edict_t *self) +{ + vec3_t offset, start, end, f, r; + vec3_t dir; + float distance; + + AngleVectors (self->owner->client->v_angle, f, r, NULL); + VectorSet(offset, 16, 16, self->owner->viewheight-8); + P_ProjectSource (self->owner->client, self->owner->s.origin, offset, f, r, start); + + VectorSubtract(start, self->owner->s.origin, offset); + + VectorSubtract (start, self->s.origin, dir); + distance = VectorLength(dir); + // don't draw cable if close + if (distance < 64) + return; + +#if 0 + if (distance > 256) + return; + + // check for min/max pitch + vectoangles (dir, angles); + if (angles[0] < -180) + angles[0] += 360; + if (fabs(angles[0]) > 45) + return; + + trace_t tr; //!! + + tr = gi.trace (start, NULL, NULL, self->s.origin, self, MASK_SHOT); + if (tr.ent != self) { + CTFResetGrapple(self); + return; + } +#endif + + // adjust start for beam origin being in middle of a segment +// VectorMA (start, 8, f, start); + + VectorCopy (self->s.origin, end); + // adjust end z for end spot since the monster is currently dead +// end[2] = self->absmin[2] + self->size[2] / 2; + + gi.WriteByte (svc_temp_entity); +#if 1 //def USE_GRAPPLE_CABLE + gi.WriteByte (TE_GRAPPLE_CABLE); + gi.WriteShort (self->owner - g_edicts); + gi.WritePosition (self->owner->s.origin); + gi.WritePosition (end); + gi.WritePosition (offset); +#else + gi.WriteByte (TE_MEDIC_CABLE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (end); + gi.WritePosition (start); +#endif + gi.multicast (self->s.origin, MULTICAST_PVS); +} + +void SV_AddGravity (edict_t *ent); + +// pull the player toward the grapple +void CTFGrapplePull(edict_t *self) +{ + vec3_t hookdir, v; + float vlen; +//WF + if (!self->owner) + return; + + if (!self->owner->client) + return; + + if (!self->owner->client->pers.weapon) + return; + + if (!self->owner->client->pers.weapon->classname) + return; +//WF + + +if (!self->owner->bot_client)//ERASER + if (strcmp(self->owner->client->pers.weapon->classname, "weapon_grapple") == 0 && + !self->owner->client->newweapon && + self->owner->client->weaponstate != WEAPON_FIRING && + self->owner->client->weaponstate != WEAPON_ACTIVATING) + { + CTFResetGrapple(self); + return; + } + + + if (self->enemy) { + if (self->enemy->solid == SOLID_NOT) { + CTFResetGrapple(self); + return; + } + if (self->enemy->solid == SOLID_BBOX) { + VectorScale(self->enemy->size, 0.5, v); + VectorAdd(v, self->enemy->s.origin, v); + VectorAdd(v, self->enemy->mins, self->s.origin); + gi.linkentity (self); + } else + VectorCopy(self->enemy->velocity, self->velocity); + if (self->enemy->takedamage && + !CheckTeamDamage (self->enemy, self->owner)) { + float volume = 1.0; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + T_Damage (self->enemy, self, self->owner, self->velocity, self->s.origin, vec3_origin, 1, 1, 0, MOD_GRAPPLE); + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhurt.wav"), volume, ATTN_NORM, 0); + } + if (!self->enemy || self->enemy->deadflag) { // he died//ERASER ADDED || self->enemy->deadflag + CTFResetGrapple(self); + return; + } + } + + CTFGrappleDrawCable(self); + + if (self->owner->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) { + // pull player toward grapple + // this causes icky stuff with prediction, we need to extend + // the prediction layer to include two new fields in the player + // move stuff: a point and a velocity. The client should add + // that velociy in the direction of the point + vec3_t forward, up; + + AngleVectors (self->owner->client->v_angle, forward, NULL, up); + VectorCopy(self->owner->s.origin, v); + v[2] += self->owner->viewheight; + VectorSubtract (self->s.origin, v, hookdir); + + vlen = VectorLength(hookdir); + + if (self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL && + vlen < 64) { + float volume = 1.0; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + self->owner->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grhang.wav"), volume, ATTN_NORM, 0); + self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_HANG; + } + + VectorNormalize (hookdir); + VectorScale(hookdir, CTF_GRAPPLE_PULL_SPEED, hookdir); + VectorCopy(hookdir, self->owner->velocity); + SV_AddGravity(self->owner); + } +} + +//ERASER START +void CTFGrappleThink( edict_t *self ) +{ + if (!self->owner || !self->owner->client) + { + G_FreeEdict(self); + return; + } + + if ( (self->owner->health <= 0) + || (self->owner->client->ctf_grapple != self)) + { + gclient_t *cl; + + cl = self->owner->client; + + if (cl->ctf_grapple == self) + { + cl->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + KillGrappleSoundKiller(self->owner); + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), 1, ATTN_NORM, 0); + cl->ctf_grapple = NULL; + cl->ctf_grapplereleasetime = level.time; + cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + } + + G_FreeEdict(self); + + return; + } + + self->nextthink = level.time + FRAMETIME; +} +//ERASER END +void CTFFireGrapple (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *grapple; + trace_t tr; + + VectorNormalize (dir); + + grapple = G_Spawn(); + VectorCopy (start, grapple->s.origin); + VectorCopy (start, grapple->s.old_origin); + vectoangles (dir, grapple->s.angles); + VectorScale (dir, speed, grapple->velocity); + grapple->movetype = MOVETYPE_FLYMISSILE; + grapple->clipmask = MASK_SHOT; + grapple->solid = SOLID_BBOX; + grapple->s.effects |= effect; + VectorClear (grapple->mins); + VectorClear (grapple->maxs); + grapple->s.modelindex = gi.modelindex ("models/weapons/grapple/hook/tris.md2"); +// grapple->s.sound = gi.soundindex ("misc/lasfly.wav"); + grapple->owner = self; + grapple->touch = CTFGrappleTouch; + grapple->nextthink = level.time + FRAMETIME;//WF24 HAS THIS CO + grapple->think = CTFGrappleThink;//WF24 HAS THIS CO + grapple->dmg = damage; + self->client->ctf_grapple = grapple; + self->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + gi.linkentity (grapple); + + tr = gi.trace (self->s.origin, NULL, NULL, grapple->s.origin, grapple, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (grapple->s.origin, -10, dir, grapple->s.origin); + grapple->touch (grapple, tr.ent, NULL, NULL); + } +} + +void CTFGrappleFire (edict_t *ent, vec3_t g_offset, int damage, int effect) +{ + vec3_t forward, right; + vec3_t start; + vec3_t offset; + float volume = 1.0; + + if (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + return; // it's already out + + AngleVectors (ent->client->v_angle, forward, right, NULL); +// VectorSet(offset, 24, 16, ent->viewheight-8+2); + VectorSet(offset, 24, 8, ent->viewheight-8+2); + 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; + + if (ent->client->silencer_shots) + volume = 0.2; + + gi.sound (ent, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grfire.wav"), volume, ATTN_NORM, 0); + CTFFireGrapple (ent, start, forward, damage, CTF_GRAPPLE_SPEED, effect); + + SpawnGrappleSoundKiller(ent);//ERASER + +#if 0 + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_BLASTER); + gi.multicast (ent->s.origin, MULTICAST_PVS); +#endif + + PlayerNoise(ent, start, PNOISE_WEAPON); +//Eraser START: record this position, so we drop a grapple node here, rather than where the player is when they leave the ground + if (!ent->bot_client) + { botDebugPrint("Grapple pull\n"); + VectorCopy(ent->s.origin, ent->animate_org);//fixme acrid + } +//ERASER END +} + + +void CTFWeapon_Grapple_Fire (edict_t *ent) +{ + int damage; + + damage = 10; + CTFGrappleFire (ent, vec3_origin, damage, 0); + ent->client->ps.gunframe++; +} + +void CTFWeapon_Grapple (edict_t *ent) +{ + static int pause_frames[] = {10, 18, 27, 0}; + static int fire_frames[] = {6, 0}; + int prevstate; + + // if the the attack button is still down, stay in the firing frame + if ((ent->client->buttons & BUTTON_ATTACK) && + ent->client->weaponstate == WEAPON_FIRING && + ent->client->ctf_grapple) + ent->client->ps.gunframe = 9; + + if (!(ent->client->buttons & BUTTON_ATTACK) && + ent->client->ctf_grapple) { + CTFResetGrapple(ent->client->ctf_grapple); + if (ent->client->weaponstate == WEAPON_FIRING) + ent->client->weaponstate = WEAPON_READY; + } + + + if (ent->client->newweapon && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY && + ent->client->weaponstate == WEAPON_FIRING) { + // he wants to change weapons while grappled + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 32; + } + + prevstate = ent->client->weaponstate; + Weapon_Generic (ent, 5, 9, 31, 36, pause_frames, fire_frames, + CTFWeapon_Grapple_Fire); + + // if we just switched back to grapple, immediately go to fire frame + if (prevstate == WEAPON_ACTIVATING && + ent->client->weaponstate == WEAPON_READY && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) { + if (!(ent->client->buttons & BUTTON_ATTACK)) + ent->client->ps.gunframe = 9; + else + ent->client->ps.gunframe = 5; + ent->client->weaponstate = WEAPON_FIRING; + } +} + +//Determine which team to switch to +int get_mercenary_team(edict_t *ent) +{ + int team1count = 0, team2count = 0; + int merc_team; + int i; + edict_t *player; + + //Count players per team to see if we can switch + for (i = 1; i <= maxclients->value; i++) { + player = &g_edicts[i]; + + if (!player->inuse) + continue; + switch (player->client->resp.ctf_team) { + case CTF_TEAM1: + team1count++; + break; + case CTF_TEAM2: + team2count++; + } + } + + //Which team is loosing? + if (ctfgame.team1 == ctfgame.team2) + merc_team = ent->client->resp.ctf_team; //same score, stay the same + else if (ctfgame.team1 > ctfgame.team2) //team 2 is loosing + { + if ((team2count - team1count) >= 2) //team2 already have enough + { + merc_team = CTF_TEAM1; + } + else + { + merc_team = CTF_TEAM2; + } + } + else //team 1 is loosing + { + if ((team1count - team2count) >= 2) //team1 already have enough + { + merc_team = CTF_TEAM2; + } + else + { + merc_team = CTF_TEAM1; + } + } + + return merc_team; + +} + +void CTFTeam_f (edict_t *ent) +{ + char *t, *s; + int desired_team; + int loosing_team; + + if (!ctf->value) + return; + + //WF + if (ent->solid == SOLID_NOT) + return; + if((level.time - ent->client->respawn_time) < 5) + { + safe_cprintf(ent, PRINT_HIGH, "Rapid Team switch not allowed.\n"); + return; + } + //WF + + t = gi.args(); + if (!*t) { + safe_cprintf(ent, PRINT_HIGH, "You are on the %s team.\n", + CTFTeamName(ent->client->resp.ctf_team)); + return; + } + if (Q_stricmp(t, "red") == 0) + desired_team = CTF_TEAM1; + else if (Q_stricmp(t, "blue") == 0) + desired_team = CTF_TEAM2; + else { + safe_cprintf(ent, PRINT_HIGH, "Unknown team %s.\n", t); + return; + } + + if (ent->client->resp.ctf_team == desired_team) { + safe_cprintf(ent, PRINT_HIGH, "You are already on the %s team.\n", + CTFTeamName(ent->client->resp.ctf_team)); + return; + } + + //Mercenary ability only allows you to switch to loosing team + if (ent->client->player_special & SPECIAL_MERCENARY) + { + loosing_team = get_mercenary_team(ent); + if (loosing_team != desired_team) + { + safe_cprintf(ent, PRINT_HIGH, "Team switch not allowed at this time.\n"); + return; + } + } + + +//// + ent->svflags = 0; + ent->flags &= ~FL_GODMODE; + + //If we are playing in CTF mode, they picked team. Otherwise it was assigned to them + ent->client->resp.ctf_team = desired_team; + ent->wf_team = desired_team; + ent->client->resp.next_ctf_team = desired_team; + + ent->client->resp.ctf_state = CTF_STATE_START; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + + //Remove all their old stuff lying around + //gi.dprintf("Team changed-removing stuff.\n"); + WFPlayer_ChangeClassTeam(ent); + + sl_LogPlayerTeamChange( &gi, + ent->client->pers.netname, + CTFTeamName(ent->client->resp.ctf_team)); + + if (ent->solid == SOLID_NOT) { // spectator + PutClientInServer (ent); + // add a teleportation effect + ent->s.event = EV_PLAYER_TELEPORT; + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + if (ctf->value) my_bprintf(PRINT_HIGH, "%s joined the %s team.\n", + ent->client->pers.netname, CTFTeamName(desired_team)); + sl_LogPlayerTeamChange( &gi, + ent->client->pers.netname, + CTFTeamName(ent->client->resp.ctf_team)); + return; + } + + ent->health = 0; + player_die (ent, ent, ent, 100000, vec3_origin); + // don't even bother waiting for death frames + ent->deadflag = DEAD_DEAD; + respawn (ent); + + //Don't clear scores for mercenary capability + if (!(ent->client->player_special & SPECIAL_MERCENARY)) + ent->client->resp.score = 0; + + my_bprintf(PRINT_HIGH, "%s changed to the %s team.\n", + ent->client->pers.netname, CTFTeamName(desired_team)); + sl_LogPlayerTeamChange( &gi, + ent->client->pers.netname, + CTFTeamName(ent->client->resp.ctf_team)); +} + +/* +================== +CTFScoreboardMessage +================== +*/ +void CTFScoreboardMessage (edict_t *ent, edict_t *killer) +{ + char entry[1024]; + char string[1400]; + char classcode; + int len; + int i, j, k, n; + int sorted[2][MAX_CLIENTS]; + int sortedscores[2][MAX_CLIENTS]; + int score, total[2], totalscore[2]; + int last[2]; + gclient_t *cl; + edict_t *cl_ent; + int team; + int maxsize = 1000; + + // sort the clients by team and score + total[0] = total[1] = 0; + last[0] = last[1] = 0; + totalscore[0] = totalscore[1] = 0; + for (i=0 ; iinuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + team = 0; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + team = 1; + else + continue; // unknown team? + + score = game.clients[i].resp.score; + for (j=0 ; j 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]++; + } + + // print level name and exit rules + // add the clients in sorted order + *string = 0; + len = 0; + + // team one + sprintf(string, "if 24 xv 8 yv 8 pic 24 endif " + "xv 40 yv 28 string \"%4d/%-3d\" " + "xv 98 yv 12 num 2 18 " + "if 25 xv 168 yv 8 pic 25 endif " + "xv 200 yv 28 string \"%4d/%-3d\" " + "xv 256 yv 12 num 2 20 ", + totalscore[0], total[0], + totalscore[1], total[1]); + len = strlen(string); + + for (i=0 ; i<16 ; i++) + { + if (i >= total[0] && i >= total[1]) + break; // we're done + +#if 0 //ndef NEW_SCORE + // set up y + sprintf(entry, "yv %d ", 42 + i * 8); + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + } +#else + *entry = 0; +#endif + + // left side + if (i < total[0]) { + cl = &game.clients[sorted[0][i]]; + cl_ent = g_edicts + 1 + sorted[0][i]; + classcode = getClassCode (cl); + +//ACRID ADDED FOR REALISM ERASER + if (cl_ent->bot_client) + { + cl->ping = (int) cl_ent->bot_stats->avg_ping + ((random() * 2) - 1) * 80; + if (cl->ping < 0) + cl->ping = 0; + } +//END + +#if 0 //ndef NEW_SCORE + sprintf(entry+strlen(entry), + "xv 0 %s \"%3d %3d %c %-12.12s\" ", + (cl_ent == ent) ? "string2" : "string", + cl->resp.score, + (cl->ping > 999) ? 999 : cl->ping, + classcode, + cl->pers.netname); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)]) + strcat(entry, "xv 56 picn sbfctf2 "); +#else + sprintf(entry+strlen(entry), + "ctf 0 %d %d %d %d ", + 42 + i * 8, + sorted[0][i], + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)]) + sprintf(entry + strlen(entry), "xv 56 yv %d picn sbfctf2 ", + 42 + i * 8); +#endif + + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + last[0] = i; + } + } + + // right side + if (i < total[1]) { + cl = &game.clients[sorted[1][i]]; + cl_ent = g_edicts + 1 + sorted[1][i]; + classcode = getClassCode (cl); + +//ACRID ADDED FOR REALISM ERASER + if (cl_ent->bot_client) + { + cl->ping = (int) cl_ent->bot_stats->avg_ping + ((random() * 2) - 1) * 80; + if (cl->ping < 0) + cl->ping = 0; + } +//END + +#if 0 //ndef NEW_SCORE + sprintf(entry+strlen(entry), + "xv 160 %s \"%3d %3d %c %-12.12s\" ", + (cl_ent == ent) ? "string2" : "string", + cl->resp.score, + (cl->ping > 999) ? 999 : cl->ping, + classcode, + cl->pers.netname); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)]) + strcat(entry, "xv 216 picn sbfctf1 "); + +#else + + sprintf(entry+strlen(entry), + "ctf 160 %d %d %d %d ", + 42 + i * 8, + sorted[1][i], + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)]) + sprintf(entry + strlen(entry), "xv 216 yv %d picn sbfctf1 ", + 42 + i * 8); +#endif + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + last[1] = i; + } + } + } + + // put in spectators if we have enough room + if (last[0] > last[1]) + j = last[0]; + else + j = last[1]; + j = (j + 2) * 8 + 42; + + k = n = 0; + if (maxsize - len > 50) { + for (i = 0; i < maxclients->value; i++) { + cl_ent = g_edicts + 1 + i; + cl = &game.clients[i]; + if (!cl_ent->inuse || + cl_ent->solid != SOLID_NOT || + cl_ent->client->resp.ctf_team != CTF_NOTEAM) + continue; + + if (!k) { + k = 1; + sprintf(entry, "xv 0 yv %d string2 \"Spectators\" ", j); + strcat(string, entry); + len = strlen(string); + j += 8; + } + + sprintf(entry+strlen(entry), + "ctf %d %d %d %d %d ", + (n & 1) ? 160 : 0, // x + j, // y + i, // playernum + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + } + + if (n & 1) + j += 8; + n++; + } + } + + if (total[0] - last[0] > 1) // couldn't fit everyone + sprintf(string + strlen(string), "xv 8 yv %d string \"..and %d more\" ", + 42 + (last[0]+1)*8, total[0] - last[0] - 1); + if (total[1] - last[1] > 1) // couldn't fit everyone + sprintf(string + strlen(string), "xv 168 yv %d string \"..and %d more\" ", + 42 + (last[1]+1)*8, total[1] - last[1] - 1); + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + +/*------------------------------------------------------------------------*/ +/* TECH */ +/*------------------------------------------------------------------------*/ + +void CTFHasTech(edict_t *who) +{ + if (level.time - who->client->ctf_lasttechmsg > 2) { + if (!who->bot_client)//ERASER + gi.centerprintf(who, "You already have a TECH powerup."); + who->client->ctf_lasttechmsg = level.time; + } +} + +gitem_t *CTFWhat_Tech(edict_t *ent) +{ + gitem_t *tech; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + return tech; + } + i++; + } + return NULL; +} + +qboolean CTFPickup_Tech (edict_t *ent, edict_t *other) +{ +//WF24 S +// gitem_t *tech; +// int i; +//WF24 E GOES WITH BELOW CO LINES +//ERASER START + if ( (other->client->pers.inventory[ITEM_INDEX(item_tech1)]) + || (other->client->pers.inventory[ITEM_INDEX(item_tech2)]) + || (other->client->pers.inventory[ITEM_INDEX(item_tech3)]) + || (other->client->pers.inventory[ITEM_INDEX(item_tech4)])) + { + CTFHasTech(other); + + if (other->bot_client && (other->movetarget == ent)) + { // stop going for this item + other->movetarget = other->goalentity = NULL; + ent->ignore_time = level.time + 3; + } + + return false; // has this one + } +/* + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + other->client->pers.inventory[ITEM_INDEX(tech)]) { + CTFHasTech(other); + return false; // has this one + } + i++; + } +*/ + // client only gets one tech + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + other->client->ctf_regentime = level.time; + other->client->ctf_has_tech = true;//ERASER CHECK FOR DUPE + return true; +} + +static void SpawnTech(gitem_t *item, edict_t *spot); + +static edict_t *FindTechSpawn(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"); + return spot; +} + +static void TechThink(edict_t *tech) +{ + edict_t *spot; + + if ((spot = FindTechSpawn()) != NULL) { + SpawnTech(tech->item, spot); + RemoveFromItemList(tech);//ERASER + G_FreeEdict(tech); + } else { + tech->nextthink = level.time + CTF_TECH_TIMEOUT; + tech->think = TechThink; + } +} + +void CTFDrop_Tech(edict_t *ent, gitem_t *item) +{ + edict_t *tech; + + //Don't drop auto-doc if player was given auto-doc on startup + if ((strcmp(item->classname, "item_tech4") == 0) && (ent->client->player_special & SPECIAL_HEALING)) + { + //Don't do anything in this case + } + else + { + tech = Drop_Item(ent, item); + tech->nextthink = level.time + CTF_TECH_TIMEOUT; + tech->think = TechThink; + ent->client->pers.inventory[ITEM_INDEX(item)] = 0; + } + +} + +void CTFDeadDropTech(edict_t *ent) +{ + gitem_t *tech; + edict_t *dropped; + int i; + + i = 0; + while (tnames[i]) + { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(tech)]) + { + //Don't drop auto-doc if player was given auto-doc on startup + if ((strcmp(tnames[i], "item_tech4") == 0) && (ent->client->player_special & SPECIAL_HEALING)) + { + //Don't do anything in this case + } + else + { + dropped = Drop_Item(ent, tech); + // hack the velocity to make it bounce random + dropped->velocity[0] = (rand() % 600) - 300; + dropped->velocity[1] = (rand() % 600) - 300; + dropped->nextthink = level.time + CTF_TECH_TIMEOUT; + dropped->think = TechThink; + dropped->owner = NULL; + ent->client->pers.inventory[ITEM_INDEX(tech)] = 0; + } + } + i++; + } +} + +static void SpawnTech(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); + 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 + CTF_TECH_TIMEOUT; + ent->think = TechThink; + + gi.linkentity (ent); +//ERASER START + bonus_head = AddToItemList(ent, bonus_head); + + // add to the tech lookup list + { + int i=0; + + while (titems[i]) + i++; + + if (i < 3) + { + titems[i] = item; + } + } +} +//ERASER END + +static void SpawnTechs(edict_t *ent) +{ + gitem_t *tech; + edict_t *spot; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + (spot = FindTechSpawn()) != NULL) + SpawnTech(tech, spot); + i++; + } +} + +// frees the passed edict! +void CTFRespawnTech(edict_t *ent) +{ + edict_t *spot; + + if ((spot = FindTechSpawn()) != NULL) + SpawnTech(ent->item, spot); + RemoveFromItemList(ent); + G_FreeEdict(ent); +} + +void CTFSetupTechSpawn(void) +{ + edict_t *ent; + +//ERASER ADDED !CTF->VALUE + if (!ctf->value || techspawn || ((int)dmflags->value & DF_CTF_NO_TECH)) + return; + + ent = G_Spawn(); + ent->nextthink = level.time + 2; + ent->think = SpawnTechs; + techspawn = true; +} + +int CTFApplyResistance(edict_t *ent, int dmg) +{ + static gitem_t *tech = NULL; + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech1"); + if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) { + // make noise + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech1.wav"), volume, ATTN_NORM, 0); + return dmg / 2; + } + return dmg; +} + +int CTFApplyStrength(edict_t *ent, int dmg) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech2"); + if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) { + return dmg * 2; + } + return dmg; +} + +qboolean CTFApplyStrengthSound(edict_t *ent) +{ + static gitem_t *tech = NULL; + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech2"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + if (ent->client->ctf_techsndtime < level.time) { + ent->client->ctf_techsndtime = level.time + 1; + if (ent->client->quad_framenum > level.framenum) + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2x.wav"), volume, ATTN_NORM, 0); + else + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2.wav"), volume, ATTN_NORM, 0); + } + return true; + } + return false; +} + + +qboolean CTFApplyHaste(edict_t *ent) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech3"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)]) + return true; + return false; +} + +void CTFApplyHasteSound(edict_t *ent) +{ + static gitem_t *tech = NULL; + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech3"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)] && + ent->client->ctf_techsndtime < level.time) { + ent->client->ctf_techsndtime = level.time + 1; + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech3.wav"), volume, ATTN_NORM, 0); + } +} + +void CTFApplyRegeneration(edict_t *ent) +{ + static gitem_t *tech = NULL; + qboolean noise = false; + gclient_t *client; +// int index; + float volume = 1.0; + + client = ent->client; + if (!client) + return; + + if (ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech4"); + if (tech && client->pers.inventory[ITEM_INDEX(tech)]) { + if (client->ctf_regentime < level.time) { + client->ctf_regentime = level.time; + if (ent->health < 100) { + ent->health += 5; + if (ent->health > 100) + ent->health = 100; + //WF - take longer to regenerate health +// client->ctf_regentime += 0.5; + client->ctf_regentime += 1.7; + noise = true; + } + + + } + HealPlayer(ent); + if (noise && ent->client->ctf_techsndtime < level.time) { + ent->client->ctf_techsndtime = level.time + 1; + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech4.wav"), volume, ATTN_NORM, 0); + } + } + if (ent->health > 100) ent->health = 100; +} + +qboolean CTFHasRegeneration(edict_t *ent) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech4"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)]) + return true; + return false; +} + +/* +====================================================================== + +SAY_TEAM + +====================================================================== +*/ + +// This array is in 'importance order', it indicates what items are +// more important when reporting their names. +struct { + char *classname; + int priority; +} loc_names[] = +{ + { "item_flag_team1", 0 }, + { "item_flag_team2", 0 }, + { "info_position", 1 }, + { "item_quad", 2 }, + { "item_invulnerability", 2 }, + { "weapon_bfg", 3 }, + { "weapon_railgun", 4 }, + { "weapon_rocketlauncher", 4 }, + { "weapon_hyperblaster", 4 }, + { "weapon_chaingun", 4 }, + { "weapon_grenadelauncher", 4 }, + { "weapon_machinegun", 4 }, + { "weapon_supershotgun", 4 }, + { "weapon_shotgun", 4 }, + { "item_power_screen", 5 }, + { "item_power_shield", 5 }, + { "item_armor_body", 6 }, + { "item_armor_combat", 6 }, + { "item_armor_jacket", 6 }, + { "item_silencer", 7 }, + { "item_breather", 7 }, + { "item_enviro", 7 }, + { "item_adrenaline", 7 }, + { "item_bandolier", 8 }, + { "item_pack", 8 }, + { NULL, 0 } +}; + + +static void CTFSay_Team_Location(edict_t *who, char *buf) +{ + edict_t *what = NULL; + edict_t *hot = NULL; + edict_t *info_pos = NULL; + + float hotdist = 999999, newdist; + vec3_t v; + int hotindex = 999; + int i; + gitem_t *item = NULL; + int nearteam = -1; + edict_t *flag1, *flag2; + qboolean hotsee = false; + qboolean cansee; + qboolean isposition = false; + + while ((what = loc_findradius(what, who->s.origin, 600)) != NULL) + { + // find what in loc_classnames + for (i = 0; loc_names[i].classname; i++) + { + if (strcmp(what->classname, loc_names[i].classname) == 0) + break; + } + if (!loc_names[i].classname) + continue; //Not in the list + +/* +gi.dprintf("DEBUG-I see class %s\n",what->classname); +if (what->message && what->message[0]) + gi.dprintf(" DEBUG-I see Message = %s\n", what->message); +*/ + + cansee = loc_CanSee(what, who); + + //For priority 1 items, assume you can always see them. + if (loc_names[i].priority == 1) cansee = true; + + // something we can see get priority over something we can't + if (cansee && !hotsee) + { + hotsee = true; + hotindex = loc_names[i].priority; + hot = what; + VectorSubtract(what->s.origin, who->s.origin, v); + hotdist = VectorLength(v); +//gi.dprintf(" DEBUG-Can See item selected\n"); + continue; + } + + // if we can't see this, but we have something we can see, skip it + if (hotsee && !cansee) + continue; + + if (hotsee && hotindex < loc_names[i].priority) + continue; + + VectorSubtract(what->s.origin, who->s.origin, v); + newdist = VectorLength(v); + if (newdist < hotdist || (cansee && loc_names[i].priority < hotindex)) +// if (newdist < hotdist || loc_names[i].priority < hotindex)) + { + hot = what; + hotdist = newdist; + hotindex = i; + hotsee = cansee; +//gi.dprintf(" DEBUG-Hot set by distance.\n" ); + } + } + + if (!hot) { + strcpy(buf, "nowhere"); + return; + } + + // we now have the closest item + // see if there's more than one in the map, if so + // we need to determine what team is closest + what = NULL; + while ((what = G_Find(what, FOFS(classname), hot->classname)) != NULL) { + if (what == hot) + continue; + // if we are here, there is more than one, find out if hot + // is closer to red flag or blue flag + if ((flag1 = G_Find(NULL, FOFS(classname), "item_flag_team1")) != NULL && + (flag2 = G_Find(NULL, FOFS(classname), "item_flag_team2")) != NULL) { + VectorSubtract(hot->s.origin, flag1->s.origin, v); + hotdist = VectorLength(v); + VectorSubtract(hot->s.origin, flag2->s.origin, v); + newdist = VectorLength(v); + if (hotdist < newdist) + nearteam = CTF_TEAM1; + else if (hotdist > newdist) + nearteam = CTF_TEAM2; + } + break; + } + + + //Is this an info_position entity? If so, it's not an item + if (strcmp(hot->classname, "info_position") == 0) + isposition = true; + else + { + item = FindItemByClassname(hot->classname); + if (!item) + { + //strcpy(buf, "%s",vtos(who->s.origin)); + strcpy(buf, vtos(who->s.origin)); + //strcpy(buf, "nowhere."); + return; + } + } + + // in water? + if (who->waterlevel) + strcpy(buf, "in the water "); + else + *buf = 0; + + // near or above + if (isposition == false) //the info_possition messages are good enough + { + VectorSubtract(who->s.origin, hot->s.origin, v); + if (fabs(v[2]) > fabs(v[0]) && fabs(v[2]) > fabs(v[1])) + if (v[2] > 0) + strcat(buf, "above "); + else + strcat(buf, "below "); + else + strcat(buf, "near "); + + if (nearteam == CTF_TEAM1) + strcat(buf, "the red "); + else if (nearteam == CTF_TEAM2) + strcat(buf, "the blue "); + else + strcat(buf, "the "); + } + + //If this is an info_position, use the message rather than classname + if (isposition == true) + { + if (hot->message && hot->message[0]) + strcat(buf, hot->message); + } + else + strcat(buf, item->pickup_name); + +} + +static void CTFSay_Team_Armor(edict_t *who, char *buf) +{ + gitem_t *item; + int index, cells; + int power_armor_type; + + *buf = 0; + + power_armor_type = PowerArmorType (who); + if (power_armor_type) + { + cells = who->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))]; + if (cells) + sprintf(buf+strlen(buf), "%s with %i cells ", + (power_armor_type == POWER_ARMOR_SCREEN) ? + "Power Screen" : "Power Shield", cells); + } + + index = ArmorIndex (who); + if (index) + { + item = GetItemByIndex (index); + if (item) { + if (*buf) + strcat(buf, "and "); + sprintf(buf+strlen(buf), "%i units of %s", + who->client->pers.inventory[index], item->pickup_name); + } + } + + if (!*buf) + strcpy(buf, "no armor"); +} + +static void CTFSay_Team_Health(edict_t *who, char *buf) +{ + if (who->health <= 0) + strcpy(buf, "dead"); + else + sprintf(buf, "%i health", who->health); +} + +static void CTFSay_Team_Tech(edict_t *who, char *buf) +{ + gitem_t *tech; + int i; + + // see if the player has a tech powerup + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + who->client->pers.inventory[ITEM_INDEX(tech)]) { + sprintf(buf, "the %s", tech->pickup_name); + return; + } + i++; + } + strcpy(buf, "no powerup"); +} + +static void CTFSay_Team_Weapon(edict_t *who, char *buf) +{ + if (who->client->pers.weapon) + strcpy(buf, who->client->pers.weapon->pickup_name); + else + strcpy(buf, "none"); +} + +static void CTFSay_Team_Sight(edict_t *who, char *buf) +{ + int i; + edict_t *targ; + int n = 0; + char s[1024]; + char s2[1024]; + + *s = *s2 = 0; + for (i = 1; i <= maxclients->value; i++) { + targ = g_edicts + i; + if (!targ->inuse || + targ == who || + !loc_CanSee(targ, who)) + continue; + if (*s2) { + if (strlen(s) + strlen(s2) + 3 < sizeof(s)) { + if (n) + strcat(s, ", "); + strcat(s, s2); + *s2 = 0; + } + n++; + } + strcpy(s2, targ->client->pers.netname); + } + if (*s2) { + if (strlen(s) + strlen(s2) + 6 < sizeof(s)) { + if (n) + strcat(s, " and "); + strcat(s, s2); + } + strcpy(buf, s); + } else + strcpy(buf, "no one"); +} + +void CTFSay_Team(edict_t *who, char *msg) +{ + char outmsg[1024]; + char buf[1024]; + int i; + char *p; + edict_t *cl_ent; + + outmsg[0] = 0; + + if (*msg == '\"') { + msg[strlen(msg) - 1] = 0; + msg++; + } + + for (p = outmsg; *msg && (p - outmsg) < sizeof(outmsg) - 1; msg++) { + if (*msg == '%') { + switch (*++msg) { + case 'l' : + case 'L' : + CTFSay_Team_Location(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + case 'a' : + case 'A' : + CTFSay_Team_Armor(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + case 'h' : + case 'H' : + CTFSay_Team_Health(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + case 't' : + case 'T' : + CTFSay_Team_Tech(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + case 'w' : + case 'W' : + CTFSay_Team_Weapon(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + + case 'n' : + case 'N' : + CTFSay_Team_Sight(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + + default : + *p++ = *msg; + } + } else + *p++ = *msg; + } + *p = 0; + + if ( strlen(outmsg) > 150) // Fix overflow with %L%L%L + outmsg[150] = 0; // commands + + if (BannedWords(who, outmsg)) + return; + + for (i = 0; i < maxclients->value; i++) { + cl_ent = g_edicts + 1 + i; + if (!cl_ent->inuse) + continue; + + if (cl_ent->client->pers.nospam_level & SPAM_NOTEAMMSG) + continue; //they don't want team messages + + if (cl_ent->client->resp.ctf_team == who->client->resp.ctf_team) + safe_cprintf(cl_ent, PRINT_CHAT, "(%s): %s\n", + who->client->pers.netname, outmsg); + } +} + +/*-----------------------------------------------------------------------*/ +/*QUAKED misc_ctf_banner (1 .5 0) (-4 -64 0) (4 64 248) TEAM2 +The origin is the bottom of the banner. +The banner is 248 tall. +*/ +static void misc_ctf_banner_think (edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_misc_ctf_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ctf/banner/tris.md2"); + if (ent->spawnflags == 1) // team2 + ent->s.skinnum = 1; + else if (ent->spawnflags == 2) + ent->s.skinnum = 2; + else if (ent->spawnflags == 3) + ent->s.skinnum = 3; + else if (ent->spawnflags == 4) + ent->s.skinnum = 4; + else if (ent->spawnflags == 5) + ent->s.skinnum = 5; + + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_ctf_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED misc_ctf_small_banner (1 .5 0) (-4 -32 0) (4 32 124) TEAM2 +The origin is the bottom of the banner. +The banner is 124 tall. +*/ +void SP_misc_ctf_small_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ctf/banner/small.md2"); + if (ent->spawnflags == 1) // team2 + ent->s.skinnum = 1; + else if (ent->spawnflags == 2) + ent->s.skinnum = 2; + else if (ent->spawnflags == 3) + ent->s.skinnum = 3; + else if (ent->spawnflags == 4) + ent->s.skinnum = 4; + else if (ent->spawnflags == 5) + ent->s.skinnum = 5; + + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_ctf_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + + +/*-----------------------------------------------------------------------*/ + +void CTFJoinTeam(edict_t *ent, int desired_team) +{ + char *s; + + PMenu_Close(ent); + + + ent->svflags &= ~SVF_NOCLIENT; + ent->client->resp.ctf_team = desired_team; + ent->client->resp.ctf_state = CTF_STATE_START; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + + // Log Join Team - MarkDavies + sl_LogPlayerName( &gi, + ent->client->pers.netname, + CTFTeamName(ent->client->resp.ctf_team)); + + + PutClientInServer (ent); + + // add a teleportation effect + ent->s.event = EV_PLAYER_TELEPORT; + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + if (ctf->value) my_bprintf(PRINT_HIGH, "%s joined the %s team.\n", + ent->client->pers.netname, CTFTeamName(desired_team)); + sl_LogPlayerTeamChange( &gi, + ent->client->pers.netname, + CTFTeamName(ent->client->resp.ctf_team)); + + //K2 + ent->client->resp.inServer = true; + //K2 3/99 acrid botcam + + //WF - Add welcome sound +// gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("welcome3.wav"), 1, ATTN_STATIC, 0); +// stuffcmd (ent, "play welcome3.wav\n"); + +} + +void CTFJoinTeam1(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.next_ctf_team = CTF_TEAM1; + WFOpenClassMenu(ent); + //CTFJoinTeam(ent, CTF_TEAM1); +} + +void CTFJoinTeam2(edict_t *ent, pmenu_t *p) +{ + ent->client->resp.next_ctf_team = CTF_TEAM2; + WFOpenClassMenu(ent); + //CTFJoinTeam(ent, CTF_TEAM2); +} + +void CTFChaseCam(edict_t *ent, pmenu_t *p) +{ + int i; + edict_t *e; + + if (ent->client->chase_target) { + ent->client->chase_target = NULL; + PMenu_Close(ent); + return; + } + + for (i = 1; i <= maxclients->value; i++) { + e = g_edicts + i; + if (e->inuse && e->solid != SOLID_NOT) { + ent->client->chase_target = e; + PMenu_Close(ent); + ent->client->update_chase = true; + break; + } + } +} + +void CTFReturnToMain(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + //CTFOpenJoinMenu(ent); +} + +void CTFCredits(edict_t *ent, pmenu_t *p); + +void DeathmatchScoreboard (edict_t *ent); + + +//WF +void WFHelp(edict_t *ent, pmenu_t *p); +void WFContinue(edict_t *ent, pmenu_t *p); +void WFShowMoreHelp(edict_t *ent, pmenu_t *p); +void WFEraserHelp(edict_t *ent, pmenu_t *p);//Acrid +void WFServer(edict_t *ent, pmenu_t *p);//Acrid +void WFRoutes(edict_t *ent, pmenu_t *p);//Acrid +void WFBindKeys(edict_t *ent, pmenu_t *p); +void WFCredits(edict_t *ent, pmenu_t *p); + +pmenu_t wfcreditsmenu[] = { + { "*The Weapons Factory", PMENU_ALIGN_CENTER, 0, NULL }, + { "*Programming", PMENU_ALIGN_CENTER, 0, NULL }, + { "Gregg 'Headache' Reno", PMENU_ALIGN_CENTER, 0, NULL }, + { "John 'Cyrect' Rittenhouse", PMENU_ALIGN_CENTER, 0, NULL }, + { "*Level Design", PMENU_ALIGN_CENTER, 0, NULL }, + { "Tom 'Tumorhead' Reno", PMENU_ALIGN_CENTER, 0, NULL }, + { "*Sound", PMENU_ALIGN_CENTER, 0, NULL }, + { "Wayne 'Tubeblaster' Reno", PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "Visit:", PMENU_ALIGN_CENTER, 0, NULL }, + { WF_URL, PMENU_ALIGN_CENTER, 0, NULL }, + { "for details", PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "Return to Main Menu", PMENU_ALIGN_LEFT, 0, CTFReturnToMain } +}; +//WF + +pmenu_t creditsmenu[] = { + { "*Quake II", PMENU_ALIGN_CENTER, 0, NULL }, + { "*ThreeWave Capture the Flag", PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "*Programming", PMENU_ALIGN_CENTER, 0, NULL }, + { "Dave 'Zoid' Kirsch", PMENU_ALIGN_CENTER, 0, NULL }, + { "*Level Design", PMENU_ALIGN_CENTER, 0, NULL }, + { "Christian Antkow", PMENU_ALIGN_CENTER, 0, NULL }, + { "Tim Willits", PMENU_ALIGN_CENTER, 0, NULL }, + { "Dave 'Zoid' Kirsch", PMENU_ALIGN_CENTER, 0, NULL }, + { "*Art", PMENU_ALIGN_CENTER, 0, NULL }, + { "Adrian Carmack Paul Steed", PMENU_ALIGN_CENTER, 0, NULL }, + { "Kevin Cloud", PMENU_ALIGN_CENTER, 0, NULL }, + { "*Sound", PMENU_ALIGN_CENTER, 0, NULL }, + { "Tom 'Bjorn' Klok", PMENU_ALIGN_CENTER, 0, NULL }, + { "*Original CTF Art Design", PMENU_ALIGN_CENTER, 0, NULL }, + { "Brian 'Whaleboy' Cozzens", PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "Return to Main Menu", PMENU_ALIGN_LEFT, 0, CTFReturnToMain } +}; + + +//WF many changed here +pmenu_t joinmenu[] = { + { "The Weapons Factory", PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { WF_VERSION, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "*Select a Team:", PMENU_ALIGN_LEFT, 0, NULL }, + { "1. Join Red Team", PMENU_ALIGN_LEFT, 0, CTFJoinTeam1 }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { "2. Join Blue Team", PMENU_ALIGN_LEFT, 0, CTFJoinTeam2 }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { "3. Chase Camera", PMENU_ALIGN_LEFT, 0, CTFChaseCam }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, +// { "Credits: id", PMENU_ALIGN_LEFT, 0, CTFCredits }, + { WF_URL, PMENU_ALIGN_LEFT, 0, NULL }, +// { "Credits: Weapons Factory", PMENU_ALIGN_LEFT, 0, WFCredits }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, 0, NULL }, + { "ENTER to select", PMENU_ALIGN_LEFT, 0, NULL }, + { "ESC to Exit Menu", PMENU_ALIGN_LEFT, 0, NULL }, + { "Type WFHELP for commands", PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "* ctf v" CTF_STRING_VERSION, PMENU_ALIGN_LEFT, 0, NULL }, + +}; +//WF + +void WFClassPicked(edict_t *ent, pmenu_t *p) +{ + int i; + int classcount[MAX_CLASSES + 1]; + + int initial_class; + + i = p->arg; + + //Default to class # 1 + if (i < 1 || i > numclasses) i = 1; + + //See if there are any class limits defined + WFClassCount(ent, classcount); + +// gi.dprintf ("class limit on %s is %d. You picked %d. Curr=%d\n", +// classinfo[i].name,classinfo[i].limit, i, classcount[i]); + + if (classcount[i] >= classinfo[i].limit) + { + safe_cprintf (ent, PRINT_HIGH, "Sorry, class limit on %s is %d. Pick another class.\n", + classinfo[i].name,classinfo[i].limit); + return; + } + + PMenu_Close(ent); + initial_class = ent->client->pers.next_player_class; + ent->client->pers.next_player_class = i; + + //If team not picked, then this is the first time + if (ent->client->resp.ctf_team == CTF_NOTEAM || initial_class == 0) + { + //CTFOpenJoinMenu(ent); + ent->client->pers.player_class = ent->client->pers.next_player_class; + if (ctf->value) + CTFJoinTeam(ent, ent->client->resp.next_ctf_team); + else //death match already has team assigned + CTFJoinTeam(ent, ent->client->resp.ctf_team); + } + //Otherwise, they are changing classes + else + { + safe_cprintf (ent, PRINT_HIGH, "You will become a %s the next time you respawn. \n", + classinfo[i].name); + + //safe_cprintf (ent, PRINT_HIGH, "Your class will be changed the next time you die.\n"); + } +} + + + +void WFPickMap(edict_t *ent, pmenu_t *p); + +pmenu_t wfmapvote[] = { + { "Please Vote For Next Map:", PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, +}; + + +pmenu_t wfclassmenu[] = { + { "The Weapons Factory", PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { WF_VERSION, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { "*Select A Class:", PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { "Help...", PMENU_ALIGN_LEFT, 0, WFShowHelp }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, 0, NULL }, + { "ENTER selects, ESC exits", PMENU_ALIGN_LEFT, 0, NULL }, +}; + +pmenu_t wfhelp[] = { + { "*HELP MENU", PMENU_ALIGN_CENTER, 0, NULL }, + { WF_VERSION, PMENU_ALIGN_CENTER, 0, NULL }, +// { "You must download files at", PMENU_ALIGN_LEFT, 0, NULL }, + { WF_URL, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "*Recommended Keys:", PMENU_ALIGN_LEFT, 0, NULL }, + { "v - Hold down to fly", PMENU_ALIGN_LEFT, 0, NULL }, + { "b - Cycle Grenades", PMENU_ALIGN_LEFT, 0, NULL }, + { "; - Special Menu", PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { "Continue", PMENU_ALIGN_LEFT, 0, WFContinue }, + { "Bind These Keys Now", PMENU_ALIGN_LEFT, 0, WFBindKeys }, + { "Other Commands...", PMENU_ALIGN_LEFT, 0, WFShowMoreHelp }, + { "Eraser Commands...", PMENU_ALIGN_LEFT, 0, WFEraserHelp },//acrid + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { "Press ENTER to continue", PMENU_ALIGN_LEFT, 0, NULL }, + { "Type WFHELP to see again", PMENU_ALIGN_LEFT, 0, NULL }, +}; + +pmenu_t wfmorehelp[] = { + { WF_VERSION, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "cmd changeclass", PMENU_ALIGN_LEFT, 0, NULL }, + { "cmd decoy", PMENU_ALIGN_LEFT, 0, NULL }, + { "cmd grapple", PMENU_ALIGN_LEFT, 0, NULL }, + { "cmd grenade1", PMENU_ALIGN_LEFT, 0, NULL }, + { "cmd grenade2", PMENU_ALIGN_LEFT, 0, NULL }, + { "cmd grenade3", PMENU_ALIGN_LEFT, 0, NULL }, + { "cmd homing", PMENU_ALIGN_LEFT, 0, NULL }, + { "cmd showclass", PMENU_ALIGN_LEFT, 0, NULL }, + { "cmd vote", PMENU_ALIGN_LEFT, 0, NULL }, + { "cmd wfflags", PMENU_ALIGN_LEFT, 0, NULL }, + { "cmd wfhelp", PMENU_ALIGN_LEFT, 0, NULL }, + { "cmd wfplay xyz.wav", PMENU_ALIGN_LEFT, 0, NULL }, + { "cmd wfplayteam xyz.wav", PMENU_ALIGN_LEFT, 0, NULL }, + { "Continue", PMENU_ALIGN_LEFT, 0, WFContinue }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { "Press ENTER to continue", PMENU_ALIGN_LEFT, 0, NULL }, + { "Type WFHELP to see again", PMENU_ALIGN_LEFT, 0, NULL }, +}; + +//Acrid +pmenu_t wferaser[] = { + { WF_VERSION, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "group", PMENU_ALIGN_LEFT, 0, NULL }, + { "disperse", PMENU_ALIGN_LEFT, 0, NULL }, + { "defendbase", PMENU_ALIGN_LEFT, 0, NULL }, + { "rushbase", PMENU_ALIGN_LEFT, 0, NULL }, + { "freestyle", PMENU_ALIGN_LEFT, 0, NULL }, + { "cam", PMENU_ALIGN_LEFT, 0, NULL }, + { "Server Commands...", PMENU_ALIGN_LEFT, 0, WFServer }, + { "Route Commands...", PMENU_ALIGN_LEFT, 0, WFRoutes }, + { "Continue", PMENU_ALIGN_LEFT, 0, WFContinue }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { "Press ENTER to continue", PMENU_ALIGN_LEFT, 0, NULL }, + { "Type WFHELP to see again", PMENU_ALIGN_LEFT, 0, NULL }, +}; +pmenu_t wfserver[] = { + { WF_VERSION, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "servcmd", PMENU_ALIGN_LEFT, 0, NULL }, + { "tips", PMENU_ALIGN_LEFT, 0, NULL }, + { "sv bluebots ", PMENU_ALIGN_LEFT, 0, NULL }, + { "sv redbots ", PMENU_ALIGN_LEFT, 0, NULL }, + { "lag 0-1000", PMENU_ALIGN_LEFT, 0, NULL }, + { "bot_num 0-32?", PMENU_ALIGN_LEFT, 0, NULL }, + { "bot_name ", PMENU_ALIGN_LEFT, 0, NULL }, + { "bot_allow_client_commands 0/1", PMENU_ALIGN_LEFT, 0, NULL }, + { "bot_free_clients 0-32?", PMENU_ALIGN_LEFT, 0, NULL }, + { "bot_show_connect_info 0/1", PMENU_ALIGN_LEFT, 0, NULL }, + { "bot_auto_skill 0/1", PMENU_ALIGN_LEFT, 0, NULL }, + { "skill 1-3", PMENU_ALIGN_LEFT, 0, NULL }, + { "bot_drop ", PMENU_ALIGN_LEFT, 0, NULL }, + { "bot_chat 0/1", PMENU_ALIGN_LEFT, 0, NULL }, + { "bot_tarzan 0/1", PMENU_ALIGN_LEFT, 0, NULL }, + { "bot_melee 0/1", PMENU_ALIGN_LEFT, 0, NULL }, + { "botpause", PMENU_ALIGN_LEFT, 0, NULL }, + { "...Go Back", PMENU_ALIGN_LEFT, 0, WFEraserHelp }, + { "Continue", PMENU_ALIGN_LEFT, 0, WFContinue }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { "Press ENTER to continue", PMENU_ALIGN_LEFT, 0, NULL }, + { "Type WFHELP to see again", PMENU_ALIGN_LEFT, 0, NULL }, +}; +pmenu_t wfroutes[] = { + { WF_VERSION, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "bot_calc_nodes 0/1", PMENU_ALIGN_LEFT, 0, NULL }, + { "bot_debug_nodes 0/1", PMENU_ALIGN_LEFT, 0, NULL }, + { "showpath", PMENU_ALIGN_LEFT, 0, NULL }, + { "bot_optimize 0/1", PMENU_ALIGN_LEFT, 0, NULL }, + { "redflag", PMENU_ALIGN_LEFT, 0, NULL }, + { "blueflag", PMENU_ALIGN_LEFT, 0, NULL }, + { "clearflags", PMENU_ALIGN_LEFT, 0, NULL }, + { "flagpath", PMENU_ALIGN_LEFT, 0, NULL }, + { "clear_flagpaths", PMENU_ALIGN_LEFT, 0, NULL }, + { "toggle_flagpaths", PMENU_ALIGN_LEFT, 0, NULL }, + { "ctf_item ", PMENU_ALIGN_LEFT, 0, NULL }, + { "clear_items", PMENU_ALIGN_LEFT, 0, NULL }, + { "botpath", PMENU_ALIGN_LEFT, 0, NULL }, + { "ctf 0/1 depends on map", PMENU_ALIGN_LEFT, 0, NULL }, + { "...Go Back", PMENU_ALIGN_LEFT, 0, WFEraserHelp }, + { "Continue", PMENU_ALIGN_LEFT, 0, WFContinue }, + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { "Press ENTER to continue", PMENU_ALIGN_LEFT, 0, NULL }, + { "Type WFHELP to see again", PMENU_ALIGN_LEFT, 0, NULL }, +}; + +void WFContinue(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + + //If a team has not been picked, go to the team menu + + if (ent->client->resp.ctf_team == CTF_NOTEAM) + CTFOpenJoinMenu(ent); + + //If a class has not been picked, go to the class menu + else if (ent->client->pers.player_class == 0) + WFOpenClassMenu(ent); + + +} + +void WFShowHelp(edict_t *ent, pmenu_t *p) +{ + if (ent->client->menu) PMenu_Close(ent); + + PMenu_Open(ent, wfhelp, -1, sizeof(wfhelp) / sizeof(pmenu_t), false, true); +} + +void WFShowMoreHelp(edict_t *ent, pmenu_t *p) +{ + if (ent->client->menu) PMenu_Close(ent); + PMenu_Open(ent, wfmorehelp, -1, sizeof(wfmorehelp) / sizeof(pmenu_t), false, true); +} + +void WFEraserHelp(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + PMenu_Open(ent, wferaser, -1, sizeof(wferaser) / sizeof(pmenu_t),false, true); +} + +void WFServer(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + PMenu_Open(ent, wfserver, -1, sizeof(wfserver) / sizeof(pmenu_t),false, true); +} + +void WFRoutes(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + PMenu_Open(ent, wfroutes, -1, sizeof(wfroutes) / sizeof(pmenu_t),false, true); +} + +void WFBindKeys(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + + stuffcmd(ent,"bind \";\" cmd special\n"); + stuffcmd(ent,"bind v +thrust\n"); + stuffcmd(ent,"bind b cmd grenade\n"); + + //If a class has not been picked, go to the class menu + if (ent->client->pers.player_class == 0) + WFOpenClassMenu(ent); +} + + +void WFFillMapNames() +{ + int i; + int pos; + + pos = 1; + for (i = 0; i < maplist.nummaps && pos < MAX_MENU_MAPS; ++i) + { + sprintf(menustring[pos],"%d. %s", pos, maplist.mapnames[i]); + wfmapvote[pos].text = menustring[pos]; + wfmapvote[pos].SelectFunc = WFPickMap; + wfmapvote[pos].arg = i; + ++pos; + } + + //Clear the rest of the menu + while (pos < MAX_MENU_MAPS) + { + wfmapvote[pos].text = ""; + wfmapvote[pos].SelectFunc = NULL; + wfmapvote[pos].arg = 0; + ++pos; + } + +} + +void WFMapVote(edict_t *ent) +{ + //WFFillMapNames(); + if (ent->client->menu) PMenu_Close(ent); + PMenu_Open(ent, wfmapvote, -1, sizeof(wfmapvote) / sizeof(pmenu_t), true, true); +} + +//Set up menu with special capabilities that can be picked +//from menu +void place_turret (edict_t *ent); +void UpgradeSentry(edict_t *self); +void PlaceBiosentry (edict_t *ent); +void BiosentryRepair(edict_t *self); + +void WFPickSpecial(edict_t *ent, pmenu_t *p) +{ + int i; +// char *s; + + i = p->arg; + + PMenu_Close(ent); + + if (!ent->client) return; + + switch (i) + { + case (SPECIAL_REMOTE_CAMERA): + cmd_CameraPlace(ent); //Place or blow up camera + break; + + case (SPECIAL_REMOTE_CAMERA + SPECIAL_OPTION1): + if (ent->remotecam) ent->remotecam->dmg = 0; //Silent removal of camera + cmd_CameraPlace(ent); + break; + + case (SPECIAL_REMOTE_CAMERA + SPECIAL_OPTION2): + cmd_CameraToggle(ent); //Switch Views + break; + + case (SPECIAL_TRIPBOMB): + cmd_TripBomb(ent); + break; + + case (SPECIAL_LASER_DEFENSE): + cmd_LaserDefense(ent); + break; + + case (SPECIAL_LASER_DEFENSE + SPECIAL_OPTION1): + cmd_RemoveLaserDefense(ent); + break; + + case (SPECIAL_SUPPLY_DEPOT): + SP_SupplyDepot(ent); + break; + + case (SPECIAL_HEALING): + SP_HealingDepot(ent); + break; + + case (SPECIAL_ALARMS): + place_alarm(1, ent); + break; + + case (SPECIAL_ALARMS + SPECIAL_OPTION1): + alarm_remove(ent->alarm1); + break; + + case (SPECIAL_PLASMA_BOMB): + //cmd_PlasmaBomb(ent); + cmd_PlasmaBombMenu(ent); + break; + + case (SPECIAL_KAMIKAZE): + Start_Kamikaze_Mode(ent); + break; + + case (SPECIAL_KAMIKAZE + SPECIAL_OPTION1): + Kamikaze_Cancel(ent); + safe_cprintf (ent, PRINT_HIGH, "Kamikaze canceled! You are safe now!\n"); + break; + + case (SPECIAL_ANTIGRAV_BOOTS): + if (ent->flags & FL_BOOTS) + { + safe_cprintf (ent, PRINT_HIGH, "Anti Gravity Boots off\n"); + ent->flags -= FL_BOOTS; + } + else + { + safe_cprintf (ent, PRINT_HIGH, "Anti Gravity Boots on\n"); + ent->flags |= FL_BOOTS; + } + break; + + case (SPECIAL_SENTRY_GUN): + place_turret (ent); + break; + case (SPECIAL_SENTRY_GUN + SPECIAL_OPTION1): + UpgradeSentry(ent); + break; + case (SPECIAL_BIOSENTRY): + PlaceBiosentry (ent); + break; +// case (SPECIAL_BIOSENTRY + SPECIAL_OPTION1): +// BiosentryRepair(ent); +// break; + case (SPECIAL_MISSILE_LAUNCHER): + place_missile(ent); + break; + case (SPECIAL_MISSILE_LAUNCHER + SPECIAL_OPTION1): + UpgradeMissileTurret(ent); + break; + case (SPECIAL_DISGUISE): + if (ent->disguised == 0) + { + if (CTFHasFlag(ent)) + { + safe_cprintf(ent, PRINT_HIGH, "You can't disguise while holding the flag!\n"); + } + else + { + ent->disguising = 0; + WFChooseDisguise(ent); + } + } + else + { + WFRemoveDisguise(ent); + } + break; + + case (SPECIAL_LASER): + if(ent->client->pers.laseron) + ent->client->pers.laseron=0; + else + ent->client->pers.laseron=1; + break; + case (SPECIAL_FEIGN): + if (CTFHasFlag(ent)) + { + safe_cprintf(ent, PRINT_HIGH, "You can't feign death while holding the flag!\n"); + } + else + Cmd_Feign_f (ent); + break; + case (SPECIAL_AUTOZOOM): + if(ent->client->pers.autozoom) + { + ent->client->pers.autozoom=0; + ent->client->ps.fov = 90; + } + else + ent->client->pers.autozoom=1; + break; + case (SPECIAL_QUICKAIM): + if(ent->client->pers.fastaim) + ent->client->pers.fastaim=0; + else + ent->client->pers.fastaim=1; + break; + case (SPECIAL_CLOAK): + Cmd_Cloak_f (ent); + break; + default: + safe_cprintf(ent, PRINT_HIGH, "Sorry, '%s' hasn't been implemented yet.\n", p->text); + break; + } + +} + + +void WFFillSpecial(edict_t *ent) +{ +// int i; + int pos; + int itemno; + + ent->client->wfspecial[0].text = "Select Special Item"; + ent->client->wfspecial[0].SelectFunc = NULL; + ent->client->wfspecial[0].align = PMENU_ALIGN_CENTER; + ent->client->wfspecial[0].arg = 0; + + ent->client->wfspecial[1].text = NULL; + ent->client->wfspecial[1].SelectFunc = NULL; + ent->client->wfspecial[1].align = PMENU_ALIGN_CENTER; + ent->client->wfspecial[1].arg = 0; + + ent->client->wfspecial[MAX_SPECIAL_MENU_ITEMS - 3].text = "Use [ and ] to move cursor"; + ent->client->wfspecial[MAX_SPECIAL_MENU_ITEMS - 3].SelectFunc = NULL; + ent->client->wfspecial[MAX_SPECIAL_MENU_ITEMS - 3].align = PMENU_ALIGN_LEFT; + ent->client->wfspecial[MAX_SPECIAL_MENU_ITEMS - 3].arg = 0; + + ent->client->wfspecial[MAX_SPECIAL_MENU_ITEMS - 2].text = "ENTER to select"; + ent->client->wfspecial[MAX_SPECIAL_MENU_ITEMS - 2].SelectFunc = NULL; + ent->client->wfspecial[MAX_SPECIAL_MENU_ITEMS - 2].align = PMENU_ALIGN_LEFT; + ent->client->wfspecial[MAX_SPECIAL_MENU_ITEMS - 2].arg = 0; + + ent->client->wfspecial[MAX_SPECIAL_MENU_ITEMS - 1].text = "ESC to Exit Menu"; + ent->client->wfspecial[MAX_SPECIAL_MENU_ITEMS - 1].SelectFunc = NULL; + ent->client->wfspecial[MAX_SPECIAL_MENU_ITEMS - 1].align = PMENU_ALIGN_LEFT; + ent->client->wfspecial[MAX_SPECIAL_MENU_ITEMS - 1].arg = 0; + + //Clear the rest + pos = 2; + while (pos < (MAX_SPECIAL_MENU_ITEMS - 3)) + { + ent->client->wfspecial[pos].text = NULL; + ent->client->wfspecial[pos].SelectFunc = NULL; + ent->client->wfspecial[pos].align = PMENU_ALIGN_LEFT; + ent->client->wfspecial[pos].arg = 0; + ++pos; + } + + + pos = 2; + itemno = 1; + + if ((ent->client->player_special & SPECIAL_FEIGN) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + if (ent->client->pers.feign == 0) + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Feign Death"); + else + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Live Again"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_FEIGN; + ++pos; + + //If they are feigning, they cant do anything else from special menu + if (ent->client->pers.feign) + return; + + } + + if ((ent->client->player_special & SPECIAL_DISGUISE) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + if (ent->disguised == 0) + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Disguise"); + else + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Remove Disguise"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_DISGUISE; + ++pos; + } + + if ((ent->client->player_special & SPECIAL_PLASMA_BOMB) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Drop Plasma Bomb"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_PLASMA_BOMB; + ++pos; + } + + if ((ent->client->player_special & SPECIAL_SENTRY_GUN) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + if (ent->sentry) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Upgrade Sentry Gun"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_SENTRY_GUN + SPECIAL_OPTION1; + ++pos; + + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Remove Sentry Gun"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_SENTRY_GUN; + ++pos; + + } + else + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Place Sentry Gun"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_SENTRY_GUN; + ++pos; + } + + } + + + if ((ent->client->player_special & SPECIAL_BIOSENTRY) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + if (ent->sentry) + { + /* + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Repair Biosentry"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_BIOSENTRY + SPECIAL_OPTION1; + ++pos; + */ + + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Remove Biosentry"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_BIOSENTRY; + ++pos; + + } + else + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Place Biosentry"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_BIOSENTRY; + ++pos; + } + } + + + if ((ent->client->player_special & SPECIAL_ANTIGRAV_BOOTS) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Use Anti-Grav Boots"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_ANTIGRAV_BOOTS; + ++pos; + } + + + if ((ent->client->player_special & SPECIAL_MISSILE_LAUNCHER) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + if (ent->missile) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Upgrade Missile Launcher"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_MISSILE_LAUNCHER + SPECIAL_OPTION1; + ++pos; + + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Remove Missile Launcher"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_MISSILE_LAUNCHER; + ++pos; + } + else + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Build Missile Launcher"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_MISSILE_LAUNCHER; + ++pos; + } + + } + + + if ((ent->client->player_special & SPECIAL_ALARMS) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + if (ent->alarm1) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Remove Alarm"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_ALARMS + SPECIAL_OPTION1; + ++pos; + } + else + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Set Alarm"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_ALARMS; + ++pos; + } + } + + if ((ent->client->player_special & SPECIAL_KAMIKAZE) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Go Kamizaze!"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_KAMIKAZE; + ++pos; + + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Cancel Kamizaze"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_KAMIKAZE + SPECIAL_OPTION1; + ++pos; + } + if ((ent->client->player_special & SPECIAL_LASER_DEFENSE) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + if (ent->client->pers.active_special[ITEM_SPECIAL_LASER_DEFENSE] < MAX_SPECIAL_LASER_DEFENSE) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Build Laser Defense"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_LASER_DEFENSE; + ++pos; + } + if (ent->client->pers.active_special[ITEM_SPECIAL_LASER_DEFENSE] > 0) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Remove All Laser Defenses"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_LASER_DEFENSE + SPECIAL_OPTION1; + ++pos; + } + } + + if ((ent->client->player_special & SPECIAL_TRIPBOMB) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Build Tripbomb"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_TRIPBOMB; + ++pos; + } + + if ((ent->client->player_special & SPECIAL_CLOAK) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + if (ent->client->cloaking == 0) + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Cloak"); + else + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Un-Cloak"); + + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_CLOAK; + ++pos; + } + + if ((ent->client->player_special & SPECIAL_SUPPLY_DEPOT) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + if (ent->supply) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Blow Up Supply Depot"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_SUPPLY_DEPOT; + ++pos; + } + else + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Build Supply Depot"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_SUPPLY_DEPOT; + ++pos; + } + } + if ((ent->client->player_special & SPECIAL_HEALING) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + if (ent->supply) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Blow Up Healing Depot"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_HEALING; + ++pos; + } + else + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Build Healing Depot"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_HEALING; + ++pos; + } + } + + if ((ent->client->player_special & SPECIAL_REMOTE_CAMERA) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + + //If there is no camera, let them place one + if (ent->remotecam == NULL) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Place Camera"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_REMOTE_CAMERA; + ++pos; + } + else + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Blow Up Camera"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_REMOTE_CAMERA; + ++pos; + + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Silently Remove Camera"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_REMOTE_CAMERA + SPECIAL_OPTION1; + ++pos; + } + + //Let user toggle camera view if a camera exists + if (ent->remotecam) + { + if (ent->client->remotetoggle) //It's already on + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Change View To Body"); + else + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Change View To Camera"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_REMOTE_CAMERA + SPECIAL_OPTION2; + ++pos; + } + } + + if((ent->client->player_special & SPECIAL_SNIPING) && pos <= MAX_SPECIAL_MENU_ITEMS - 4) + { + if (ent->client->pers.laseron) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Turn off sniping laser"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_LASER; + ++pos; + } + else + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Turn on sniping laser"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_LASER; + ++pos; + } + if (ent->client->pers.autozoom) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Turn off autozoom"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_AUTOZOOM; + ++pos; + } + else + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Turn on autozoom"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_AUTOZOOM; + ++pos; + } + if (ent->client->pers.fastaim) + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Switch to normal aiming"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_QUICKAIM; + ++pos; + } + else + { + sprintf(ent->client->wfmenustr[pos],"%d. %s", itemno++, "Switch to quick aiming"); + ent->client->wfspecial[pos].text = ent->client->wfmenustr[pos]; + ent->client->wfspecial[pos].SelectFunc = WFPickSpecial; + ent->client->wfspecial[pos].arg = SPECIAL_QUICKAIM; + ++pos; + } + + } +} + +void WFSpecialMenu(edict_t *ent) +{ + if (ent->client->menu) + { + PMenu_Close(ent); + return; + } + + WFFillSpecial(ent); + PMenu_Open(ent, ent->client->wfspecial, -1, sizeof(ent->client->wfspecial) / sizeof(pmenu_t), true, false); +} + + +void WFHelp(edict_t *ent, pmenu_t *p) +{ + if (ent->client->menu) PMenu_Close(ent); + PMenu_Open(ent, wfhelp, -1, sizeof(wfhelp) / sizeof(pmenu_t), false, true); +} + +void WFPickMap(edict_t *ent, pmenu_t *p) +{ + int i; + + i = p->arg; + + PMenu_Close(ent); + if (i >= 0 && i < MAX_MENU_MAPS) + { + //If ref picked the map, go to it now + if (wf_game.ref_ent == ent && wf_game.ref_picked_map == 1) + { + maplist.votes[i] = 999; + wf_game.ref_picked_map = 0; + WFEndDMLevel (maplist.mapnames[i]); + } + else + { + ++maplist.votes[i]; + ent->client->pers.HasVoted = true; + my_bprintf(PRINT_HIGH,"%s Voted for %s\n", + ent->client->pers.netname, maplist.mapnames[i]); + } + } + else + gi.dprintf("Bad Menu Item #%d\n",i); + +} + +//Fix the motd text so there are no invalid characters +/* +void FixMOTD(unsigned char *s, int len) +{ + int i; + for (i = 0; i < len; ++i) + { + //Convert special characters + if (s[i] == 128) s[i] = 40; // ( + if (s[i] == 130) s[i] = 41; // ) + if (s[i] == 144) s[i] = 91; // [ + if (s[i] == 145) s[i] = 93; // ] + if (s[i] == 129) s[i] = 61; // = + if (s[i] == 133) s[i] = 46; // . + + //Convert all other characters + if (s[i] >= 128) s[i] = s[i] - 128; + + //get rid of other non-printable characters + if (s[i] < 32 && s[i] > 0) + s[i] = ' '; + if (s[i] > 126) + s[i] = ' '; + + } + +} +*/ + +int CTFUpdateJoinMenu(edict_t *ent) +{ + static char levelname[32]; + static char team1players[32]; + static char team2players[32]; + int num1, num2, i; + +//WF + //Load the message of the day file if available +/* + FILE *motd_file; + static char motd_line1[80]; + static char motd_line2[80]; + static char motd_line3[80]; + + + motd_line1[0] = '\0'; + motd_line2[0] = '\0'; + motd_line2[3] = '\0'; + + if (motd_file = fopen("motd.txt", "r")) + { + readline(motd_file, motd_line1, 79); + readline(motd_file, motd_line2, 79); + readline(motd_file, motd_line3, 79); + // close the file + fclose(motd_file); + } + FixMOTD(motd_line1, 80); + FixMOTD(motd_line2, 80); + FixMOTD(motd_line3, 80); + + if (motd_line1[0] != '\0') + { + joinmenu[0].text = motd_line1; + wfclassmenu[0].text = motd_line1; + } + if (motd_line2[0] != '\0') + { + joinmenu[1].text = motd_line2; + wfclassmenu[1].text = motd_line2; + } + if (motd_line3[0] != '\0') + { + joinmenu[10].text = motd_line3; //probably a URL + wfhelp[2].text = motd_line3; + } +*/ + if (wf_game.motd[0][0] != '\0') + { + joinmenu[0].text = wf_game.motd[0]; + wfclassmenu[0].text = wf_game.motd[0]; + } + if (wf_game.motd[1][0] != '\0') + { + joinmenu[1].text = wf_game.motd[1]; + wfclassmenu[1].text = wf_game.motd[1]; + } + if (wf_game.motd[2][0] != '\0') + { + joinmenu[11].text = wf_game.motd[2]; //probably a URL + wfhelp[2].text = wf_game.motd[2]; + } + + +//gi.dprintf("Mod id = %d\n", modID); + + //Show if this is a WF or MOCK mod + if (modID == MOD_ID_WF) + { + wfclassmenu[2].text = WF_VERSION; + wfhelp[1].text = WF_VERSION; + joinmenu[17].text = "*(WF Approved Config)"; + } + else + { + wfclassmenu[2].text = "(MOCK Modified Game)"; + wfhelp[1].text = "(MOCK Modified Game)"; + joinmenu[17].text = "*(MOCK Modified Game)"; + } + + + //Set the team names + joinmenu[5].text = "1. Join Red Team"; + joinmenu[5].SelectFunc = CTFJoinTeam1; + joinmenu[7].text = "2. Join Blue Team"; + joinmenu[7].SelectFunc = CTFJoinTeam2; + + if (ctf_forcejoin->string && *ctf_forcejoin->string) { + if (stricmp(ctf_forcejoin->string, "red") == 0) { + joinmenu[7].text = NULL; + joinmenu[7].SelectFunc = NULL; + } else if (stricmp(ctf_forcejoin->string, "blue") == 0) { + joinmenu[5].text = NULL; + joinmenu[5].SelectFunc = NULL; + } + } + + if (ent->client->chase_target) + joinmenu[9].text = "3. Leave Chase Camera"; + else + joinmenu[9].text = "3. Chase Camera"; + + 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.ctf_team == CTF_TEAM1) + num1++; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + num2++; + } + + sprintf(team1players, " (%d players)", num1); + sprintf(team2players, " (%d players)", num2); + + joinmenu[17].text = "*Current Map"; + joinmenu[18].text = levelname; + if (joinmenu[5].text) + joinmenu[6].text = team1players; + else + joinmenu[6].text = NULL; + if (joinmenu[7].text) + joinmenu[8].text = team2players; + else + joinmenu[8].text = NULL; + + if (num1 > num2) + return CTF_TEAM1; + else if (num2 > num1) + return CTF_TEAM1; + return (rand() & 1) ? CTF_TEAM1 : CTF_TEAM2; +} + +void WFClassCount(edict_t *self, int *classcount) +{ + edict_t *e; + int i; + int j; + + for (j = 1; j <= MAX_CLASSES; ++j) + classcount[j] = 0; + + //Loop through all the entities to find players on the same team + for (i = 1, e = g_edicts + i; i < game.maxclients; i++, e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + //When using the menu, the team isn't set yet. Must + //use next team variable +// if (e->wf_team != self->wf_team) + if (e->client->resp.ctf_team == self->client->resp.next_ctf_team) + continue; + if (e->client->resp.ctf_team != CTF_TEAM1 && e->client->resp.ctf_team != CTF_TEAM2) + continue; + + j = e->client->pers.player_class; + if ((j >= 0) && (j <= MAX_CLASSES)) + ++classcount[j]; + } +} + +void WFUpdateClassMenu(edict_t *self) +{ + int i; + int pos; + + + int classcount[MAX_CLASSES + 1]; + +// i = 0; +// pos = 1 / i; + + sprintf(menutitle,"*Choose a class (%s)",classdefname); + + WFClassCount(self, classcount); + + pos = 6; + for (i = 1; i <= numclasses && i <= MAX_CLASSES; ++i) + { + sprintf(classmenustring[i], "%d. %s (%d)", i, classinfo[i].name, classcount[i]); + wfclassmenu[pos].text = classmenustring[i]; + wfclassmenu[pos].SelectFunc = WFClassPicked; + wfclassmenu[pos].arg = i; + ++pos; + } + + //Clear the rest of the menu + while (pos <= MAX_CLASSES) + { + wfclassmenu[pos].text = ""; + wfclassmenu[pos].SelectFunc = NULL; + wfclassmenu[pos].arg = 0; + ++pos; + } +} + +//WF +void WFOpenClassMenu(edict_t *ent) +{ + if (ent->client->menu) PMenu_Close(ent); + WFUpdateClassMenu(ent); + + //Open class menu if classes are on. Otherwise skip to team menu + if (((int)wfflags->value & WF_NO_PLAYER_CLASSES) == 0) + PMenu_Open(ent, wfclassmenu, -1, sizeof(wfclassmenu) / sizeof(pmenu_t), true, true); +// else +// CTFOpenJoinMenu(ent); +} +//WF + +void CTFOpenJoinMenu(edict_t *ent) +{ + int team; + + if (ent->client->menu) PMenu_Close(ent); + team = CTFUpdateJoinMenu(ent); + if (ent->client->chase_target) + team = 8; + else if (team == CTF_TEAM1) + team = 4; + else + team = 6; + PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t), true, true); +} + +void CTFCredits(edict_t *ent, pmenu_t *p) +{ + if (ent->client->menu) PMenu_Close(ent); + PMenu_Open(ent, creditsmenu, -1, sizeof(creditsmenu) / sizeof(pmenu_t), false, true); +} + +//WF +void WFCredits(edict_t *ent, pmenu_t *p) +{ + if (ent->client->menu) PMenu_Close(ent); + PMenu_Open(ent, wfcreditsmenu, -1, sizeof(wfcreditsmenu) / sizeof(pmenu_t), false, true); +} +//WF + +qboolean CTFStartClient(edict_t *ent) +{ + +//gi.dprintf("DEBUG-1 - Next class = %d\n", ent->client->pers.next_player_class); + + //If CTF is off, use class num to see if this is the first time joining + if (!ctf->value) + { +// if(ent->client->resp.inServer)//acrid 3/99 botcam ,fix this? +// return false; +//gi.dprintf("DEBUG-2 - Next class = %d\n", ent->client->pers.next_player_class); + if (ent->client->pers.player_class) + return false; +//gi.dprintf("DEBUG-3 - Next class = %d\n", ent->client->pers.next_player_class); + + if (!((int)dmflags->value & DF_CTF_FORCEJOIN)) { + // start as 'observer' + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + //ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + WFOpenClassMenu(ent); + } + return true; + } + + + + if (ent->client->resp.ctf_team != CTF_NOTEAM) + return false; + + if (!((int)dmflags->value & DF_CTF_FORCEJOIN)) { + // start as 'observer' + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + +//WF + CTFOpenJoinMenu(ent); + //WFOpenClassMenu(ent); +//WF + + return true; + } + return false; +} + +qboolean CTFCheckRules(void) +{ + if (capturelimit->value && + (ctfgame.team1 >= capturelimit->value || + ctfgame.team2 >= capturelimit->value)) { + my_bprintf (PRINT_HIGH, "Capturelimit hit.\n"); + return true; + } + + //give them the frag limit warning + if (((int)wfflags->value & WF_MAP_VOTE) && + (ctfgame.team1 >= (capturelimit->value - 1) || ctfgame.team2 >= (capturelimit->value-1)) && + (maplist.warning_given == false)) + { + my_bprintf (PRINT_MEDIUM,"-- Capture Limit Warning (1 left): Type 'vote' to pick next map --\n"); + maplist.warning_given = true; + } + + return false; +} + +/*-------------------------------------------------------------------------- + * just here to help old map conversions + *--------------------------------------------------------------------------*/ + +static void old_teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + vec3_t forward; + + if (!other->client) + return; + + //See if there is a team specific indicator + if ((self->wf_team) && (self->wf_team != other->wf_team)) + return; + + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + +//ZOID + CTFPlayerResetGrapple(other); +//ZOID + + // 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->enemy->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]); + + other->s.angles[PITCH] = 0; + other->s.angles[YAW] = dest->s.angles[YAW]; + other->s.angles[ROLL] = 0; + VectorCopy (dest->s.angles, other->client->ps.viewangles); + VectorCopy (dest->s.angles, other->client->v_angle); + + // give a little forward velocity + AngleVectors (other->client->v_angle, forward, NULL, NULL); + VectorScale(forward, 200, other->velocity); + + // kill anything at the destination + if (!KillBox (other)) + { + } + + gi.linkentity (other); +} + +/*QUAKED trigger_teleport (0.5 0.5 0.5) ? +Players touching this will be teleported +*/ +void SP_trigger_teleport (edict_t *ent) +{ + edict_t *s; + int i; + + if (!ent->target) + { + gi.dprintf ("teleporter without a target.\n"); + G_FreeEdict (ent); + return; + } + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + ent->touch = old_teleporter_touch; + gi.setmodel (ent, ent->model); + gi.linkentity (ent); + + // noise maker and splash effect dude + s = G_Spawn(); + ent->enemy = s; + for (i = 0; i < 3; i++) + s->s.origin[i] = ent->mins[i] + (ent->maxs[i] - ent->mins[i])/2; + s->s.sound = gi.soundindex ("world/hum1.wav"); + gi.linkentity(s); + +} + +/*QUAKED info_teleport_destination (0.5 0.5 0.5) (-16 -16 -24) (16 16 32) +Point trigger_teleports at these. +*/ +void SP_info_teleport_destination (edict_t *ent) +{ + ent->s.origin[2] += 16; +} + +void WFDisguiseTeam(edict_t *ent, pmenu_t *p); +void WFDisguiseClassCount(edict_t *self, int *classcount); +void WFDisguiseTeamChoose(edict_t *ent, pmenu_t *p); +void WFUpdateDisguiseClassMenu(edict_t *self); +void WFDisguiseClass(edict_t *ent, pmenu_t *p); +void WFDisguise(edict_t *ent, pmenu_t *p); + + +pmenu_t wfdisguiseclassmenu[] = { + { "The Weapons Factory", PMENU_ALIGN_CENTER, 0, NULL }, + { WF_VERSION, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "*Select A Class To Disguise As:", PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { "Help...", PMENU_ALIGN_LEFT, 0, WFShowHelp }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, 0, NULL }, + { "ENTER selects a class", PMENU_ALIGN_LEFT, 0, NULL }, + { "ESC Exits Menu", PMENU_ALIGN_LEFT, 0, NULL }, +}; +pmenu_t wfdisguiseteammenu[] = { + { "The Weapons Factory", PMENU_ALIGN_CENTER, 0, NULL }, + { WF_VERSION, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "*Disguise as Team:", PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + + { "1. On", PMENU_ALIGN_LEFT, 0, WFDisguiseTeamChoose}, + { "2. Off", PMENU_ALIGN_LEFT, 1, WFDisguiseTeamChoose}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { "Help...", PMENU_ALIGN_LEFT, 0, WFShowHelp }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, 0, NULL }, + { "ENTER selects your choice", PMENU_ALIGN_LEFT, 0, NULL }, + { "ESC Exits Menu", PMENU_ALIGN_LEFT, 0, NULL }, +}; + +pmenu_t wfdisguisemenu[] = { + { "The Weapons Factory", PMENU_ALIGN_CENTER, 0, NULL }, + { WF_VERSION, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "*Disguise Team or Class:", PMENU_ALIGN_LEFT, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + + { "1. Team", PMENU_ALIGN_LEFT, 0, WFDisguiseTeam}, + { "2. Class", PMENU_ALIGN_LEFT, 0, WFDisguiseClass}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { "Help...", PMENU_ALIGN_LEFT, 0, WFShowHelp }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, 0, NULL }, + { "ENTER selects your choice", PMENU_ALIGN_LEFT, 0, NULL }, + { "ESC Exits Menu", PMENU_ALIGN_LEFT, 0, NULL }, +}; + +void WFDisguiseClassCount(edict_t *self, int *classcount) +{ + edict_t *e; + int i; + int j; + + for (j=1; j <= MAX_CLASSES; ++j) classcount[j] = 0; + + //Loop through all the entities to find players on the same team + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + if (e->client->resp.ctf_team == self->client->resp.next_ctf_team) + continue; + ++i; + + j = e->client->pers.player_class; + if (j >= 0 && j <= 9) ++classcount[j]; + } +} + +void WFUpdateDisguiseClassMenu(edict_t *self) +{ + int i; + int pos; + int classcount[MAX_CLASSES + 1]; + + //sprintf(menutitle,"*Choose a class (%s)",classdefname); + + WFDisguiseClassCount(self, classcount); + + pos = 5; + for (i = 1; i <= numclasses && i <= MAX_CLASSES; ++i) + { + sprintf(disguisemenustring[i], "%d. %s (%d)", i, classinfo[i].name, classcount[i]); + wfdisguiseclassmenu[pos].text = disguisemenustring[i]; + wfdisguiseclassmenu[pos].SelectFunc = WFDisguise; + wfdisguiseclassmenu[pos].arg = i; + ++pos; + } + + //Clear the rest of the menu + while (pos <= MAX_CLASSES) + { + wfdisguiseclassmenu[pos].text = ""; + wfdisguiseclassmenu[pos].SelectFunc = NULL; + wfdisguiseclassmenu[pos].arg = 0; + ++pos; + } +} + +void WFChooseDisguise(edict_t *ent) +{ +// PMenu_Open(ent, wfdisguisemenu, -1, sizeof(wfdisguisemenu) / sizeof(pmenu_t), true, false); + WFUpdateDisguiseClassMenu(ent); + PMenu_Open(ent, wfdisguiseclassmenu, -1, sizeof(wfdisguiseclassmenu) / sizeof(pmenu_t), true, false); +} + +void WFDisguiseClass(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + if(ent->disguisedteam) + WFUpdateDisguiseClassMenu(ent); + PMenu_Open(ent, wfdisguiseclassmenu, -1, sizeof(wfdisguiseclassmenu) / sizeof(pmenu_t), true, false); +} + +void WFDisguiseTeam(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + + PMenu_Open(ent, wfdisguiseteammenu, -1, sizeof(wfdisguiseteammenu) / sizeof(pmenu_t), true, false); +} + +/*================= +CMD_Feign - command to fake death +=================*/ +void Cmd_Feign_f (edict_t *ent) +{ + char *string; + + string=gi.args(); + + if (ent->health <=0) + return;//3/99 + + // Make sure player hasn't got the enemy flag + if (CTFHasFlag(ent)) + { + safe_cprintf(ent, PRINT_HIGH, "You can't feign death while holding the flag!\n"); + return; + } + + if (Q_stricmp ( string, "on") == 0) + { + safe_cprintf (ent, PRINT_HIGH, "Feign On\n"); + ent->client->pers.feign = 1; + } + else if (Q_stricmp ( string, "off") == 0) + { + safe_cprintf (ent, PRINT_HIGH, "Feign Off\n"); + ent->client->pers.feign = 0; + } + else //If no "on" or "off", toggle state + { + if (ent->client->pers.feign == 0) + { + safe_cprintf (ent, PRINT_HIGH, "Feign On\n"); + ent->client->pers.feign = 1; + } + else + { + safe_cprintf (ent, PRINT_HIGH, "Feign Off\n"); + ent->client->pers.feign = 0; + } + + if (ent->client->pers.feign == 1) + feign_on (ent); + else + feign_off (ent); + } +} + +/*================= +cmd_Disguise - command to disguise +=================*/ +void cmd_Disguise (edict_t *ent) +{ + int i; + char *s; + + // Make sure player hasn't got the enemy flag + if (CTFHasFlag(ent)) + { + safe_cprintf(ent, PRINT_HIGH, "You can't disguise while holding the flag!\n"); + return; + } + + i = atoi (gi.argv(1)); //argument = class number + if (i < 1 || i > numclasses) i = 1; + + ent->disguised = i; + ent->disguising = i; + ent->disguisedteam = ent->disguisingteam; + ent->disguiseshots=1; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + safe_cprintf(ent, PRINT_HIGH, "You are disguised.\n"); +} + +void WFDisguise(edict_t *ent, pmenu_t *p) +{ + char *s; + + //ent->disguisetime= level.time+5; + PMenu_Close(ent); + + // Make sure player hasn't got the enemy flag + if (CTFHasFlag(ent)) + { + safe_cprintf(ent, PRINT_HIGH, "You can't disguise while holding the flag!\n"); + return; + } + + ent->disguising = p->arg; + if (ent->disguising) + { + ent->disguised = ent->disguising; + ent->disguisedteam=ent->disguisingteam; + ent->disguiseshots=1; //was 4 (G.R.) + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + safe_cprintf(ent, PRINT_HIGH, "You are disguised.\n"); + } +} + +void WFDisguiseTeamChoose(edict_t *ent, pmenu_t *p) +{ +// char *s; + ent->disguisingteam = p->arg; + ent->disguisetime= level.time+5; + + PMenu_Close(ent); +} + +void WFRemoveDisguise(edict_t *ent) +{ + char *s; + + ent->disguised = 0; + ent->disguising = 0; + ent->disguisetime = 0; + ent->disguisingteam = 0; + ent->disguisedteam = 0; + ent->disguiseshots = 0; + + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + safe_cprintf(ent, PRINT_HIGH, "Disguise Off\n"); +} + + +/* +qboolean WFPickup_Key(edict_t *ent, edict_t *other) +{ + int i; + edict_t *player; + gitem_t *key_item; + qboolean ret; + + + //Cant create decoy in observer/spectator mode + if (other->solid == SOLID_NOT) + { + safe_cprintf(other, PRINT_HIGH, "Nice try, but observers can't pick up the flag!.\n"); + return false; + } + + key_item = ??find key by name?? + + + other->client->pers.inventory[ITEM_INDEX(key_item)] = 1; + other->client->resp.ctf_flagsince = level.time; + + + // pick up the flag + // if it's not a dropped flag, we just make is disappear + // if it's dropped, it will be removed by the pickup caller + if (!(ent->spawnflags & DROPPED_ITEM)) + { + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + return true; + +} + +static void WFDropKeyTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //owner (who dropped us) can't touch for two secs + if (other == ent->owner && + ent->nextthink - level.time > CTF_AUTO_FLAG_RETURN_TIMEOUT-2) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void WFDropKeyThink(edict_t *ent) +{ + // auto return the flag + // reset flag will remove ourselves + if (strcmp(ent->classname, "item_flag_team1") == 0) { + CTFResetFlag(CTF_TEAM1); + my_bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + } else if (strcmp(ent->classname, "item_flag_team2") == 0) { + CTFResetFlag(CTF_TEAM2); + my_bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM2)); + } +} + +int WFHasKey(edict_t *self) +{ + int hasflag; + + hasflag = 0; + if (self->client->pers.inventory[ITEM_INDEX(flag1_item)]) + hasflag = 1; + else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)]) + hasflag = 1; + + return (hasflag); +} + +// Called from PlayerDie, to drop the flag from a dying player +void WFDeadDropKey(edict_t *self) +{ + edict_t *dropped = NULL; + + if (!flag1_item || !flag2_item) + CTFInit(); + + if (self->client->pers.inventory[ITEM_INDEX(flag1_item)]) + { + dropped = Drop_Item(self, flag1_item); + self->client->pers.inventory[ITEM_INDEX(flag1_item)] = 0; + my_bprintf(PRINT_HIGH, "%s lost the %s flag!\n", + self->client->pers.netname, CTFTeamName(CTF_TEAM1)); + flag1dropped = 1; + } + else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)]) + { + dropped = Drop_Item(self, flag2_item); + self->client->pers.inventory[ITEM_INDEX(flag2_item)] = 0; + my_bprintf(PRINT_HIGH, "%s lost the %s flag!\n", + self->client->pers.netname, CTFTeamName(CTF_TEAM2)); + flag2dropped = 1; + } + + if (dropped) { + dropped->think = CTFDropFlagThink; + dropped->nextthink = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT; + dropped->touch = CTFDropFlagTouch; + } +} + +static void WFKeyThink(edict_t *ent) +{ + ent->nextthink = level.time + FRAMETIME; +} + + +*/ +pmenu_t wfMapHelpMenu[] = { + { "The Weapons Factory", PMENU_ALIGN_CENTER, 0, NULL }, + { WF_VERSION, PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "Map Help", PMENU_ALIGN_CENTER, 0, NULL }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + { NULL, PMENU_ALIGN_LEFT, 0, NULL}, + + { NULL, PMENU_ALIGN_LEFT, 0, NULL }, + { "Help...", PMENU_ALIGN_LEFT, 0, WFShowHelp }, + { NULL, PMENU_ALIGN_CENTER, 0, NULL }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, 0, NULL }, + { "ENTER selects your choice", PMENU_ALIGN_LEFT, 0, NULL }, + { "ESC Exits Menu", PMENU_ALIGN_LEFT, 0, NULL }, +}; + +void WFFillMapHelp(edict_t *ent) +{ + wfMapHelpMenu[5].text = level.level_name; + wfMapHelpMenu[7].text = game.helpmessage1; + wfMapHelpMenu[9].text = game.helpmessage2; +} + +void Cmd_MapHelp_f (edict_t *ent) +{ + if (ent->client->menu) + { + PMenu_Close(ent); + return; + } + + WFFillMapHelp(ent); + PMenu_Open(ent, wfMapHelpMenu, -1, sizeof(wfMapHelpMenu) / sizeof(pmenu_t), true, false); +} \ No newline at end of file diff --git a/g_ctf.h b/g_ctf.h new file mode 100644 index 0000000..3a96eb7 --- /dev/null +++ b/g_ctf.h @@ -0,0 +1,202 @@ + +#define CTF_VERSION 1.02 +#define CTF_VSTRING2(x) #x +#define CTF_VSTRING(x) CTF_VSTRING2(x) +#define CTF_STRING_VERSION CTF_VSTRING(CTF_VERSION) + +#define STAT_CTF_TEAM1_PIC 17 +#define STAT_CTF_TEAM1_CAPS 18 +#define STAT_CTF_TEAM2_PIC 19 +#define STAT_CTF_TEAM2_CAPS 20 +#define STAT_CTF_FLAG_PIC 21 +#define STAT_CTF_JOINED_TEAM1_PIC 22 +#define STAT_CTF_JOINED_TEAM2_PIC 23 +#define STAT_CTF_TEAM1_HEADER 24 +#define STAT_CTF_TEAM2_HEADER 25 +#define STAT_CTF_TECH 26 +#define STAT_CTF_ID_VIEW 27 + +//WF +#define STAT_DAMAGE 28 +#define STAT_DAMAGE_ICON 29 +#define STAT_TIMEOUT_ICON 30 +//WF + +typedef enum { + CTF_NOTEAM, + CTF_TEAM1, + CTF_TEAM2 +} ctfteam_t; + +typedef enum { + CTF_STATE_START, + CTF_STATE_PLAYING +} ctfstate_t; + +typedef enum { + CTF_GRAPPLE_STATE_FLY, + CTF_GRAPPLE_STATE_PULL, + CTF_GRAPPLE_STATE_HANG +} ctfgrapplestate_t; + + +typedef struct ctfgame_s +{ + int team1, team2; + int total1, total2; // these are only set when going into intermission! + float last_flag_capture; + int last_capture_team; +} ctfgame_t; + + +extern cvar_t *ctf; + +#define CTF_TEAM1_SKIN "ctf_r" +#define CTF_TEAM1_SKIN_RECON "xrec_r" +#define CTF_TEAM1_SKIN_NURSE "xnur_r" +#define CTF_TEAM1_SKIN_ENGINEER "xeng_r" +#define CTF_TEAM1_SKIN_MARINE "xmar_r" +#define CTF_TEAM1_SKIN_CYBORG "xcyb_r" +#define CTF_TEAM1_SKIN_ARSONIST "xars_r" + +#define CTF_TEAM2_SKIN "ctf_b" +#define CTF_TEAM2_SKIN_RECON "xrec_b" +#define CTF_TEAM2_SKIN_NURSE "xnur_b" +#define CTF_TEAM2_SKIN_ENGINEER "xeng_b" +#define CTF_TEAM2_SKIN_MARINE "xmar_b" +#define CTF_TEAM2_SKIN_CYBORG "xcyb_b" +#define CTF_TEAM2_SKIN_ARSONIST "xars_b" + +#define DF_CTF_FORCEJOIN 131072 +#define DF_ARMOR_PROTECT 262144 +#define DF_CTF_NO_TECH 524288 + +#define CTF_FLAG_RETURN_TIME 40 // seconds until auto return + +//WF - Made these variables so they can be set by server admin +#ifdef WFMAIN +int CTF_FRAG_POINTS = 1; // Points for a frag +int CTF_SUICIDE_POINTS = -1; // Points for a suicide +int CTF_SENTRY_POINTS = 1; // Points for killing a sentry gun +int CTF_CAPTURE_BONUS = 15; // what you get for capture +int CTF_TEAM_BONUS = 10; // what your team gets for capture +int CTF_RECOVERY_BONUS = 1; // what you get for recovery +int CTF_FLAG_BONUS = 0; // what you get for picking up enemy flag +int CTF_FRAG_CARRIER_BONUS = 2; // what you get for fragging enemy flag carrier +int CTF_CARRIER_DANGER_PROTECT_BONUS = 2; // bonus for fraggin someone who has recently hurt your flag carrier +int CTF_CARRIER_PROTECT_BONUS = 1; // bonus for fraggin someone while either you or your target are near your flag carrier +int CTF_FLAG_DEFENSE_BONUS = 1; // bonus for fraggin someone while either you or your target are near your flag +int CTF_RETURN_FLAG_ASSIST_BONUS = 1; // awarded for returning a flag that causes a capture to happen almost immediately +int CTF_FRAG_CARRIER_ASSIST_BONUS = 2; // award for fragging a flag carrier if a capture happens almost immediately +#else +extern int CTF_FRAG_POINTS; +extern int CTF_SUICIDE_POINTS; +extern int CTF_SENTRY_POINTS; +extern int CTF_CAPTURE_BONUS; +extern int CTF_TEAM_BONUS; +extern int CTF_RECOVERY_BONUS; +extern int CTF_FLAG_BONUS; +extern int CTF_FRAG_CARRIER_BONUS; +extern int CTF_CARRIER_DANGER_PROTECT_BONUS; +extern int CTF_CARRIER_PROTECT_BONUS; +extern int CTF_FLAG_DEFENSE_BONUS; +extern int CTF_RETURN_FLAG_ASSIST_BONUS; +extern int CTF_FRAG_CARRIER_ASSIST_BONUS; +#endif + +#define CTF_TARGET_PROTECT_RADIUS 400 // the radius around an object being defended where a target will be worth extra frags +#define CTF_ATTACKER_PROTECT_RADIUS 400 // the radius around an object being defended where an attacker will get extra frags when making kills + +#define CTF_CARRIER_DANGER_PROTECT_TIMEOUT 8 +#define CTF_FRAG_CARRIER_ASSIST_TIMEOUT 10 +#define CTF_RETURN_FLAG_ASSIST_TIMEOUT 10 + +#define CTF_AUTO_FLAG_RETURN_TIMEOUT 45 // number of seconds before dropped flag auto-returns + +#define CTF_TECH_TIMEOUT 60 // seconds before techs spawn again + +#define CTF_GRAPPLE_SPEED 650 // speed of grapple in flight +#define CTF_GRAPPLE_PULL_SPEED 650 // speed player is pulled at + +void CTFInit(void); + +void SP_info_player_team1(edict_t *self); +void SP_info_player_team2(edict_t *self); + +char *CTFTeamName(int team); +char *CTFOtherTeamName(int team); +void CTFAssignSkin(edict_t *ent, char *s); +void CTFAssignTeam(gclient_t *who, qboolean is_bot); +edict_t *SelectCTFSpawnPoint (edict_t *ent); +qboolean CTFPickup_Flag(edict_t *ent, edict_t *other); +qboolean CTFDrop_Flag(edict_t *ent, gitem_t *item); +void CTFEffects(edict_t *player); +void CTFCalcScores(void); +void SetCTFStats(edict_t *ent); +void CTFDeadDropFlag(edict_t *self); +void CTFScoreboardMessage (edict_t *ent, edict_t *killer); +void CTFTeam_f (edict_t *ent); +void CTFID_f (edict_t *ent); +void CTFSay_Team(edict_t *who, char *msg); +void CTFFlagSetup (edict_t *ent); +void CTFResetFlag(int ctf_team); +void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker); +void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker); + +// GRAPPLE +void CTFWeapon_Grapple (edict_t *ent); +void CTFPlayerResetGrapple(edict_t *ent); +void CTFGrapplePull(edict_t *self); +void CTFResetGrapple(edict_t *self); + +//TECH +gitem_t *CTFWhat_Tech(edict_t *ent); +qboolean CTFPickup_Tech (edict_t *ent, edict_t *other); +void CTFDrop_Tech(edict_t *ent, gitem_t *item); +void CTFDeadDropTech(edict_t *ent); +void CTFSetupTechSpawn(void); +int CTFApplyResistance(edict_t *ent, int dmg); +int CTFApplyStrength(edict_t *ent, int dmg); +qboolean CTFApplyStrengthSound(edict_t *ent); +qboolean CTFApplyHaste(edict_t *ent); +void CTFApplyHasteSound(edict_t *ent); +void CTFApplyRegeneration(edict_t *ent); +qboolean CTFHasRegeneration(edict_t *ent); +void CTFRespawnTech(edict_t *ent); + +void CTFOpenJoinMenu(edict_t *ent); +qboolean CTFStartClient(edict_t *ent); + +qboolean CTFCheckRules(void); + +void SP_misc_ctf_banner (edict_t *ent); +void SP_misc_ctf_small_banner (edict_t *ent); + +extern char *ctf_statusbar; + +void UpdateChaseCam(edict_t *ent); +void ChaseNext(edict_t *ent); +void ChasePrev(edict_t *ent); + +void SP_trigger_teleport (edict_t *ent); +void SP_info_teleport_destination (edict_t *ent); + +//WF +void stuffcmd(edict_t *ent, char *s); +void WFShowHelp(edict_t *ent, pmenu_t *p); +//WF + +//NEWGRAPPLE Acrid 4/99 +qboolean Started_Grappling(gclient_t *client); +qboolean Ended_Grappling(gclient_t *client); +qboolean Is_Grappling(gclient_t *client); +void CTFPlayerResetGrapple2(edict_t *ent); +void CTFResetGrapple2(edict_t *ent); +void CTFGrappleFire2 (edict_t *ent, vec3_t g_offset, int damage, int effect); +void CTFWeapon_Grapple_Fire2 (edict_t *ent); +void CTFWeapon_Grapple2 (edict_t *ent); +void CTFGrappleTouch2 (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); +void CTFFireGrapple2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect); +void CTFGrappleTouch2 (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); +void CTFGrapplePull2(edict_t *self); +void CTFGrappleThink2( edict_t *self ); \ No newline at end of file diff --git a/g_ent.c b/g_ent.c new file mode 100644 index 0000000..21abd15 --- /dev/null +++ b/g_ent.c @@ -0,0 +1,61 @@ +/*============================== +Acrid Added 5/99 +===============================*/ +#include "g_local.h" + +char *ReadEntFile(char *filename) +{ + + FILE *fp; + char *filestring = NULL; + long int i = 0; + int ch; + + while (true) + { + fp = fopen(filename, "r"); + if (!fp) break; + + for (i=0; (ch = fgetc(fp)) != EOF; i++) + ; + + filestring = gi.TagMalloc(i+1, TAG_LEVEL); + if (!filestring) break; + + fseek(fp, 0, SEEK_SET); + for (i=0; (ch = fgetc(fp)) != EOF; i++) + filestring[i] = ch; + filestring[i] = '\0'; + + break; + } + + if (fp) fclose(fp); + + return(filestring); +} + +char *LoadEntFile(char *mapname, char *entities) +{ + char entfilename[MAX_QPATH] = ""; + char *newentities; + int i; + + sprintf(entfilename, "wf/ent/%s.ent", mapname); + // convert string to all lowercase (for Linux) + for (i = 0; entfilename[i]; i++) + entfilename[i] = tolower(entfilename[i]); + + newentities = ReadEntFile(entfilename); + + if (newentities) + { //leave these dprints active they show up in the server init console section + gi.dprintf("%s.ent Loaded\n", mapname); + return(newentities); // reassign the ents + } + else + { + gi.dprintf("No .ent File for %s.bsp\n", mapname); + return(entities); + } +} diff --git a/g_func.c b/g_func.c new file mode 100644 index 0000000..6487830 --- /dev/null +++ b/g_func.c @@ -0,0 +1,2388 @@ +#include "g_local.h" +//WF34 START +void turret_self_remove(edict_t *ent); +void WFAddBonus(edict_t *self, edict_t *other); +//WF34 END +#include "p_trail.h"//ERASER +#include "bot_procs.h"//ERASER + +/* +========================================================= + + 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 +/*FIXME ACRID WF24 USES THESE 4 LINES +#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 + + +// +// 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; +} + +edict_t *plat_ent = NULL, *start_trail; //ERASER used for node laying + +void plat_go_down (edict_t *ent); + +void plat_hit_top (edict_t *ent)//acrid fixme look for grapple fix +{//ERASER START + if ((bot_calc_nodes->value) && (ent->movetarget) && (ent->frags != 1) && plat_ent && (ent->movetarget->groundentity == ent)) + { + int i; + vec3_t save_org; + + NodeDebug("Dropping PLAT nodes\n"); + + // move plat back to starting pos, so we can see other trails + VectorCopy(ent->s.origin, save_org); + VectorCopy(ent->jump_velocity, ent->s.origin); + gi.linkentity(ent); + + // make sure the start_trail is directly below current position of player + plat_ent->s.origin[0] = ent->movetarget->s.origin[0]; + plat_ent->s.origin[1] = ent->movetarget->s.origin[1]; + + // drop the starting position node + PlayerTrail_Add(plat_ent, plat_ent->s.origin, NULL, false, false, NODE_PLAT); + start_trail = plat_ent->last_trail_dropped; + + if (start_trail) + start_trail->target_ent = ent; + + // restore position + VectorCopy(save_org, ent->s.origin); + gi.linkentity(ent); + + // drop the top node + PlayerTrail_Add(ent->movetarget, ent->movetarget->s.origin, NULL, false, false, NODE_NORMAL); + + // set visibility of nodes + NodeDebug("Connecting PLAT nodes\n"); + + if (start_trail) + { + for (i=0; i < MAX_PATHS; i++) + { + if ((start_trail->paths[i] == -1) || (start_trail->paths[i] == ent->movetarget->last_trail_dropped->trail_index)) + break; + } + if (i >= MAX_PATHS) // max_nodes, so override first slot + start_trail->paths[0] = ent->movetarget->last_trail_dropped->trail_index; + else + start_trail->paths[i] = ent->movetarget->last_trail_dropped->trail_index; + + CalcRoutes(start_trail->trail_index); + } + + +NodeDebug("Plat start node has %i visible nodes\n", i); + + CalcRoutes(ent->movetarget->last_trail_dropped->trail_index); + + G_FreeEdict(plat_ent); + plat_ent = NULL; + + ent->frags = 1; // don't drop more than 1 start node for each platform + } + + if (ent->movetarget) + { // turn off player riding plat flag, so they can resume dropping nodes + + if (!ent->movetarget->bot_client && bot_calc_nodes->value) + { + int i; + + if ((i = ClosestNodeToEnt(ent->movetarget, false, true)) > -1) + { + ent->movetarget->last_trail_dropped = trail[i]; + } + } + + ent->movetarget->flags &= ~FL_TEAMSLAVE; + ent->movetarget = NULL; + } +//ERASER END + 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) +{//ERASER START + if (bot_calc_nodes->value && ent->movetarget) + { // set plat_ent position + vec3_t org, orgdest, dir; + trace_t trace; + + // make sure the plat is somewhat below us + AngleVectors(ent->movetarget->s.angles, dir, NULL, NULL); + VectorMA(ent->movetarget->s.origin, 12, dir, org); + VectorSubtract(org, tv(0,0,32), orgdest); + + trace = gi.trace(org, VEC_ORIGIN, VEC_ORIGIN, orgdest, ent->movetarget, MASK_SOLID); + + if (trace.ent != ent) + goto not_on_plat; + + ent->movetarget->flags |= FL_TEAMSLAVE; // always set this, so we don't drop a shitload of trails while riding the plat + + if (ent->frags != 1) + { +NodeDebug("Setting plat_ent\n"); + if (!plat_ent) + plat_ent = G_Spawn(); + + plat_ent->classname = "plat_ent"; + VectorCopy(tv(-16, -16, -24), plat_ent->mins); + VectorCopy(tv(16, 16, 32), plat_ent->maxs); + + VectorCopy(VEC_ORIGIN, plat_ent->velocity); + VectorCopy(ent->movetarget->s.origin, plat_ent->s.origin); + + // store the starting position for node dropping + VectorCopy(ent->s.origin, ent->jump_velocity); + + ent->movetarget->flags |= FL_TEAMSLAVE; + } + } + +not_on_plat: +//ERASER END + 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) + {//WF34 + if (!strcmp(other->classname, "SentryGun") ) + turret_self_remove(other); + else + BecomeExplosion1 (other);//NORMAL LINE + }//WF34 + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1 + (other->bot_client)*60, 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 + + if ((ent->wf_team) && (ent->wf_team != other->wf_team)) + return; + 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; + +//ERASER START hack, don't go up if the bot doesn't specifically want to + if (other->bot_client && (!other->goalentity || ((other->goalentity->s.origin[2] + 48) < other->s.origin[2]))) + return; +//ERASER END +//WF34See if there is a team specific indicator + if ((ent->wf_team) && (ent->wf_team != other->wf_team)) + return; //WF34 + ent = ent->enemy; // now point at the plat, not the trigger + + ent->movetarget = other; //ERASER NODES: tell the plat who is riding it + + 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; + + trigger->wf_team = ent->wf_team; + + 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 +"bonustype" +1) Capture bonus +2) Player Frag bonus +3) Team Frag bonus +"bonusvalue" +"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) +{ + if (wfdebug) gi.dprintf("button_return.\n");//WF34 + 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) +{ + if (wfdebug) gi.dprintf("button_wait.\n");//WF34 + self->moveinfo.state = STATE_TOP; + self->s.effects &= ~EF_ANIM01; + self->s.effects |= EF_ANIM23; + +if (wfdebug) gi.dprintf("Self team=%d, activator_team = %d, activator=%s.\n", + self->wf_team, self->activator->wf_team, self->activator->classname); + if ((self->wf_team) && (self->wf_team != self->activator->wf_team)) + {} + else + { + 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 (wfdebug) gi.dprintf("button_fire.\n"); + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + return; +if (wfdebug) gi.dprintf("button_fire-1.\n"); + + 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); + + //Should we award a bonus? + if (self->bonustype) + { +if (wfdebug) gi.dprintf("button_fire-2.\n"); + WFAddBonus(self, self->activator); + } + +} + +void button_use (edict_t *self, edict_t *other, edict_t *activator) +{ +if (wfdebug) gi.dprintf("button_use.\n"); + self->activator = activator; + button_fire (self); +} + +void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ +if (wfdebug) gi.dprintf("button_touch.\n"); + if (!other->client) + return; + + if (other->health <= 0) + return; + +//ERASER START nodes, drop a node here if not touched yet + if (self->frags < 1) + { + self->frags = 1; + PlayerTrail_Add(other, other->s.origin, NULL, false, true, NODE_BUTTON); + } + else if (other->bot_client) + { + other->activator = NULL; + other->activator_time = level.time + 8; + } +//ERASER END + + //Team specific? + if ((self->wf_team) && (self->wf_team != other->wf_team)) + return; + +if (wfdebug) gi.dprintf("button_touch-1.\n"); + + self->activator = other; + button_fire (self); +} + +void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + if (wfdebug) gi.dprintf("button_killed. Inflictor=%s,Attacker=%s \n", inflictor->classname, attacker->classname); + + self->activator = attacker; + self->health = self->max_health; + self->takedamage = DAMAGE_NO; + + button_fire (self); +} + +void botButtonThink(edict_t *ent);//ERASER + +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); + //WF34 +if (wfdebug) gi.dprintf("func_button: bonustype = %d\n", + ent->bonustype);//WF34 + 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 + allready 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 +"wf_team" the team the door is for 0 means not a team door + +*/ + +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); + +//ERASER START Bot stuff + if (activator && activator->bot_client) + { + activator->bot_plat_pausetime = level.time + 0.8; + activator->checkstuck_time = level.time + 1.5; // don't worry about not moving + } + +} +//ERASER END +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) + { + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_down (ent); + } + return; + } + } + + // 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) +{ + if (other->health <= 0) + return; + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + return; +//WF34 S +//gi.dprintf("trigger team=%d, entity team = %d\n", self->wf_team, other->wf_team); + + if(self->wf_team)//WF John added this for team doors 6/26/98 + { + if(self->wf_team != other->wf_team) + return; + } +//WF34 E +// if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER)) +// return;//WF24 FIXME USES THESE LINES + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 1.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; + other->wf_team = ent->wf_team; +//STWF WAS ALREADY CO +//gi.dprintf("door spawn. = %d\n",ent->wf_team ); + //WAS ALREADY CO + 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) + {//WF34 + if (!strcmp(other->classname, "SentryGun") ) + turret_self_remove(other); + else + BecomeExplosion1 (other);//NORMAL + }//WF34 + return; + } + + 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; + + if (!other->bot_client)//ERASER + {//E + gi.centerprintf (other, "%s", self->message); + gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + }//E +} + +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); + +//gi.dprintf("SP_func_door. = %d\n",ent->wf_team);//WAS ALREADY CO + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (deathmatch->value)//WF24 + ent->speed *= 2;//WF24 + + 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) + {//WF34 + if (!strcmp(other->classname, "SentryGun") ) + turret_self_remove(other); + else + BecomeExplosion1 (other);//NORMAL + }//WF34 + 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); + self->s.event = EV_OTHER_TELEPORT;//WF34 + 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) + {//WF34 + if (!strcmp(other->classname, "SentryGun") ) + turret_self_remove(other); + else + BecomeExplosion1 (other);//NORMAL + }//WF34 + 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; +} +/*QUAKED rotating_light (0 .5 .8) (-8 -8 -8) (8 8 8) START_OFF ALARM +"health" if set, the light may be killed. +*/ + +// RAFAEL +// note to self +// the lights will take damage from explosions +// this could leave a player in total darkness very bad +//Acrid lightcode 1/29/99 +#define START_OFF 1 + +void rotating_light_alarm (edict_t *self) +{ + if (self->spawnflags & START_OFF) + { + self->think = NULL; + self->nextthink = 0; + } + else + { + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->nextthink = level.time + 1; + } +} + +void rotating_light_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WELDING_SPARKS); + gi.WriteByte (30); + gi.WritePosition (self->s.origin); + gi.WriteDir (vec3_origin); + gi.WriteByte (0xe0 + (rand()&7)); + gi.multicast (self->s.origin, MULTICAST_PVS); + + self->s.effects &= ~EF_SPINNINGLIGHTS; + self->use = NULL; + + self->think = G_FreeEdict; + self->nextthink = level.time + 0.1; + +} + +static void rotating_light_use (edict_t *self, edict_t *other, edict_t *activator) +{ + + //Don't do rotating light if WF_SPECIAL_LIGHTS is not enabled + if (((int)wfflags->value & WF_SPECIAL_LIGHTS) == 0) + return; + + if (self->spawnflags & START_OFF) + { + self->spawnflags &= ~START_OFF; + self->s.effects |= EF_SPINNINGLIGHTS; + + if (self->spawnflags & 2) + { + self->think = rotating_light_alarm; + self->nextthink = level.time + 0.1; + } + } + else + { + self->spawnflags |= START_OFF; + self->s.effects &= ~EF_SPINNINGLIGHTS; + } + botDebugPrint("health = %i\n",self->health); +} + + +void SP_rotating_light (edict_t *self) +{ + + self->movetype = MOVETYPE_STOP; + self->solid = SOLID_BBOX; + + self->s.modelindex = gi.modelindex ("models/objects/light/tris.md2"); + + self->s.frame = 0; + + self->use = rotating_light_use; + + if (self->spawnflags & START_OFF) + self->s.effects &= ~EF_SPINNINGLIGHTS; + else + { + self->s.effects |= EF_SPINNINGLIGHTS; + } + + if (!self->speed)//speed doesnt appear to work acrid + { + self->speed = 32; + // this is a real cheap way + // to set the radius of the light + // self->s.frame = self->speed; + } + if (self->health < 0)//Acrid added + { + self->max_health = self->health; + self->takedamage = DAMAGE_NO; + } + else if (!self->health) + { + self->health = 10; + self->max_health = self->health; + self->die = rotating_light_killed; + self->takedamage = DAMAGE_YES; + } + else + { + self->max_health = self->health; + self->die = rotating_light_killed; + self->takedamage = DAMAGE_YES; + } + + if (self->spawnflags & 2) + { + self->moveinfo.sound_start = gi.soundindex ("alarm.wav"); + } + + gi.linkentity (self); + +} diff --git a/g_items.c b/g_items.c new file mode 100644 index 0000000..410463b --- /dev/null +++ b/g_items.c @@ -0,0 +1,4103 @@ +#include "g_local.h" + +//ERASER START +#include "g_items.h" +#include "bot_procs.h" +#include "p_trail.h" +int CanReach(edict_t *self, edict_t *targ); +//ERASER END + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other); +void Use_Weapon (edict_t *ent, gitem_t *inv); +void Use_Grapple (edict_t *ent, gitem_t *inv);//5/99 +void Drop_Weapon (edict_t *ent, gitem_t *inv); +void Create_Home_Base(edict_t *flag, int team); + +void Weapon_Blaster (edict_t *ent); +void Weapon_BoltedBlaster (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); + +//STWF Weapons +void Weapon_BulletGrenadeLauncher (edict_t *ent); +void Weapon_FlareLauncher (edict_t *ent); +void Weapon_TimedNukeLauncher (edict_t *ent); +void Weapon_Nag (edict_t *ent); +void Weapon_PelletRocketLauncher (edict_t *ent); +void Weapon_DiseaseGrenadeLauncher (edict_t *ent); +void Weapon_FlameThrower (edict_t *ent); +void Weapon_SHCRifle (edict_t *ent); +void Weapon_FlareGun (edict_t *ent); +void Weapon_FlareGun2 (edict_t *ent); +void Weapon_NapalmGrenadeLauncher (edict_t *ent); +void Weapon_RocketNapalmLauncher (edict_t *ent); +void Weapon_NormalRocketLauncher (edict_t *ent); +void Weapon_NailGun (edict_t *ent); +void Weapon_Lightninggun (edict_t *ent); +void Weapon_Needler (edict_t *ent); +void Weapon_Telsa (edict_t *ent); +void Weapon_ChainMBPC (edict_t *ent); +void Weapon_InfectedDartLauncher (edict_t *ent); +void Weapon_ArmorDartLauncher (edict_t *ent); +void Weapon_TranquilizerRifle (edict_t *ent); +void Weapon_RocketClusterLauncher (edict_t *ent); +void Weapon_Snipe (edict_t *ent); +void Weapon_ComputerGuidedProjectileLauncher (edict_t *ent); +void Weapon_SentryRocketLauncher (edict_t *ent); +void Weapon_MegaChaingun (edict_t *ent); +void Weapon_PoisonDartLauncher (edict_t *ent); +void Weapon_Knife (edict_t *ent); +void Weapon_AK47 (edict_t *ent); +void Weapon_Pistol (edict_t *ent); +void Weapon_StingerLauncher (edict_t *ent); +void Weapon_Freezer (edict_t *ent);//acrid 3/99 + +//========= +//Rogue Weapons +void Weapon_Disintegrator (edict_t *ent); +void Weapon_ETF_Rifle (edict_t *ent); +void Weapon_Heatbeam (edict_t *ent); +//Rogue Weapons +//========= + +// RAFAEL +void Weapon_Ionripper (edict_t *ent); +void Weapon_Phalanx (edict_t *ent); +// RAFAEL + + +//Red = body, combat = yellow, jacket = green +// { base count, max, norm, energy prot, armor type } +gitem_armor_t jacketarmor_info = { 25, 100, .30, .00, ARMOR_JACKET}; +gitem_armor_t combatarmor_info = { 50, 200, .60, .30, ARMOR_COMBAT}; +//gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY}; +gitem_armor_t bodyarmor_info = {100, 400, .80, .60, ARMOR_BODY}; + +int jacket_armor_index; +int combat_armor_index; +int body_armor_index; +int power_screen_index; +int power_shield_index; + +#define HEALTH_IGNORE_MAX 1 +#define HEALTH_TIMED 2 +#define HEALTH_MEGAMAX_HEALTH 200 + +void Use_Quad (edict_t *ent, gitem_t *item); +static int quad_drop_timeout_hack; + +extern char *tnames[]; +//====================================================================== + +//====================================================================== +void WFAddArmor (edict_t *other, int armorval) +{ + int index; + + if (!other->client) return; + + //What armor do they have? + index = ArmorIndex (other); + if (index && other->client->pers.inventory[index] < other->client->player_armor) + { + other->client->pers.inventory[index] += armorval; + gi.sound (other, CHAN_VOICE, gi.soundindex ("misc/ar2_pkup.wav"), 1, ATTN_NORM, 0); + } + + //If they don't have any armor, give them something if they are allowed it + if (index == 0 && other->client->player_items & ITEM_JACKETARMOR) + { + index = ITEM_INDEX(FindItem("Jacket Armor")); + if (index) + { + other->client->pers.inventory[index] += armorval; + gi.sound (other, CHAN_VOICE, gi.soundindex ("misc/ar2_pkup.wav"), 1, ATTN_NORM, 0); + } + } + + //Don't allow greater than max armor for each class + if (index != 0 && other->client->pers.inventory[index] > other->client->player_armor) + { + other->client->pers.inventory[index] = other->client->player_armor; + } + +} + +//ERASER START +/* +=============== +AddToItemList + + Adds an item to the respective linked list, returns the head of that list +=============== +*/ +edict_t *AddToItemList(edict_t *ent, edict_t *head) +{ + edict_t *trav; + + // make sure this item isn't already in the list + trav = head; + while (trav) + { + if (trav == ent) // already in there! + return head; + + trav = trav->node_target; + } + + if (head) + head->last_goal = ent; + + ent->node_target = head; + ent->last_goal = NULL; + + return ent; +}; + +void RemoveFromItemList(edict_t *ent) +{ + if (ent->node_target) + ent->node_target->last_goal = ent->last_goal; + + if (ent->last_goal) + { + ent->last_goal->node_target = ent->node_target; +// ent->last_goal->last_goal = NULL; + } + else if (ent->node_target) // ent must be head + { + if (ent->item->pickup == Pickup_Weapon) + weapons_head = ent->node_target; + else if (ent->item->pickup == Pickup_Health) + health_head = ent->node_target; + else if (ent->item->pickup == Pickup_Ammo) + ammo_head = ent->node_target; + else + bonus_head = ent->node_target; + } + + ent->node_target = NULL; + ent->last_goal = NULL; +}; + +void RemoveDroppedItem(edict_t *ent) +{ + + //Is this ammo? + if (ent->item->pickup == Pickup_Ammo) + { + //if (wfdebug) gi.dprintf("Remove ammo. Dropped total = %d\n", TOTALDROPPEDAMMO); + --TOTALDROPPEDAMMO; + } + + + RemoveFromItemList(ent); + G_FreeEdict(ent); +}; +//ERASER END + +/* +=============== +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; +//ZOID +//in ctf, when we are weapons stay, only the master of a team of weapons +//is spawned + if (ctf->value && + ((int)dmflags->value & DF_WEAPONS_STAY) && + master->item && (master->item->flags & IT_WEAPON)) + ent = master; + else { +//ZOID + 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 ((other->bot_client) || ((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM)))//ERASER +// 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; +} + +void Drop_General (edict_t *ent, gitem_t *item) +{ + if (item->flags & IT_KEY) + { + safe_cprintf (ent, PRINT_HIGH, "You can't drop keys\n"); + return; + } + + //Don't allow observer to drop anything + if (ent->solid == SOLID_NOT) + { + safe_cprintf (ent, PRINT_HIGH, "You can't drop anything as an observer\n"); + return; + } + + if (strcmp(ent->classname, "item_breather") == 0) + { + safe_cprintf (ent, PRINT_HIGH, "You can't drop the rebreather\n"); + return; + } + + + 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; + +/* This is now handled with class specific ammo limits + 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; +} +//////////////////////////bot specials//////////////////////////////////// +void place_turret (edict_t *other); +void UpgradeSentry(edict_t *other); +void PlaceBiosentry (edict_t *other); +void SP_SupplyDepot(edict_t *other); +void SP_HealingDepot(edict_t *other); + + +qboolean Place_Special (edict_t *ent, edict_t *other) +{ + //this is the pickup it should always return false //42 + if (strcmp(ent->classname, "item_sentryspot") == 0) + if (ent->owner && !ent->owner->sentry) + ent->owner = NULL; + + if (strcmp(ent->classname, "item_depotspot") == 0) + if (ent->owner && !ent->owner->supply) + ent->owner = NULL; + + if (ent->owner) + { + botDebugPrint("%s owns me g items\n",ent->owner->client->pers.netname); + return false; + } + else botDebugPrint("noone owns me g items\n"); + + if (strcmp(ent->classname, "item_sentryspot") == 0) + { + //for classes with sentry special + if (!ent->owner && other->client->player_special & SPECIAL_SENTRY_GUN) + { + //makes the bot own the nodeitem + ent->owner = other; + place_turret (other); + UpgradeSentry(other); + if (bot_melee->value) + UpgradeSentry(other);//alt for L3 + } + + //for classes with bio special + if (!ent->owner && other->client->player_special & SPECIAL_BIOSENTRY) + { + ent->owner = other; + PlaceBiosentry (other); + } + } + + if (strcmp(ent->classname, "item_depotspot") == 0) + { + if (!ent->owner && other->client->player_special & SPECIAL_SUPPLY_DEPOT) + { + ent->owner = other; + SP_SupplyDepot(other); + } + + if (!ent->owner && other->client->player_special & SPECIAL_HEALING) + { + ent->owner = other; + SP_HealingDepot(other); + } + } + + return false; +} + + +/////////////////////////////bot specials end///////////////////////////// + +qboolean Pickup_Pack (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + int old_armor_index; + old_armor_index = ArmorIndex (other); + + //See if there is a team specific indicator + //Team=9 means that any team can pick it up, but it will respawn in 5 seconds + if ((ent->wf_team) && (ent->wf_team != other->wf_team) && (ent->wf_team != 9)) + return false; + +//Acrid pack pickup fixes 5/99 stop the real player hogs + if (!other->bot_client) + if ((other->client->pers.inventory[ITEM_INDEX(item_bullets)] != other->client->pers.max_bullets)|| + (other->client->pers.inventory[ITEM_INDEX(item_shells)] != other->client->pers.max_shells) || + (other->client->pers.inventory[ITEM_INDEX(item_rockets)] != other->client->pers.max_rockets)|| + (other->client->pers.inventory[ITEM_INDEX(item_grenades)] != other->client->pers.max_grenades)|| + (other->client->pers.inventory[ITEM_INDEX(item_cells)] != other->client->pers.max_cells ) || + (other->client->pers.inventory[ITEM_INDEX(item_slugs)] != other->client->pers.max_slugs) || + (other->health != other->max_health) || + (other->client->pers.inventory[old_armor_index] != other->client->player_armor)) + botDebugPrint("test\n"); + else + return false; + +//removed old code here + + 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; + } + + //WF - Added large health + item = FindItem("Health"); + if ((item) && (other->health < other->max_health)) + { + index = ITEM_INDEX(item); + other->health += 20; + if (other->health > other->max_health) + other->health = other->max_health; + } + + //WF - Added armor + WFAddArmor(other, 20); + + //Set the respawn time. If no team specified, use default. If it is + //a team pack, respawn in 5 seconds. + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + { + if (ent->wf_team) + SetRespawn (ent, 5); + else + 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); +//ERASER START + if (ent->bot_client) + { // select best weapon for Quad + botPickBestWeapon(ent); + } +//ERASER END +} + +//====================================================================== + +void Use_Breather (edict_t *ent, gitem_t *item) +{ +//ERASER START + gclient_t *client; + + if (ent->client) + client = ent->client; + else + return; +//ERASER END REMOVED ALL ENT-> + client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (client->breather_framenum > level.framenum) + client->breather_framenum += 300; + else + 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) +{ +//ERASER START + gclient_t *client; + + if (ent->client) + client = ent->client; + else + return; +//ERASER END REMOVED ALL ENT-> + client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (client->enviro_framenum > level.framenum) + client->enviro_framenum += 300; + else + 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) +{ +//ERASER START + gclient_t *client; + + if (ent->client) + client = ent->client; + else + return; +//ERASER END REMOVED ALL ENT-> + client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (client->invincible_framenum > level.framenum) + client->invincible_framenum += 300; + else + 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) +{ +//ERASER START + gclient_t *client; + + if (ent->client) + client = ent->client; + else + return; +//ERASER END REMOVED ALL ENT-> + client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (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) +{ +//gi.dprintf("Key pickup. team = %d\n", ent->wf_team); + + //If this is team specifc, check for correct team + if ((ent->wf_team) && (ent->wf_team != other->wf_team)) + { + return false; + } + + 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; + } + + if (other->client->pers.inventory[ITEM_INDEX(ent->item)]) + { + safe_cprintf (ent, PRINT_HIGH, "You already have one of those.\n"); + return false; + } + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value)) + SetRespawn (ent, 60); + + return true; +} + +//====================================================================== +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count) +{ + int index; + int max; + gclient_t *client;//ERASER +//ERASER START + if (ent->client) + client = ent->client; + else + return false; +//WF24 if (!ent->client) +//WF24 return false; +//ERASER END REMOVED ALL ENT-> + if (item->tag == AMMO_BULLETS) + max = client->pers.max_bullets; + else if (item->tag == AMMO_SHELLS) + max = client->pers.max_shells; + else if (item->tag == AMMO_ROCKETS) + max = client->pers.max_rockets; + else if (item->tag == AMMO_GRENADES) + max = client->pers.max_grenades; + else if (item->tag == AMMO_CELLS) + max = client->pers.max_cells; + else if (item->tag == AMMO_SLUGS) + max = client->pers.max_slugs; + else + return false; + + index = ITEM_INDEX(item); + + if (client->pers.inventory[index] == max) + return false; + + client->pers.inventory[index] += count; + + if (client->pers.inventory[index] > max) + client->pers.inventory[index] = max; + + return true; +} + +qboolean Pickup_Ammo (edict_t *ent, edict_t *other) +{ + int oldcount; + int count; + qboolean weapon; + qboolean canuse; + + //WF - Only allow certain classes to use certain ammo + if (other->client && !other->bot_client)//ACRID FIXME FIXME FIXME + { + canuse = wf_CanUse(other->client, ent); + if (canuse == false) return false; + } + + + 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)) + { +//ERASER START + if (other->bot_client && (other->movetarget == ent)) + { // ignore this item for a while + ent->ignore_time = level.time + 3; + other->movetarget = NULL; + bot_roam(other, false); + } +//ERASER END + return false; + }//E + + if (weapon && !oldcount) + { + if (other->client->pers.weapon != ent->item && ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) ) + other->client->newweapon = ent->item; + } + + //WF - If we are picking up dropped ammo, decrease max dropped ammo counter + if (ent->spawnflags & DROPPED_ITEM) + { + --TOTALDROPPEDAMMO; + if (TOTALDROPPEDAMMO < 0) TOTALDROPPEDAMMO = 0; + + //if (wfdebug) gi.dprintf("Picked up dropped ammo. Dropped total = %d\n", TOTALDROPPEDAMMO); + } + + 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; +//ERASER START + gclient_t *client; + + if (ent->client) + client = ent->client; + else + return; +//ERASER END REMOVED ALL ENT-> + + //WF - limit the number of dropped ammo items + if (TOTALDROPPEDAMMO >= MAX_DROPPEDAMMO) + { + safe_cprintf (ent, PRINT_HIGH, "Only %d dropped ammo allowed in world at a time\n",MAX_DROPPEDAMMO); + } + else + { + ++TOTALDROPPEDAMMO; + index = ITEM_INDEX(item); + dropped = Drop_Item (ent, item); + if (client->pers.inventory[index] >= item->quantity) + dropped->count = item->quantity; + else + dropped->count = client->pers.inventory[index]; + + if (client->pers.weapon && + client->pers.weapon->tag == AMMO_GRENADES && + item->tag == AMMO_GRENADES && + client->pers.inventory[index] - dropped->count <= 0) + { + safe_cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n"); + G_FreeEdict(dropped); + return; + } + + client->pers.inventory[index] -= dropped->count; + ValidateSelectedItem (ent); + } +} + + +//====================================================================== + +void MegaHealth_think (edict_t *self) +{ + if (self->owner->health > self->owner->max_health +//ZOID + && !CTFHasRegeneration(self->owner) +//ZOID + ) + { + self->nextthink = level.time + 1; + self->owner->health -= 1; + return; + } + + if (self->health > MAX_HEALTH) self->health = MAX_HEALTH;//WF24 USES 200 + + if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (self, 20); + else + G_FreeEdict (self); +} + +qboolean Pickup_Health (edict_t *ent, edict_t *other) +{ +// char *s; + if (!(ent->style & HEALTH_IGNORE_MAX)) + if (other->health >= other->max_health) +//ERASER START + { + if (other->bot_client && (other->movetarget == ent)) + { // ignore this item for a while + ent->ignore_time = level.time + 1; + other->movetarget = NULL; + bot_roam(other, false); + } +//ERASER END + return false; + }//E + + if (other->health >= 250 && ent->count > 25)//ERASER ADDED + return false; + + other->health += ent->count; + // set skin for damage +// s = Info_ValueForKey (other->client->pers.userinfo, "skin"); + //CHeck to see if CTF is set +// if (ctf->value) +// CTFAssignSkin(other, s); +//ZOID//ERASER ADDED + if (other->health > 250 && ent->count > 25) + other->health = 250; +//ZOID//ERASER ADDED + 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 > HEALTH_MEGAMAX_HEALTH) + other->health = HEALTH_MEGAMAX_HEALTH; + } + +//ZOID + if ((ent->style & HEALTH_TIMED) + && !CTFHasRegeneration(other) +//ZOID + ) + { + 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) +{ +//ERASER START + gclient_t *client; + + if (ent->client) + client = ent->client; + else + return false; +//WF if (!ent->client) +//WF return 0; +//ERASER START REMOVED ALL ENT-> + if (client->pers.inventory[jacket_armor_index] > 0) + return jacket_armor_index; + + if (client->pers.inventory[combat_armor_index] > 0) + return combat_armor_index; + + if (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; + int index;//WF34 + gclient_t *client;//ERASER + qboolean canuse;//WF24 +//ERASER START + if (other->client) + client = other->client; + else + return false; +//ERASER END + // 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) + { + // Does class allow jacket armor? + if (other->client->player_items & ITEM_JACKETARMOR) + other->client->pers.inventory[jacket_armor_index] = 2; + else + return false; + } + else + { + client->pers.inventory[old_armor_index] += 2; + + //Don't allow greater than max armor for each class + if (other->client->pers.inventory[old_armor_index] > other->client->player_armor) + other->client->pers.inventory[old_armor_index] = other->client->player_armor; + } + return true;//WF24 + } +//ACRID FIXME FIXME FIXME CANUSE WILL HAVE TO BE FIXED FOR BOTS SOMEHOW + + //Only allow certain classes to use different armor//WF24 DIF + canuse = wf_CanUse(other->client, ent); + + if (canuse == false) + { + //safe_cprintf (other, PRINT_HIGH, "Sorry, your class can't use that type of armor.\n"); + return false; + } + +//Acrid Armor pickup fixes 5/99 stop the real player hogs + if (client->pers.inventory[combat_armor_index] && (ent->item->tag == ARMOR_COMBAT)) + return false; + + if (client->pers.inventory[body_armor_index] && (ent->item->tag == ARMOR_COMBAT || ent->item->tag == ARMOR_BODY) ) + return false; + + + // if player has no armor, just use it + if (!old_armor_index) + { + 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 + + // ++TeT Just add it to the old armor + //salvage = oldinfo->normal_protection / newinfo->normal_protection; + //salvagecount = salvage * other->client->pers.inventory[old_armor_index]; + //newcount = newinfo->base_count + salvagecount; + newcount = newinfo->base_count + other->client->pers.inventory[old_armor_index]; + //--TeT + + if (newcount > newinfo->max_count) + newcount = newinfo->max_count; + + // zero count of old armor so it goes away + client->pers.inventory[old_armor_index] = 0; + + // change armor to new item with computed value//FIXME OTHER + index = ITEM_INDEX(ent->item); + other->client->pers.inventory[index] = newcount; + + //Don't allow greater than max armor for each class + if (other->client->pers.inventory[index] > other->client->player_armor) + other->client->pers.inventory[index] = other->client->player_armor; + + } + else + { + // calc new armor values + + //++TeT Just add it to the old armor + //salvage = newinfo->normal_protection;//TeT / oldinfo->normal_protection; + //salvagecount = salvage * newinfo->base_count; + //newcount = other->client->pers.inventory[old_armor_index] + salvagecount; + newcount = other->client->pers.inventory[old_armor_index] + newinfo->base_count; + //--TeT + + if (newcount > oldinfo->max_count) + newcount = oldinfo->max_count; + + // if we're already maxed out then we don't need the new armor + if (client->pers.inventory[old_armor_index] >= newcount) + { +//ERASER START + if (other->bot_client && (other->movetarget == ent)) + { + other->movetarget = NULL; + } + ent->ignore_time = level.time + 2; +//ERASER END + return false; + } + + // update current armor value//FIXME OTHERS + other->client->pers.inventory[old_armor_index] = newcount; + + //Don't allow greater than max armor for each class + if (other->client->pers.inventory[old_armor_index] > other->client->player_armor) + other->client->pers.inventory[old_armor_index] = other->client->player_armor; + } + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 20); + + return true; +} + +//====================================================================== + +int PowerArmorType (edict_t *ent) +{ +//ERASER START + gclient_t *client; + + if (ent->client) + client = ent->client; + else + return POWER_ARMOR_NONE; +//WF24 if (!ent->client) +//WF24 return POWER_ARMOR_NONE; +//ERASER END + + if (!(ent->flags & FL_POWER_ARMOR)) + return POWER_ARMOR_NONE; + + if (client->pers.inventory[power_shield_index] > 0) + return POWER_ARMOR_SHIELD; + + if (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; + gclient_t *client;//ERASER +//ERASER START +//////////////////////////////////special node//////////////////////////// +if (strcmp(ent->classname, "item_sentryspot") == 0) +{ + //to stop accidental touching + if (other->bot_client && other->sentry && ent->item->pickup == Place_Special) + { botDebugPrint("bot cant touchthis\n"); + return; + } + //to stop real player from touching + if (!bot_debug_nodes->value) + if (!other->bot_client && ent->item->pickup == Place_Special) + { botDebugPrint("I cant touch this\n"); + return; + } + if (bot_debug_nodes->value) + if (other->client && other->sentry && ent->item->pickup == Place_Special) + { botDebugPrint("I cant touch this\n"); + return; + } +} +if (strcmp(ent->classname, "item_depotspot") == 0) +{ + //to stop accidental touching + if (other->bot_client && other->supply && ent->item->pickup == Place_Special) + { botDebugPrint("bot cant touchthis\n"); + return; + } + //to stop real player from touching + if (!bot_debug_nodes->value) + if (!other->bot_client && ent->item->pickup == Place_Special) + { botDebugPrint("I cant touch this\n"); + return; + } + if (bot_debug_nodes->value) + if (other->client && other->supply && ent->item->pickup == Place_Special) + { botDebugPrint("I cant touch this\n"); + return; + } +} +////////////////////////////////special node end////////////////////////// + + if (other->bot_client) + { + if (!( (ent->item->pickup == Pickup_Weapon) + || (ent->item->pickup == Pickup_Health) + || (ent->item->pickup == Pickup_Ammo) + || (ent->item->pickup == Pickup_Powerup) + || (ent->item->pickup == Pickup_Armor) + || (ent->item->pickup == Pickup_Pack) + || (ent->item->pickup == Pickup_Bandolier) + || (ent->item->pickup == Place_Special)//acrid 42 + || (ent->item->pickup == CTFPickup_Flag) + || (ent->item->pickup == CTFPickup_Tech))) + { + return; + } + else if (other->movetarget == ent) // if this was their movetarget, then clear it + { + other->movetarget = other->goalentity = NULL; + + // force search for new item next think + other->last_nopaths_roam = 0; + other->last_roam_time = 0; + } + + } + + if (other->client) + client = other->client; + else + return; +//ERASER END +//WF if (!other->client) +//WF 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 + 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 +//ERASER START + { + if ((ent->item->pickup == Pickup_Weapon) || + (ent->item->pickup == Pickup_Health) || + (ent->item->pickup == Pickup_Ammo) || + (ent->item->pickup == Pickup_Powerup) || + (ent->item->pickup == Pickup_Armor) || + (ent->item->pickup == Place_Special) ||//acrid42 + (ent->item->pickup == CTFPickup_Flag) || + (ent->item->pickup == CTFPickup_Tech)) + { + RemoveFromItemList(ent); + } +//ERASER END + G_FreeEdict (ent); + }}//E +} + +//====================================================================== + +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); +} +//WF34 START +static void remove_ammo(edict_t *ent) +{ + //if (wfdebug) gi.dprintf("Remove ammo. Dropped total = %d\n", TOTALDROPPEDAMMO); + --TOTALDROPPEDAMMO; + ent->nextthink = level.time + .1; + ent->think = G_FreeEdict; +} +//WF34 END +void drop_make_touchable (edict_t *ent) +{ + ent->touch = Touch_Item; + if (deathmatch->value) + { +//ERASER START + // Set visible nodes + if ((ent->item->pickup == Pickup_Weapon) || + (ent->item->pickup == Pickup_Health) || + (ent->item->pickup == Pickup_Ammo) || + (ent->item->pickup == Pickup_Armor) || + (ent->item->pickup == Pickup_Powerup) || + (ent->item->pickup == Place_Special) ||//acrid42 + (ent->item->pickup == CTFPickup_Flag) || + (ent->item->pickup == CTFPickup_Tech)) + { + CalcItemPaths(ent); + + ent->nextthink = level.time + 29; + ent->think = RemoveDroppedItem; + } + else + { +//ERASER END + ent->nextthink = level.time + 29; + + //If this is ammo, reduce world ammo count//WF34 + if ( (ent->spawnflags & DROPPED_ITEM) && (ent->item->flags == IT_AMMO)) + { + ent->think = remove_ammo; + } + else + { + ent->think = G_FreeEdict;//NORMAL + } + }}//E +} + + +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); + 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); + trace = gi.trace (ent->s.origin, dropped->mins, dropped->maxs, + dropped->s.origin, ent, CONTENTS_SOLID); + 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; + + //if (wfdebug) gi.dprintf("Drop item: %s\n", item->classname); + + 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->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); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + //G_FreeEdict (ent); //G.A.R. Will this fix disappearing flags? + BecomeExplosion1(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; + } + +//ERASER START + if (((int)dmflags->value & DF_INFINITE_AMMO) && (ent->item->pickup == Pickup_Ammo)) + { // no ammo, since infinite ammo is set + G_FreeEdict(ent); + return; + } + + // Set visible nodes + if ((ent->item->pickup == Pickup_Weapon) || + (ent->item->pickup == Pickup_Health) || + (ent->item->pickup == Pickup_Ammo) || + (ent->item->pickup == Pickup_Armor) || + (ent->item->pickup == Pickup_Powerup) || + (ent->item->pickup == Pickup_Pack) || + (ent->item->pickup == Pickup_Bandolier) || + (ent->item->pickup == Place_Special) ||//acrid42 + (ent->item->pickup == CTFPickup_Flag) || + (ent->item->pickup == CTFPickup_Tech)) + { + CalcItemPaths(ent); + } +//ERASER END + 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) + { +//WF // If player classes are in effect, remove weapons from maps + if (((int)wfflags->value & WF_NO_PLAYER_CLASSES) == 0) + { + if (wf_IsWeapon(ent->classname)) + { + //gi.dprintf ("WF removed: %s\n",ent->classname); + G_FreeEdict (ent); + return; + } + + } + if ((int)dmflags->value & DF_CTF_NO_TECH) + { + if (item->pickup == CTFPickup_Tech) + { + gi.dprintf ("WF removed: %s\n",ent->classname); + G_FreeEdict (ent); + return; + } + + } +//WF + 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; + } + } + if ( (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; + } +//ZOID +//Don't spawn the flags unless enabled + if (!ctf->value && + (strcmp(ent->classname, "item_flag_team1") == 0 || + strcmp(ent->classname, "item_flag_team2") == 0)) { + G_FreeEdict(ent); + return; + } +//ZOID + 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); +///////////////////special node///////////////////////////////////////// + //for making the special nodeitems visible + if (bot_debug_nodes->value) + { + if (strcmp(ent->classname, "item_sentryspot") == 0) + { +// item->world_model = "models/objects/debris1/tris.md2"; + item->world_model = "models/items/snode/tris.md2"; +// item->world_model = "models/items/mega_h/tris.md2"; + } + if (strcmp(ent->classname, "item_depotspot") == 0) + { +// item->world_model = "models/items/healing/stimpack/tris.md2"; +// item->world_model = "models/objects/debris1/tris.md2"; + item->world_model = "models/items/snode/tris.md2"; + } + } +///////////////////////////////special nodes end////////////////////////// + +//ZOID +//flags are server animated and have special handling + if (strcmp(ent->classname, "item_flag_team1") == 0 || + strcmp(ent->classname, "item_flag_team2") == 0) { + ent->think = CTFFlagSetup; + } +//ZOID + +//WF + //Create an entity where flags are spawned to support + //Team Fortress style flag captures + if (((int)wfflags->value & WF_ZOID_FLAGCAP) == 0) + { + /* + if (strcmp(ent->classname, "item_flag_team1") == 0) + Create_Home_Base(ent, CTF_TEAM1); + if (strcmp(ent->classname, "item_flag_team2") == 0) + Create_Home_Base(ent, CTF_TEAM2); + */ + } + +//WF +} + +//====================================================================== + +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, + 0, //3.20 + &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, + 0, //3.20 + &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, + 0, //3.20 + &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, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + NULL, + 0, +/* precache */ "misc/power2.wav misc/power1.wav" + }, +//////////////////////////////special////////////////////////// + // + //NEW BOT NODES + // + // Special + // + +/*QUAKED item_sentryspot (.3 .3 1) (-16 -16 -16) (16 16 16)42 +*/ + { + "item_sentryspot", + Place_Special, + NULL, + NULL, + NULL, + NULL, +// "models/objects/debris1/tris.md2", 0, + "models/items/snode/tris.md2", 0, + NULL, +/* icon */ NULL, +/* pickup */ "SentrySpot", +/* width */ 0, + 0, + NULL, + IT_WEAPON|IT_STAY_COOP, + 0, //3.20 + NULL, + 0, +/* precache */ "" + }, +/*QUAKED item_depotspot (.3 .3 1) (-16 -16 -16) (16 16 16)42 +*/ + { + "item_depotspot", + Place_Special, + NULL, + NULL, + NULL, + NULL, + "models/items/snode/tris.md2", 0, +// "models/items/healing/stimpack/tris1.md2", 0, + NULL, +/* icon */ NULL, +/* pickup */ "DepotSpot", +/* width */ 0, + 0, + NULL, + IT_WEAPON|IT_STAY_COOP, + 0, //3.20 + NULL, + 0, +/* precache */ "" + }, +////////////////////////////////////special end////////////////////////// + // + // 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, + IT_WEAPON|IT_STAY_COOP, + WEAP_BLASTER, //3.20 + NULL, + WEAPON_BLASTER, +/* 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", + IT_WEAPON|IT_STAY_COOP, + WEAP_SHOTGUN, //3.20 + NULL, + WEAPON_SHOTGUN, +/* 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", + IT_WEAPON|IT_STAY_COOP, + WEAP_SUPERSHOTGUN, //3.20 + NULL, + WEAPON_SUPERSHOTGUN, +/* 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", + IT_WEAPON|IT_STAY_COOP, + WEAP_MACHINEGUN, //3.20 + NULL, + WEAPON_MACHINEGUN, +/* 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", + IT_WEAPON|IT_STAY_COOP, + WEAP_CHAINGUN, //3.20 + NULL, + WEAPON_CHAINGUN, +/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav" + }, + +/*QUAKED weapon_megachaingun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_megachaingun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_MegaChaingun, + "misc/w_pkup.wav", + "models/weapons/g_chain/tris.md2", EF_ROTATE, + "models/weapons/v_chain/tris.md2", +// "models/weapons/v_mega/tris.md2", +/* icon */ "w_chaingun", +/* pickup */ "Mega Chaingun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_CHAINGUN, //3.20 + NULL, + WEAPON_MEGACHAINGUN, +/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav" + }, + +/*QUAKED weapon_pulsecannon (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_pulsecannon", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_ChainMBPC, + "misc/w_pkup.wav", + "models/weapons/g_chain/tris.md2", EF_ROTATE, + "models/weapons/v_chain/tris.md2", +/* icon */ "w_chaingun", +/* pickup */ "Pulse Cannon", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_CHAINGUN, //3.20 + NULL, + WEAPON_PULSE, +/* 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", + IT_AMMO|IT_WEAPON, + WEAP_GRENADES, //3.20 + NULL, + AMMO_GRENADES, +/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + +/*QUAKED ammo_grenades2 (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_grenades2", + Pickup_Ammo, + Use_Weapon, + Drop_Ammo, + Weapon_Grenade, + "misc/am_pkup.wav", + "models/items/ammo/grenades/medium/tris.md2", 0, + "models/weapons/v_handgr2/tris.md2", +/* icon */ "a_grenades", +/* pickup */ "Grenades2", +/* width */ 3, + 5, + "grenades", + IT_AMMO|IT_WEAPON, + WEAP_GRENADES, //3.20 + NULL, + AMMO_GRENADES, +/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + +/*QUAKED ammo_grenades3 (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_grenades3", + Pickup_Ammo, + Use_Weapon, + Drop_Ammo, + Weapon_Grenade, + "misc/am_pkup.wav", + "models/items/ammo/grenades/medium/tris.md2", 0, + "models/weapons/v_handgr3/tris.md2", +/* icon */ "a_grenades", +/* pickup */ "Grenades3", +/* width */ 3, + 5, + "grenades", + IT_AMMO|IT_WEAPON, + WEAP_GRENADES, //3.20 + NULL, + AMMO_GRENADES, +/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + +/*QUAKED weapon_lrprojectile (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_lrprojectile", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_ComputerGuidedProjectileLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Projectile Launcher", + 0, + 10, + "Grenades", + IT_WEAPON, + WEAP_GRENADELAUNCHER, //3.20 + NULL, + WEAPON_LRPROJECTILE, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + +/*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", + IT_WEAPON|IT_STAY_COOP, + WEAP_HYPERBLASTER, //3.20 + NULL, + WEAPON_HYPERBLASTER, +/* 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", + IT_WEAPON|IT_STAY_COOP, + WEAP_RAILGUN, //3.20 + NULL, + WEAPON_RAILGUN, +/* 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", + IT_WEAPON|IT_STAY_COOP, + WEAP_BFG, //3.20 + NULL, + WEAPON_BFG, +/* 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" + }, + + // + // 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, + IT_AMMO, + 0, //3.20 + 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, + IT_AMMO, + 0, //3.20 + 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, + IT_AMMO, + 0, //3.20 + 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, + IT_AMMO, + 0, //3.20 + 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, + IT_AMMO, + 0, //3.20 + NULL, + AMMO_SLUGS, +/* precache */ "" + }, + + + // + // POWERUP ITEMS + // +/*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, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + 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, + IT_STAY_COOP|IT_POWERUP, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + 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 */ "Bandolier", +/* width */ 2, + 60, + NULL, + 0, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + 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, + 0, //3.20 + NULL, + 0, +/* precache */ "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav" + }, + //new weapons + + /* weapon_Nag (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_nag", + NULL, + Use_Weapon, + NULL, + Weapon_Nag, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Nervous Accelerator Gun", + 0, + 1, + "Cells", + IT_WEAPON, + WEAP_BLASTER, //3.20 + NULL, + WEAPON_NAG, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + /* weapon_flaregun (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_flaregun", + NULL, + Use_Weapon, + NULL, +// Weapon_FlareGun, + Weapon_FlareGun2, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/grapple/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Flare Gun", + 0, + 1, + "Bullets", + IT_WEAPON, + WEAP_GRAPPLE, //3.20 + NULL, + WEAPON_FLAREGUN, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + /*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_shcrifle", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_SHCRifle, + "misc/w_pkup.wav", + "models/weapons/g_shotg/tris.md2", EF_ROTATE, + "models/weapons/v_shotg/tris.md2", +/* icon */ "w_shotgun", +/* pickup */ "SHC Rifle", + 0, + 1, + "Shells", + IT_WEAPON, + WEAP_SHOTGUN, //3.20 + NULL, + WEAPON_SHC, +/* precache */ "weapons/v_shotg/flash2/tris.md2 weapons/shotgf1b.wav weapons/shotgr1b.wav" + }, + + +/*QUAKED weapon_nailgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_nailgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_NailGun, + "misc/w_pkup.wav", + "models/weapons/g_chain/tris.md2", EF_ROTATE, + "models/nailgun/tris.md2", +/* icon */ "w_chaingun", +/* pickup */ "NailGun", + 0, + 1, + "Bullets", + IT_WEAPON, + WEAP_CHAINGUN, //3.20 + NULL, + WEAPON_NAILGUN, +/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.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", + IT_WEAPON, + WEAP_GRENADELAUNCHER, //3.20 + NULL, + WEAPON_GRENADELAUNCHER, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + +/*QUAKED weapon_flarelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_flarelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_FlareLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Flare Launcher", + 0, + 1, + "Grenades", + IT_WEAPON, + WEAP_GRENADELAUNCHER, //3.20 + NULL, + 0, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, +/* weapon_bulletgrenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_bulletgrenadelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_BulletGrenadeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Shrapnal Grenade Launcher", + 0, + 1, + "Grenades", + IT_WEAPON, + WEAP_GRENADELAUNCHER, //3.20 + NULL, + 0, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + +/* weapon_timednukelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_timednukelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_TimedNukeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Timed Nuke Launcher", + 0, + 25, + "Grenades", + IT_WEAPON, + WEAP_GRENADELAUNCHER, //3.20 + NULL, + 0, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + + /* weapon_napalmgrenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_napalmgrenadelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_NapalmGrenadeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Napalm Grenade Launcher", + 0, + 2, + "Grenades", + IT_WEAPON, + WEAP_GRENADELAUNCHER, //3.20 + NULL, + 0, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + +/* weapon_infected_dart (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_infected_dart", + NULL, + Use_Weapon, + NULL, + Weapon_InfectedDartLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, +// "models/weapons/v_launch/tris.md2", + "models/weapons/v_dartinfect/tris.md2", +// NULL, 0, +// "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Infected Dart Launcher", + 0, + 1, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_GRENADELAUNCHER, //3.20 + NULL, + WEAPON_INFECTEDDART, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, +/* weapon_poison_dart (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_poison_dart", + NULL, + Use_Weapon, + NULL, + Weapon_ArmorDartLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, +// "models/weapons/v_launch/tris.md2", + "models/weapons/v_dartarmor/tris.md2", +// NULL, 0, +// "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Poison Dart Launcher", + 0, + 1, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_GRENADELAUNCHER, //3.20 + NULL, + WEAPON_ARMORDART, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + /* weapon_poison_dart (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_poison_dartb", + NULL, + Use_Weapon, + NULL, + Weapon_PoisonDartLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_darttranq/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Tranquilizer Dart Launcher", + 0, + 1, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_GRENADELAUNCHER, //3.20 + NULL, + WEAPON_TRANQUILDART, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, +/* weapon_tranquilizer (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_tranquilizer", + NULL, + Use_Weapon, + NULL, + Weapon_TranquilizerRifle, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Tranquilizer", + 0, + 1, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_BLASTER, //3.20 + NULL, + WEAPON_TRANQUILIZER, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + + +/*QUAKED weapon_sniper_rifle (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_sniper_rifle", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Snipe, + "misc/w_pkup.wav", + "models/weapons/v_sniper2/tris.md2", EF_ROTATE, + "models/weapons/v_sniper2/tris.md2", +/* icon */ "w_railgun", +/* pickup */ "Sniper Rifle", + 0, + 1, + "Slugs", + IT_WEAPON|IT_STAY_COOP, + 0, //3.20 + NULL, + WEAPON_SNIPERRIFLE, +/* precache */ //"weapons/shotgf1b.wav weapons/shotgr1b.wav" +/* precache */ "tank\tnkatck5.wav weapons/shotgr1b.wav" + }, +/*QUAKED weapon_cluster_missiles (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_cluster_missiles", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_RocketClusterLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, + "models/weapons/v_rocket/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Cluster Rocket Launcher", + 0, + 5, + "Rockets", + IT_WEAPON, + WEAP_ROCKETLAUNCHER, //3.20 + NULL, + WEAPON_CLUSTERROCKET, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + + + /* weapon_diseasegrenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_diseasegrenadelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_DiseaseGrenadeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Disease Grenade Launcher", + 0, + 1, + "Grenades", + IT_WEAPON, + 0, //3.20 + NULL, + 0, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, +/*QUAKED weapon_pelletrocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_pelletrocketlauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_PelletRocketLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, + "models/weapons/v_rocket/tris.md2", + +/* icon */ "w_rlauncher", +/* pickup */ "Pellet Rocket Launcher", + 0, + 2, + "Rockets", + IT_WEAPON, + WEAP_ROCKETLAUNCHER, //3.20 + NULL, + WEAPON_PELLET, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, +/*QUAKED weapon_sentrykiller_missiles (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_sentry_missiles", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_SentryRocketLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, + "models/weapons/v_rocket/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Sentry Killer", + 0, + 10, + "Rockets", + IT_WEAPON, + WEAP_ROCKETLAUNCHER, //3.20 + NULL, + WEAPON_SENTRYKILLER, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + +/*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", + "models/weapons/v_homing/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Rocket Launcher", + 0, + 1, + "Rockets", + IT_WEAPON, + WEAP_ROCKETLAUNCHER, //3.20 + NULL, + WEAPON_ROCKETLAUNCHER, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + /*QUAKED weapon_rocketnapalmlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_rocketnapalmlauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_RocketNapalmLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, +// "models/weapons/v_rocket/tris.md2", + "models/weapons/v_napalm/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Rocket Napalm Launcher", + 0, + 2, + "Rockets", + IT_WEAPON, + WEAP_ROCKETLAUNCHER, //3.20 + NULL, + WEAPON_NAPALMMISSLE, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + + + /*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_flamethrower", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_FlameThrower, + "misc/w_pkup.wav", + "models/weapons/g_bfg/tris.md2", EF_ROTATE, + "models/weapons/v_flamethrower/tris.md2", +/* icon */ "w_bfg", +/* pickup */ "FlameThrower", + 0, + 1, + "Cells", + IT_WEAPON, + WEAP_BFG, //3.20 + NULL, + WEAPON_FLAMETHROWER, +/* 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" + }, + +/* weapon_timednukelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +/*QUAKED weapon_lightninggun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_lightninggun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Lightninggun, + "misc/w_pkup.wav", + "models/weapons/g_rail/tris.md2", EF_ROTATE, + "models/weapons/v_lightn/tris.md2", +/* icon */ "w_railgun", +/* pickup */ "Lightning Gun", + 0, + 1, + "Slugs", + IT_WEAPON|IT_STAY_COOP, + WEAP_RAILGUN, //3.20 + NULL, + WEAPON_LIGHTNING, +/* precache */ "weapons/rg_hum.wav sounds/electric.wav sounds/lshoot.wav" + }, + +/*QUAKED weapon_knife (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_knife", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Knife, + "misc/w_pkup.wav", + "models/weapons/g_shotg/tris.md2", EF_ROTATE, + "models/weapons/v_knife/tris.md2", +/* icon */ "w_shotgun", +/* pickup */ "Knife", + 0, + 0, //uses no ammo + NULL, //"Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_KNIFE, //3.20 + NULL, + WEAPON_KNIFE, +/* precache */ "weapons/shotgf1b.wav weapons/shotgr1b.wav" + }, + +/*QUAKED weapon_ak47 (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_ak47", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_AK47, + "misc/w_pkup.wav", + "models/weapons/g_machn/tris.md2", EF_ROTATE, + "models/weapons/v_ak47/tris.md2", +/* icon */ "w_machinegun", +/* pickup */ "AK47", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_MACHINEGUN, //3.20 + NULL, + WEAPON_AK47, +/* precache */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav" + }, + +/*QUAKED weapon_pistol (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_pistol", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Pistol, + "misc/w_pkup.wav", + "models/weapons/g_shotg/tris.md2", EF_ROTATE, + "models/weapons/v_pistol/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Pistol", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_BLASTER, //3.20 + NULL, + WEAPON_PISTOL, +/* precache */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav" + }, + +/* weapon_boltedblaster (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_boltedblaster", +// Pickup_Weapon, + NULL, + Use_Weapon, + NULL, + Weapon_BoltedBlaster, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Mag Bolted", + 0, + 0, + NULL, + IT_WEAPON|IT_STAY_COOP, + WEAP_BLASTER, //3.20 + NULL, + WEAPON_MAGBOLTED, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + +/* weapon_needler (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_needler", + NULL, + Use_Weapon, + NULL, + Weapon_Needler, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/v_blast/tris.md2", + +/* icon */ "w_blaster", +/* pickup */ "Needler", + 0, + 1, + "Bullets", + IT_WEAPON, + WEAP_BLASTER, //3.20 + NULL, + WEAPON_NEEDLER, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + //ZOID +/*QUAKED item_flag_team1 (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + "item_flag_team1", + CTFPickup_Flag, + NULL, + CTFDrop_Flag, //Should this be null if we don't want players to drop it manually? + NULL, + "ctf/flagtk.wav", + "players/male/flag1.md2", EF_FLAG1, + NULL, +/* icon */ "i_ctf1", +/* pickup */ "Red Flag", +/* width */ 2, + 0, + NULL, + 0, + 0, //3.20 + NULL, + 0, +/* precache */ "ctf/flagcap.wav" + }, + +/*QUAKED item_flag_team2 (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + "item_flag_team2", + CTFPickup_Flag, + NULL, + CTFDrop_Flag, //Should this be null if we don't want players to drop it manually? + NULL, + "ctf/flagtk.wav", + "players/male/flag2.md2", EF_FLAG2, + NULL, +/* icon */ "i_ctf2", +/* pickup */ "Blue Flag", +/* width */ 2, + 0, + NULL, + 0, + 0, //3.20 + NULL, + 0, +/* precache */ "ctf/flagcap.wav" + }, +/////////////$$$///////////Eraser Support Acrid////////$$$//////////////// +/*QUAKED item_flagreturn_team1 (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + "item_flagreturn_team1", + CTFPickup_Flag, + NULL, + NULL, + NULL, + NULL, +// "models/objects/flagbase1/tris.md2", 0, + "models/objects/flagbase/tris.md2", 0, + NULL, +/* icon */ NULL, +/* pickup */ NULL, +/* width */ 0, + 0, + NULL, + 0, + 0, //3.20 + NULL, + 0, +/* precache */ "ctf/flagcap.wav" + }, +/*QUAKED item_flagreturn_team2 (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + "item_flagreturn_team2", + CTFPickup_Flag, + NULL, + NULL, + NULL, + NULL, +// "models/objects/flagbase/tris1.md2", 0, + "models/objects/flagbase/tris.md2", 0, + NULL, +/* icon */ NULL, +/* pickup */ NULL, +/* width */ 0, + 0, + NULL, + 0, + 0, //3.20 + NULL, + 0, +/* precache */ "ctf/flagcap.wav" + }, +/////////////$$$///////////END Eraser Support Acrid/////////$$$/////////////// + + /* Resistance Tech */ + { + "item_tech1", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/resistance/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech1", +/* pickup */ "Disruptor Shield", +/* width */ 2, + 0, + NULL, + IT_TECH, + 0, //3.20 + NULL, + 0, +/* precache */ "ctf/tech1.wav" + }, + +/* Strength Tech */ + { + "item_tech2", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/strength/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech2", +/* pickup */ "Power Amplifier", +/* width */ 2, + 0, + NULL, + IT_TECH, + 0, //3.20 + NULL, + 0, +/* precache */ "ctf/tech2.wav ctf/tech2x.wav" + }, + +/* Haste Tech */ + { + "item_tech3", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/haste/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech3", +/* pickup */ "Time Accel", +/* width */ 2, + 0, + NULL, + IT_TECH, + 0, //3.20 + NULL, + 0, +/* precache */ "ctf/tech3.wav" + }, + +/* Regeneration Tech */ + { + "item_tech4", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/regeneration/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech4", +/* pickup */ "AutoDoc", +/* width */ 2, + 0, + NULL, + IT_TECH, + 0, //3.20 + NULL, + 0, +/* precache */ "ctf/tech4.wav" + }, +/* weapon_grapple (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_grapple", + NULL, + //Use_Weapon, + Use_Grapple,//5/99 + NULL, +// CTFWeapon_Grapple, + CTFWeapon_Grapple2,//newgrap 4/99 + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/grapple/tris.md2", +/* icon */ "w_grapple", +/* pickup */ "Grapple", + 0, + 0, + NULL, + IT_WEAPON, + WEAP_GRAPPLE, //3.20 + NULL, + 0, +/* precache */ "weapons/grapple/grfire.wav weapons/grapple/grpull.wav weapons/grapple/grhang.wav weapons/grapple/grreset.wav weapons/grapple/grhit.wav" + }, +/* QUAKED weapon_freezer (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + {//acrid 3/99 + "weapon_freezer", + NULL, + Use_Weapon, + NULL, + Weapon_Freezer, + "misc/w_pkup.wav", + "models/weapons/g_rail/tris.md2", EF_ROTATE, + "models/weapons/v_freeze/tris.md2", + "w_railgun", + "Freezer", + 0, + 1, + "Slugs", + IT_WEAPON, + WEAP_RAILGUN,//3.20 + NULL, + WEAPON_FREEZER, + "weapons/blastf1a.wav mics/lasfly.wav" + }, + /*QUAKED weapon_telsa (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_telsa", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Telsa, + "misc/w_pkup.wav", + "models/weapons/g_hyperb/tris.md2", EF_ROTATE, + "models/weapons/v_hyperb/tris.md2", +// "models/weapons/telsa/tris.md2", EF_ROTATE, +// "models/weapons/telsa/tris.md2", +/* icon */ "w_hyperblaster", + +/* pickup */ "Telsa Coil", + 0, + 4, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_HYPERBLASTER, //3.20 + NULL, + WEAPON_TELSA, +/* precache */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav" + }, + // end of list marker + // ROGUE +/*QUAKED weapon_etf_rifle (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_etf_rifle", // classname + Pickup_Weapon, // pickup function + Use_Weapon, // use function + Drop_Weapon, // drop function + Weapon_ETF_Rifle, // weapon think function + "misc/w_pkup.wav", // pick up sound + "models/weapons/g_etf_rifle/tris.md2", EF_ROTATE, // world model, world model flags + "models/weapons/v_etf_rifle/tris.md2", // view model + "w_etf_rifle", // icon + "ETF Rifle", // name printed when picked up + 0, // number of digits for statusbar + 1, // amount used / contained + "SLUGS", // ammo type used + IT_WEAPON, // inventory flags +// WEAP_ETFRIFLE, // visible weapon + WEAP_BLASTER, + NULL, // info (void *) + WEAPON_ETF_RIFLE, // tag + "weapons/nail1.wav models/proj/flechette/tris.md2", // precaches + }, + // ROGUE +/*QUAKED weapon_plasmabeam (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_plasmabeam", // classname + Pickup_Weapon, // pickup function + Use_Weapon, // use function + Drop_Weapon, // drop function + Weapon_Heatbeam, // weapon think function + "misc/w_pkup.wav", // pick up sound + "models/weapons/g_beamer/tris.md2", EF_ROTATE, // world model, world model flags + "models/weapons/v_beamer/tris.md2", // view model + "w_heatbeam", // icon + "Plasma Beam", // name printed when picked up + 0, // number of digits for statusbar + // FIXME - if this changes, change it in NoAmmoWeaponChange as well + 2, // amount used / contained + "Cells", // ammo type used + IT_WEAPON, // inventory flags +// WEAP_PLASMA, // visible weapon + WEAP_BLASTER, + NULL, // info (void *) + WEAPON_PLASMA_BEAM, // tag + "models/weapons/v_beamer2/tris.md2 weapons/bfg__l1a.wav", // precaches + }, + //Rogue weapon +/*QUAKED weapon_disintegrator (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN +*/ + { + "weapon_disintegrator", // classname + Pickup_Weapon, // pickup function + Use_Weapon, // use function + Drop_Weapon, // drop function + Weapon_Disintegrator, // weapon think function + "misc/w_pkup.wav", // pick up sound + "models/weapons/g_dist/tris.md2", EF_ROTATE, // world model, world model flags + "models/weapons/v_dist/tris.md2", // view model + "w_disintegrator", // icon + "Disruptor", // name printed when picked up + 0, // number of digits for statusbar + 1, // amount used / contained +// "Rounds", // ammo type used + "Cells", // ammo type used +#ifdef KILL_DISRUPTOR + IT_NOT_GIVEABLE, +#else + IT_WEAPON, // inventory flags +#endif +// WEAP_DISRUPTOR, // visible weapon + WEAP_BLASTER, + NULL, // info (void *) + 1, // tag + "models/items/spawngro/tris.md2 models/proj/disintegrator/tris.md2 weapons/disrupt.wav weapons/disint2.wav weapons/disrupthit.wav", // precaches + }, + //XATRIX +/*QUAKED weapon_ioripper (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_ionripper", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Ionripper, + "misc/w_pkup.wav", + "models/weapons/g_boom/tris.md2", EF_ROTATE, + "models/weapons/v_boomer/tris.md2", +/* icon */ "w_ripper", +/* pickup */ "Ionripper", + 0, + 2, + "Cells", + IT_WEAPON, +// WEAP_BOOMER, + WEAP_BLASTER, + NULL, + WEAPON_ION_RIPPER, +/* precache */ "weapons/rg_hum.wav weapons/rippfire.wav" + }, +// RAFAEL 14-APR-98 - XATRIX +/*QUAKED weapon_phalanx (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + + { + "weapon_phalanx", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Phalanx, + "misc/w_pkup.wav", + "models/weapons/g_shotx/tris.md2", EF_ROTATE, + "models/weapons/v_shotx/tris.md2", +/* icon */ "w_phallanx", +/* pickup */ "Phalanx", + 0, + 1, +// "Mag Slug", + "Slugs", + IT_WEAPON, +// WEAP_PHALANX, + WEAP_BLASTER, + NULL, + WEAPON_PHALANX, +/* precache */ "weapons/plasshot.wav" + }, + + /*QUAKED weapon_stingerlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_stingerlauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_StingerLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, + "models/weapons/v_stinger/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Stinger Launcher", + 0, + 5, + "Rockets", + IT_WEAPON|IT_STAY_COOP, + WEAP_ROCKETLAUNCHER, //3.20 + NULL, + WEAPON_STINGER, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + + + + {NULL} +}; + + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health (edict_t *self) +{ + if ( 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 ( 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 ( 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 ( 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")); +} +int JacketArmor (void) +{ + return jacket_armor_index; +} \ No newline at end of file diff --git a/g_items.h b/g_items.h new file mode 100644 index 0000000..2d750ad --- /dev/null +++ b/g_items.h @@ -0,0 +1,90 @@ +/***************************************************************** + + Eraser Bot source code - by Ryan Feltrin, Added to by Acrid- + + .............................................................. + + This file is Copyright(c) 1998, Ryan Feltrin, All Rights Reserved. + + .............................................................. + + All other files are Copyright(c) Id Software, Inc. + + Please see liscense.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 Eraser, you MUST + include the following text (minus the BEGIN and END lines) in the + documentation for your modification. + + --- BEGIN --- + + The Eraser Bot is a product of Ryan Feltrin, and is available from + the Eraser Bot homepage, at http://impact.frag.com. + + This program is a modification of the Eraser Bot, and is therefore + in NO WAY supported by Ryan Feltrin. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Ryan Feltrin immediately, via + the Eraser Bot homepage. + + --- END --- + + .............................................................. + + You will find p_trail.c has not been included with the Eraser + source code release. This is NOT an error. I am unable to + distribute this file because it contains code that is bound by + legal documents, and signed by myself, never to be released + to the public. Sorry guys, but law is law. + + I have therefore include the compiled version of these files + in .obj form in the src\Release and src\Debug directories. + So while you cannot edit and debug code within these files, + you can still compile this source as-is. Although these will only + work in MSVC v5.0, linux versions can be made available upon + request. + + NOTE: When compiling this source, you will get a warning + message from the compiler, regarding the missing p_trail.c + file. Just ignore it, it will still compile fine. + + .............................................................. + + I, Ryan Feltrin/Acrid-, hold no responsibility for any harm caused by the + use of this source code. I also am NOT willing to provide any form + of help or support for this source code. It is provided as-is, + as a service by me, with no documentation, other then the comments + contained within the code. If you have any queries, I suggest you + visit the "official" Eraser source web-board, at + http://www.telefragged.com/epidemic/. I will stop by there from + time to time, to answer questions and help with any problems that + may arise. + + Otherwise, have fun, and I look forward to seeing what can be done + with this. + + -Ryan Feltrin + -Acrid- + + *****************************************************************/ +qboolean Pickup_Weapon (edict_t *ent, edict_t *other); +qboolean Pickup_Health (edict_t *ent, edict_t *other); +qboolean Pickup_Ammo (edict_t *ent, edict_t *other); +qboolean Pickup_Armor (edict_t *ent, edict_t *other); +qboolean Pickup_Pack (edict_t *ent, edict_t *other); +qboolean Place_Special (edict_t *ent, edict_t *other);//42 Acrid + +edict_t *AddToItemList(edict_t *ent, edict_t *head); + +extern int jacket_armor_index; +extern int combat_armor_index; +extern int body_armor_index; +extern int power_screen_index; +extern int power_shield_index; + +#define HEALTH_IGNORE_MAX 1 +#define HEALTH_TIMED 2 diff --git a/g_local.h b/g_local.h new file mode 100644 index 0000000..5722a65 --- /dev/null +++ b/g_local.h @@ -0,0 +1,1618 @@ +// g_local.h -- local definitions for game module + +#include "q_shared.h" +#include //ERASER + +// 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" + +//ZOID +#include "p_menu.h" +//ZOID + +// the "gameversion" client command will print this plus compile date +#define GAMEVERSION "WeaponsFactory" + +// 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 + +//ROGUE +#define FL_MECHANICAL 0x00002000 // entity is mechanical, use sparks not blood +#define FL_SAM_RAIMI 0x00004000 // entity is in sam raimi cam mode +#define FL_DISGUISED 0x00008000 // entity is in disguise, monsters will not recognize. +#define FL_NOGIB 0x00010000 // player has been vaporized by a nuke, drop no gibs +//ROGUE + +#define FL_SHOWPATH 0x00020000 // used to show current path (debugging) +#define FL_SHOW_FLAGPATHS 0x00040000 //WF DIF FL_BOOTS + +//OTHER +#define FL_BOOTS 0x00080000 //Anti-Gravity boots flag +#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 + +// plat constants +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +#define MELEE_DISTANCE 80 + +#define BODY_QUEUE_SIZE 8 + +static vec3_t VEC_ORIGIN = {0,0,0};//WF24 + +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 +} 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, +//WF +MOVETYPE_FLYRICOCHET, // For bouncing blaster +MOVETYPE_FLOAT // No gravity, but slows down and stops in mid air +//WF + +} 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 +//ZOID +#define IT_TECH 64 +//ZOID + +// gitem_t->weapmodel for weapons indicates model index +#define WEAP_BLASTER 1 +#define WEAP_SHOTGUN 2 +#define WEAP_SUPERSHOTGUN 3 +#define WEAP_MACHINEGUN 4 +#define WEAP_CHAINGUN 5 +#define WEAP_GRENADES 6 +#define WEAP_GRENADELAUNCHER 7 +#define WEAP_ROCKETLAUNCHER 8 +#define WEAP_HYPERBLASTER 9 +#define WEAP_RAILGUN 10 +#define WEAP_BFG 11 +#define WEAP_GRAPPLE 12 +#define WEAP_KNIFE 13 + +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 + + int weapmodel; // weapon model index (for weapons) + 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 pic_damage; // icon for weapon damage + int pic_timeout; // icon for timeout called by ref +} 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 +#define MOD_GRAPPLE 34 + +//WF - Weapons Factory means of death +#define MOD_WF_LASERBALL 35 +#define MOD_WF_GOODYEAR 36 +#define MOD_WF_PROXIMITY 37 +#define MOD_WF_CLUSTER 38 +#define MOD_WF_PIPEBOMB 39 +#define MOD_WF_EARTHQUAKE 40 +#define MOD_REVERSE_TELEFRAG 41 +#define MOD_WF_TURRET 42 +#define MOD_WF_FLAME 43 +#define MOD_NAG 44 +#define MOD_MBPC 45 +#define MOD_SHRAPNEL 46 +#define MOD_CLUSTERROCKET 47 +#define MOD_PLASMABOMB 48 +#define MOD_DISEASE 49 +#define MOD_SNIPERRIFLE 50 +#define MOD_NAIL 51 +#define MOD_SHC 52 +#define MOD_NEEDLER 53 +#define MOD_CONCUSSION 54 +#define MOD_ARMORDART 55 +#define MOD_INFECTEDDART 56 +#define MOD_NAPALMROCKET 57 +#define MOD_LIGHTNING 58 +#define MOD_TELSA 59 +#define MOD_MAGNOTRON 60 +#define MOD_SHOCK 61 +#define MOD_PELLET 62 +#define MOD_FLAREGUN 64 +#define MOD_TRANQUILIZER 65 +#define MOD_BOLTEDBLASTER 66 +#define MOD_SENTRY 67 +#define MOD_LRPROJECTILE 68 +#define MOD_FLARE 69 +#define MOD_KAMIKAZE 70 +#define MOD_DEPOT 71 +#define MOD_SENTRYKILLER 72 +#define MOD_MEGACHAINGUN 73 +#define MOD_HOMINGROCKET 74 +#define MOD_TRANQUILIZERDART 75 +#define MOD_SNIPERRIFLE_LEG 76 +#define MOD_SNIPERRIFLE_HEAD 77 +#define MOD_SENTRY_ROCKET 78 +#define MOD_KNIFE 79 +#define MOD_KNIFEBACK 80 +#define MOD_HEALINGDEPOT 81 +#define MOD_LASERCUTTER 82 +#define MOD_NAPALMGRENADE 83 +#define MOD_FLAMETHROWER 84 +#define MOD_BIOSENTRY 85 +#define MOD_AK47 86 +#define MOD_PISTOL 87 + +//XATRIX and ROGUE +#define MOD_RIPPER 88 +#define MOD_PHALANX 89 +#define MOD_ETF_RIFLE 90 +#define MOD_TESLA 91 +#define MOD_HEATBEAM 92 +#define MOD_DEFENDER_SPHERE 93 +#define MOD_BLASTER2 94 +#define MOD_TRACKER 95 +#define MOD_NUKE 96 + +//More WF +#define MOD_LASER_DEFENSE 97 +#define MOD_GASGRENADE 98 +#define MOD_STINGER 99 +#define MOD_MISSILE 100 +#define MOD_CAMERA 101 +#define MOD_FEIGN 102 +#define MOD_FREEZER 103 //acrid 3/99 +#define MOD_DEPOT_EXPLODE 104 // depot destroyed by someone other then owner + +#define MOD_FRIENDLY_FIRE 0x8000000 + +extern int meansOfDeath; + + +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; +extern cvar_t *skill; +extern cvar_t *fraglimit; +extern cvar_t *timelimit; +//ZOID +extern cvar_t *capturelimit; +//ZOID + +//WF +extern cvar_t *wfflags; +extern cvar_t *wfconfig; +//extern cvar_t *classdef; +extern cvar_t *gamedir; +extern cvar_t *mock; +extern cvar_t *mod; +//extern cvar_t *unbalanced_limit; +extern cvar_t *filterban; //1=(default) don't allow anyone on list + //0= only allow those on list +//extern cvar_t *noclass; //bit mask to turn off classes + +//extern cvar_t *banweapon; +//extern cvar_t *bangrenade; +//extern cvar_t *banspecial; +//extern cvar_t *logfilename; + +extern int wfdebug; //special messages if turned on +extern int flag1dropped; //flags to determine if flag was dropped +extern int flag2dropped; +//WF + +extern cvar_t *needpass; +extern cvar_t *sv_maplist; + +extern cvar_t *wfpassword; +extern cvar_t *g_select_empty; +extern cvar_t *dedicated; + +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; +extern cvar_t *floodertime; +//ZOID +extern qboolean is_quad; +//ZOID + +//ERASER +extern cvar_t *bot_num; +extern cvar_t *bot_name; +extern cvar_t *bot_allow_client_commands; +extern cvar_t *bot_free_clients; +extern cvar_t *bot_debug; +extern cvar_t *bot_show_connect_info; +extern cvar_t *bot_calc_nodes; +extern cvar_t *bot_debug_nodes; +extern cvar_t *bot_auto_skill; +extern cvar_t *bot_drop; +extern cvar_t *bot_chat; +extern cvar_t *bot_optimize; +extern cvar_t *bot_tarzan; + +extern cvar_t *players_per_team; +extern cvar_t *addteam; +extern cvar_t *teamplay; +extern cvar_t *ctf_auto_teams; +//extern cvar_t *grapple;//acrid +extern cvar_t *ctf_special_teams; // ~~JLH +extern cvar_t *ctf_humanonly_teams; // ~~JLH +extern cvar_t *bot_melee;//Acrid added +//ERASER + +#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 *findEnemyWithinRadius (edict_t *self, 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); + +//ERASER START +// Ridah +void stuffcmd(edict_t *ent, char *text); +float entdist(edict_t *ent1, edict_t *ent2); +void AddModelSkin (char *modelfile, char *skinname); + +void my_bprintf (int printlevel, char *fmt, ...); +// end +// ACE compatibility routines +void safe_bprintf (int printlevel, char *fmt, ...); +void safe_centerprintf (edict_t *ent, char *fmt, ...); +void safe_cprintf (edict_t *ent, int printlevel, char *fmt, ...); +//ERASER END + +// +// g_combat.c +// +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2); +qboolean CanDamage (edict_t *targ, edict_t *inflictor); +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker); +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, int mod); +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, int mod); +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); + +//ERASER +qboolean visible_box (edict_t *self, edict_t *other); +qboolean visible_fullbox (edict_t *self, edict_t *other); +//ERASER +// +// 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, int mod); +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int mod); +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius); +void fire_freezer (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect); +//acrid 3/99 + +// +// g_ptrail.c +// +void PlayerTrail_Init (void); +void PlayerTrail_Add (edict_t *self, vec3_t spot, edict_t *goalent, int nocheck, int calc_routes, int node_type); +//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); + +//ERASER START +void PlayerTrail_FindPaths(int marker); +edict_t *PlayerTrail_VisibleTrailInRange(edict_t *ent1, edict_t *ent2, float range); +edict_t *bot_PickBestTrail (edict_t *self, edict_t *target, int recurse_depth); +edict_t *bot_TouchedTrail(edict_t *ent, edict_t *targ); + +qboolean nodes_done; // used to determine whether or not to enable node calculation +edict_t *check_nodes_done; // after 20 mins of play, this ent checks if we should turn off node calc. at set intervals + +//edict_t *last_trail_dropped; +qboolean loaded_trail_flag; + +#define TRAIL_LENGTH 750 +edict_t *trail[TRAIL_LENGTH]; // the actual trail! + +#define TRAIL_TELEPORT_SOURCE 1 +//ERASER END + +// +// 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, edict_t *ent); +void InitBodyQue (void); +void ClientBeginServerFrame (edict_t *ent); +//WF +qboolean IsFemale (edict_t *ent); +//WF +//WF//FIXME ACRID +//ERASER START +float last_trail_time; +//ERASER END + +// +// 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); + +// +// p_view.c +// +void ClientEndServerFrame (edict_t *ent); + +// +// p_hud.c +// +void MoveClientToIntermission (edict_t *client); +void G_SetStats (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); +void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)); + +// +// 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); + +// WF //G_ZIP OPEN TROUBLE +#include "wf_include.h" +/// WF + +//ERASER START +//#define BOT_FRAMETIME 0.1 +double bot_frametime; + +int max_bots; +float last_bot_spawn; +int bot_male_names_used; +int bot_female_names_used; +int bot_count; + +#define MAX_BOT_FEMALE_NAMES 10 +#define MAX_BOT_MALE_NAMES 15 + +// +// Zip tools +// + +#ifdef WIN32 +int G_UnzipFile(char *zipfile, char *extract_dir); +int G_ZipFile(char *zipfile, char *filename); +#endif +//ERASER END + + +//============================================================================ + +// 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 +#define ANIM_REVERSE 6 + +// 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; + int savedFlags; + 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 + +//WF +#include "wf_local_pers.h" +//WF + +} 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 +//ZOID + int ctf_team; // CTF team + int ctf_state; + float ctf_lasthurtcarrier; + float ctf_lastreturnedflag; + float ctf_flagsince; + float ctf_lastfraggedcarrier; + qboolean id_state; +//ZOID + vec3_t cmd_angles; // angles sent over in the last command + int game_helpchanged; + int helpchanged; + +//WF + int next_ctf_team; // CTF team +//WF +// -- ANTI ZBOT -- // + /* + int bot_start; + int bot_end; + int bot_end2; + int bot_end3; + int bot_retries; + int bot_retries2; + int bot_retries3; + */ +// -- ANTI ZBOT -- // + +// ANTI ZBOT LITHIUM TEST + short angles[2][2]; + int tog; + int jitter; + float jitter_time; + float jitter_last; +// ANTI ZBOT LITHIUM TEST + + + //K2: Begin 3/99 + qboolean inServer;//botcam + //K2: End + + + +} client_respawn_t; + +//ERASER START +//======================================================================================== +typedef struct +{ + float accuracy; // (1-5) aiming accuracy + float aggr; // (1-5) higher aggression will attack more players, rather than collect items + float combat; // (1-5) effects strafing, jumping and crouching while firing + gitem_t *fav_weapon; // will seek this weapon from further distances, and attack more frequently when gained + int quad_freak; // (0/1) won't seek quad from far away if no + int camper; // (0/1) more likely to find a dark corner with favourite weapon + int avg_ping; // average ping time shown on scoreboard (just for looks) +} bot_stats_t; + +// this stores a linked list of bots, which are read in from bots.cfg when the game starts +struct bot_info_s +{ + struct bot_info_s *next; + + char *name; + char *skin; + char *wfclass; + int ingame_count; // incremented each time this bot enters the game + + // stats + bot_stats_t bot_stats; + +}; + +typedef struct bot_info_s bot_info_t; + +// TEAMPLAY structures +#define MAX_PLAYERS_PER_TEAM 32 +#define MAX_TEAMS 64 + +struct bot_team_s +{ + // static data + char *teamname; + char *abbrev; + char *default_skin; + + bot_info_t *bots[MAX_PLAYERS_PER_TEAM]; + + // in-game stuff + int ingame; // set when this team has been spawned + int score; + int num_players; // incremented each time a player/bot joins this team + int num_bots; + + float last_grouping; +}; + +typedef struct bot_team_s bot_team_t; + +bot_team_t *bot_teams[MAX_TEAMS]; +int total_teams; +// END Teamplay structure +//======================================================================================== + +typedef vec3_t lag_trail_t[10]; +// ERASER END + +// 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 +//ZOID + qboolean inmenu; // in menu + pmenuhnd_t *menu; // current menu +//ZOID + 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 respawn_time; // can respawn when time > this + + ///Q2 Camera Begin ERASER START + qboolean bIsCamera; + int iMode; + edict_t *pTarget; + qboolean bWatchingTheDead; + vec3_t vDeadOrigin; + double fXYLag; + double fZLag; + double fAngleLag; + ///Q2 Camera End + + // Eraser additions + bot_team_t *team; + float firing_delay; + float latency; + lag_trail_t *lag_trail; // stores the last 1 second of movements for lag simulation + lag_trail_t *lag_angles; + qboolean ctf_has_tech; + float slide_time; + float reached_goal_time; +// ERASER END + +//ZOID + edict_t *ctf_grapple; // entity of grapple + int ctf_grapplestate; // true if pulling + float ctf_grapplereleasetime; // time of grapple release + float ctf_grapplestart; + float ctf_regentime; // regen tech + float ctf_techsndtime; + float ctf_lasttechmsg; + edict_t *chase_target; + qboolean update_chase; +//ZOID +//======= +//ROGUE + float double_framenum; + float ir_framenum; +// float torch_framenum; + float nuke_framenum; + float tracker_pain_framenum; + + edict_t *owned_sphere; // this points to the player's sphere +//ROGUE +//======= +//WF +#include "wf_local_client.h" +//WF + +}; + +//ERASER START +// this stores the route-table data +typedef struct +{ + short int route_path[TRAIL_LENGTH]; // visible node that will take us to each node + unsigned short int route_dist[TRAIL_LENGTH]; // dist to each node +} routes_t; + +#define MAX_PATHS 24 // each node cannot see more than MAX_PATHS nodes +edict_t *the_client; // points to the first client to enter the game (used for debugging) +// ERASER END + +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; + + //WF + // freezer code + //WF ACRID3 +//WF MOVED ALL THIS BELOW FOR TESTING +//ERASER START + // bot data (FIXME: these should really be in a separate structure!) + edict_t *last_goal; + float last_goal_time; + float movetogoal_time; + float strafe_dir, strafe_changedir_time; + float crouch_attack_time; + float last_enemy_sight, search_time; + float last_besttrail_dist; // updated when a trail is found + float last_path_enemy_timestamp; + float bored_suicide_time; // for suiciding when getting stuck + edict_t *node_target; + vec3_t jump_velocity; + vec3_t checkstuck_origin; + float checkstuck_time; + qboolean bot_client; +//ACO struct gclient_s *bot_client; // stores client relevant data for bots, like weapon ammo, health, etc//WAS ALREADY CO + float fire_interval, last_fire; + void (*bot_fire) (edict_t *self); + float last_ladder_touch; + float bot_plat_pausetime; + int closest_trail; + float closest_trail_time; + float last_think_time; + float last_reached_trail; + +#ifdef _WIN32 + struct _timeb lastattack_time; +#else + struct timeb lastattack_time; +#endif + + vec3_t animate_org; + float last_inair; + int movetarget_want; + edict_t *avoid_ent; // avoid grenade, rocket, etc + vec3_t avoid_dir; // direction to run to avoid danger + float avoid_dir_time; // amount of time to spend avoiding in this direction, before looking for a new direction + float last_roam_time; + float sight_enemy_time; + float last_best_direction; + + bot_stats_t *bot_stats; + bot_info_t *botdata; + int skill_level; + + int last_closest_node; + float last_closest_time; + float last_move_nocloser; + + edict_t *giveup_lastgoal; // checked each frame to see if we've been going for the same goal for too long + float last_seek_enemy; + + float last_insult, last_help, last_drop, last_group; + float group_pausetime; + float last_pain, last_jump; + float last_nopaths_roam; + edict_t *save_movetarget, *save_goalentity; + edict_t *last_movegoal; + + vec3_t last_forward, last_start; // used to prevent calculating twice per FRAMETIME + edict_t *flagpath_goal; + float activator_time; + + // trail data + int paths[MAX_PATHS]; + int ignore; + int trail_index; // for backwards referencing + edict_t *trigger_ent; // points to the trigger entity that is associated with this node + char node_type; // uses NOTE_* values + + routes_t *routes; // routes to all other nodes + + edict_t *last_trail_dropped; // this player's last trail node dropped + + // misc + float ignore_time; // ignore hunting this ent while > level.time + int frags; // used by plats/teleporters so we know they've been routed + + // used by player, for node calculation + float last_max_z; + edict_t *last_groundentity; + edict_t *jump_ent; // set everytime the player jumps + edict_t *duck_ent; // set everytime the player begins to duck + + // used to speed up View Weapons code, so we don't need to find the model each frame, for each player + char *modelname; + + // special ACE vars + qboolean isbot; + int ace_last_node; + +// =========================================================================== +// DO NOT MODIFY ANYTHING ABOVE THIS !! +// ERASER NEEDS THE FIELDS IN THIS ORDER (since P_TRAIL.C is precompiled) +// +// You may safely add fields below this point +// ERASER END + //WF +//WF +#include "wf_local_edict.h" +//WF +}; + +//ERASER START +int num_players; +edict_t *players[MAX_CLIENTS]; // pointers to all players in the game +edict_t *weapons_head; // pointers to all weapons in the game (use node_target and last_goal to traverse forward/back) +edict_t *health_head; +edict_t *bonus_head; // armour, Quad, etc +edict_t *ammo_head; + +// the following are just faster ways of accessing FindItem("item_name"), set in Worldspawn +gitem_t *item_shells, *item_cells, *item_rockets, *item_grenades, *item_slugs, *item_bullets; +gitem_t *item_shotgun, *item_hyperblaster, *item_supershotgun, *item_grenadelauncher, + *item_chaingun, *item_railgun, *item_machinegun, *item_bfg10k, *item_rocketlauncher, + *item_blaster, *item_sniperrifle, *item_lightninggun, *item_infecteddart, + *item_pulsecannon, *item_telsacoil, *item_flamethrower, *item_needler , + *item_pelletrocketlauncher, *item_rocketnapalmlauncher ,*item_rocketclusterlauncher, + *item_shc, *item_handgrenades, *item_poisondart, *item_ak47, *item_pistol, + *item_sentryspot, *item_depotspot, *item_armordart, *item_stingerrocketlauncher, *item_knife; + + +bot_info_t *botinfo_list; +int total_bots; // number of bots read in from bots.cfg + +bot_info_t *teambot_list; // bots that were generated solely for teamplay + +qboolean paused; // fake a paused game during deathmatch +// ERASER END + + +//ZOID +#include "g_ctf.h" +//ZOID diff --git a/g_main.c b/g_main.c new file mode 100644 index 0000000..de570dd --- /dev/null +++ b/g_main.c @@ -0,0 +1,1331 @@ +//Main Routine + +#define WFMAIN 1 + +#include "g_local.h" +#include "stdlog.h" // StdLog - Mark Davies + +//ERASER START +#include "bot_procs.h" +#include "p_trail.h" +///Q2 Camera Begin +#include "camclient.h" +///Q2 Camera End +//ERASER END + +game_locals_t game; +level_locals_t level; +game_import_t gi; +game_export_t globals; +spawn_temp_t st; + +int sm_meat_index; +int snd_fry; +int meansOfDeath; + +edict_t *g_edicts; + +cvar_t *deathmatch; +cvar_t *coop; +cvar_t *dmflags; +cvar_t *skill; +cvar_t *fraglimit; +cvar_t *timelimit; +cvar_t *needpass; +cvar_t *sv_maplist; + +//WF +cvar_t *wfflags; +//cvar_t *classdef; +cvar_t *wfconfig; +cvar_t *gamedir; +cvar_t *mock; +cvar_t *mod; +cvar_t *filterban; + +int wfdebug = 0; +int flag1dropped = 0; +int flag2dropped = 0; + + +//WF + +//ZOID +cvar_t *capturelimit; +//ZOID +cvar_t *wfpassword; +cvar_t *maxclients; +cvar_t *maxentities; +cvar_t *g_select_empty; +cvar_t *dedicated; + +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; + +//ERASER START +cvar_t *bot_num; +cvar_t *bot_name; +cvar_t *bot_allow_client_commands; +cvar_t *bot_free_clients; +cvar_t *bot_debug; +cvar_t *bot_show_connect_info; +cvar_t *bot_calc_nodes; +cvar_t *bot_debug_nodes; +cvar_t *bot_auto_skill; +cvar_t *bot_drop; +cvar_t *bot_chat; +cvar_t *bot_optimize; +cvar_t *bot_tarzan; +cvar_t *bot_melee;//acrid added +cvar_t *players_per_team; +cvar_t *addteam; +cvar_t *teamplay; +cvar_t *ctf_auto_teams; +cvar_t *ctf_special_teams; // ~JLH +cvar_t *ctf_humanonly_teams; // ~JLH +//cvar_t *grapple; + +//ERASER 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 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 WriteTrail ();//ERASER +void ShutdownGame (void) +{ + WriteTrail();//ERASER + gi.dprintf ("==== ShutdownGame ====\n"); + + sl_GameEnd( &gi, level ); // StdLog + + 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 || ent->bot_client)//ERASER || ent->bot_client + continue; + ClientEndServerFrame (ent); + } + +} + +/* +================= +EndDMLevel + +The timelimit or fraglimit has been exceeded +================= +*/ +void EndDMLevel (void) +{ + edict_t *ent; +//WF & LAC +// int i; +//WF +///ERASER START Q2 Camera Begin REMOVE THIS + EnitityListClean(); +///Q2 Camera End + + TOTALDROPPEDAMMO = 0; + TOTALWORLDFLAMES = 0; + + // MAP MOD +/* + if (map_mod_) + { + char *nextmap; + + if (nextmap = map_mod_next_map()) + { // only use a map_mod map if the current level is known + strcpy(level.nextmap,nextmap); + } + } +*/ + // MAP MOD + + //ERASER END + // stay on same level flag + if ((int)dmflags->value & DF_SAME_LEVEL) + { + ent = G_Spawn (); + ent->classname = "target_changelevel"; + ent->map = level.mapname; + } +//WF & LAC+++ + // if you also want this to happen in co-op, you will probably + // have to put similar code in ExitLevel(). + else if (maplist.active) + { + ent = G_Spawn (); + ent->classname = "target_changelevel"; + MaplistNextMap(ent); + maplist.warning_given = false; //clear warning flag + + } +//WF & LAC+++ + else if (level.nextmap[0]) + { // go to a specific map + ent = G_Spawn (); + ent->classname = "target_changelevel"; + 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"; + ent->map = level.mapname; + } + } + + BeginIntermission (ent); + WriteTrail();//ERASER +} + + +/* +================= +WFEndDMLevel + +End the level and go to selected map +================= +*/ +void WFEndDMLevel (char *mapname) +{ + edict_t *ent; + EnitityListClean(); + + TOTALDROPPEDAMMO = 0; + TOTALWORLDFLAMES = 0; + +gi.dprintf("Ref is switching to map '%s'\n", mapname); + ent = G_Spawn (); + ent->classname = "target_changelevel"; + ent->map = mapname; + BeginIntermission (ent); + WriteTrail();//ERASER +} + +/* +================= +CheckNeedPass +================= +*/ +void CheckNeedPass (void) +{ + int need; + + // if password or spectator_password has changed, update needpass + // as needed + if (wfpassword->modified ) + { + wfpassword->modified = false; + + need = 0; + + if (*wfpassword->string && Q_stricmp(wfpassword->string, "none")) + need |= 1; + + gi.cvar_set("needpass", va("%d", need)); + } +} +//WF34 END +/* +================= +CheckDMRules +================= +*/ +void CheckDMRules (void) +{ + int i; + gclient_t *cl; + + if (level.intermissiontime) + return; + + if (!deathmatch->value) + return; + + if (timelimit->value) + { +//WF24 START - Map voting + //give them the 1 minute warning + if (((int)wfflags->value & WF_MAP_VOTE) && + (level.time >= (timelimit->value - 1)*60) && + (maplist.warning_given == false)) + { + gi.bprintf (PRINT_MEDIUM,"-- One Minute Warning: Type 'vote' to pick next map --\n"); + maplist.warning_given = true; + } + + +//WF24 E WF ADDED ELSE BELOW + else if (level.time >= timelimit->value*60) + { + gi.bprintf (PRINT_HIGH, "Timelimit hit.\n"); + EndDMLevel (); + return; + } + } +//WF IS A BIT DIF FRAGLIMIT VALUE LINE IS HERE +//ERASER CO if (fraglimit->value) +//ERASER CO { +//ZOID + if (ctf->value) { + if (CTFCheckRules()) { + EndDMLevel (); + } + } +//ZOID + 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) + {//ERASER USES MY_ WAS GI. + my_bprintf (PRINT_HIGH, "Fraglimit hit.\n"); + EndDMLevel (); + return; + } + +//WF - Map voting + //give them the frag limit warning + if (((int)wfflags->value & WF_MAP_VOTE) && + (cl->resp.score >= (fraglimit->value-3)) && + (maplist.warning_given == false)) + { + gi.bprintf (PRINT_MEDIUM,"-- Frag Limit Warning (3 left): Type 'vote' to pick next map --\n"); + maplist.warning_given = true; + } + + +//WF24 E + + } + } + +//++TeT WF Flag Pickup Delay Countdown + + if (level.time <= 30)//eraser change all gi. to my_ + { + if ( level.time == 1) + { + my_bprintf (PRINT_MEDIUM,"-- 30 Seconds Till Flag Pick Up Allowed --\n"); + } + if ( level.time == 10) + { + my_bprintf (PRINT_MEDIUM,"-- 20 Seconds Till Flag Pick Up Allowed --\n"); + } + if ( level.time == 15) + { + my_bprintf (PRINT_MEDIUM,"-- 15 Seconds Till Flag Pick Up Allowed --\n"); + } + if ( level.time == 20) + { + my_bprintf (PRINT_MEDIUM,"-- 10 Seconds Till Flag Pick Up Allowed --\n"); + } + if ( level.time == 25) + { + my_bprintf (PRINT_MEDIUM,"-- 5 Seconds Till Flag Pick Up Allowed --\n"); + } + if ( level.time == 26) + { + my_bprintf (PRINT_MEDIUM,"4\n"); + } + if ( level.time == 27) + { + my_bprintf (PRINT_MEDIUM,"3\n"); + } + if ( level.time == 28) + { + my_bprintf (PRINT_MEDIUM,"2\n"); + } + if ( level.time == 29) + { + my_bprintf (PRINT_MEDIUM,"1\n"); + } + if ( level.time == 30) + { + my_bprintf (PRINT_MEDIUM,"Flag Pick Up Allowed\n"); + } + } +//--TeT WF Flag Pickup Delay Countdown + +} +//ERASER START +char respawn_bots[64][256]; +bot_team_t *respawn_bot_teams[64]; +int respawn_ctf_teams[64]; +extern int force_team; +qboolean respawn_init = false, respawn_flag = false; +//ERASER END + +/* +============= +ExitLevel +============= +*/ +void ExitLevel (void) +{ + int i,j;//ERASER ADDED J + 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 (); + +//ERASER START + memset(respawn_bots, 0, sizeof(respawn_bots)); + memset(respawn_bot_teams, 0, sizeof(bot_teams)); + + // clear some things before going to next level + j = 0; +//ERASER END + 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; +//ERASER START + // save the list of bots for respawning in next level + if (ent->bot_client) + { // add to list + Com_sprintf (respawn_bots[j], sizeof(respawn_bots[j]), ent->client->pers.netname); + if (teamplay->value || !ctf->value) + respawn_bot_teams[j] = ent->client->team; + if (ctf->value) + respawn_ctf_teams[j] = ent->client->resp.ctf_team; + + j++; + } + } + + respawn_flag = true; +//ERASER END + +//ZOID + CTFInit(); +//ZOID + +} +//ERASER START +void RemoveDroppedItem(edict_t *ent); + +qboolean AllItemsHaveMovetarget(edict_t *list_head) +{ + edict_t *trav; + + trav = list_head; + while (trav) + { + if (!trav->movetarget && (trav->think != RemoveDroppedItem)) + return false; + + trav = trav->node_target; + } + + return true; +} + +/* +================ +CheckNodeCalcDisable + + Checks at set intervals to see if we can safely disable node calc. +================ +*/ +void CheckNodeCalcDisable(edict_t *ent) +{ + // check all item lists, to see if they have a movetarget (visible node) + // BUT only check static items, dropped items don't count + if (!AllItemsHaveMovetarget(weapons_head)) + goto failure; + if (!AllItemsHaveMovetarget(health_head)) + goto failure; + if (!AllItemsHaveMovetarget(bonus_head)) + goto failure; + if (!AllItemsHaveMovetarget(ammo_head)) + goto failure; + + // all items are visible from a node, so we're done + + gi.cvar_set("bot_calc_nodes", "0"); + gi.dprintf("Dynamic node calculation DISABLED\n"); + + G_FreeEdict(ent); + return; + +failure: + + ent->nextthink = level.time + 15; +} + +qboolean PlayerNameExists(char *name) +{ + int i,j; + char testname[128]; + + for (i=0; iclient->pers.netname); + + for (j=0; testname[j]; j++) + { + if (testname[j] == '[') + { + testname[j] = 0; + break; + } + } + +#ifdef _WIN32 + if (!_stricmp(testname, name)) +#else + if (!strcasecmp(testname, name)) +#endif + return true; + } + + return false; +} + +extern int force_team; + +// enforce ctf_special_teams with real humans +// what enforce_ctf_special_teams does is keep the teams even with the human players +// based on a multiplier ctf_special_teams. +// if one human enters the games then one bot times ctf_special_teams is added. +// normally you would set ctf_special_teams = 1. However, if ctf_special_teams = 2 +// then 2 bots for every 1 human is added until the connections are used up. +// If on the otherhand a human enters the other team then the teams are +// automatically evened up. However, if ctf_humanonly_teams is set to 1 then we +// only allow humans in one team and no bots. Human in this case may start in +// any team. But any other human that tries to enter into the game on the +// other team will be forced to join other team. +// +static qboolean enforce_ctf_special_teams() +{ + qboolean bRet = FALSE; + int i; + static float s_iLastTime = 0; + + if (level.time < s_iLastTime) + s_iLastTime = 0; + + if ( ctf->value && ctf_special_teams->value ) + { + bRet = TRUE; + + if ( s_iLastTime < (level.time - 1) ) + { + extern void CTFJoinTeam(edict_t *ent, int desired_team); + qboolean bRet = FALSE; // set to true if this is valid command + qboolean bHumanOnly = ctf_humanonly_teams->value; + int iTeam1Count=0; // total current team players + int iTeam2Count=0; + int iTeam1NeedCount=0; // how many bots we need to even it out + int iTeam2NeedCount=0; + int iTeam1Humans=0; // number of humans in game + int iTeam2Humans=0; + double dSpecialTeams = ctf_special_teams->value; + int iMaxPlayers = maxclients->value; + edict_t *pDropBotTeam1=NULL; // player we drop from team is needed + edict_t *pDropBotTeam2=NULL; + edict_t *pNewBot; // new bot created + + + // count number of players on each team + for (i=0; iclient->resp.ctf_team == CTF_TEAM1) + { + iTeam1Count++; + + if (players[i]->bot_client) + { + if (!pDropBotTeam1 || (players[i]->client->resp.score < pDropBotTeam1->client->resp.score)) + pDropBotTeam1 = players[i]; + } + else + iTeam1Humans++; + } + else if (players[i]->client->resp.ctf_team == CTF_TEAM2) + { + iTeam2Count++; + + if (players[i]->bot_client) + { + if (!pDropBotTeam2 || (players[i]->client->resp.score < pDropBotTeam2->client->resp.score)) + pDropBotTeam2 = players[i]; + } + else + iTeam2Humans++; + } + } + + // Range check special to 1+ + if ( dSpecialTeams < 1 ) + dSpecialTeams = 1; + // First pass, set teams to max of other + + if (iTeam1Count > iTeam2Count) + iTeam1NeedCount = iTeam2NeedCount = iTeam1Count; // even teams + else + iTeam1NeedCount = iTeam2NeedCount = iTeam2Count; // even teams + + if ( iTeam1Humans > 0 && iTeam2Humans > 0 ) + { + // Do the following only if we have set ctf_humanonly_teams + if ( bHumanOnly && force_team != CTF_NOTEAM ) + { + // scan all players and look for human in the wrong place + for ( i = 0; i < num_players; i++ ) + if ( !players[i]->bot_client ) // if this is a human + if ( players[i]->client->resp.ctf_team == force_team ) // if they are on bot team + { + if ( force_team == CTF_TEAM1 ) // switch them to the other team + CTFJoinTeam( players[i], CTF_TEAM2 ); + else + CTFJoinTeam( players[i], CTF_TEAM1 ); + return TRUE; // we need to restart the count again + } + } + } + else if ( iTeam1Humans == 0 && iTeam2Humans == 0 ) + { + iTeam1NeedCount = iTeam2NeedCount = 0; // no humans in game no playing! + force_team = CTF_NOTEAM; + } + else + if ( iTeam1Humans == 0 || iTeam2Humans == 0 ) + { + if ( iTeam1Humans == 0 ) // no humans on team 1 + { + force_team = CTF_TEAM1; + iTeam1NeedCount = (int) ( (double) iTeam2Humans * dSpecialTeams ); + // if human only then don't allow bot to be created in Team2 + if ( bHumanOnly ) + iTeam2NeedCount = iTeam2Count; + // Adjust BOT if multiplier is >1 and <2 you could have 1.5 bots per teams + if ( iTeam1NeedCount == 1 && ( dSpecialTeams > 1 && dSpecialTeams < 2 ) ) + iTeam1NeedCount++; + // Range check this to make sure we are not over doing it + if ( iTeam1NeedCount + iTeam2Count > iMaxPlayers ) + iTeam1NeedCount = iMaxPlayers - iTeam2Count; + } + else + { // no humans on team 2 + force_team = CTF_TEAM2; + iTeam2NeedCount = (int) ( (double) iTeam1Humans * dSpecialTeams ); + // if human only then don't allow bot to be created in Team1 + if ( bHumanOnly ) + iTeam1NeedCount = iTeam1Count; + // Adjust BOT if multiplier is >1 and <2 you could have 1.5 bots per team + if ( iTeam2NeedCount == 1 && ( dSpecialTeams > 1 && dSpecialTeams < 2 ) ) + iTeam2NeedCount++; + // Range check this to make sure we are not over doing it + if ( iTeam2NeedCount + iTeam1Count > iMaxPlayers ) + iTeam2NeedCount = iMaxPlayers - iTeam1Count; + } + } + // Let add bots to team 1 if necessary + if ( iTeam1Count != iTeam1NeedCount ) + { + pNewBot = NULL; + if (iTeam1Count > iTeam1NeedCount ) + { + if (pDropBotTeam1) + botDisconnect(pDropBotTeam1); // drop it + } + else + botDebugPrint("SPAWN_BOT NEW BOT1(ACRID)\n"); + pNewBot = spawn_bot(NULL); // the game needs more players + // make sure its on the right team + //if ( pNewBot && pNewBot->client->resp.ctf_team != CTF_TEAM1 ) + // CTFJoinTeam( pNewBot, CTF_TEAM1 ); + } + // Let add bots to team 2 if necessary + if ( iTeam2Count != iTeam2NeedCount ) + { + // Lets fix up team 2 + pNewBot = NULL; + if ( iTeam2Count > iTeam2NeedCount ) + { + if (pDropBotTeam2) + botDisconnect(pDropBotTeam2); // drop it + } + else // the game needs more players + botDebugPrint("SPAWN_BOT NEW BOT 2(ACRID)\n"); + pNewBot = spawn_bot(NULL); + // make sure its on the right team + //if ( pNewBot && pNewBot->client->resp.ctf_team != CTF_TEAM2 ) + // CTFJoinTeam( pNewBot, CTF_TEAM2 ); + } + s_iLastTime = level.time; + } + } + return bRet; +} +void ClientUserinfoChanged (edict_t *ent, char *userinfo); + +qboolean execed_launcher_cfg=false; +//ERASER END +/* +================ +G_RunFrame + +Advances the world by 0.1 seconds +================ +*/ +void G_RunFrame (void) +{ + int i; + edict_t *ent; + static float last_ctf_teams=0;//ERASER START + + if (paused) + return; + + if (level.time < last_ctf_teams) + last_ctf_teams = 0; +//ERASER END + level.framenum++; + level.time = level.framenum*FRAMETIME; +//ERASER START + // check for disabling node calc. + if ((bot_calc_nodes->value) && (level.time > 300) && (!check_nodes_done)) + { // start checking for disabling node calc + check_nodes_done = G_Spawn(); + check_nodes_done->think = CheckNodeCalcDisable; + check_nodes_done->nextthink = level.time + 0.1; + } + + if (!execed_launcher_cfg && (level.time > 1)) + { + gi.AddCommandString("exec launcher.cfg\n"); + execed_launcher_cfg = true; + } + + // do BOT stuff + + roam_calls_this_frame = 0; + bestdirection_callsthisframe = 0; + + if (level.time < last_bot_spawn) + last_bot_spawn = 0; + + if (level.time > 8) + { // give the clients some time to reconnect + if (!respawn_init) + { + memset(respawn_bots, 0, 64*256); + respawn_init = true; + } + else if (respawn_flag) + { // we need to spawn some bots from the previous level + int count=0; + static float last_spawn=0; + + if (level.time < last_spawn) + last_spawn = 0; + + if (last_spawn > (level.time - 0.3)) + goto no_spawnbots; + + last_spawn = level.time; + + for (i=0; i<64; i++) + { + if (respawn_bots[i][0] == '\0') + continue; + + force_team = respawn_ctf_teams[i]; + ent = spawn_bot(respawn_bots[i]); + botDebugPrint("SPAWN_BOT FORCE TEAM (ACRID)\n"); + force_team = CTF_NOTEAM; //~~JLH + + if (ent && respawn_bot_teams[i]) + { + ent->client->team = respawn_bot_teams[i]; + } + + // set name + if (ent) + strcpy(ent->client->pers.netname, respawn_bots[i]); + + respawn_bots[i][0] = '\0'; + botDebugPrint("SPAWN_BOT NULL 8(ACRID)\n"); + if (++count > 2) + goto no_spawnbots; + } + + respawn_flag = false; + + goto no_spawnbots; + } +//MIGHT BE THE SPAWN TROUBLE FIXME ACRID + // enforce ctf_auto_teams as best as possible, and keep teams even + if (ctf->value && ctf_auto_teams->value) + { + if (last_ctf_teams < (level.time - 1)) + { + int team1=0, team2=0; + edict_t *bot_team1=NULL, *bot_team2=NULL; + + // count number of players on each team + for (i=0; iclient->resp.ctf_team == CTF_TEAM1) + { + team1++; + + if (players[i]->bot_client) + { + if (!bot_team1 || (players[i]->client->resp.score < bot_team1->client->resp.score)) + bot_team1 = players[i]; + } + } + else if (players[i]->client->resp.ctf_team == CTF_TEAM2) + { + team2++; + + if (players[i]->bot_client) + { + if (!bot_team2 || (players[i]->client->resp.score < bot_team2->client->resp.score)) + bot_team2 = players[i]; + } + } + } + + if (team1 != team2) + { + if (team1 > team2) + { + if (team1 > ctf_auto_teams->value) + { + if (bot_team1) + { // drop it + botDisconnect(bot_team1); + } + else // add a bot to the game, which will get added to team2 + { + botDebugPrint("SPAWN_BOT NULL 1(ACRID)\n"); + spawn_bot(NULL); + } + } + else // the game needs more players + if (team1 < ctf_auto_teams->value ) // ~~JLH fix respawn forever + { + botDebugPrint("SPAWN_BOT NULL 2(ACRID)\n"); + spawn_bot(NULL); + } + } + else // team2 has more players than team1 + { + if (team2 > ctf_auto_teams->value) + { + if (bot_team2) + { // drop it + botDisconnect(bot_team2); + } + else // add a bot to the game, which will get added to team1 + { + botDebugPrint("SPAWN_BOT NULL 3(ACRID)\n"); + spawn_bot(NULL); + } + } + else // the game needs more players + if (team2 < ctf_auto_teams->value ) // ~~JLH fix respawn forever + { + botDebugPrint("SPAWN_BOT NULL 4(ACRID)\n"); + spawn_bot(NULL); + } + } + } + else // teams are equal + { + if (team1 > ctf_auto_teams->value) + { + if (bot_team1 && bot_team2) + { + // drop the lowest scoring bot from team1 & team2 + botDisconnect(bot_team1); + botDisconnect(bot_team2); + } + } + else if (team1 < ctf_auto_teams->value) + { + botDebugPrint("SPAWN_BOT NULL DOUBLE(ACRID)\n"); + spawn_bot(NULL); + spawn_bot(NULL); + } + } + } + } + else if (((spawn_bots > 0) || (bot_count < bot_num->value)) && (last_bot_spawn < (level.time - 0.5))) + { + if (num_players < (maxclients->value - bot_free_clients->value)) + { + botDebugPrint("SPAWN_BOT NULL 6(ACRID)\n"); + + spawn_bot(NULL); + } + else + { +// gi.dprintf("Cannot spawn bot: not enough free client spaces (bot_free_clients = %i)\n", (int) bot_free_clients->value); + } + + if (spawn_bots > 0) + spawn_bots--; + + botDebugPrint("SPAWN_BOT NULL 9(ACRID)\n"); + } + else if (strlen(bot_name->string) > 1) + { + if (num_players < (maxclients->value - bot_free_clients->value)) + { + botDebugPrint("SPAWN_BOT NULL 7(ACRID)\n"); + spawn_bot(bot_name->string); + } + else + { + gi.dprintf("Cannot spawn bot: not enough free client spaces (bot_free_clients = %i)\n", (int)bot_free_clients->value); + } + + gi.cvar_set("bot_name", ""); + } + + if (!ctf->value && teamplay->value && (strlen(addteam->string) > 0)) + { // spawn the new team + // find the team + i=0; + while (iteamname, addteam->string) || !_stricmp(bot_teams[i]->abbrev, addteam->string)) +#else + if (!strcasecmp(bot_teams[i]->teamname, addteam->string) || !strcasecmp(bot_teams[i]->abbrev, addteam->string)) +#endif + { // found the team, so add the bots + bot_teams[i]->ingame = true; // bots will be added automatically (below) + break; + } + + i++; + } + + if (i == MAX_TEAMS) + { + gi.dprintf("Team \"%s\" does not exist.\n", addteam->string); + } + + gi.cvar_set("addteam", ""); + } + + if (players_per_team->value > MAX_PLAYERS_PER_TEAM) + { + char str[16]; + + sprintf(str, "%i", MAX_PLAYERS_PER_TEAM); + gi.cvar_set("players_per_team", str); + + gi.dprintf("MAX_PLAYERS_PER_TEAM = %i\n", MAX_PLAYERS_PER_TEAM); + } + + // check for a team that needs more players + if (!ctf->value && teamplay->value) + for (i=0; iingame) + continue; + + if (bot_teams[i]->num_players < players_per_team->value) + { // try to spawn a new bot + + for (bot=0; botvalue; bot++) + { + if (bot_teams[i]->bots[bot]) + { + if (bot_teams[i]->bots[bot]->ingame_count) + continue; + + if (PlayerNameExists(bot_teams[i]->bots[bot]->name)) + continue; + + if (strlen(bot_teams[i]->default_skin) > 0) + { // overwrite this bot's skin, with the team's default + strcpy(bot_teams[i]->bots[bot]->skin, bot_teams[i]->default_skin); + botDebugPrint("SPAWN_BOT SKIN 1(ACRID)\n"); + } + + newbot = spawn_bot(bot_teams[i]->bots[bot]->name); + + if (newbot) + { + char userinfo[MAX_INFO_STRING]; + + bot_teams[i]->num_players++; + bot_teams[i]->num_bots++; + newbot->client->team = bot_teams[i]; + + // add the abbreviation to the name + strcat(newbot->client->pers.netname, "["); + strcat(newbot->client->pers.netname, bot_teams[i]->abbrev); + strcat(newbot->client->pers.netname, "]"); + + // add bot's name to userinfo + strcpy(userinfo, newbot->client->pers.userinfo); + Info_SetValueForKey (userinfo, "name", newbot->client->pers.netname); + ClientUserinfoChanged(newbot, userinfo); + + if (bot_teams[i]->num_players == players_per_team->value) + break; // we have enough players + } + } + } + } + } + } + + if ((bot_count > 0) && (num_players > (maxclients->value - bot_free_clients->value))) + { // drop a bot to free a client spot + edict_t *trav, *lowest=NULL; + int i; + + // drop the lowest scoring bot + for (i=0; i < num_players; i++) + { + trav = players[i]; + + if (!trav->bot_client) + continue; + + if (!lowest || (trav->client->resp.score < lowest->client->resp.score)) + { + lowest = trav; + } + } + + if (lowest) // if NULL, then must be full of real players + { + botDisconnect(lowest); + } + } + + // Enforce players_per_team->value + if (!ctf->value && teamplay->value) + { + int t; + + for (t=0; tingame) + { + if (bot_teams[t]->num_players > players_per_team->value) + { // kick the lowest scoring bot from this team + edict_t *trav, *lowest=NULL; + + // drop the lowest scoring bot + for (i=0; i < num_players; i++) + { + trav = players[i]; + + if (!trav->bot_client || (trav->client->team != bot_teams[t])) + continue; + + if (!lowest || (trav->client->resp.score < lowest->client->resp.score)) + { + lowest = trav; + } + } + + if (lowest) // if NULL, then must be full of real players + { + botDisconnect(lowest); + } + } + + if ((level.time - (float)floor(level.time)) == 0) // only check every second + { + // start a team grouping? + if (bot_teams[t]->last_grouping < (level.time - 15)) + { // check for a new grouping + + for (i=0; ibot_client) + && (players[i]->client->team == bot_teams[t]) + && (players[i]->health > 80) + && (players[i]->waterlevel == 0) + && (players[i]->bot_fire != botBlaster) + && (players[i]->bot_fire != botShotgun) + && (!players[i]->target_ent) + && (!players[i]->enemy) + && (!players[i]->movetarget || (entdist(players[i], players[i]->movetarget) > 400))) + { // this bot shall start a gathering + TeamGroup(players[i]); + break; + } + } + } + } + } + } + } + + if (strlen(bot_drop->string) > 0) + { // kill the bot + edict_t *trav; + int i; + + // locate the bot + for (i=0; i < num_players; i++) + { + trav = players[i]; + + if (!trav->bot_client) + continue; + +#ifdef _WIN32 + if (!_stricmp(trav->client->pers.netname, bot_drop->string)) +#else + if (!strcasecmp(trav->client->pers.netname, bot_drop->string)) +#endif + { + botDisconnect(trav); + } + } + + gi.cvar_set("bot_drop", ""); + } + +no_spawnbots: + + // run bot animations + if (num_players >= 1) + { + for (i=0; i< num_players; i++) + { + if (players[i]->bot_client) + { + bot_AnimateFrames(players[i]); + + // check for a bot that has stopped thinking + if (players[i]->nextthink < (level.time - 0.2)) + players[i]->nextthink = level.time + 0.1; + } + } + } + + // end BOT stuff +//ERASER END + + // choose a client for monsters to target this frame +// AI_SetSightClient ();//WF USES THIS LINE + +//ERASER START + if (level.intermissiontime && (level.intermissiontime < (level.time - 10))) + { + level.exitintermission = true; + } +//ERASER END + + // 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; +//ERASER LINE BELOW + if (!ent->s.old_origin[0] && !ent->s.old_origin[1] && !ent->s.old_origin[2]) + 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) && !ent->bot_client)//ERASER ADDED && !ent->bot_client) + { + ClientBeginServerFrame (ent); + VectorCopy (ent->s.origin, ent->s.old_origin);//ERASER + continue; + } + + VectorCopy (ent->s.origin, ent->s.old_origin);//ERASER + + G_RunEntity (ent); + } + + // see if it is time to end a deathmatch + CheckDMRules (); + + // see if needpass needs updated WF34 + CheckNeedPass (); + + // build the playerstate_t structures for all players + ClientEndServerFrames (); + + if (dedicated->value)//ERASER + OptimizeRouteCache();//ERASER + +} + + diff --git a/g_misc.c b/g_misc.c new file mode 100644 index 0000000..4494edc --- /dev/null +++ b/g_misc.c @@ -0,0 +1,2023 @@ +// g_misc.c + +#include "g_local.h" +#include "p_trail.h"//ERASER +qboolean PassTeamCheck(edict_t *ent, edict_t *other); + + +/*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; // allways 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; + //freezer additions +// gib->s.effects |= EF_GIB;//acrid 3/99 orig + gib->s.effects = self->s.effects |= EF_GIB;//acrid 3/99 set ef to body ef + gib->s.renderfx = self->s.renderfx;//acrid 3/99 set fx to body fx + + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + //WF JR Water FLoating stuff + gib->CanFloat=1; + gib->waterlevel = 0; + gib->HitTopOfWater = 0; + gib->turrettime=-1; + gib->watertype=0; + //WF JR End Water Floating Stuff + + 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()*5; + + 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; + //WF JR Water FLoating stuff + self->CanFloat=1; + self->waterlevel = 0; + self->HitTopOfWater = 0; + self->turrettime=-1; + self->watertype=0; + //WF JR End Water Floating Stuff + + 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()*500; + + self->think = G_FreeEdict; + self->nextthink = level.time + 10 + random()*4; + + gi.linkentity (self); +} + + +void ThrowClientHead (edict_t *self, int damage) +{ + vec3_t vd; + char *gibname; + +if (!self->bot_client)//ERASER + 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;//acrid 3/99 fixme added | for freeze code//looks like a old mistake + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + //WF JR Water FLoating stuff + self->CanFloat=1; + self->waterlevel = 0; + self->HitTopOfWater = 0; + self->turrettime=-1; + self->watertype=0; + //WF JR End Water Floating Stuff + + self->movetype = MOVETYPE_BOUNCE; + VelocityForDamage (damage, vd); + VectorAdd (self->velocity, vd, self->velocity); + ClipGibVelocity (self); + + 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; + } + else + { + self->think = NULL; + self->nextthink = 0; + } + + 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) +{ +//ZOID + //flags are important + if (strcmp(self->classname, "item_flag_team1") == 0) { + CTFResetFlag(CTF_TEAM1); // this will free self! + my_bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + return; + } + if (strcmp(self->classname, "item_flag_team2") == 0) { + CTFResetFlag(CTF_TEAM2); // this will free self! + my_bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + return; + } + // techs are important too + if (self->item && (self->item->flags & IT_TECH)) { + CTFRespawnTech(self); // this frees self! + return; + } +//ZOID + + 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->s.event = EV_OTHER_TELEPORT; + } + + 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 +*/ +static int robotron[4]; + +void TH_viewthing(edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 7; +// ent->s.frame = (ent->s.frame + 1) % 9; + ent->nextthink = level.time + FRAMETIME; +// return; + + if (ent->spawnflags) + { + if (ent->s.frame == 0) + { + ent->spawnflags = (ent->spawnflags + 1) % 4 + 1; + ent->s.modelindex = robotron[ent->spawnflags - 1]; + } + } +} + +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/player_y/tris.md2"); + 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)//acrid lightcode 1/29/99 || deathmatch->value) + { + G_FreeEdict (self); + return; + } + + //Don't do targeted light if WF_SPECIAL_LIGHTS is not enabled + if (((int)wfflags->value & WF_SPECIAL_LIGHTS) == 0) + { + 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) +{ + +//WF - Support for LMCTF +/* + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } +*/ +//WF - Support for LMCTF + + 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); +} + + +/*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 + 15; + 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 + 15; + 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 + 15; + 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, *last_trail;//ERASER ADDED *LAST_TRAIL + int i; + + if (!other->client) + return; + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + + //See if we pass the team check WF34 + if ( PassTeamCheck(self,other) == false) return; + + +//ZOID + if(other->bot_client)//newgrap 4/99 + CTFPlayerResetGrapple(other); + else//newgrap 4/99 + CTFPlayerResetGrapple2(other);//newgrap 4/99 + +//ZOID +//ERASER START +// self->owner->nextthink = level.time + 0.2; +// dest->nextthink = level.time + 0.2; + + if ((!other->bot_client) && (bot_calc_nodes->value)) + { + vec3_t org; + + VectorCopy(dest->s.origin, org); + org[2] += 9; + + if (!self->owner->frags && (!matching_trail(org) || !(self->owner->frags = 1))) + { + NodeDebug("Dropping teleporter route..\n"); + + // drop a teleport route + PlayerTrail_Add(other, other->s.origin, NULL, false, true, NODE_TELEPORT); + last_trail = other->last_trail_dropped; + } + else // look for a node to use for future checking + { + int i; + + if ((i = ClosestNodeToEnt(other, false, true)) > -1) + { + other->last_trail_dropped = trail[i]; + } + } + } +//ERASER END + // 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); + + //newgrap 4/99 + if (Is_Grappling(other->client)) + { + CTFPlayerResetGrapple2(other->client->ctf_grapple); + } + + gi.linkentity (other); + //ERASER START + if (!other->bot_client && (bot_calc_nodes->value)) + { + if (!self->owner->frags) // only add one route per teleporter + { + int i; + + self->owner->frags = 1; + + // drop a teleport dest node + PlayerTrail_Add(other, other->s.origin, NULL, false, false, NODE_NORMAL); + + // add the route + for (i=0; ipaths[i] == -1) + break; + + if (i == MAX_PATHS) + { +// gi.dprintf("WARNING: Unable to add teleport route, MAX_PATHS reached\n"); + i--; // just use the last spot + } + + last_trail->paths[i] = other->last_trail_dropped->trail_index; + last_trail->flags = TRAIL_TELEPORT_SOURCE; + + last_trail->routes->route_dist[other->last_trail_dropped->trail_index] = 0; + last_trail->routes->route_path[other->last_trail_dropped->trail_index] = (short int) other->last_trail_dropped->trail_index; + + CalcRoutes(other->last_trail_dropped->trail_index); + CalcRoutes(last_trail->trail_index); + } + } + + // Bots should check for a new node after teleporting + if (other->bot_client) + other->goalentity = NULL; +} +//ERASER END + +/*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.dprintf("SP_misc_teleporter. = %d\n",ent->wf_team); + + 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; + trig->wf_team = ent->wf_team;//WF34 + VectorCopy (ent->s.origin, trig->s.origin); + VectorSet (trig->mins, -8, -8, 8); + VectorSet (trig->maxs, 8, 8, 24); + gi.linkentity (trig); + + ent->frags = 0;//ERASER +} + +/*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) +{ + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 0; + ent->solid = SOLID_BBOX; +// ent->s.effects |= EF_FLIES; + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); +} + diff --git a/g_monster.c b/g_monster.c new file mode 100644 index 0000000..7c83645 --- /dev/null +++ b/g_monster.c @@ -0,0 +1,943 @@ +#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, int mod)//ERASER ADDED INT MOD +{ + fire_bullet (self, start, dir, damage, kick, hspread, vspread, mod);//WF24 USES MOD_UNKNOWN fixme +//ERASER START + if (self->map && (flashtype != MZ2_ACTOR_MACHINEGUN_1)) // bot + gi.WriteByte (svc_muzzleflash); + else +//ERASER END + 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, int mod)//ERASER ADDED INT MOD +{ + fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, mod);//WF24 USES MON_UNKNOWN fixme + + if (self->map) // bot + gi.WriteByte (svc_muzzleflash); + else + 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); +//ERASER START + if (self->map) // bot + gi.WriteByte (svc_muzzleflash); + else +//ERASER END + 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); +//ERASER START + if (self->map) // bot + gi.WriteByte (svc_muzzleflash); + else +//ERASER END + 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, MOD_ROCKET);//fixme 34 has MOD_ROCKET +//ERASER START + if (self->map) // bot + gi.WriteByte (svc_muzzleflash); + else +//ERASER END + 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, MOD_RAILGUN); +//ERASER START + if (self->map) // bot + gi.WriteByte (svc_muzzleflash); + else +//ERASER END + 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); +//ERASER START + if (self->map) // bot + gi.WriteByte (svc_muzzleflash); + else +//ERASER END + 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; +} + +int CanJump(edict_t *ent);//ERASER + +void M_CheckGround (edict_t *ent) +{ + vec3_t point; + trace_t trace; + +//WF24 S + if (ent->flags & (FL_SWIM|FL_FLY)) + return; +//WF24E +//ERASER START + if (ent->health <= 0) + return; +//ERASER END + if (ent->velocity[2] > 210)//WF24-34 USES 100 FIXME + { + 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] - 1;//WF24-34 USES - 0.25;FIXME + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_PLAYERSOLID);//WF24 USES MONSTERSOLID +//ERASER START + if ((trace.startsolid) && !(trace.ent->client) && !(trace.ent->health <= 0)) + { // we're stuck + // find a safe position? +// int x, y; + qboolean safe=false; + + ent->maxs[2] = 4; // duck + ent->crouch_attack_time = level.time + 2; +/* + for (x=-16; x<=16; x+=8) + for (y=-16; y<=16; y+=8) + { + if (!x && !y) + continue; + + point[0] = ent->s.origin[0] + x; + point[1] = ent->s.origin[1] + y; + point[2] = ent->s.origin[2]; + + trace = gi.trace(point, ent->mins, ent->maxs, point, ent, MASK_PLAYERSOLID); + + if (!trace.startsolid) + { + safe = true; + goto done; + } + } +*/ + // still stuck, try moving up + ent->s.origin[2] += 26; + + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2]; + + trace = gi.trace(point, ent->mins, ent->maxs, point, ent, MASK_PLAYERSOLID); + + if (!trace.startsolid) + { + safe = true; + goto done; + } + + ent->s.origin[2] -= 26; + +done: + + if (!safe) + { + if (ent->maxs[2] < 32) + { + T_Damage(ent, ent, ent, VEC_ORIGIN, ent->s.origin, VEC_ORIGIN, 100000, 0, DAMAGE_NO_PROTECTION, MOD_SUICIDE); + } + else // try ducking + { + ent->maxs[2] = 4; + ent->crouch_attack_time = level.time + 2; + } + } + + return; + } +//ERASER END + // check steepness//WF24-34 DIF HERE FIXME? + if ( (trace.fraction < 1) && (trace.ent == world) && (trace.plane.normal[2] <= 0.4)) + { + + VectorScale(trace.plane.normal, 300, ent->velocity);//E + VectorCopy(ent->velocity, ent->jump_velocity);//E + +//ACO if (CanJump(ent)) +//ACO ent->s.origin[2] += 1; + + ent->groundentity = NULL; + gi.linkentity(ent);//E + + return; + + } + + if (trace.fraction < 1) + ent->groundentity = trace.ent; + else + ent->groundentity = NULL; + +} + +//WF24 MUCH DIFFEENT ABOVE FIXME ACRID +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) +{ + //acrid 3/99 + if (ent->bot_client && ent->frozen)//white shell botfreeze + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + return; + } + 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); +} +//ERASER START +extern edict_t *current_player; +extern gclient_t *current_client; +void P_WorldEffects (void); +//ERASER END +void ClientUserinfoChanged (edict_t *ent, char *userinfo);//botfreeze 3/99 +//WF24-34 IS ALOT DIFFERENT BELOW COPY OLD THINK AND RENAME FOR DECOYS +void monster_think (edict_t *self) +{ +// int playernum; +// char userinfo[MAX_INFO_STRING];//acrid 3/99 freeze + + // Yes, this is a hack. This is the result of starting such a project + // without knowing what anything does. Idealistically, bot's should have + // their own totally unique think, and shouldn't interfer with the normal + // monster code, but the Eraser started out as a normal monster, so I had + // to use this code. It is possible to put back the original code and + // use a check for self->bot_client to disperse bot's and normal monsters. + // That way, a coop bot would be possible (but not probable, given the + // amount of work required). + + if (!self->bot_client) + { + gi.error("\nCannot play Eraser in single player mode.\n\nType \"deathmatch 1\" to play the Eraser Bot.\n\n"); + return; + } +//acrid 3/99 + if (self->bot_client && self->frozen) + { + if( level.time < self->frozentime ) + { + + } + else { +// playernum = self - g_edicts - 1; +// strcpy( userinfo, self->client->pers.userinfo ); + self->frozen = 0; +// Info_SetValueForKey( userinfo, "skin", self->oldskin ); +// ClientUserinfoChanged( self, userinfo ); + } + } + if (self->last_think_time == level.time) + return; + + if (self->client->showscores) // must be in intermission + return; + if (level.intermissiontime) + return; // already activated + + bot_frametime = 0.1; + + // check X/Y jumping velocity, make sure we continue moving forwards when obstructed by a stair + // or other solids before landing + if (!self->groundentity && (self->waterlevel < 2)) + { +// trace_t tr; +// vec3_t dest; + +// if (self->jump_velocity[0] && self->jump_velocity[1]) +// { + self->velocity[0] = self->jump_velocity[0]; + self->velocity[1] = self->jump_velocity[1]; +// } +/* + VectorCopy(self->s.origin, dest); + dest[2] -= 8; + + tr = gi.trace(self->s.origin, self->mins, self->maxs, dest, self, MASK_PLAYERSOLID); + + // only really inair if way above ground + if (tr.fraction == 1) + { + self->last_inair = level.time; + } +*/ + self->last_inair = level.time; + + if (!self->client->ctf_grapple || (self->client->ctf_grapplestate <= CTF_GRAPPLE_STATE_FLY)) + { + // this is some really ugly hacks to fix in-air velocity stuff + if (self->velocity[2] > self->wait) + self->wait = self->velocity[2]; + + if ((self->velocity[2] > -300) && (self->velocity[2] < 0)) + self->velocity[2] = self->wait - sv_gravity->value*FRAMETIME; + } + } + else + { + if (self->jump_velocity[0] || self->jump_velocity[1]) + { + self->jump_velocity[0] = self->jump_velocity[1] = 0;; + } + + if (!self->last_inair || (self->last_inair > level.time)) + self->last_inair = 0; + else // make sure we remove it next frame + self->last_inair = level.time + 1; + } + + self->wait = self->velocity[2]; + + self->monsterinfo.run(self); + self->last_think_time = level.time; + +// if (self->linkcount != self->monsterinfo.linkcount) +// { +// self->monsterinfo.linkcount = self->linkcount; + if (!self->client->ctf_grapple) + M_CheckGround (self); +// } + M_CatagorizePosition (self); + + current_player = self; + current_client = self->client; + + P_WorldEffects (); + M_SetEffects (self); + + self->nextthink = level.time + FRAMETIME; +//self->nextthink = -1; +} + +//WF24 IS ALOT DIFFFERENT ABOVE +/* +================ +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); +} + + +//============================================================================ + +void monster_start (edict_t *self) +{ +/*WF24 USES TOO BUT ITS ACO THERE TOO + if ((deathmatch->value || nomonsters->value) && (!strcmp(self->classname, "bot"))) + { + 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;//WF24 USES + self->max_health = self->health; + self->clipmask = MASK_MONSTERSOLID; + +//WF - skin is set in decoy code +// self->s.skinnum = 0; +//WF + + 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); + +/*//WF24 USES THIS + 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; +*///WF24 USES THIS +} + +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)//FIXME ACRID decoy problem +{ + 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..5f67a25 --- /dev/null +++ b/g_phys.c @@ -0,0 +1,1086 @@ +// g_phys.c + +#include "g_local.h" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + + +/* +============ +SV_TestEntityPosition + +============ +*/ +edict_t *SV_TestEntityPosition (edict_t *ent) +{ + trace_t trace; + int mask; + + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask); + + if (trace.startsolid) + return g_edicts; + + return NULL; +} + + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (edict_t *ent) +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (ent->velocity[i] > sv_maxvelocity->value) + ent->velocity[i] = sv_maxvelocity->value; + else if (ent->velocity[i] < -sv_maxvelocity->value) + ent->velocity[i] = -sv_maxvelocity->value; + } +} + +/* +============= +SV_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +qboolean SV_RunThink (edict_t *ent) +{ + float thinktime; + + thinktime = ent->nextthink; + if (thinktime <= 0) + return true; + if (thinktime > level.time+0.001) + return true; + + ent->nextthink = 0; + if (!ent->think) + gi.error ("NULL ent->think"); + ent->think (ent); + + return false; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void SV_Impact (edict_t *e1, trace_t *trace) +{ + edict_t *e2; +// cplane_t backplane; + + e2 = trace->ent; + + if (e1->touch && e1->solid != SOLID_NOT) + e1->touch (e1, e2, &trace->plane, trace->surface); + + if (e2->touch && e2->solid != SOLID_NOT) + e2->touch (e2, e1, NULL, NULL); +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + int i, blocked; + + blocked = 0; + if (normal[2] > 0) + blocked |= 1; // floor + if (!normal[2]) + blocked |= 2; // step + + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + return blocked; +} + + +/* WF24 S +================== +ReduceVelocity + +Reduce the velocity by a multiplier +Example: multiplier of 0.9 will reduce velocity by 10% + +================== +*/ +#define STOP_EPSILON 0.1 + +void ReduceVelocity (vec3_t in, vec3_t out, float multiplier) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + out[i] = in[i] * multiplier; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } +} +//WF24 E + +/* +============ +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, old_org;//ERASER , old_org; + 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); + VectorCopy (ent->s.origin, old_org);//ERASER + numplanes = 0; + + time_left = time; + + ent->groundentity = NULL; + for (bumpcount=0 ; bumpcounts.origin[i] + time_left * ent->velocity[i]; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask); + + if (trace.allsolid) + { // entity is trapped in another solid +// VectorCopy (vec3_origin, ent->velocity);//WF24 USES + ent->velocity[0] = ent->jump_velocity[0] = 0;//ERASER + ent->velocity[1] = ent->jump_velocity[1] = 0;//ERASER + 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 + } +//ERASER START + else if (trace.plane.normal[2] < 0.1) // hit ceiling, go back to old position + { + VectorCopy(old_org, ent->s.origin); + + ent->velocity[0] = 0; + ent->velocity[1] = 0; + + if (ent->velocity[2] > 0) + ent->velocity[2] = 0; + + return 3; + } +//ERASER END +// +// 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); + VectorCopy (vec3_origin, ent->jump_velocity);//ERASER + 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; + + trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask); + + VectorCopy (trace.endpos, ent->s.origin); + gi.linkentity (ent); + + if (trace.fraction != 1.0) + { + SV_Impact (ent, &trace); + + // if the pushed entity went away and the pusher is still there + if (!trace.ent->inuse && ent->inuse) + { + // move the pusher back and try again + VectorCopy (start, ent->s.origin); + gi.linkentity (ent); + goto retry; + } + } + + if (ent->inuse) + G_TouchTriggers (ent); + + return trace; +} + + +typedef struct +{ + edict_t *ent; + vec3_t origin; + vec3_t angles; + float deltayaw; +} pushed_t; +pushed_t pushed[MAX_EDICTS], *pushed_p; + +edict_t *obstacle; + +/* +============ +SV_Push + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +============ +*/ +qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove) +{ + int i, e; + edict_t *check, *block; + vec3_t mins, maxs; + pushed_t *p; + vec3_t org, org2, move2, forward, right, up; + + // clamp the move to 1/8 units, so the position will + // be accurate for client side prediction + for (i=0 ; i<3 ; i++) + { + float temp; + temp = move[i]*8.0; + if (temp > 0.0) + temp += 0.5; + else + temp -= 0.5; + move[i] = 0.125 * (int)temp; + } + + // find the bounding box + for (i=0 ; i<3 ; i++) + { + mins[i] = pusher->absmin[i] + move[i]; + maxs[i] = pusher->absmax[i] + move[i]; + } + +// we need this for pushing things later + VectorSubtract (vec3_origin, amove, org); + AngleVectors (org, forward, right, up); + +// save the pusher's original position + pushed_p->ent = pusher; + VectorCopy (pusher->s.origin, pushed_p->origin); + VectorCopy (pusher->s.angles, pushed_p->angles); + if (pusher->client) + pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW]; + pushed_p++; + +// move the pusher to it's final position + VectorAdd (pusher->s.origin, move, pusher->s.origin); + VectorAdd (pusher->s.angles, amove, pusher->s.angles); + gi.linkentity (pusher); + +// see if any solid entities are inside the final position + check = g_edicts+1; + for (e = 1; e < globals.num_edicts; e++, check++) + { + if (!check->inuse) + continue; + if (check->movetype == MOVETYPE_PUSH + || check->movetype == MOVETYPE_STOP + || check->movetype == MOVETYPE_NONE + || check->movetype == MOVETYPE_NOCLIP) + continue; + + if (!check->area.prev) + continue; // not linked in anywhere + + // if the entity is standing on the pusher, it will definitely be moved + if (check->groundentity != pusher) + { + // see if the ent needs to be tested + if ( check->absmin[0] >= maxs[0] + || check->absmin[1] >= maxs[1] + || check->absmin[2] >= maxs[2] + || check->absmax[0] <= mins[0] + || check->absmax[1] <= mins[1] + || check->absmax[2] <= mins[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (!SV_TestEntityPosition (check)) + continue; + } + + if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher)) + { + // move this entity + pushed_p->ent = check; + VectorCopy (check->s.origin, pushed_p->origin); + VectorCopy (check->s.angles, pushed_p->angles); + pushed_p++; + + // try moving the contacted entity + VectorAdd (check->s.origin, move, check->s.origin); + if (check->client) + { // FIXME: doesn't rotate monsters? + check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; + } + + // figure movement due to the pusher's amove + VectorSubtract (check->s.origin, pusher->s.origin, org); + org2[0] = DotProduct (org, forward); + org2[1] = -DotProduct (org, right); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move2); + VectorAdd (check->s.origin, move2, check->s.origin); + + // may have pushed them off an edge + if (check->groundentity != pusher) + check->groundentity = NULL; + + block = SV_TestEntityPosition (check); + if (!block) + { // pushed ok + gi.linkentity (check); + // impact? + continue; + } + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // FIXME: this doesn't acount for rotation + VectorSubtract (check->s.origin, move, check->s.origin); + block = SV_TestEntityPosition (check); + if (!block) + { + pushed_p--; + continue; + } + } + + // save off the obstacle so we can call the block function + obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for (p=pushed_p-1 ; p>=pushed ; p--) + { + VectorCopy (p->origin, p->ent->s.origin); + VectorCopy (p->angles, p->ent->s.angles); + if (p->ent->client) + { + p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw; + } + gi.linkentity (p->ent); + } + return false; + } + +//FIXME: is there a better way to handle this? + // see if anything we moved has touched a trigger + for (p=pushed_p-1 ; p>=pushed ; p--) + G_TouchTriggers (p->ent); + + return true; +} + +/* +================ +SV_Physics_Pusher + +Bmodel objects don't interact with each other, but +push all box objects +================ +*/ +void SV_Physics_Pusher (edict_t *ent) +{ + vec3_t move, amove; + edict_t *part, *mv; + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out +//retry: + pushed_p = pushed; + for (part = ent ; part ; part=part->teamchain) + { + //newgrap start 4/99 + qboolean blocked = false; + + if (part->mynoise2 && part->mynoise2->think == CTFGrappleThink2) + { + edict_t *hook = part->mynoise2; + vec3_t org, org2, forward, right, up, move2; + + if (hook->enemy == NULL || hook->inuse == false) + continue; + VectorScale (part->velocity, FRAMETIME, move); + VectorScale (part->avelocity, FRAMETIME, amove); + + VectorAdd(hook->s.origin, move, hook->s.origin); + VectorAdd(hook->s.angles, amove, hook->s.angles); + // figure movement due to the pusher's amove + VectorSubtract (vec3_origin, amove, org); + AngleVectors (org, forward, right, up); + VectorSubtract (hook->s.origin, part->s.origin, org); + org2[0] = DotProduct (org, forward); + org2[1] = -DotProduct (org, right); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move2); + VectorAdd (hook->s.origin, move2, hook->s.origin); + } + //newgrap 4/99 end + + if (part->velocity[0] || part->velocity[1] || part->velocity[2] || + part->avelocity[0] || part->avelocity[1] || part->avelocity[2] + ) + { // object is moving + VectorScale (part->velocity, FRAMETIME, move); + VectorScale (part->avelocity, FRAMETIME, amove); + + if (!SV_Push (part, move, amove)) + break; // move was blocked + } + } + if (pushed_p > &pushed[MAX_EDICTS]) + gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); + + if (part) + { + // the move failed, bump all nextthink times and back out moves + for (mv = ent ; mv ; mv=mv->teamchain) + { + if (mv->nextthink > 0) + mv->nextthink += FRAMETIME; + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (part->blocked) + part->blocked (part, obstacle); +#if 0 + // if the pushed entity went away and the pusher is still there + if (!obstacle->inuse && part->inuse) + goto retry; +#endif + } + else + { + // the move succeeded, so call all think functions + for (part = ent ; part ; part=part->teamchain) + { + SV_RunThink (part); + } + } +} + +//================================================================== + +/* +============= +SV_Physics_None + +Non moving objects can only think +============= +*/ +void SV_Physics_None (edict_t *ent) +{ +// regular thinking + SV_RunThink (ent); +} + +/* +============= +SV_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip (edict_t *ent) +{ +// regular thinking + if (!SV_RunThink (ent)) + return; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin); + + gi.linkentity (ent); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss (edict_t *ent) +{ + trace_t trace; + vec3_t move; + float backoff; + edict_t *slave; + qboolean wasinwater; + qboolean isinwater; + vec3_t old_origin,offset;//WF34 OFFSET + +// 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 && !ent->CanFloat)//WF34 E-CF + return; + + VectorCopy (ent->s.origin, old_origin); + + SV_CheckVelocity (ent); + +//WF34 S //WHERES ADD GRAV check for water transition + wasinwater = (ent->watertype & MASK_WATER); + if(ent->solid==SOLID_BBOX) + { + VectorCopy(ent->s.origin,offset); + offset[2]+=ent->mins[2]; + ent->watertype = gi.pointcontents (offset); + } + else + ent->watertype = gi.pointcontents (ent->s.origin); + + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = 1; + else + ent->waterlevel = 0; + if((ent->waterlevel==1) && (ent->CanFloat==1) && (!ent->HitTopOfWater)) + { +// dprint("In water"); + ent->velocity[2]=0.1*ent->gravity*sv_gravity->value; + } + else if((wasinwater)&&(ent->CanFloat)&&(!ent->HitTopOfWater)) + { + ent->velocity[2]=0.1; + ent->HitTopOfWater=1; + if(!(ent->turrettime>0)) + { + ent->s.origin[2]-=0.5; + if(Q_stricmp (ent->classname, "depot") != 0) + ent->s.origin[2]-=0.4; + } + } + else if(!ent->HitTopOfWater) + { + if (ent->movetype != MOVETYPE_FLY + && ent->movetype != MOVETYPE_FLYMISSILE + && ent->movetype != MOVETYPE_FLOAT //WF + && ent->movetype != MOVETYPE_FLYRICOCHET) //WF + SV_AddGravity (ent); + } + else + { //1,2,3,4,5,6,7,8,9 + //0,1,2,3,4,5,6,7,8 + if((ent->turrettime>=1)&&(ent->turrettime<=9)) + { + ent->s.origin[2]-=0.1; + ent->turrettime++; + } + else + { + ent->s.origin[2]+=0.1; + if(ent->turrettime==18) + ent->turrettime=1; + else + ent->turrettime++; + } + ent->velocity[2]=0; + ent->velocity[0]=ent->velocity[0]*0.9; + ent->velocity[1]=ent->velocity[1]*0.9; + if(ent->velocity[0]<0.01) + ent->velocity[0]=0; + if(ent->velocity[1]<0.01) + ent->velocity[1]=0; + } +// 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; + +//WF24 S + //For float type, slow it down each time + if (ent->movetype == MOVETYPE_FLOAT) + ReduceVelocity (ent->velocity, ent->velocity, 0.9); + + //Only if we hit anything is trace.fraction < 1 +//WF24 E + if (trace.fraction < 1) + { + if (ent->movetype == MOVETYPE_FLYRICOCHET)//WF34 + backoff = 1.8;//WF34 + else if (ent->movetype == MOVETYPE_BOUNCE) + backoff = 1.5; + else + backoff = 1; + + ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff); + +//WF24 S: re-align the entity's angles after it bounces +// off the wall. Simply set it's angles to its velocity vector + if (ent->movetype == MOVETYPE_FLYRICOCHET) + vectoangles(ent->velocity, ent->s.angles); +//WF24 E + + // stop if on ground + +//WF: changed next line so bolts dont stop on the ground +// if (trace.plane.normal[2] > 0.7) + if (trace.plane.normal[2] > 0.7 && ent->movetype != MOVETYPE_FLYRICOCHET) + { + if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE ) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + VectorCopy (vec3_origin, ent->velocity); + VectorCopy (vec3_origin, ent->avelocity); + } + } + +// if (ent->touch) +// ent->touch (ent, trace.ent, &trace.plane, trace.surface); + } + + if(!ent->CanFloat) + { + 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; + } + +//ERASER ADDED LINES TO BELOW LINE EBC AND EG + if ((!ent->bot_client || !ent->groundentity) && (ent->velocity[2] || ent->velocity[1] || ent->velocity[0])) + { + // apply friction + // let dead monsters who aren't completely onground slide + if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) + if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) + { + vel = ent->velocity; + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); + if (speed) + { + friction = sv_friction; + + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - FRAMETIME*control*friction; + + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + vel[0] *= newspeed; + vel[1] *= newspeed; + } + } + + if (ent->svflags & SVF_MONSTER) + mask = MASK_MONSTERSOLID; + else + mask = MASK_SOLID; + SV_FlyMove (ent, FRAMETIME, mask); + + gi.linkentity (ent); + G_TouchTriggers (ent); + + if (!ent->inuse) + return; +//ERASER ADDED EM BELOW + if (!ent->map && ent->groundentity) // ent->map so that bot's don't make the landing sound + if (!wasonground) + if (hitsound) + gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0); + } + +// regular thinking + SV_RunThink (ent); +} + +//============================================================================ +/* +================ +G_RunEntity + +================ +*/ +void G_RunEntity (edict_t *ent) +{ + if (ent->prethink) + ent->prethink (ent); + + switch ( (int)ent->movetype) + { + case MOVETYPE_PUSH: + case MOVETYPE_STOP: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + SV_Physics_None (ent); + break; + case MOVETYPE_NOCLIP: + SV_Physics_Noclip (ent); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + case MOVETYPE_FLYRICOCHET: //WF24 + case MOVETYPE_FLOAT: //WF24 + SV_Physics_Toss (ent); + break; + case MOVETYPE_WALK://ERASER START + SV_RunThink (ent); + break;//ERASER END + 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..b1cb0c3 --- /dev/null +++ b/g_save.c @@ -0,0 +1,844 @@ + +#include "g_local.h" +#include "stdlog.h" // StdLog - Mark Davies + +void SetDefaultClassInfo(); +void zbotFileOpen(); +int ProcessConfigFile(); +int LoadClassInfo(char *filename); + +#define Function(f) {#f, f} + +mmove_t mmove_reloc; + +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}, + {"wf_team", FOFS(wf_team), F_INT}, //WF team ID field + {"bonustype", FOFS(bonustype), F_INT}, //WF + {"bonusvalue", FOFS(bonusvalue), F_INT}, //WF + +//3.20 + {"goalentity", FOFS(goalentity), F_EDICT, FFL_NOSPAWN}, + {"movetarget", FOFS(movetarget), F_EDICT, FFL_NOSPAWN}, + {"enemy", FOFS(enemy), F_EDICT, FFL_NOSPAWN}, + {"oldenemy", FOFS(oldenemy), F_EDICT, FFL_NOSPAWN}, + {"activator", FOFS(activator), F_EDICT, FFL_NOSPAWN}, + {"groundentity", FOFS(groundentity), F_EDICT, FFL_NOSPAWN}, + {"teamchain", FOFS(teamchain), F_EDICT, FFL_NOSPAWN}, + {"teammaster", FOFS(teammaster), F_EDICT, FFL_NOSPAWN}, + {"owner", FOFS(owner), F_EDICT, FFL_NOSPAWN}, + {"mynoise", FOFS(mynoise), F_EDICT, FFL_NOSPAWN}, + {"mynoise2", FOFS(mynoise2), F_EDICT, FFL_NOSPAWN}, + {"target_ent", FOFS(target_ent), F_EDICT, FFL_NOSPAWN}, + {"chain", FOFS(chain), F_EDICT, FFL_NOSPAWN}, + + {"prethink", FOFS(prethink), F_FUNCTION, FFL_NOSPAWN}, + {"think", FOFS(think), F_FUNCTION, FFL_NOSPAWN}, + {"blocked", FOFS(blocked), F_FUNCTION, FFL_NOSPAWN}, + {"touch", FOFS(touch), F_FUNCTION, FFL_NOSPAWN}, + {"use", FOFS(use), F_FUNCTION, FFL_NOSPAWN}, + {"pain", FOFS(pain), F_FUNCTION, FFL_NOSPAWN}, + {"die", FOFS(die), F_FUNCTION, FFL_NOSPAWN}, + + {"stand", FOFS(monsterinfo.stand), F_FUNCTION, FFL_NOSPAWN}, + {"idle", FOFS(monsterinfo.idle), F_FUNCTION, FFL_NOSPAWN}, + {"search", FOFS(monsterinfo.search), F_FUNCTION, FFL_NOSPAWN}, + {"walk", FOFS(monsterinfo.walk), F_FUNCTION, FFL_NOSPAWN}, + {"run", FOFS(monsterinfo.run), F_FUNCTION, FFL_NOSPAWN}, + {"dodge", FOFS(monsterinfo.dodge), F_FUNCTION, FFL_NOSPAWN}, + {"attack", FOFS(monsterinfo.attack), F_FUNCTION, FFL_NOSPAWN}, + {"melee", FOFS(monsterinfo.melee), F_FUNCTION, FFL_NOSPAWN}, + {"sight", FOFS(monsterinfo.sight), F_FUNCTION, FFL_NOSPAWN}, + {"checkattack", FOFS(monsterinfo.checkattack), F_FUNCTION, FFL_NOSPAWN}, + {"currentmove", FOFS(monsterinfo.currentmove), F_MMOVE, FFL_NOSPAWN}, + + {"endfunc", FOFS(moveinfo.endfunc), F_FUNCTION, FFL_NOSPAWN}, + +//3.20 + + // temp spawn vars -- only valid when the spawn function is called + {"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP}, + {"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP}, + {"height", STOFS(height), F_INT, FFL_SPAWNTEMP}, + {"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP}, + {"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP}, + {"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP}, + +//need for item field in edict struct, FFL_SPAWNTEMP item will be skipped on saves + {"item", FOFS(item), F_ITEM}, + + {"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP}, + {"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP}, + {"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP}, + {"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP}, + {"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP}, + + {0, 0, 0, 0} +}; + +field_t levelfields[] = +{ + {"changemap", LLOFS(changemap), F_LSTRING}, + + {"sight_client", LLOFS(sight_client), F_EDICT}, + {"sight_entity", LLOFS(sight_entity), F_EDICT}, + {"sound_entity", LLOFS(sound_entity), F_EDICT}, + {"sound2_entity", LLOFS(sound2_entity), F_EDICT}, + + {NULL, 0, F_INT} +}; + +field_t clientfields[] = +{ + {"pers.weapon", CLOFS(pers.weapon), F_ITEM}, + {"pers.lastweapon", CLOFS(pers.lastweapon), F_ITEM}, + {"newweapon", CLOFS(newweapon), F_ITEM}, + + {NULL, 0, F_INT} +}; + +qboolean default_exec = false;//ERASER +/* +============ +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) +{ + int retval; +// cvar_t *tempvar; + + gi.dprintf ("==== InitGame ====\n"); + + gun_x = gi.cvar ("gun_x", "0", 0); + gun_y = gi.cvar ("gun_y", "0", 0); + gun_z = gi.cvar ("gun_z", "0", 0); + + //WF - Set up classes + TOTALDROPPEDAMMO = 0; + TOTALWORLDFLAMES = 0; + //WF + + //FIXME: sv_ prefix is wrong for these + sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0); + sv_rollangle = gi.cvar ("sv_rollangle", "2", 0); + sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0); + sv_gravity = gi.cvar ("sv_gravity", "800", 0); + + // noset vars + dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET); + + // latched vars + sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH); + gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH); + gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH); + + maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); + deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH); + coop = gi.cvar ("coop", "0", CVAR_LATCH); + skill = gi.cvar ("skill", "1", CVAR_LATCH); + maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH); + +//ZOID +//This game.dll only supports deathmatch + if (!deathmatch->value) { + gi.dprintf("Forcing deathmatch."); + gi.cvar_set("deathmatch", "1"); + } + //force coop off + if (coop->value) + gi.cvar_set("coop", "0"); +//ZOID + + // change anytime vars + dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO|CVAR_ARCHIVE);//ERASER ADDED |CVAR_ARCHIVE +// dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO); + fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO); + timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO); + + needpass = gi.cvar ("needpass", "0", CVAR_SERVERINFO); + +//WF + gamedir = gi.cvar ("gamedir", "", CVAR_SERVERINFO); + wfflags = gi.cvar ("wfflags", "0", CVAR_SERVERINFO); + wfconfig = gi.cvar ("wfconfig", "wfserver.ini", CVAR_SERVERINFO); + filterban = gi.cvar ("filterban", "1", CVAR_SERVERINFO); +//WF + +//ZOID + capturelimit = gi.cvar ("capturelimit", "0", CVAR_SERVERINFO); +//ZOID + wfpassword = gi.cvar ("password", "", CVAR_USERINFO); + + 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); + +//ERASER START + // bot commands + bot_num = gi.cvar ("bot_num", "0", 0); + bot_name = gi.cvar ("bot_name", "", 0); + bot_allow_client_commands = gi.cvar ("bot_allow_client_commands", "0", CVAR_ARCHIVE); + bot_free_clients = gi.cvar ("bot_free_clients", "0", CVAR_ARCHIVE); + bot_debug = gi.cvar ("bot_debug", "0", 0); + bot_show_connect_info = gi.cvar ("bot_show_connect_info", "1", CVAR_ARCHIVE); + bot_calc_nodes = gi.cvar ("bot_calc_nodes", "1", 0); + bot_debug_nodes = gi.cvar ("bot_debug_nodes", "0", 0); + bot_auto_skill = gi.cvar ("bot_auto_skill", "0", CVAR_ARCHIVE); + bot_drop = gi.cvar ("bot_drop", "", 0); + bot_chat = gi.cvar ("bot_chat", "1", CVAR_ARCHIVE); + bot_optimize = gi.cvar ("bot_optimize", "1200", 0); + bot_tarzan = gi.cvar ("bot_tarzan", "0", CVAR_ARCHIVE); + bot_melee = gi.cvar ("bot_melee", "0", CVAR_ARCHIVE);//acrid added + players_per_team = gi.cvar ("players_per_team", "4", CVAR_LATCH|CVAR_ARCHIVE); + addteam = gi.cvar ("addteam", "", 0); + teamplay = gi.cvar ("teamplay", "0", CVAR_LATCH|CVAR_ARCHIVE); + ctf_auto_teams = gi.cvar ("ctf_auto_teams", "0", 0); + ctf_special_teams = gi.cvar ("ctf_special_teams", "0", 0); //~~JLH + ctf_humanonly_teams = gi.cvar ("ctf_humanonly_teams", "0", 0); //~~JLH + // Diable ctf_auto_teams if ctf_special_teams is enabled //~~JLH + if ( ctf_auto_teams->value > 0 && ctf_special_teams->value > 0 ) // ~~JLH + gi.cvar_set ("ctf_auto_teams", "0"); +// grapple = gi.cvar ("grapple", "0", CVAR_LATCH);//FIXME ACRID???? + +//ERASER END + + // items + InitItems (); + + Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), ""); + Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), ""); + + // initialize all entities for this game + game.maxentities = maxentities->value; + g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME); + globals.edicts = g_edicts; + globals.max_edicts = game.maxentities; + + // initialize all clients for this game + game.maxclients = maxclients->value; + game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME); + globals.num_edicts = game.maxclients+1; + +//ZOID + CTFInit(); +//ZOID + +//WF + //Read IP ban list + ReadBans(); + + ProcessConfigFile(); + + //Now set the server variables we want displayed + if ((int)wfflags->value & WF_ZBOT_DETECT) + { + gi.cvar ("ZBotCheck", "1", CVAR_SERVERINFO); + } + gi.cvar ("mock", "1", CVAR_SERVERINFO); + gi.cvar ("version", WF_VERSION, CVAR_SERVERINFO); +// gi.cvar_set("features", "ZbotKick"); + + //Was there a class name defined? + if (wf_game.classdef_name[0] == '\0') //nope + { + retval = LoadClassInfo("team10.class"); + + if (retval == 0) + { + gi.dprintf("ERROR: Could not load class file in quake2 directory\n"); + } + } + + zbotFileOpen(); +//WF +} + +//========================================================= + +void WriteField1 (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + if (field->flags & FFL_SPAWNTEMP) + return; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + case F_GSTRING: + if ( *(char **)p ) + len = strlen(*(char **)p) + 1; + else + len = 0; + *(int *)p = len; + break; + case F_EDICT: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(edict_t **)p - g_edicts; + *(int *)p = index; + break; + case F_CLIENT: + if ( *(gclient_t **)p == NULL) + index = -1; + else + index = *(gclient_t **)p - game.clients; + *(int *)p = index; + break; + case F_ITEM: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(gitem_t **)p - itemlist; + *(int *)p = index; + break; + + //relative to code segment + case F_FUNCTION: + if (*(byte **)p == NULL) + index = 0; + else + index = *(byte **)p - ((byte *)InitGame); + *(int *)p = index; + break; + + //relative to data segment + case F_MMOVE: + if (*(byte **)p == NULL) + index = 0; + else + index = *(byte **)p - (byte *)&mmove_reloc; + *(int *)p = index; + break; + + default: + gi.error ("WriteEdict: unknown field type"); + } +} + +void WriteField2 (FILE *f, field_t *field, byte *base) +{ + int len; + void *p; + + if (field->flags & FFL_SPAWNTEMP) + return; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_LSTRING: + if ( *(char **)p ) + { + len = strlen(*(char **)p) + 1; + fwrite (*(char **)p, len, 1, f); + } + break; + } +} + +void ReadField (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + if (field->flags & FFL_SPAWNTEMP) + return; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + len = *(int *)p; + if (!len) + *(char **)p = NULL; + else + { + *(char **)p = gi.TagMalloc (len, TAG_LEVEL); + fread (*(char **)p, len, 1, f); + } + break; + case F_EDICT: + index = *(int *)p; + if ( index == -1 ) + *(edict_t **)p = NULL; + else + *(edict_t **)p = &g_edicts[index]; + break; + case F_CLIENT: + index = *(int *)p; + if ( index == -1 ) + *(gclient_t **)p = NULL; + else + *(gclient_t **)p = &game.clients[index]; + break; + case F_ITEM: + index = *(int *)p; + if ( index == -1 ) + *(gitem_t **)p = NULL; + else + *(gitem_t **)p = &itemlist[index]; + break; + + //relative to code segment + case F_FUNCTION: + index = *(int *)p; + if ( index == 0 ) + *(byte **)p = NULL; + else + *(byte **)p = ((byte *)InitGame) + index; + break; + + //relative to data segment + case F_MMOVE: + index = *(int *)p; + if (index == 0) + *(byte **)p = NULL; + else + *(byte **)p = (byte *)&mmove_reloc + index; + break; + + default: + gi.error ("ReadEdict: unknown field type"); + } +} + +//========================================================= + +/* +============== +WriteClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteClient (FILE *f, gclient_t *client) +{ + field_t *field; + gclient_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = *client; + + // change the pointers to lengths or indexes + for (field=clientfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=clientfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)client); + } +} + +/* +============== +ReadClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadClient (FILE *f, gclient_t *client) +{ + field_t *field; + + fread (client, sizeof(*client), 1, f); + + for (field=clientfields ; field->name ; field++) + { + ReadField (f, field, (byte *)client); + } +} + +/* +============ +WriteGame + +This will be called whenever the game goes to a new level, +and when the user explicitly saves the game. + +Game information include cross level data, like multi level +triggers, help computer info, and all client states. + +A single player death will automatically restore from the +last save position. +============ +*/ +void WriteGame (char *filename, qboolean autosave) +{ + FILE *f; + int i; + char str[16]; + + if (!autosave) + SaveClientData (); + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + memset (str, 0, sizeof(str)); + strcpy (str, __DATE__); + fwrite (str, sizeof(str), 1, f); + + game.autosaved = autosave; + fwrite (&game, sizeof(game), 1, f); + game.autosaved = false; + + for (i=0 ; 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=fields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)ent); + } + +} + +/* +============== +WriteLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteLevelLocals (FILE *f) +{ + field_t *field; + level_locals_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = level; + + // change the pointers to lengths or indexes + for (field=levelfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=levelfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)&level); + } +} + + +/* +============== +ReadEdict + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadEdict (FILE *f, edict_t *ent) +{ + field_t *field; + + fread (ent, sizeof(*ent), 1, f); + + for (field=fields ; field->name ; field++) + { + ReadField (f, field, (byte *)ent); + } +} + +/* +============== +ReadLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadLevelLocals (FILE *f) +{ + field_t *field; + + fread (&level, sizeof(level), 1, f); + + for (field=levelfields ; field->name ; field++) + { + ReadField (f, field, (byte *)&level); + } +} + +/* +================= +WriteLevel + +================= +*/ +void WriteLevel (char *filename) +{ + int i; + edict_t *ent; + FILE *f; + void *base; + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // write out edict size for checking + i = sizeof(edict_t); + fwrite (&i, sizeof(i), 1, f); + + // write out a function pointer for checking + base = (void *)InitGame; + fwrite (&base, sizeof(base), 1, f); + + // write out level_locals_t + WriteLevelLocals (f); + + // write out all the entities + for (i=0 ; iinuse) + continue; + fwrite (&i, sizeof(i), 1, f); + WriteEdict (f, ent); + } + i = -1; + fwrite (&i, sizeof(i), 1, f); + + fclose (f); +} + + +/* +================= +ReadLevel + +SpawnEntities will allready have been called on the +level the same way it was when the level was saved. + +That is necessary to get the baselines +set up identically. + +The server will have cleared all of the world links before +calling ReadLevel. + +No clients are connected yet. +================= +*/ +void ReadLevel (char *filename) +{ + int entnum; + FILE *f; + int i; + void *base; + edict_t *ent; + + f = fopen (filename, "rb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // free any dynamic memory allocated by loading the level + // base state + gi.FreeTags (TAG_LEVEL); + + // wipe all the entities + memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0])); + globals.num_edicts = maxclients->value+1; + + // check edict size + fread (&i, sizeof(i), 1, f); + if (i != sizeof(edict_t)) + { + fclose (f); + gi.error ("ReadLevel: mismatched edict size"); + } + + // check function pointer base address + fread (&base, sizeof(base), 1, f); +#ifdef _WIN32 + if (base != (void *)InitGame) + { + fclose (f); + gi.error ("ReadLevel: function pointers have moved"); + } +#else + gi.dprintf("Function offsets %d\n", ((byte *)base) - ((byte *)InitGame)); +#endif + + // load the level locals + ReadLevelLocals (f); + + // load all the entities + while (1) + { + if (fread (&entnum, sizeof(entnum), 1, f) != 1) + { + fclose (f); + gi.error ("ReadLevel: failed to read entnum"); + } + if (entnum == -1) + break; + if (entnum >= globals.num_edicts) + globals.num_edicts = entnum+1; + + ent = &g_edicts[entnum]; + ReadEdict (f, ent); + + // let the server rebuild world links for this ent + memset (&ent->area, 0, sizeof(ent->area)); + gi.linkentity (ent); + } + + fclose (f); + + // 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..43b8e10 --- /dev/null +++ b/g_spawn.c @@ -0,0 +1,1264 @@ + +#include "g_local.h" +#include "stdlog.h" //WF24 StdLog +#include "bot_procs.h"//ERASER +#include "p_trail.h"//ERASER + +char *LoadEntFile(char *mapname, char *entities);//newent 5/99 +void Create_All_Home_Bases (void);//WF34 + +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); +//WF34 -support for lmctf +void SP_info_position (edict_t *self); +void SP_item_flagreturn (edict_t *self); +//WF34 +void SP_rotating_light (edict_t *self);//Acrid lightcode 1/29/99 + + +spawn_t spawns[] = { + {"item_health", SP_item_health}, + {"item_health_small", SP_item_health_small}, + {"item_health_large", SP_item_health_large}, + {"item_health_mega", SP_item_health_mega}, + + {"info_player_start", SP_info_player_start}, + {"info_player_deathmatch", SP_info_player_deathmatch}, + {"info_player_coop", SP_info_player_coop}, + {"info_player_intermission", SP_info_player_intermission}, +//ZOID + {"info_player_team1", SP_info_player_team1}, + {"info_player_team2", SP_info_player_team2}, +//ZOID + +//WF34 - Support for LMCTF maps + {"info_player_red", SP_info_player_team1}, + {"info_player_blue", SP_info_player_team2}, + {"info_position", SP_info_position}, +// more WF + {"item_flagreturn_team1", SP_item_flagreturn}, + {"item_flagreturn_team2", SP_item_flagreturn}, +//WF34 + {"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}, + {"rotating_light", SP_rotating_light},//Acrid lightcode 1/29/99 + + {"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}, +#if 0 //WF24 remove monster code + {"target_actor", SP_target_actor}, +#endif + {"target_lightramp", SP_target_lightramp}, + {"target_earthquake", SP_target_earthquake}, + {"target_character", SP_target_character}, + {"target_string", SP_target_string}, + + {"worldspawn", SP_worldspawn}, + {"viewthing", SP_viewthing}, + + {"light", SP_light}, + {"light_mine1", SP_light_mine1}, + {"light_mine2", SP_light_mine2}, + {"info_null", SP_info_null}, + {"func_group", SP_info_null}, + {"info_notnull", SP_info_notnull}, + {"path_corner", SP_path_corner}, + {"point_combat", SP_point_combat}, + + {"misc_explobox", SP_misc_explobox}, + {"misc_banner", SP_misc_banner}, +//ZOID + {"misc_ctf_banner", SP_misc_ctf_banner}, + {"misc_ctf_small_banner", SP_misc_ctf_small_banner}, +//ZOID + {"misc_satellite_dish", SP_misc_satellite_dish}, +#if 0 // remove monster code + {"misc_actor", SP_misc_actor}, +#endif + {"misc_gib_arm", SP_misc_gib_arm}, + {"misc_gib_leg", SP_misc_gib_leg}, + {"misc_gib_head", SP_misc_gib_head}, +#if 0 // remove monster code + {"misc_insane", SP_misc_insane}, +#endif + {"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}, +//ZOID + {"trigger_teleport", SP_trigger_teleport}, + {"info_teleport_destination", SP_info_teleport_destination}, +//ZOID + {"misc_blackhole", SP_misc_blackhole}, + {"misc_eastertank", SP_misc_eastertank}, + {"misc_easterchick", SP_misc_easterchick}, + {"misc_easterchick2", SP_misc_easterchick2}, + +#if 0 // remove monster code + {"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}, +#endif + + {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 + SpawnItem (ent, item); + return; + } + } + + // check normal spawn functions + for (s=spawns ; s->name ; s++) + { + if (!strcmp(s->name, ent->classname)) + { // found it + s->spawn (ent); + return; + } + } + gi.dprintf ("%s doesn't have a spawn function\n", ent->classname); +} + +/* +============= +ED_NewString +============= +*/ +char *ED_NewString (char *string) +{ + char *newb, *new_p; + int i,l; + + l = strlen(string) + 1; + + newb = gi.TagMalloc (l, TAG_LEVEL); + + new_p = newb; + + for (i=0 ; i< l ; i++) + { + if (string[i] == '\\' && i < l-1) + { + i++; + if (string[i] == 'n') + *new_p++ = '\n'; + else + *new_p++ = '\\'; + } + else + *new_p++ = string[i]; + } + + return newb; +} + + + + +/* +=============== +ED_ParseField + +Takes a key/value pair and sets the binary values +in an edict +=============== +*/ +void ED_ParseField (char *key, char *value, edict_t *ent) +{ + field_t *f; + byte *b; + float v; + vec3_t vec; + + for (f=fields ; f->name ; f++) + { + if (!(f->flags & FFL_NOSPAWN) && !Q_stricmp(f->name, key))//WF34 F->F & FFL + { // 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); +} +//ERASER START +void CheckNodeCalculation(edict_t *ent) +{ + if (nodes_done || (trail_head >= 500)) // assume it's done if > 500 nodes + { +// if (bot_calc_nodes->value) +// { + gi.cvar_set("bot_calc_nodes", "0"); + gi.dprintf("\nDynamic node-table generation DISABLED\n\n"); +// } + } + else + { +// if (!bot_calc_nodes->value) +// { + gi.cvar_set("bot_calc_nodes", "1"); + gi.dprintf("\nDynamic node-table generation ENABLED\n\n"); +// } + } + + G_FreeEdict(ent); +} +//ERASER END +/* +============== +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") && !Q_stricmp(ent->model, "*27")) + ent->spawnflags &= ~SPAWNFLAG_NOT_HARD; + + // remove things (except the world) from different skill levels or deathmatch + if (ent != g_edicts) + { + if (deathmatch->value) + { + if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + else + { + if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */ + ((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || + ((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || + (((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)) + ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + + ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH); + } + + ED_CallSpawn (ent); + } + PlayerTrail_Init ();//ACRID MOVED WF34 + gi.dprintf ("%i entities inhibited\n", inhibit); + +#ifdef DEBUG + i = 1; + ent = EDICT_NUM(i); + while (i < globals.num_edicts) { + if (ent->inuse != 0 || ent->inuse != 1) + Com_DPrintf("Invalid entity %d\n", i); + i++, ent++; + } +#endif + + G_FindTeams (); +//ERASER START + if (!G_Find(NULL, FOFS(classname), "item_flag_team1")) + { // not a CTF level + gi.cvar_forceset("ctf", "0"); + } + else // make sure CTF is enabled, since there is a flag + { + gi.cvar_forceset("ctf", "1"); +// gi.cvar_forceset("grapple", "1"); + + if (ctf->value) { + gi.configstring (CS_STATUSBAR, ctf_statusbar); + //precaches + //gi.imageindex("sbctf1"); + //gi.imageindex("sbctf2"); + gi.imageindex("i_ctf1"); + gi.imageindex("i_ctf2"); + gi.imageindex("i_ctf1d"); + gi.imageindex("i_ctf2d"); + gi.imageindex("i_ctf1t"); + gi.imageindex("i_ctf2t"); + gi.imageindex("i_ctfj"); + } + } +//ERASER END + + +//ZOID + CTFSetupTechSpawn(); +//ZOID + +//WF + Create_All_Home_Bases (); + + sl_Logging( &gi, "The Weapons Factory" ); // StdLog + sl_GameStart( &gi, level ); // StdLog +//WF +//ERASER START spawn an entity, that will decide whether or not to disable node calculation + ent = G_Spawn(); + ent->think = CheckNodeCalculation; + ent->nextthink = level.time + 1.5; // give the items time to set their reachable nodes + + check_nodes_done = NULL; +//ERASER 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 " +; + +char *dm_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 246 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " + +// frags +"xr -50 " +"yt 2 " +"num 3 14" +; +//ERASER START +extern qboolean respawn_flag; +extern int num_dm_spots; +//ERASER END + +/*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 + + //--------------- +//ERASER START + num_players = 0; + bot_count = 0; + bot_frametime = 0.1; // set if using distributed thinking + botdebug = 0; + spawn_bots = 0; + num_dm_spots = 0; + paused = false; + + weapons_head = health_head = bonus_head = ammo_head = NULL; +//ERASER END + // reserve some spots for dead player bodies for coop / deathmatch + InitBodyQue (); + + // set configstrings for items + SetItemNames (); + + if (st.nextmap) + strcpy (level.nextmap, st.nextmap); + + // make some data visible to the server + + if (ent->message && ent->message[0]) + { + gi.configstring (CS_NAME, ent->message); + strncpy (level.level_name, ent->message, sizeof(level.level_name)); + } + else + strncpy (level.level_name, level.mapname, sizeof(level.level_name)); + + if (st.sky && st.sky[0]) + gi.configstring (CS_SKY, st.sky); + else + gi.configstring (CS_SKY, "unit1_"); + + gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) ); + + gi.configstring (CS_SKYAXIS, va("%f %f %f", + st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) ); + + gi.configstring (CS_CDTRACK, va("%i", ent->sounds) ); + + gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) ); + + // status bar program + if (deathmatch->value) +//ZOID + if (ctf->value) { + gi.configstring (CS_STATUSBAR, ctf_statusbar); + //precaches + //gi.imageindex("sbctf1"); + //gi.imageindex("sbctf2"); + gi.imageindex("i_ctf1"); + gi.imageindex("i_ctf2"); + gi.imageindex("i_ctf1d"); + gi.imageindex("i_ctf2d"); + gi.imageindex("i_ctf1t"); + gi.imageindex("i_ctf2t"); + gi.imageindex("i_ctfj"); + } else +//ZOID + gi.configstring (CS_STATUSBAR, dm_statusbar); + else + gi.configstring (CS_STATUSBAR, single_statusbar); + + //--------------- + +//ERASER START//FIXME ACRID + // set ammo item lookups + item_shells = FindItem("shells"); + item_cells = FindItem("cells"); + item_bullets = FindItem("bullets"); + item_rockets = FindItem("rockets"); + item_slugs = FindItem("slugs"); + item_grenades = FindItem("grenades"); + + item_blaster = FindItem("Blaster"); + item_shotgun = FindItem("shotgun"); + item_supershotgun = FindItem("super shotgun"); + item_machinegun = FindItem("machinegun"); + item_chaingun = FindItem("chaingun"); + item_grenadelauncher = FindItem("grenade launcher"); + item_rocketlauncher = FindItem("rocket launcher"); + item_railgun = FindItem("railgun"); + item_hyperblaster = FindItem("hyperblaster"); + item_bfg10k = FindItem("bfg10k"); + +//ACRID TEST NEW WF WEAPONS + item_sniperrifle = FindItem("Sniper Rifle"); + item_lightninggun = FindItem("Lightning Gun"); + item_infecteddart = FindItem("Infected Dart Launcher"); + item_pulsecannon = FindItem("Pulse Cannon"); + item_needler = FindItem("Needler"); + item_flamethrower = FindItem("FlameThrower"); + item_telsacoil = FindItem("Telsa Coil"); + item_pelletrocketlauncher = FindItem("Pellet Rocket Launcher"); + item_rocketnapalmlauncher = FindItem("Rocket Napalm Launcher"); + item_rocketclusterlauncher = FindItem("Cluster Rocket Launcher"); + item_shc = FindItem("SHC Rifle"); + item_handgrenades = FindItem("Grenades"); + item_poisondart = FindItem("Poison Dart Launcher"); + item_ak47 = FindItem("AK47"); + item_pistol = FindItem("Pistol"); + item_knife = FindItem("Knife"); + //*item_sentryspot, *item_depotspot + item_armordart = FindItem("Poison Dart Launcher"); + item_stingerrocketlauncher = FindItem("Stinger Launcher"); + +//ACRID END NEW WF WEAPONS + + if (!respawn_flag) // only read in bots once, or team links for players will become corrupted + { + // read data from bots.cfg + ReadBotConfig(); + } + else if (teamplay->value) // clear the scores + { + int i; + + for (i=0; iscore = 0; + bot_teams[i]->last_grouping = 0; + } + } +//ERASER END + // help icon for statusbar + gi.imageindex ("i_help"); + level.pic_health = gi.imageindex ("i_health"); + level.pic_damage = gi.imageindex ("damage"); + level.pic_timeout = gi.imageindex ("timeout"); + gi.imageindex ("help"); + gi.imageindex ("inventory"); + gi.imageindex ("field_3"); + + //compass 5/99 + gi.imageindex ("north"); + gi.imageindex ("northwest"); + gi.imageindex ("northeast"); + gi.imageindex ("west"); + gi.imageindex ("east"); + gi.imageindex ("southwest"); + gi.imageindex ("southeast"); + gi.imageindex ("south"); + gi.imageindex ("up"); + gi.imageindex ("down"); +// gi.imageindex ("here"); + + 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"); +//ERASER START + gi.soundindex ("player/female/death1.wav"); + gi.soundindex ("player/female/death2.wav"); + gi.soundindex ("player/female/death3.wav"); + gi.soundindex ("player/female/death4.wav"); + + gi.soundindex ("player/female/fall1.wav"); + gi.soundindex ("player/female/fall2.wav"); + + gi.soundindex ("player/female/gurp1.wav"); // drowning damage + gi.soundindex ("player/female/gurp2.wav"); + + gi.soundindex ("player/female/jump1.wav"); // player jump + + gi.soundindex ("player/female/pain25_1.wav"); + gi.soundindex ("player/female/pain25_2.wav"); + gi.soundindex ("player/female/pain50_1.wav"); + gi.soundindex ("player/female/pain50_2.wav"); + gi.soundindex ("player/female/pain75_1.wav"); + gi.soundindex ("player/female/pain75_2.wav"); + gi.soundindex ("player/female/pain100_1.wav"); + gi.soundindex ("player/female/pain100_2.wav"); + + //------------------- +//ERASER END + // sexed models//WF34 S + // THIS ORDER MUST MATCH THE DEFINES IN g_local.h + // you can add more, max 15 + gi.modelindex ("#w_blaster.md2"); + gi.modelindex ("#w_shotgun.md2"); + gi.modelindex ("#w_sshotgun.md2"); + gi.modelindex ("#w_machinegun.md2"); + gi.modelindex ("#w_chaingun.md2"); + gi.modelindex ("#a_grenades.md2"); + gi.modelindex ("#w_glauncher.md2"); + gi.modelindex ("#w_rlauncher.md2"); + gi.modelindex ("#w_hyperblaster.md2"); + gi.modelindex ("#w_railgun.md2"); + gi.modelindex ("#w_bfg.md2"); + gi.modelindex ("#w_grapple.md2");//TRY THIS ERASER + //gi.modelindex ("#a_knife.md2"); + gi.modelindex ("#w_knife.md2"); +//WF34 E + //------------------- + + 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"); + +//WF24 S + //models + gi.modelindex ("wfactory/models/decoys/female/tris.md2"); + gi.modelindex ("wfactory/models/decoys/male/tris.md2"); + gi.modelindex ("wfactory/models/decoys/cyborg/tris.md2"); + gi.modelindex ("models/objects/flagbase/tris.md2"); + gi.modelindex ("models/sentry/turret1/tris.md2"); + gi.modelindex ("models/sentry/turret2/tris.md2"); + gi.modelindex ("models/sentry/turret3/tris.md2"); + gi.modelindex ("models/stand/tris.md2"); + gi.modelindex ("models/flare/tris.md2"); + gi.modelindex ("models/bolt/tris.md2"); + gi.modelindex ("models/dart/tris.md2");//WF34 S + gi.modelindex ("models/nailgun/tris.md2"); + gi.modelindex ("models/sight/tris.md2"); + gi.modelindex ("models/spike/tris.md2"); + gi.modelindex ("models/weapons/v_sniper2/tris.md2"); + gi.modelindex ("models/objects/grenade/tris.md2"); + gi.modelindex ("models/objects/grenade2/tris.md2"); + gi.modelindex ("models/objects/grenade3/tris.md2"); + gi.modelindex ("models/objects/turretg/tris.md2");//WF34 E + +//ERASER START FIXME ACRID DUEL VIEWWEP???? + // 3.15+ VWeap support +/* gi.modelindex( "#w_blaster.md2"); gi.modelindex( "#w_shotgun.md2"); + gi.modelindex( "#w_sshotgun.md2"); gi.modelindex( "#w_machinegun.md2"); + gi.modelindex( "#w_chaingun.md2"); gi.modelindex( "#a_grenades.md2"); + gi.modelindex( "#w_glauncher.md2"); gi.modelindex( "#w_rlauncher.md2"); + gi.modelindex( "#w_hyperblaster.md2"); gi.modelindex( "#w_railgun.md2"); + gi.modelindex( "#w_bfg.md2"); gi.modelindex( "#w_grapple.md2"); +*/ + //ERASER END ACRID CO +//WF34 Sounds + gi.soundindex ("cdecoyof.wav"); + gi.soundindex ("cdecoyon.wav"); + gi.soundindex ("fdecoyof.wav"); + gi.soundindex ("fdecoyon.wav"); + gi.soundindex ("mdecoyof.wav"); + gi.soundindex ("mdecoyon.wav"); + gi.soundindex ("ctf/flagcap.wav"); + gi.soundindex ("ctf/tech4.wav"); + gi.soundindex ("darthit.wav"); + gi.soundindex ("electric.wav"); + gi.soundindex ("homelock.wav"); + gi.soundindex ("jetfly.wav"); + gi.soundindex ("jumppack.wav"); + gi.soundindex ("lshoot.wav"); + gi.soundindex ("misc/keyuse.wav"); + gi.soundindex ("misc/lasfly.wav"); + gi.soundindex ("needle.wav"); + gi.soundindex ("scream.wav"); + gi.soundindex ("soldier/solatck1.wav"); + gi.soundindex ("soldier/solidle1.wav"); + gi.soundindex ("soldier/solpain1.wav"); + gi.soundindex ("soldier/solsght1.wav"); + gi.soundindex ("soldier/solsrch1.wav"); + gi.soundindex ("weapons/knifehit.wav"); + gi.soundindex ("weapons/knifescream.wav"); + gi.soundindex ("weapons/knifeswish.wav"); + gi.soundindex ("weapons/disembowel.wav"); + gi.soundindex ("weapons/rockfly.wav"); + gi.soundindex ("world/airhiss1.wav"); + gi.soundindex ("world/laser.wav"); + gi.soundindex ("world/quake.wav"); + +//WF34 E +// gi.imageindex (PIC_DOT); +// gi.imageindex (PIC_INVDOT); +// gi.imageindex (PIC_QUADDOT); +// gi.imageindex (PIC_SCANNER); +// gi.imageindex (PIC_LEFT); +// gi.imageindex (PIC_RIGHT); +// gi.imageindex (PIC_UP); +// gi.imageindex (PIC_DOWN); + +// +// 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"); + + +} + diff --git a/g_svcmds.c b/g_svcmds.c new file mode 100644 index 0000000..4f2f81b --- /dev/null +++ b/g_svcmds.c @@ -0,0 +1,261 @@ + +#include "g_local.h" +#include "bot_procs.h"//ERASER +char *getClassName (gclient_t *c); +//WF34 S +void Cmd_Classdef_f (); +//int SaveClassInfo(); +void Adm_Test(char *cmd); +void CTFResetFlags(void); +//WF34 E + +//Show all the players +void sv_ShowPlayers() +{ +// edict_t *cl_ent; +// gclient_t *cl; + edict_t *e; + + int i; + + //Show red players first + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + //getClassName(e->client, classname); + if (e->client->resp.ctf_team == CTF_TEAM1) + { + gi.dprintf ("Red %3d %8s %s\n", + e->client->resp.score, + getClassName(e->client), + e->client->pers.netname); + } + } + + //Next blue + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + //getClassName(e->client, classname); + if (e->client->resp.ctf_team == CTF_TEAM2) + { + gi.dprintf ("Blue %3d %8s %s\n", + e->client->resp.score, + getClassName(e->client), + e->client->pers.netname); + } + } + + //Then observers + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + //getClassName(e->client, classname); + if (e->client->resp.ctf_team <= 0) + { + gi.dprintf ("Obsv %3d %8s %s\n", + e->client->resp.score, + getClassName(e->client), + e->client->pers.netname); + } + } +} + +//ERASER START +void Svcmd_Test_f (void) +{ + safe_cprintf (NULL, PRINT_HIGH, "Svcmd_Test_f()\n"); +} + +void Svcmd_Bots_f (void) +{ + int i=2, j, len; + char name[128]; + + while (i < gi.argc()) + { + strcpy(name, gi.argv(i)); + len = strlen(name); + + // convert '~' to ' ' + for (j=0; jteamname, team) || !_stricmp(bot_teams[i]->abbrev, team)) +#else + if (!strcasecmp(bot_teams[i]->teamname, team) || !strcasecmp(bot_teams[i]->abbrev, team)) +#endif + { // found the team, so add the bots + bot_teams[i]->ingame = true; // bots will be added automatically (below) + break; + } + + i++; + } + + arg++; + } +} + +int force_team = CTF_NOTEAM; + +void Svcmd_Blueteam_f (void) +{ + int i=2; + + force_team = CTF_TEAM2; + while (i < gi.argc()) + { +//gi.dprintf("Spawning: \"%s\"\n", gi.argv(i)); + spawn_bot(gi.argv(i)); + + i++; + } + + force_team = CTF_NOTEAM; +} + +void Svcmd_Redteam_f (void) +{ + int i=2; + + force_team = CTF_TEAM1; + while (i < gi.argc()) + { + spawn_bot(gi.argv(i)); + + i++; + } + + force_team = CTF_NOTEAM; +} +//ERASER END +/* +================= +ServerCommand + +ServerCommand will be called when an "sv" command is issued. +The game can issue gi.argc() / gi.argv() commands to get the rest +of the parameters +================= +*/ +void SV_WFFlags_f ();//WF24 + +void ServerCommand (void) +{ + char *cmd; + char *arg1; + + cmd = gi.argv(1); + arg1 = gi.argv(2);//WF34 +//WF24 + if (Q_stricmp (cmd, "showplayers") == 0) + { + sv_ShowPlayers (); + } + + else if (Q_stricmp (cmd, "maplist") == 0) + { + Cmd_Maplist_f (NULL); + } + + else if (Q_stricmp (cmd, "classdef") == 0)//WF34 + { + Cmd_Classdef_f (NULL); + } + + else if (Q_stricmp (cmd, "wfflags") == 0) + { + SV_WFFlags_f (); + } + + else if (Q_stricmp (cmd, "resetflags") == 0)//WF34 + { + CTFResetFlags(); + } + + else if (Q_stricmp (cmd, "addip") == 0) //WF34 + { + Adm_Ban(arg1); + // Adm_KickBan(edict_t *ent); + } + + else if (Q_stricmp (cmd, "testip") == 0)//WF34 + { + Adm_Test(arg1); + // Adm_KickBan(edict_t *ent); + } + else if (Q_stricmp (cmd, "listip") == 0)//WF34 + { + Adm_Bans(arg1); + } + + else if (Q_stricmp (cmd, "writeip") == 0)//WF34 + { + WriteBans(); + } + + else if (Q_stricmp (cmd, "removeip") == 0)//WF34 + { + Adm_Unban(arg1); + } + + else if (Q_stricmp (cmd, "debug") == 0)//WF34 + { + wfdebug = 1; + gi.dprintf("WF Debug On\n"); + } + + else if (Q_stricmp (cmd, "nodebug") == 0)//WF34 + { + wfdebug = 0; + gi.dprintf("WF Debug Off\n"); + } +//ERASER + else if (Q_stricmp (cmd, "test") == 0) + Svcmd_Test_f (); + else if (Q_stricmp (cmd, "bots") == 0) + Svcmd_Bots_f (); + else if (Q_stricmp (cmd, "teams") == 0) + Svcmd_Teams_f (); + else if (Q_stricmp (cmd, "bluebots") == 0) + Svcmd_Blueteam_f (); + else if (Q_stricmp (cmd, "redbots") == 0) + Svcmd_Redteam_f (); +//ERASER END + else + gi.dprintf ("Unknown server command \"%s\"\n", cmd); +} + diff --git a/g_target.c b/g_target.c new file mode 100644 index 0000000..7fef67a --- /dev/null +++ b/g_target.c @@ -0,0 +1,959 @@ +#include "g_local.h" + +/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8) +Fire an origin based temp entity event to the clients. +"style" type byte +*/ +void laser_cleanup(edict_t *self);//WF34 + +void Use_Target_Tent (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (ent->style); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); +} + +void SP_target_temp_entity (edict_t *ent) +{ + ent->use = Use_Target_Tent; +} + + +//========================================================== + +//========================================================== + +/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable +"noise" wav file to play +"attenuation" +-1 = none, send to whole level +1 = normal fighting sounds +2 = idle sound level +3 = ambient sound level +"volume" 0.0 to 1.0 + +Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers. + +Looped sounds are allways atten 3 / vol 1, and the use function toggles it on/off. +Multiple identical looping sounds will just increase volume without any speed cost. +*/ +void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator) +{ + int chan; + + if (ent->spawnflags & 3) + { // looping sound toggles + if (ent->s.sound) + ent->s.sound = 0; // turn it off + else + ent->s.sound = ent->noise_index; // start it + } + else + { // normal sound + if (ent->spawnflags & 4) + chan = CHAN_VOICE|CHAN_RELIABLE; + else + chan = CHAN_VOICE; + // use a positioned_sound, because this entity won't normally be + // sent to any clients because it is invisible + gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0); + } +} + +void SP_target_speaker (edict_t *ent) +{ + char buffer[MAX_QPATH]; + + if(!st.noise) + { + gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin)); + return; + } + if (!strstr (st.noise, ".wav")) + Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise); + else + strncpy (buffer, st.noise, sizeof(buffer)); + ent->noise_index = gi.soundindex (buffer); + + if (!ent->volume) + ent->volume = 1.0; + + if (!ent->attenuation) + ent->attenuation = 1.0; + else if (ent->attenuation == -1) // use -1 so 0 defaults to 1 + ent->attenuation = 0; + + // check for prestarted looping sound + if (ent->spawnflags & 1) + ent->s.sound = ent->noise_index; + + ent->use = Use_Target_Speaker; + + // must link the entity so we get areas and clusters so + // the server can determine who to send updates to + gi.linkentity (ent); +} + + +//========================================================== + +void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->spawnflags & 1) + strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1); + else + strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1); + + game.helpchanged++; +} + +/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1 +When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars. +*/ +void SP_target_help(edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + if (!ent->message) + { + gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + ent->use = Use_Target_Help; +} + +//========================================================== + +/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8) +Counts a secret found. +These are single use targets. +*/ +void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_secrets++; + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_secret (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_secret; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_secrets++; + // map bug hack + if (!Q_stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624) + ent->message = "You have found a secret area."; +} + +//========================================================== + +/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8) +Counts a goal completed. +These are single use targets. +*/ +void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_goals++; + + if (level.found_goals == level.total_goals) + gi.configstring (CS_CDTRACK, "0"); + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_goal (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_goal; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_goals++; +} + +//========================================================== + + +/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8) +Spawns an explosion temporary entity when used. + +"delay" wait this long before going off +"dmg" how much radius damage should be done, defaults to 0 +*/ +void target_explosion_explode (edict_t *self) +{ + float save; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PHS); + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + save = self->delay; + self->delay = 0; + G_UseTargets (self, self->activator); + self->delay = save; +} + +void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (!self->delay) + { + target_explosion_explode (self); + return; + } + + self->think = target_explosion_explode; + self->nextthink = level.time + self->delay; +} + +void SP_target_explosion (edict_t *ent) +{ + ent->use = use_target_explosion; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) +Changes level to "map" when fired +*/ +void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator) +{ + if (level.intermissiontime) + return; // allready activated + + if (!deathmatch->value && !coop->value) + { + if (g_edicts[1].health <= 0) + return; + } + + // if noexit, do a ton of damage to other + if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world) + { + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT); + return; + } + + // if multiplayer, let everyone know who hit the exit + if (deathmatch->value) + { + if (activator && activator->client) + gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname); + } + + // if going to a new unit, clear cross triggers + if (strstr(self->map, "*")) + game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK); + + BeginIntermission (self); +} + +void SP_target_changelevel (edict_t *ent) +{ + if (!ent->map) + { + gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + // ugly hack because *SOMEBODY* screwed up their map + if((Q_stricmp(level.mapname, "fact1") == 0) && (Q_stricmp(ent->map, "fact3") == 0)) + ent->map = "fact3$secret1"; + + ent->use = use_target_changelevel; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8) +Creates a particle splash effect when used. + +Set "sounds" to one of the following: + 1) sparks + 2) blue water + 3) brown water + 4) slime + 5) lava + 6) blood + +"count" how many pixels in the splash +"dmg" if set, does a radius damage at this location when it splashes + useful for lava/sparks +*/ + +void use_target_splash (edict_t *self, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (self->count); + gi.WritePosition (self->s.origin); + gi.WriteDir (self->movedir); + gi.WriteByte (self->sounds); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (self->dmg) + T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH); +} + +void SP_target_splash (edict_t *self) +{ + self->use = use_target_splash; + G_SetMovedir (self->s.angles, self->movedir); + + if (!self->count) + self->count = 32; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8) +Set target to the type of entity you want spawned. +Useful for spawning monsters and gibs in the factory levels. + +For monsters: + Set direction to the facing you want it to have. + +For gibs: + Set direction if you want it moving and + speed how fast it should be moving otherwise it + will just be dropped +*/ +void ED_CallSpawn (edict_t *ent); + +void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; +//WF34 START + if ((self->target == NULL) || (self->target[0] == 0)) + { + gi.dprintf("Warning: target_spawn map entity without target set\n"); + return; + } +//WF34 END + 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; + int mod; + qboolean hitSomeone = false; + + 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; + } +//ERASER START + if (!strcmp(self->classname, "path_beam")) + { + ignore = self; + VectorCopy (self->s.origin, start); + VectorAdd (self->s.origin, self->movedir, end); + VectorCopy(end, self->s.old_origin); + self->nextthink = level.time + FRAMETIME; + return; + } + else + { + ignore = self;//normal + VectorCopy (self->s.origin, start);//normal + VectorMA (start, 2048, self->movedir, end);//normal + } +//ERASER END +//WF++ + //Determine means of death. Could be a laserball laser + if (strcmp(self->classname,"laserball laser") == 0) + mod = MOD_WF_LASERBALL; + else + mod = MOD_TARGET_LASER; +//WF-- + if (strcmp(self -> classname,"laser_defense") == 0) + { + if (level.time > self -> delay) + { + // bit of damage + T_RadiusDamage (self, self, 512, NULL, 25,mod); + // BANG ! + /*//FIXME? WF24 HAS THESE + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition(self -> s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); // bye bye laser + */ + laser_cleanup(self);//WF34 + //G_FreeEdict (self); + return; + } + } + + while(1) + { +//24 USES THESE tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + tr = gi.trace (start, NULL, NULL, end, ignore, MASK_ALL); + + if (!tr.ent) + break; + if (strcmp(self -> classname,"lb") == 0) + { + if (tr.ent) + { + // bit of damage + T_RadiusDamage (self, self, 512, NULL, 25,mod); + // BANG ! + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition(self -> s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); // bye bye laser + G_FreeEdict (self); + return; + } + } + // hurt it if we can + if ((tr.ent->takedamage) + && !(tr.ent->flags & FL_IMMUNE_LASER) + && (tr.ent->wf_team != self->wf_team) + && !tr.ent->disguised // don't hit spies + && visible(tr.ent, self)) // make sure we can see it + + { + //T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER); + T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, mod); + hitSomeone = true; + } + + + + // 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); + } + + if (hitSomeone) + { + // This draws the laser + VectorCopy (tr.endpos, self->s.old_origin); + } + self->nextthink = level.time + FRAMETIME; +} +//WF34 START +void target_laser_def_think (edict_t *self) +{ + edict_t *ignore; + vec3_t start; + vec3_t end; + trace_t tr; + int count; + int mod; + + if (self->spawnflags & 0x80000000) + count = 8; + else + count = 4; + + + ignore = self; + VectorCopy (self->creator->s.origin, start); + VectorMA (start, 2048, self->movedir, end); + + mod = MOD_TARGET_LASER; + + if (level.time > self -> delay) + { + // bit of damage + T_RadiusDamage (self, self, 512, NULL, 25,mod); + // BANG ! + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition(self -> s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); // bye bye laser + laser_cleanup(self->creator); + return; + } + + while(1) + { +// tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + tr = gi.trace (start, NULL, NULL, self->s.origin, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) + && (tr.ent->wf_team != self->wf_team)) + { + if (!tr.ent->disguised) + T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, mod); + } + + // if we hit something that's not a monster or player or is immune to lasers, we're done + if (tr.ent && (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + && (strcmp(tr.ent->classname,"laser_defense_gr") != 0)) + { + 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); + } + // here's the real trick, copy the grenades position to our old origin + VectorCopy (self->creator->s.origin, self->s.old_origin); + self->nextthink = level.time + FRAMETIME; +} +//WF34 END +void target_laser_on (edict_t *self) +{ + if (!self->activator) + self->activator = self; + self->spawnflags |= 0x80000001; + self->svflags &= ~SVF_NOCLIENT; + + target_laser_think (self); +} +//WF34 START +void target_laser_def_on (edict_t *self) +{ + if (!self->activator) + self->activator = self; + self->spawnflags |= 0x80000001; + self->svflags &= ~SVF_NOCLIENT; + + target_laser_def_think (self); +} +//WF34 END +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) +{ + //See if there is a team specific indicator WF34 LINES + if ((self->wf_team) && (self->wf_team != other->wf_team)) + return; + + 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) //WF-red + self->s.skinnum = 0xf2f2f0f0; + else if (self->spawnflags & 4) //WF-green + self->s.skinnum = 0xd0d1d2d3; + else if (self->spawnflags & 8) //WF-blue? + self->s.skinnum = 0xf3f3f1f1; + else if (self->spawnflags & 16) //WF-yellow + self->s.skinnum = 0xdcdddedf; + else if (self->spawnflags & 32) //WF-orange + 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..da66bcc --- /dev/null +++ b/g_trigger.c @@ -0,0 +1,625 @@ +#include "g_local.h" +void WFAddBonus(edict_t *self, edict_t *other);//WF24 + + +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; +} +//WF34 START +qboolean PassTeamCheck(edict_t *ent, edict_t *other) +{ + +//gi.dprintf("Pass Team Check, team=%d, other team = %d\n", ent->wf_team, other->wf_team); + + //If no team specified, return true + if (!ent->wf_team) + return true; + + //See if there is a team specific indicator + if (ent->wf_team == other->wf_team) + return true; //Same team + else + return false; //Different team +} + + +// 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); + + //Should we award a bonus?//WF34 + if (ent->bonustype) WFAddBonus(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; + + //See if we pass the team check//WF24-34 + if ( PassTeamCheck(self,other) == false) 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; + + if (!activator->bot_client)//ERASER + gi.centerprintf (activator, "You need the %s", self->item->pickup_name); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0); + return; + } + + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0); + if (coop->value) + { + int player; + edict_t *ent; + + if (strcmp(self->item->classname, "key_power_cube") == 0) + { + int cube; + + for (cube = 0; cube < 8; cube++) + if (activator->client->pers.power_cubes & (1 << cube)) + break; + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->client->pers.power_cubes & (1 << cube)) + { + ent->client->pers.inventory[index]--; + ent->client->pers.power_cubes &= ~(1 << cube); + } + } + } + else + { + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + ent->client->pers.inventory[index] = 0; + } + } + } + else + { + activator->client->pers.inventory[index]--; + } + + G_UseTargets (self, activator); + + self->use = NULL; +} + +void SP_trigger_key (edict_t *self) +{ + if (!st.item) + { + gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin)); + return; + } + self->item = FindItemByClassname (st.item); + + if (!self->item) + { + gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin)); + return; + } + + if (!self->target) + { + gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin)); + return; + } + + gi.soundindex ("misc/keytry.wav"); + gi.soundindex ("misc/keyuse.wav"); + + self->use = trigger_key_use; +} + + +/* +============================================================================== + +trigger_counter + +============================================================================== +*/ + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. + +If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished. + +After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself. +*/ + +void trigger_counter_use(edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->count == 0) + return; + + self->count--; + + if (self->count) + { + if (! (self->spawnflags & 1)) + { + if (!activator->bot_client)//ERASER + gi.centerprintf(activator, "%i more to go...", self->count); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + return; + } + + if (! (self->spawnflags & 1)) + { + if (!activator->bot_client)//ERASER + gi.centerprintf(activator, "Sequence completed!"); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + self->activator = activator; + multi_trigger (self); +} + +void SP_trigger_counter (edict_t *self) +{ + self->wait = -1; + if (!self->count) + self->count = 2; + + self->use = trigger_counter_use; +} + + +/* +============================================================================== + +trigger_always + +============================================================================== +*/ + +/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) +This trigger will always fire. It is activated by the world. +*/ +void SP_trigger_always (edict_t *ent) +{ + // we must have some delay to make sure our use targets are present + if (ent->delay < 0.2) + ent->delay = 0.2; + G_UseTargets(ent, ent); +} + + +/* +============================================================================== + +trigger_push + +============================================================================== +*/ + +#define PUSH_ONCE 1 + +static int windsound; + +void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + + //See if we pass the team check (3.2)//WF24-34 START + if ( PassTeamCheck(self,other) == false) return; +//WF24-34 END + 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); + } +//Eraser START, Nav, don't drop reverse jump nodes if jumping + if (bot_calc_nodes->value && other->jump_ent) + { + other->jump_ent->flags |= FL_NO_KNOCKBACK; + } +//ERASER END + } + } + 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; +//WF24 START + //See if we pass the team check + if ( PassTeamCheck(self,other) == false) return; + + //Special case for decoys. For some reason, it may crash + //server if the decoys are killed with trigger_hurt + if (strcmp(other->classname, "decoy") == 0) return; +//WF24 END + + 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_unzip.c b/g_unzip.c new file mode 100644 index 0000000..b3e18b9 --- /dev/null +++ b/g_unzip.c @@ -0,0 +1,279 @@ +/* + This is a very simplistic example of how to load and make a call into the + dll. This has been compiled and tested for a 32-bit console version, but + not under 16-bit windows. However, the #ifdef's have been left in for the + 16-bit code, simply as an example. + + */ + +//#define WIN32 + +#include "g_local.h" +#include +#include +#include +#include +#include "g_unzip.h" +#include + +#define UNZ_DLL_NAME "UNZIP32.DLL\0" + +#define DLL_WARNING "Cannot find %s."\ + " The DLL must be in the application directory, the path, "\ + "the Windows directory or the Windows System directory." +#define DLL_VERSION_WARNING "%s has the wrong version number."\ + " Insure that you have the correct dll's installed, and that "\ + "an older dll is not in your path or Windows System directory." + +int hFile; /* file handle */ + +LPUSERFUNCTIONS lpUserFunctions; +HANDLE hUF = (HANDLE)NULL; +LPDCL lpDCL = NULL; +HANDLE hDCL = (HANDLE)NULL; +HINSTANCE hUnzipDll; +HANDLE hZCL = (HANDLE)NULL; +#ifdef WIN32 +DWORD dwPlatformId = 0xFFFFFFFF; +#endif + + +/* Forward References */ +int WINAPI DisplayBuf(char far *, unsigned long); +int WINAPI GetReplaceDlgRetVal(char *); +int WINAPI password(char *, char *, int, char *); +void WINAPI ReceiveDllMessage(unsigned long,unsigned long, + ush, ush, ush, ush, ush, ush, char, char *, char *, unsigned long, char); +_DLL_UNZIP windll_unzip; +_USER_FUNCTIONS UzInit; +void FreeUpMemory(void); +#ifdef WIN32 +BOOL IsNT(VOID); +#endif + +// Extracts all files in zipfile to extract_dir. +// NOTE: make sure the current dir is the directory in which the .dll files are +// in before calling this! +int G_UnzipFile(char *zipfile, char *extract_dir) +{ +//int exfc, infc; +//char **exfv, **infv; +//char *x_opt; +//DWORD dwVerInfoSize; +//DWORD dwVerHnd; +char szFullPath[PATH_MAX]; +int retcode; +char *ptr; +//HANDLE hMem; /* handle to mem alloc'ed */ + +if (!zipfile) /* We must have an archive to unzip */ + return 0; + +hDCL = GlobalAlloc( GPTR, (DWORD)sizeof(DCL)); +if (!hDCL) + { + return 0; + } +lpDCL = (LPDCL)GlobalLock(hDCL); +if (!lpDCL) + { + GlobalFree(hDCL); + return 0; + } + +hUF = GlobalAlloc( GPTR, (DWORD)sizeof(USERFUNCTIONS)); +if (!hUF) + { + GlobalUnlock(hDCL); + GlobalFree(hDCL); + return 0; + } +lpUserFunctions = (LPUSERFUNCTIONS)GlobalLock(hUF); + +if (!lpUserFunctions) + { + GlobalUnlock(hDCL); + GlobalFree(hDCL); + GlobalFree(hUF); + return 0; + } + +lpUserFunctions->password = password; +lpUserFunctions->print = DisplayBuf; +lpUserFunctions->sound = NULL; +lpUserFunctions->replace = GetReplaceDlgRetVal; +lpUserFunctions->SendApplicationMessage = ReceiveDllMessage; + +/* First we go look for the unzip dll */ +#ifdef WIN32 +if (SearchPath( + NULL, /* address of search path */ + UNZ_DLL_NAME, /* address of filename */ + NULL, /* address of extension */ + PATH_MAX, /* size, in characters, of buffer */ + szFullPath, /* address of buffer for found filename */ + &ptr /* address of pointer to file component */ + ) == 0) +#else +hfile = OpenFile(UNZ_DLL_NAME, &ofs, OF_SEARCH); +if (hfile == HFILE_ERROR) +#endif + { // cannot find unzip32.dll file +// char str[256]; +// wsprintf (str, DLL_WARNING, UNZ_DLL_NAME); +// gi.dprintf("%s\n", str); + FreeUpMemory(); + return 0; + } +#ifndef WIN32 +else + lstrcpy(szFullPath, ofs.szPathName); +_lclose(hfile); +#endif + +/* Okay, now we know that the dll exists, and has the proper version + * information in it. We can go ahead and load it. + */ +hUnzipDll = LoadLibrary(UNZ_DLL_NAME); +#ifndef WIN32 +if (hUnzipDll > HINSTANCE_ERROR) +#else +if (hUnzipDll != NULL) +#endif + { + (_DLL_UNZIP)windll_unzip = (_DLL_UNZIP)GetProcAddress(hUnzipDll, "windll_unzip"); + } +else + { // unable to load the dll +// char str[256]; +// wsprintf (str, "Could not load %s", UNZ_DLL_NAME); +// printf("%s\n", str); + FreeUpMemory(); + return 0; + } + +/* + Here is where the actual extraction process begins. First we set up the + flags to be passed into the dll. + */ +lpDCL->ncflag = 0; /* Write to stdout if true */ +lpDCL->fQuiet = 2; /* We want all messages. + 1 = fewer messages, + 2 = no messages */ +lpDCL->ntflag = 0; /* test zip file if true */ +lpDCL->nvflag = 0; /* give a verbose listing if true */ +lpDCL->nUflag = 0; /* Do not extract only newer */ +lpDCL->nzflag = 0; /* display a zip file comment if true */ +lpDCL->ndflag = 0; /* Recreate directories if true */ +lpDCL->noflag = 1; /* Over-write all files if true */ +lpDCL->naflag = 0; /* Do not convert CR to CRLF */ +lpDCL->lpszZipFN = (LPSTR) zipfile; /* The archive name */ +lpDCL->lpszExtractDir = (LPSTR) extract_dir; + /* The directory to extract to. This is set + to NULL if you are extracting to the + current directory. + */ + +// UNZIP the file! + +retcode = (*windll_unzip)(0, NULL, 0, NULL, lpDCL, lpUserFunctions); + +if (retcode != 0) + gi.dprintf("Error unzipping \"%s\"\n", zipfile); + +FreeUpMemory(); +FreeLibrary(hUnzipDll); +return (retcode == 0); +} + +int WINAPI GetReplaceDlgRetVal(char *filename) +{ +/* This is where you will decide if you want to replace, rename etc existing + files. + */ +return 1; +} + +void FreeUpMemory(void) +{ +if (hDCL) + { + GlobalUnlock(hDCL); + GlobalFree(hDCL); + } +if (hUF) + { + GlobalUnlock(hUF); + GlobalFree(hUF); + } +} + +/* This simply determines if we are running on NT or Windows 95 */ +#ifdef WIN32 +BOOL IsNT(VOID) +{ +if(dwPlatformId != 0xFFFFFFFF) + return dwPlatformId; +else +/* note: GetVersionEx() doesn't exist on WinNT 3.1 */ + { + if(GetVersion() < 0x80000000) + { + (BOOL)dwPlatformId = TRUE; + } + else + { + (BOOL)dwPlatformId = FALSE; + } + } +return dwPlatformId; +} +#endif + +/* This is a very stripped down version of what is done in WiZ. Essentially + what this function is for is to do a listing of an archive contents. It + is actually never called in this example, but a dummy procedure had to + be put in, so this was used. + */ +void WINAPI ReceiveDllMessage(unsigned long ucsize,unsigned long csiz, + ush cfactor, ush mo, ush dy, ush yr, ush hh, ush mm, + char c, char *filename, char *methbuf, unsigned long crc, char fCrypt) +{ +//char psLBEntry[PATH_MAX]; +char LongHdrStats[] = + "%7lu %7lu %4s %02u-%02u-%02u %02u:%02u %c%s"; +char CompFactorStr[] = "%c%d%%"; +char CompFactor100[] = "100%%"; +char szCompFactor[10]; +char sgn; + +if (csiz > ucsize) + sgn = '-'; +else + sgn = ' '; +if (cfactor == 100) + lstrcpy(szCompFactor, CompFactor100); +else + sprintf(szCompFactor, CompFactorStr, sgn, cfactor); +//wsprintf(psLBEntry, LongHdrStats, +// ucsize, csiz, szCompFactor, mo, dy, yr, hh, mm, c, filename); + +//printf("%s\n", psLBEntry); +} + +/* Password entry routine - see password.c in the wiz directory for how + this is actually implemented in WiZ. If you have an encrypted file, + this will probably give you great pain. + */ +int WINAPI password(char *p, char *m, int n, char *name) +{ +return 1; +} + +/* Dummy "print" routine that simply outputs what is sent from the dll */ +int WINAPI DisplayBuf(char far *buf, unsigned long size) +{ +//printf("%s", buf); +return (unsigned int) size; +} + diff --git a/g_unzip.h b/g_unzip.h new file mode 100644 index 0000000..df1096d --- /dev/null +++ b/g_unzip.h @@ -0,0 +1,132 @@ +/* + Example header file + + Do not use this header file in the WiZ application, use WIZ.H + instead. + +*/ +#ifndef _EXAMPLE_H +#define _EXAMPLE_H + +#include +//#include /* required for all Windows applications */ +#include +#include +#include +#include +#include + + +//=============================== VERSION INFO ============================== +#ifndef __unzver_h /* prevent multiple inclusions */ +#define __unzver_h + +#define UNZ_DLL_VERSION "5.32\0" +#define COMPANY_NAME "Info-ZIP\0" + +#endif /* __unzver_h */ +//=========================================================================== + + +//================================ STRUCTURES =============================== +#ifndef _STRUCTS_H +#define _STRUCTS_H + +#ifndef Far +# define Far far +#endif + +/* Porting definitions between Win 3.1x and Win32 */ +#ifdef WIN32 +# define far +# define _far +# define __far +# define near +# define _near +# define __near +# ifndef FAR +# define FAR +# endif +#endif + +#ifndef PATH_MAX +# define PATH_MAX 128 /* max total file or directory name path */ +#endif + +#ifndef DEFINED_ONCE +#define DEFINED_ONCE +#ifndef ush +typedef unsigned short ush; +#endif +typedef int (WINAPI DLLPRNT) (LPSTR, unsigned long); +typedef int (WINAPI DLLPASSWORD) (char *, char *, int, char *); +#endif +typedef void (WINAPI DLLSND) (void); +typedef int (WINAPI DLLREPLACE)(LPSTR); +typedef void (WINAPI DLLMESSAGE)(unsigned long,unsigned long, + ush, ush, ush, ush, ush, ush, char, LPSTR, LPSTR, unsigned long, char); + +typedef struct { +DLLPRNT *print; +DLLSND *sound; +DLLREPLACE *replace; +DLLPASSWORD *password; +DLLMESSAGE *SendApplicationMessage; +WORD cchComment; +unsigned long TotalSizeComp; +unsigned long TotalSize; +int CompFactor; +unsigned int NumMembers; +} USERFUNCTIONS, far * LPUSERFUNCTIONS; + +typedef struct { +int ExtractOnlyNewer; +int SpaceToUnderscore; +int PromptToOverwrite; +int fQuiet; +int ncflag; +int ntflag; +int nvflag; +int nUflag; +int nzflag; +int ndflag; +int noflag; +int naflag; +int nZIflag; +int C_flag; +int fPrivilege; +LPSTR lpszZipFN; +LPSTR lpszExtractDir; +} DCL, _far *LPDCL; + +#endif /* _STRUCTS_H */ +//======================================================================= + +/* Defines */ +#ifndef MSWIN +#define MSWIN +#endif + +typedef int (WINAPI * _DLL_UNZIP)(int, char **, int, char **, + LPDCL, LPUSERFUNCTIONS); +typedef int (WINAPI * _USER_FUNCTIONS)(LPUSERFUNCTIONS); + +/* Global variables */ + +extern LPUSERFUNCTIONS lpUserFunctions; +extern LPDCL lpDCL; + +extern HINSTANCE hUnzipDll; + +extern int hFile; /* file handle */ + +/* Global functions */ + +extern _DLL_UNZIP windll_unzip; +extern _USER_FUNCTIONS UzInit; +int WINAPI DisplayBuf(char far *, unsigned long int); + +/* Procedure Calls */ +void WINAPI ReceiveDllMessage(unsigned long,unsigned long, + ush, ush, ush, ush, ush, ush, char, char *, char *, unsigned long, char); +#endif /* _EXAMPLE_H */ \ No newline at end of file diff --git a/g_utils.c b/g_utils.c new file mode 100644 index 0000000..4b8746e --- /dev/null +++ b/g_utils.c @@ -0,0 +1,982 @@ +// g_utils.c -- misc utility functions for game module + +#include "g_local.h" +void laserball_cleanup (edict_t *ent);//WF34 + + +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; +//WF34 S ++TeT + float vecLength; + float radSquared = rad * rad; +//WF34 E --TeT + + 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); +//WF34 S ++TeT - inlined vectorLength func which returned sqrt of vecLength, +// instead we compare it to the square of rad + vecLength = 0; + vecLength += eorg[0]*eorg[0]; + vecLength += eorg[1]*eorg[1]; + vecLength += eorg[2]*eorg[2]; + if (vecLength > radSquared) + continue; +// --TeT + return from; + } + + return NULL; +} + +/* +================= +findEnemyWithinRadius + +Returns entities that have origins within a spherical area + +findEnemyWithinRadius (origin, radius) +================= +*/ +edict_t *findEnemyWithinRadius (edict_t *self, edict_t *from, vec3_t org, float rad) +{ + vec3_t eorg; + int j; +//WF34 S ++TeT + float vecLength; + float radSquared = rad * rad; +//WF34 E --TeT + + 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; + if (!(from->svflags & SVF_MONSTER) && !from->client) + continue; + //dont attack same team + if (from->wf_team == self->wf_team) + continue; + if (from->disguised) + continue; + if (!from->takedamage) + continue; + if (from->health <= 0) + continue; + if (!visible(self, from)) + continue; + for (j=0 ; j<3 ; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5); +//WF34 S ++TeT - inlined vectorLength func which returned sqrt of vecLength, +// instead we compare it to the square of rad + vecLength = 0; + vecLength += eorg[0]*eorg[0]; + vecLength += eorg[1]*eorg[1]; + vecLength += eorg[2]*eorg[2]; + if (vecLength > radSquared) + continue; +// --TeT + 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 +//WF34 S +//if (wfdebug) gi.dprintf("G_UseTargets:1. Ent = %s, Activator=%s\n", +// ent->classname, activator->classname); +//WF34 E + if (ent->delay) + { + // create a temp object to fire at a later time + t = G_Spawn(); + t->classname = "DelayedUse"; + t->nextthink = level.time + ent->delay; + t->think = Think_Delay; + t->activator = activator; + if (!activator) + gi.dprintf ("Think_Delay with no activator\n"); + t->message = ent->message; + t->target = ent->target; + t->killtarget = ent->killtarget; + return; + } + + +// +// print the message +// + if ((ent->message) && !(activator->svflags & SVF_MONSTER)) + + { + gi.centerprintf (activator, "%s", ent->message); + + if (ent->noise_index) + + gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); + + else + + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + + } + +// +// kill killtargets +// + + if (ent->killtarget) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->killtarget))) + + { + + G_FreeEdict (t); + + if (!ent->inuse) + + { + + gi.dprintf("entity was removed while using killtargets\n"); + + return; + + } + + } + } + +// gi.dprintf("TARGET: activating %s\n", ent->target); + + + +// +// fire targets +// +//if (wfdebug) gi.dprintf("G_UseTargets:4\n"); + + 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 + {//WF34S +//if (wfdebug) gi.dprintf("G_UseTargets: 4a-use. Target=%s\n", t->classname); + if (t->use) + t->use (t, ent, activator); + } + if (!ent->inuse) + { + gi.dprintf("entity was removed while using targets\n"); + return; + + } + + } + } +//if (wfdebug) gi.dprintf("G_UseTargets:5\n");//WF34 +} + + +/* +============= +TempVector + +This is just a convenience function +for making temporary vectors for function calls +============= +*/ +float *tv (float x, float y, float z) +{ + static int index; + static vec3_t vecs[8]; + float *v; + + // use an array so that multiple tempvectors won't collide + // for a while + v = vecs[index]; + index = (index + 1)&7; + + v[0] = x; + v[1] = y; + v[2] = z; + + return v; +} + + +/* +============= +VectorToString + +This is just a convenience function +for printing vectors +============= +*/ +char *vtos (vec3_t v) +{ + static int index; + static char str[8][32]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[index]; + index = (index + 1)&7; + + Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]); + + return s; +} + + +vec3_t VEC_UP = {0, -1, 0}; +vec3_t MOVEDIR_UP = {0, 0, 1}; +vec3_t VEC_DOWN = {0, -2, 0}; +vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void G_SetMovedir (vec3_t angles, vec3_t movedir) +{ + if (VectorCompare (angles, VEC_UP)) + { + VectorCopy (MOVEDIR_UP, movedir); + } + else if (VectorCompare (angles, VEC_DOWN)) + { + VectorCopy (MOVEDIR_DOWN, movedir); + } + else + { + AngleVectors (angles, movedir, NULL, NULL); + } + + VectorClear (angles); +} + + +float vectoyaw (vec3_t vec) +{ + float yaw; + + if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0) + { + yaw = 0; + if (vec[YAW] > 0) + yaw = 90; + else if (vec[YAW] < 0) + yaw = -90; + } + else + { + yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + } + + return yaw; +} + + +void vectoangles (vec3_t value1, vec3_t angles) +{ + float forward; + float yaw, pitch; + + if (value1[1] == 0 && value1[0] == 0) + { + yaw = 0; + if (value1[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + if (value1[0]) + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + else if (value1[1] > 0) + yaw = 90; + else + yaw = -90; + if (yaw < 0) + yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + +char *G_CopyString (char *in) +{ + char *out; + + out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL); + strcpy (out, in); + return out; +} + + +void G_InitEdict (edict_t *e) +{ + e->inuse = true; + e->classname = "noclass"; + e->gravity = 1.0; + e->s.number = e - g_edicts; +//ERASER START + e->trail_index = 0; + e->closest_trail = 0; + e->ignore_time = 0; + e->last_reached_trail = 0; +//ERASER END +} + +/* +================= +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) +{ + +if (wfdebug) + { +// gi.dprintf("G_FreeEdict: %s\n", ed->classname); + } +//WF34 S Check for entity limits + if ((ed->grenade_index) && (ed->owner) && (ed->owner->client)) + { + --ed->owner->client->pers.active_grenades[ed->grenade_index]; + } + + if (ed->special_index) //if set, this is an index into the active_special array + { + --ed->owner->client->pers.active_special[ed->special_index]; + } + + //Special handling for other WF items + + //Sentry gun + if (!strcmp(ed->classname, "SentryGun") ) + { + if (ed->sentry) ed->sentry->health = 0; + if (ed->creator) ed->creator->sentry = NULL; + } + + //Supply depot + else if (!strcmp(ed->classname, "depot") ) + { + if (ed->owner) ed->owner->supply = NULL; + } + + //Healing depot + else if (!strcmp(ed->classname, "healingdepot") ) + { + if (ed->owner) ed->owner->supply = NULL; + } + + //Laserball + else if (!strcmp(ed->classname, "laserball") ) + { + laserball_cleanup(ed); + } +//WF34 E + 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; + } +} + + +/*================= + * 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; + int i;//ERASER + edict_t *trav;//ERASER + + gi.unlinkentity(ent);//ERASER + + while (1) + { + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID); + if (!tr.ent) + break; + + //WF & K2:Begin + // ++TeT + if (!strcmp(tr.ent->classname, "SentryStand")) + { + tr.ent = tr.ent->creator; + tr.ent->noteamdamage = false; + } + if (!strcmp(tr.ent->classname, "biosentry")) + { + tr.ent->noteamdamage = false; + } + + // --TeT + // ++TeT Removed Reverse Telefrag - If they weren't smart enough to get out of the way, kill em + //Need to check for reverse telefrag condition for respawn protection +// if(K2_IsProtected(tr.ent) && !K2_IsProtected(ent) ) +// { +// //Reverse Telefrag +// T_Damage (ent,tr.ent,tr.ent,vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_REVERSE_TELEFRAG); +// } + else + //WF & K2:End + // nail it + // --TeT remove reverse telefrag + 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) + //ERASER START + { +// if (tr.ent == world) + break;//ERASER + +// gi.dprintf("Couldn't KillBox(), something is wrong.\n"); +// return false;//ERASER ,WF USES + } + } + + // fixes wierd teleporter/spawning inside others + i = -1; + while (++i < num_players) + { + if ((trav = players[i]) == ent) + continue; + + if (entdist(trav, ent) > 42) + continue; + + // nail it + T_Damage (trav, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); +//ERASER END + //??FIXME return false; + + } + + + + return true; // all clear + +} +//ERASER START +// Ridah +/* +================== +stuffcmd + + QC equivalent, sends a command to the client's consol +================== +*//* ACRID +void stuffcmd(edict_t *ent, char *text) +{ + gi.WriteByte(11); // 11 = svc_stufftext + gi.WriteString(text); + gi.unicast(ent, 1); +}*///ACRID + +float entdist(edict_t *ent1, edict_t *ent2) +{ + vec3_t vec; + + VectorSubtract(ent1->s.origin, ent2->s.origin, vec); + return VectorLength(vec); +} + +/* +================= +AddModelSkin + + Adds a skin reference to an .md2 file, saving as filename.md2new +================= +*/ +void AddModelSkin (char *modelfile, char *skinname) +{ + FILE *f, *out; + int count = 0, buffer_int, i; + char filename[256], infilename[256]; + char buffer; +// botDebugPrint("ADDMODELSKIN (ACRID)\n"); + i = sprintf(infilename , modelfile); + + f = fopen (infilename, "rb"); + if (!f) + { + gi.dprintf("Cannot open file %s\n", infilename); + return; + } + + i = sprintf(filename, ".\\"); + i += sprintf(filename + i, GAMEVERSION); + i += sprintf(filename + i, "\\"); + i += sprintf(filename + i, modelfile); + i += sprintf(filename + i, "new"); + + out = fopen (filename, "wb"); + if (!out) + return; + + // mirror header stuff before skinnum + for (i=0; i<5; i++) + { + fread(&buffer_int, sizeof(buffer_int), 1, f); + fwrite(&buffer_int, sizeof(buffer_int), 1, out); + } + + // increment skinnum + fread(&buffer_int, sizeof(buffer_int), 1, f); + ++buffer_int; + fwrite(&buffer_int, sizeof(buffer_int), 1, out); + + // mirror header stuff before skin_ofs + for (i=0; i<5; i++) + { + fread(&buffer_int, sizeof(buffer_int), 1, f); + fwrite(&buffer_int, sizeof(buffer_int), 1, out); + } + + // copy the skins offset value, since it doesn't change + fread(&buffer_int, sizeof(buffer_int), 1, f); + fwrite(&buffer_int, sizeof(buffer_int), 1, out); + + // increment all offsets by 64 to make way for new skin + for (i=0; i<5; i++) + { + fread(&buffer_int, sizeof(buffer_int), 1, f); + buffer_int += 64; + fwrite(&buffer_int, sizeof(buffer_int), 1, out); + } + + // write the new skin + for (i=0; ivalue) + safe_cprintf(NULL, printlevel, bigbuffer); + + for (i=0 ; ivalue ; i++) + { + cl_ent = g_edicts + 1 + i; + if (!cl_ent->inuse || cl_ent->bot_client) + continue; + + safe_cprintf(cl_ent, printlevel, bigbuffer); + } +/* + for (i=0; ibot_client) + safe_cprintf(players[i], printlevel, bigbuffer); + } +*/ +} + +//====================================================================== +// New ACE-compatible message routines + +// botsafe cprintf +void safe_cprintf (edict_t *ent, int printlevel, char *fmt, ...) +{ + char bigbuffer[0x10000]; + va_list argptr; + int len; + + va_start (argptr,fmt); + len = vsprintf (bigbuffer,fmt,argptr); + va_end (argptr); + + if (!ent) + { + gi.cprintf(ent, printlevel, bigbuffer); + return; + } + + if (!ent->inuse || ent->isbot || ent->bot_client) + return; + + if (!ent->client) return; + + + 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->isbot || ent->bot_client) + 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) + safe_cprintf(NULL, printlevel, bigbuffer); + + // This is to be compatible with Eraser (ACE) + //for (i=0; ivalue ; i++) + { + cl_ent = g_edicts + 1 + i; + + if (cl_ent->inuse && !cl_ent->bot_client && !cl_ent->isbot) + safe_cprintf(cl_ent, printlevel, bigbuffer); + } + +} +//ERASER END \ No newline at end of file diff --git a/g_weapon.c b/g_weapon.c new file mode 100644 index 0000000..6816df4 --- /dev/null +++ b/g_weapon.c @@ -0,0 +1,1483 @@ +#include "g_local.h" + + + +/* +================= +check_dodge + +This is a support routine used when a client is firing +a non-instant attack weapon. It checks to see if a +monster's dodge function should be called. +================= +*/ +void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed) +{ + vec3_t end; + vec3_t v; + trace_t tr; + float eta; + + // easy mode only ducks one quarter the time + if (skill->value == 0) + { + if (random() > 0.25) + return; + } + VectorMA (start, 8192, dir, end); + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self)) + { + VectorSubtract (tr.endpos, start, v); + eta = (VectorLength(v) - tr.ent->maxs[0]) / speed; + tr.ent->monsterinfo.dodge (tr.ent, self, eta); + } +} + + +/* +================= +fire_hit + +Used for all impact (hit/punch/slash) attacks +================= +*/ +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t v; + vec3_t point; + float range; + vec3_t dir; + + //see if enemy is in range + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); + range = VectorLength(dir); + if (range > aim[0]) + return false; + + if (aim[1] > self->mins[0] && aim[1] < self->maxs[0]) + { + // the hit is straight on so back the range up to the edge of their bbox + range -= self->enemy->maxs[0]; + } + else + { + // this is a side hit so adjust the "right" value out to the edge of their bbox + if (aim[1] < 0) + aim[1] = self->enemy->mins[0]; + else + aim[1] = self->enemy->maxs[0]; + } + + VectorMA (self->s.origin, range, dir, point); + + tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT); + if (tr.fraction < 1) + { + if (!tr.ent->takedamage) + return false; + // if it will hit any client/monster then hit the one we wanted to hit + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + tr.ent = self->enemy; + } + + AngleVectors(self->s.angles, forward, right, up); + VectorMA (self->s.origin, range, forward, point); + VectorMA (point, aim[1], right, point); + VectorMA (point, aim[2], up, point); + VectorSubtract (point, self->enemy->s.origin, dir); + + // do the damage + T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT); + + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + return false; + + // do our special form of knockback here + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v); + VectorSubtract (v, point, v); + VectorNormalize (v); + VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity); + if (self->enemy->velocity[2] > 0) + self->enemy->groundentity = NULL; + return true; +} + + +/* +================= +fire_lead + +This is an internal support routine used for bullet/pellet based weapons. +================= +*/ +static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + +if (wfdebug) gi.dprintf("Fire Lead...\n"); + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; +//ERASER START + // long traceline's eat CPU + if (self->bot_client) + { + VectorMA (start, 1000, forward, end); + r = (1/8) * r; + r = (1/8) * u; + } + else +//ERASER END + VectorMA (start, 8192, forward, end); + + //Don't add random spread if it's the sniper riffle + if (mod != MOD_SNIPERRIFLE) + { + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + else + { +if (wfdebug) gi.dprintf(" --didn't hit anything!\n"); + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { +if (wfdebug) gi.dprintf(" --hit something: %s\n", tr.ent->classname); +// if (tr.ent->takedamage) + if ((tr.ent->takedamage) && (tr.ent->wf_team != self->wf_team)) + { + if (self->owner) + T_Damage (tr.ent, self, self->owner, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod); + else + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod); + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + else + { +if (wfdebug) gi.dprintf(" --nothing in the middle\n"); + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } +} + + +/* +================= +fire_bullet + +Fires a single round. Used for machinegun and chaingun. Would be fine for +pistols, rifles, etc.... +================= +*/ +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod) +{ + fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod); +} + + +/* +================= +fire_shotgun + +Shoots shotgun pellets. Used by shotgun and super shotgun. +================= +*/ +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod) +{ + int i; + + for (i = 0; i < count; i++) + fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod); +} +/* +================= +fire_plasmabeam + +Fires PlasmaBeam. Used in new hyperblaster +by Fireball +================= +*/ +void fire_plasmabeam (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick) +{ + vec3_t from; + vec3_t end, offset; + trace_t tr; + edict_t *ignore; + int mask; + qboolean water; + + VectorMA (start, 8192, aimdir, end); + VectorCopy (start, from); + ignore = self; + water = false; + mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + while (ignore) + { + tr = gi.trace (from, NULL, NULL, end, ignore, mask); + + if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA); + water = true; + } + else + { + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + 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_HYPERBLASTER); + } + + VectorCopy (tr.endpos, from); + } + VectorCopy (start, offset); + + // send gun puff / flash + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_HEATBEAM); + + gi.WriteShort (self - g_edicts); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + 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_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 + { +//WF: don't stop if it hits. remove the return statement for normal use +// return; +//WF + + //Original code + 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); +} + +//Original blaster shooting +void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->svflags = SVF_DEADMONSTER; + // yes, I know it looks weird that projectiles are deadmonsters + // what this means is that when prediction is used against the object + // (blaster/hyperblaster shots), the player won't be solid clipped against + // the object. Right now trying to run into a firing hyperblaster + // is very jerky since you are predicted 'against' the shots. + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2"); + bolt->s.sound = gi.soundindex ("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster_touch; + bolt->nextthink = level.time + 2; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + if (hyper) + bolt->spawnflags = 1; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + +//WF - New fire blaster for lasers +void fire_blaster_wf (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper) + { + edict_t *bolt; + trace_t tr; + vec3_t from; + vec3_t end; + + VectorNormalize (dir); + + if(effect & EF_BLASTER)// if blaster, spawn blaster bolt + { + bolt = G_Spawn(); + bolt->svflags = SVF_DEADMONSTER; + // yes, I know it looks weird that projectiles are deadmonsters + // what this means is that when prediction is used against the object + // (blaster/hyperblaster shots), the player won't be solid clipped against + // the object. Right now trying to run into a firing hyperblaster + // is very jerky since you are predicted 'against' the shots. + 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"; + + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, + MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } + } + else // laser hyperblaster + { + // set origin of laser beam at gun barrel. + // note that the barrel is rotating, so the beams will + // originate from different places each time. + VectorMA (start, 8192, dir, end); + VectorCopy (start, from); + // trace for end point of laser beam. + // the laser aim is perfect. + // no random aim like the machinegun + tr = gi.trace (from, NULL, NULL, end, self, MASK_SHOT); + tr = gi.trace (from, NULL, NULL, end, self, MASK_SHOT); + // send laser beam temp entity to clients + VectorCopy (tr.endpos, from); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); + + if ((tr.ent != self) && (tr.ent->takedamage)) + T_Damage (tr.ent, self, self, dir, tr.endpos, tr.plane.normal, + damage, 0, 0, MOD_HYPERBLASTER); + else if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { // hit a brush, send clients + // a light flash and sparks temp entity. + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + } + } + +/* +================= +fire_grenade +================= +*/ +//WF +//static void Grenade_Explode (edict_t *ent) +//WF +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); +} + +//WF safe version of the explosion +void GenericGrenade_Explode (edict_t *ent) +{ + vec3_t origin; + int mod; + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + if (ent->mod) + mod = ent->mod; + else 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); + + //removed enemy code + + 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); +} + +void GenericGrenade_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = GenericGrenade_Explode; +} + +void Grenade_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = Grenade_Explode; +} + +void Rocket_Explode (edict_t *ent) +{ + int i; vec3_t org; float spd; + vec3_t origin; + + 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); + + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, NULL, ent->dmg_radius, ent->mod); + 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_PVS); +// make some glowing shrapnel + spd = 15.0 * ent->dmg / 200; + for (i = 0; i < 3; i++) + { + org[0] = ent->s.origin[0] + crandom() * ent->size[0]; + org[1] = ent->s.origin[1] + crandom() * ent->size[1]; + org[2] = ent->s.origin[2] + crandom() * ent->size[2]; + ThrowShrapnel4 (ent, "models/objects/debris2/tris.md2", spd, org); + } + make_debris (ent); + if ((ent->homing_lock) && (ent->mod != MOD_STINGER)) + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, ent, ent->dmg_radius,MOD_HOMINGROCKET); + else + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, ent, ent->dmg_radius, ent->mod); + //T_ShockItems(ent); + //T_ShockWave(ent, 255, 1024); + BecomeNewExplosion (ent); +} + +void Rocket_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = Rocket_Explode; +} + +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) + { + int i; + + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } +//ERASER START + // look for bot's to alarm + for (i=0; ibot_client) && (players[i]->health > 0) && (entdist(players[i], ent) < 256)) + { + players[i]->avoid_ent = ent; + } + } +//ERASER END + return; + } + + ent->enemy = other; + Grenade_Explode (ent); +} + +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); +// grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2"); + grenade->s.modelindex = gi.modelindex (GRLAUNCHER_MODEL); + grenade->s.skinnum = GRLAUNCHER_SKIN; + 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->s.modelindex = gi.modelindex (GRNORMAL_MODEL); + grenade->s.skinnum = GRNORMAL_SKIN; + 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); + } +} + + +// WF & CCH: New think function for homing missiles +void homing_think (edict_t *ent) +{ + edict_t *target = NULL; + edict_t *blip = NULL; + vec3_t targetdir, blipdir; + vec_t speed; + + while ((blip = findradius(blip, ent->s.origin, 750)) != NULL) + { + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + { + //The only non-client to track is turret grenades and sentry guns + if ( (strcmp(blip->classname, "turret") != 0) && + (strcmp(blip->classname, "SentryGun") != 0) && + (strcmp(blip->classname, "laser_defense_gr") != 0)) + { + continue; + } + } + if (blip == ent->owner) + continue; + //dont aim at same team unless friendly fire is on + if ((blip->wf_team == ent->wf_team) && (((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE)==0)) + //if (blip->wf_team == ent->wf_team) + continue; + if (!blip->takedamage) + continue; + if (blip->health <= 0) + continue; + if (!visible(ent, blip)) + continue; + if (!infront(ent, blip)) + continue; + VectorSubtract(blip->s.origin, ent->s.origin, blipdir); + blipdir[2] += 16; + if ((target == NULL) || (VectorLength(blipdir) < VectorLength(targetdir))) + { + target = blip; + VectorCopy(blipdir, targetdir); + } + } + + if (target != NULL) + { + // target acquired, nudge our direction toward it + VectorNormalize(targetdir); + VectorScale(targetdir, 0.135, targetdir); + VectorAdd(targetdir, ent->movedir, targetdir); + VectorNormalize(targetdir); + VectorCopy(targetdir, ent->movedir); + vectoangles(targetdir, ent->s.angles); + speed = VectorLength(ent->velocity); + VectorScale(targetdir, speed, ent->velocity); + + //is this the first time we locked in? sound warning for the target + if (ent->homing_lock == 0) + { + gi.sound (target, CHAN_AUTO, gi.soundindex ("homelock.wav"), 1, ATTN_NORM, 0); +// gi.sound (target, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0); + ent->homing_lock = 1; + } + } + + ent->nextthink = level.time + .1; +} +//WF + +/* +================= +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, ent->mod); + } + 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, ent->mod); +// 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); +} + +//ERASER START +void RocketInformDanger(edict_t *self) +{ + int i; + edict_t *bot; + vec3_t org2, vec; + float dist; + + if (self->timestamp < (level.time - 5)) + { + G_FreeEdict(self); + return; + } + + VectorMA(self->s.origin, 0.25, self->velocity, org2); + + // scan for bot's to inform + for (i=0; ibot_client && (bot->bot_stats->combat < 4)) + continue; + + if ((fabs(bot->s.origin[0] - self->s.origin[0]) > 300) || + (fabs(bot->s.origin[1] - self->s.origin[1]) > 300)) + continue; + + // make sure rocket is heading somewhat towards this bot + VectorSubtract(org2, bot->s.origin, vec); + if (((dist=entdist(bot, self)) - VectorLength(vec)) > 75) + { // yup, inform bot of danger + bot->avoid_ent = self; + } + } + + self->nextthink = level.time + 0.3; +} +//ERASER END + +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage, int mod) +{ + 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; + +//WF & CCH - Add homing lock status + rocket->homing_lock = 0; + + rocket->mod = mod; + + //Set the team of the rocket + rocket->wf_team = self->wf_team; + + //Turn off homing state if the WF_NO_HOMING flag is set + //This is set here so the feature can be turned off mid-game + if (self->client && ((int)wfflags->value & WF_NO_HOMING)) + self->client->pers.homing_state = 0; + +//Eraser START: Modified to scan for Bots to inform of danger + rocket->timestamp = level.time; + rocket->nextthink = level.time + 0.1; + rocket->think = RocketInformDanger; +//ERASER END + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + + // See if this is a player and if they have homing on + if (self->client && self->client->pers.homing_state) + { + // If they have 5 cells, start homing, otherwise normal rocket think + if (self->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] >= 5) + { + self->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= 5; + rocket->nextthink = level.time + .1; + rocket->think = homing_think; + + //reduce damage of homing rockets to 1/2 of normal rocket + rocket->dmg = damage / 2; + + if (mod == MOD_ROCKET) mod = MOD_HOMINGROCKET; + } + else { + safe_cprintf(self, PRINT_HIGH, "No cells for homing missile.\n"); + rocket->nextthink = level.time + 8000/speed; + rocket->think = G_FreeEdict; + } + } + else { + rocket->nextthink = level.time + 8000/speed; + rocket->think = G_FreeEdict; + } +//WF + + 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); +} + +#define STANDING_HEAD_POS 20 //above center of body +#define STANDING_LEG_POS 12 //below center of body +#define CROUCHED_HEAD_POS 1 //above center of body //Acrid 5/99 was 8 +#define CROUCHED_LEG_POS 22 //below center of body //Acrid fix this too? + +/* +================= +fire_rail +================= +*/ +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int mod) +{ + vec3_t from; + vec3_t end; + trace_t tr; + edict_t *ignore; + int mask; + qboolean water; + qboolean ducked; + int leg_pos; + int head_pos; + char *name; + + int bodypos = 0; //Where on the body was the hit? + +if (wfdebug) gi.dprintf("Rail start damage = %d\n", damage); + + VectorMA (start, 8192, aimdir, end); + VectorCopy (start, from); + ignore = self; + water = false; + mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + while (ignore) + { + tr = gi.trace (from, NULL, NULL, end, ignore, mask); + + if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA); + water = true; + } + else + { + //ZOID--added so rail goes through SOLID_BBOX entities (gibs, etc) + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) || + (tr.ent->solid == SOLID_BBOX)) + ignore = tr.ent; + else + ignore = NULL; + + //5/99 Acrid ,to tell your the owner, so you dont shoot yourself on top of a dead body + if (tr.ent == self) + return; + + //Location damage! Was it a head or leg shot? + if (tr.ent->client && mod == MOD_SNIPERRIFLE) + { + if (tr.ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + head_pos = CROUCHED_HEAD_POS; + leg_pos = CROUCHED_LEG_POS; + ducked = true; + } + else + { + head_pos = STANDING_HEAD_POS; + leg_pos = STANDING_LEG_POS; + ducked = false; + } + + if (tr.endpos[2] < (tr.ent->s.origin[2] - leg_pos)) + { + bodypos = 1; // leg shot + mod = MOD_SNIPERRIFLE_LEG; + damage = damage * .333; // 1/3 damage for leg shot + //Dont slow them down if it's a team mate + if (tr.ent->wf_team != self->wf_team) + { + tr.ent->lame = 1; //slow them down! + if (tr.ent->client) + { + if (self->client) name = &self->client->pers.netname[0]; + else name = "(unknown)"; + safe_cprintf(tr.ent, PRINT_HIGH, "You were hit in the leg by %s!\n", name); + } + if (self->client) + { + if (tr.ent->client) name = &tr.ent->client->pers.netname[0]; + else name = "(unknown)"; + safe_cprintf(self, PRINT_HIGH, "You shot %s in the leg!\n",name); + } + } + } + else if (tr.endpos[2] > ((tr.ent->s.origin[2] + head_pos))) + { + bodypos = 2; + mod = MOD_SNIPERRIFLE_HEAD; + damage = damage * 3; // Increase damage for head shot + + //Squirt some blood! +//gi.dprintf("HEADSHOT\n"); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (60); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (SPLASH_BLOOD); + gi.multicast (tr.endpos, MULTICAST_PVS); + + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLOOD); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + else + { + bodypos = 0; + } + } + else if (!tr.ent->client) + { + //For non-clients, do less damage + damage = damage * .7; + } + + if (mod == MOD_WF_TURRET) + T_Damage (tr.ent, self, self->owner, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, mod); + else + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, mod); +if (wfdebug) +{ + gi.dprintf("Rail adjusted damage = %d.", damage); + if (tr.ent->client ) + gi.dprintf(": %s\n", tr.ent->client->pers.netname); + else + gi.dprintf("\n"); + +} + } + + VectorCopy (tr.endpos, from); + } + + + // send gun puff / flash + + //Dont do railgun train if sniper or sentry gun +// if (((int)wfflags->value & WF_NO_RAILGUN_EFFECT) || (mod == MOD_SNIPERRIFLE) || mod == MOD_SENTRY) + if (mod == MOD_SNIPERRIFLE || mod == MOD_SENTRY || mod == MOD_SNIPERRIFLE_LEG || mod == MOD_SNIPERRIFLE_HEAD) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_GUNSHOT); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + else + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, 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) +//WF - reduce the damage of bfg +// dmg = 5; + dmg = 2; +//WF + else + dmg = 10; + + ent = NULL; + while ((ent = findradius(ent, self->s.origin, 256)) != NULL) + { + if (ent == self) + continue; + + if (ent == self->owner) + continue; + + if (!ent->takedamage) + continue; + + if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) + continue; + +//ZOID + //don't target players in CTF + if (ctf->value && ent->client && + self->owner->client && + ent->client->resp.ctf_team == self->owner->client->resp.ctf_team) + continue; +//ZOID + + VectorMA (ent->absmin, 0.5, ent->size, point); + + VectorSubtract (point, self->s.origin, dir); + VectorNormalize (dir); + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, dir, end); + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner)) + T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER); + + // if we hit something that's not a monster or player we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (4); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (self->s.origin); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); + } + + self->nextthink = level.time + FRAMETIME; +} + + +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius) +{ + edict_t *bfg; + + bfg = G_Spawn(); + VectorCopy (start, bfg->s.origin); + VectorCopy (dir, bfg->movedir); + vectoangles (dir, bfg->s.angles); + VectorScale (dir, speed, bfg->velocity); + bfg->movetype = MOVETYPE_FLYMISSILE; + bfg->clipmask = MASK_SHOT; + bfg->solid = SOLID_BBOX; + bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST; + VectorClear (bfg->mins); + VectorClear (bfg->maxs); + bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2"); +// bfg->s.modelindex = gi.modelindex ("sprites/fire.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); +} \ No newline at end of file diff --git a/g_zip.c b/g_zip.c new file mode 100644 index 0000000..ac97c38 --- /dev/null +++ b/g_zip.c @@ -0,0 +1,284 @@ +/* + A very simplistic example of how to load the zip dll and make a call into it. + Note that none of the command line options are implemented in this example. + + */ + +//#define WIN32 +#define API + +#include "g_local.h" +#include +#include +#include +#include +#include +#include "g_zip.h" + +//======================= VERSION INFO ========================== +#ifndef __zipver_h /* prevent multiple inclusions */ +#define __zipver_h + +#define ZIP_DLL_VERSION "2.2\0" +#define COMPANY_NAME "Info-ZIP\0" + +#endif /* __zipver_h */ +//=============================================================== + +//#include +#include + +#define ZIP_DLL_NAME "ZIP32.DLL\0" + +#define DLL_WARNING "Cannot find %s."\ + " The Dll must be in the application directory, the path, "\ + "the Windows directory or the Windows System directory." +#define DLL_VERSION_WARNING "%s has the wrong version number."\ + " Insure that you have the correct dll's installed, and that "\ + "an older dll is not in your path or Windows System directory." + +int hFile; /* file handle */ + +ZCL ZpZCL; +LPZIPUSERFUNCTIONS lpZipUserFunctions; +HANDLE hZUF = (HANDLE)NULL; +HINSTANCE hUnzipDll; +HANDLE hFileList; +ZPOPT ZpOpt; +extern DWORD dwPlatformId; +HINSTANCE hZipDll; + + +/* Forward References */ +_DLL_ZIP ZpArchive; +_ZIP_USER_FUNCTIONS ZpInit; +ZIPSETOPTIONS ZpSetOptions; + +void FreeUpMemoryZip(void); +int WINAPI DummyPassword(char *, char *, int, char *); +int far DummyPrint(FILE *, unsigned int, char *); +char far * WINAPI DummyComment(char far *); + +#ifdef WIN32 +extern BOOL IsNT(VOID); +#endif + +/**************************************************************************** + + FUNCTION: Main(int argc, char **argv) + +****************************************************************************/ +#ifdef __BORLANDC__ +# ifdef WIN32 +#pragma argsused +# endif +#endif + +// Zips up filename into zipfile +// NOTE: make sure the current dir is the directory in which the .dll files are +// in before calling this! +int G_ZipFile(char *zipfile, char *filename) +{ +LPSTR szFileList; +char **index, *sz; +int retcode/*, i*/, cc; +//DWORD dwVerInfoSize; +//DWORD dwVerHnd; +char szFullPath[PATH_MAX]; +char *ptr; +//HANDLE hMem; /* handle to mem alloc'ed */ + +if (!filename || !zipfile) + return 0; /* Exits if not proper number of arguments */ + +hZUF = GlobalAlloc( GPTR, (DWORD)sizeof(ZIPUSERFUNCTIONS)); +if (!hZUF) + { + return 0; + } +lpZipUserFunctions = (LPZIPUSERFUNCTIONS)GlobalLock(hZUF); + +if (!lpZipUserFunctions) + { + GlobalFree(hZUF); + return 0; + } + +lpZipUserFunctions->print = &DummyPrint; +lpZipUserFunctions->password = DummyPassword; +lpZipUserFunctions->comment = DummyComment; + +/* Let's go find the dll */ +if (SearchPath( + NULL, /* address of search path */ + ZIP_DLL_NAME, /* address of filename */ + NULL, /* address of extension */ + PATH_MAX, /* size, in characters, of buffer */ + szFullPath, /* address of buffer for found filename */ + &ptr /* address of pointer to file component */ + ) == 0) + { +// char str[256]; +// wsprintf (str, DLL_WARNING, ZIP_DLL_NAME); +// printf("%s\n", str); + FreeUpMemoryZip(); + return 0; + } + +/* Okay, now we know that the dll exists, and has the proper version + * information in it. We can go ahead and load it. + */ +hZipDll = LoadLibrary(ZIP_DLL_NAME); +if (hZipDll != NULL) + { + (_DLL_ZIP)ZpArchive = (_DLL_ZIP)GetProcAddress(hZipDll, "ZpArchive"); + (ZIPSETOPTIONS)ZpSetOptions = (ZIPSETOPTIONS)GetProcAddress(hZipDll, "ZpSetOptions"); + if (!ZpArchive || !ZpSetOptions) + { +// char str[256]; +// wsprintf (str, "Could not get entry point to %s", ZIP_DLL_NAME); +// MessageBox((HWND)NULL, str, "Info-ZIP Example", MB_ICONSTOP | MB_OK); + FreeUpMemoryZip(); + return 0; + } + } +else + { +// char str[256]; +// wsprintf (str, "Could not load %s", ZIP_DLL_NAME); +// printf("%s\n", str); + FreeUpMemoryZip(); + return 0; + } + +(_ZIP_USER_FUNCTIONS)ZpInit = (_ZIP_USER_FUNCTIONS)GetProcAddress(hZipDll, "ZpInit"); +if (!ZpInit) + { +// printf("Cannot get address of ZpInit in Zip dll. Terminating..."); + FreeLibrary(hZipDll); + FreeUpMemoryZip(); + return 0; + } +if (!(*ZpInit)(lpZipUserFunctions)) + { +// printf("Application functions not set up properly. Terminating..."); + FreeLibrary(hZipDll); + FreeUpMemoryZip(); + return 0; + } + +/* Here is where the action starts */ +ZpOpt.fSuffix = FALSE; /* include suffixes (not yet implemented) */ +ZpOpt.fEncrypt = FALSE; /* true if encryption wanted */ +ZpOpt.fSystem = FALSE; /* true to include system/hidden files */ +ZpOpt.fVolume = FALSE; /* true if storing volume label */ +ZpOpt.fExtra = FALSE; /* true if including extra attributes */ +ZpOpt.fNoDirEntries = FALSE; /* true if ignoring directory entries */ +//ZpOpt.fDate = FALSE; /* true if excluding files earlier than a +// specified date */ +ZpOpt.fVerbose = FALSE; /* true if full messages wanted */ +ZpOpt.fQuiet = TRUE; /* true if minimum messages wanted */ +ZpOpt.fCRLF_LF = FALSE; /* true if translate CR/LF to LF */ +ZpOpt.fLF_CRLF = FALSE; /* true if translate LF to CR/LF */ +ZpOpt.fJunkDir = TRUE; /* true if junking directory names */ +ZpOpt.fRecurse = FALSE; /* true if recursing into subdirectories */ +ZpOpt.fGrow = FALSE; /* true if allow appending to zip file */ +ZpOpt.fForce = FALSE; /* true if making entries using DOS names */ +ZpOpt.fMove = FALSE; /* true if deleting files added or updated */ +ZpOpt.fUpdate = FALSE; /* true if updating zip file--overwrite only + if newer */ +ZpOpt.fFreshen = FALSE; /* true if freshening zip file--overwrite only */ +ZpOpt.fJunkSFX = FALSE; /* true if junking sfx prefix*/ +ZpOpt.fLatestTime = FALSE; /* true if setting zip file time to time of + latest file in archive */ +ZpOpt.fComment = FALSE; /* true if putting comment in zip file */ +ZpOpt.fOffsets = FALSE; /* true if updating archive offsets for sfx + files */ +ZpOpt.fDeleteEntries = FALSE; /* true if deleting files from archive */ +ZpOpt.Date[0] = '\0'; /* Not using, set to 0 length */ +getcwd(ZpOpt.szRootDir, MAX_PATH); /* Set directory to current directory */ + +ZpZCL.argc = 1; /* number of files to archive - adjust for the + actual number of file names to be added */ +ZpZCL.lpszZipFN = (LPSTR) zipfile; /* archive to be created/updated */ + +/* Copy over the appropriate portions of argv, basically stripping out argv[0] + (name of the executable) and argv[1] (name of the archive file) + */ + +hFileList = GlobalAlloc( GPTR, 0x10000L); +if ( hFileList ) + { + szFileList = (char far *)GlobalLock(hFileList); + } +index = (char **)szFileList; +cc = (sizeof(char *) * ZpZCL.argc); +sz = szFileList + cc; +/* +for (i = 0; i < ZpZCL.argc; i++) + { + cc = lstrlen(argv[i+2]); + lstrcpy(sz, argv[i+2]); + index[i] = sz; + sz += (cc + 1); + } +*/ + cc = lstrlen(filename); + lstrcpy(sz, filename); + index[0] = sz; + sz += (cc + 1); +ZpZCL.FNV = (char **)szFileList; // list of files to archive + + +/* Set the options */ +ZpSetOptions(ZpOpt); + +/* Go zip 'em up */ +retcode = ZpArchive(ZpZCL); +if (retcode != 0) + gi.dprintf("Error during archiving.\nUnable to create \"%s\"\n", zipfile); + +GlobalUnlock(hFileList); +GlobalFree(hFileList); +FreeUpMemoryZip(); +FreeLibrary(hZipDll); +return (retcode == 0); +} + +void FreeUpMemoryZip(void) +{ +if (hZUF) + { + GlobalUnlock(hZUF); + GlobalFree(hZUF); + } +} + +/* Password entry routine - see password.c in the wiz directory for how + this is actually implemented in Wiz. If you have an encrypted file, + this will probably give you great pain. Note that none of the + parameters are being used here, and this will give you warnings. + */ +int WINAPI DummyPassword(char *p, char *n, int m, char * name) +{ +return 1; +} + +/* Dummy "print" routine that simply outputs what is sent from the dll */ +int far DummyPrint(FILE *buf, unsigned int size, char *msg) +{ +//printf("%s", buf); +return (unsigned int) size; +} + + +/* Dummy "comment" routine. See comment.c in the wiz directory for how + this is actually implemented in Wiz. This will probably cause you + great pain if you ever actually make a call into it. + */ +char far * WINAPI DummyComment(char far *szBuf) +{ +szBuf[0] = '\0'; +return szBuf; +} + diff --git a/g_zip.h b/g_zip.h new file mode 100644 index 0000000..13db284 --- /dev/null +++ b/g_zip.h @@ -0,0 +1,64 @@ +/* + Example header file +*/ +#ifndef _EXAMPLE_H +#define _EXAMPLE_H + +#include +//#include /* required for all Windows applications */ +#include +#include +//#include +#include +#include + +//=================================== STRUCTS ============================== +#ifndef _ZIP_STRUCTS_H +#define _ZIP_STRUCTS_H + +#ifndef Far +# define Far far +#endif + +# define far +# define _far +# define __far +# define near +# define _near +# define __near + +#ifndef PATH_MAX +# define PATH_MAX 128 +#endif + +#include "z_api.h" + +#endif /* _ZIP_STRUCTS_H */ +//================================================================== + +/* Defines */ +#ifndef MSWIN +#define MSWIN +#endif + +typedef int (WINAPI * _DLL_ZIP)(ZCL); +typedef int (WINAPI * _ZIP_USER_FUNCTIONS)(LPZIPUSERFUNCTIONS); +typedef BOOL (WINAPI * ZIPSETOPTIONS)(ZPOPT); + +/* Global variables */ + +extern LPZIPUSERFUNCTIONS lpZipUserFunctions; + +extern HINSTANCE hZipDll; + +extern int hFile; /* file handle */ + +/* Global functions */ + +extern _DLL_ZIP ZpArchive; +extern _ZIP_USER_FUNCTIONS ZpInit; +int WINAPI DisplayBuf(char far *, unsigned long int); +extern ZIPSETOPTIONS ZpSetOptions; + +#endif /* _EXAMPLE_H */ + diff --git a/game.def b/game.def new file mode 100644 index 0000000..4461243 --- /dev/null +++ b/game.def @@ -0,0 +1,2 @@ +EXPORTS + GetGameAPI diff --git a/game.h b/game.h new file mode 100644 index 0000000..17317ad --- /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/grapple.c b/grapple.c new file mode 100644 index 0000000..5f67422 --- /dev/null +++ b/grapple.c @@ -0,0 +1,616 @@ +/***************************************************************** + + Grapple source code - by Acrid-, acridcola@hotmail.com + + .............................................................. + + This file is Copyright(c) 1999, Acrid-, All Rights Reserved. + + .............................................................. + + + Should you decide to release a modified version of the Grapple, you + MUST include the following text (minus the BEGIN and END lines) in + the documentation for your modification, and also on all web pages + related to your modification, should they exist. + + --- BEGIN --- + + The Grapple and related code ,minus the pushcode or lines marked tut, + are a product of Acrid- designed for Weapons Factory, and is available as + part of the Weapons Factory Source Code or a seperate tutorial. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Acrid- at: + acridcola@hotmail.com + + --- END --- + + have fun, + + Acrid- + + *****************************************************************/ + +//#define USE_OLD_GRAPPLE 1 + + +/* +============================== +Grapple Code by Acrid + +4/6/99 +code in other files: noted by newgrap 4/99 +g_cmds.c +g_items.c +g_misc.c +g_phys.c +p_client.c +wf_local_client.h +ctf.h +I looked a few tutors but they never quite stopped bouncing on my system. +This code stops all bounce by turning off gravity and changing velocity +to 100 when length is 64. +It also uses pusher code from a tutorial I found to correct bounce when +grappled to objects like doors,plats etc. +Plus you can use this code for a normal ctfsytle grapple,a offhand +ctfsytle grapple and a ctfsytle weapon grapple. +WF notes: +bind key +grapple for offhand +bind key grapple for normal +use grapple for weapon_grapple +by using BUTTON_USE offhand alias will no longer get reversed. +============================== +*/ +#include "g_local.h" +#define DELAY_TIME 5000 + +void CTFFireGrapple (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect); +void CTFGrappleFire (edict_t *ent, vec3_t g_offset, int damage, int effect); + + +void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +qboolean Ended_Grappling (gclient_t *client) + { + return (!(client->buttons & BUTTON_USE) && client->oldbuttons & BUTTON_USE); + } + +qboolean Is_Grappling (gclient_t *client) + { + return (client->ctf_grapple == NULL) ? false : true; + } +void CTFGrappleTouch2 (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + float volume = 1.0; + + // Release if hitting its owner + if (other == self->owner) + return; + //tut s + if (!Is_Grappling(self->owner->client) && self->health == 0) + { botDebugPrint("!isgrap\n"); + return; + }// tut e + + self->health = 0;//tut + + if (self->owner->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY) + return; + + if (surf && surf->flags & SURF_SKY) + { + botDebugPrint("reset\n"); + CTFPlayerResetGrapple2(self->owner); + return; + } + +// VectorCopy(vec3_origin, self->velocity); + + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other != g_edicts && other->clipmask == MASK_SHOT)//tut + return;//tut + + if (other->takedamage || other->client) + { //ERASER ADDED || other->client + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, 0, MOD_GRAPPLE); + CTFResetGrapple2(self); + return; + } + + self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_PULL; // we're on hook + self->enemy = other; + + self->solid = SOLID_NOT; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grpull.wav"), volume, ATTN_NORM, 0); + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhit.wav"), volume, ATTN_NORM, 0); + + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPARKS); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); +//end zoid + + //Pusher code + if (other != g_edicts && other->inuse && (other->movetype == MOVETYPE_PUSH || + other->movetype == MOVETYPE_STOP)) + { + other->mynoise2 = self; + self->owner->client->hook_touch = other; +// self->enemy = other; + self->groundentity = NULL; + self->flags |= FL_TEAMSLAVE; + } + + VectorClear(self->velocity); + VectorClear(self->avelocity); + self->touch = NULL; + self->movetype = MOVETYPE_NONE; + self->delay = level.time + DELAY_TIME; + self->owner->client->on_hook = true; + self->owner->groundentity = NULL; + CTFGrapplePull2(self->owner); +} + +void CTFGrappleThink2( edict_t *self ) +{ + if (!self->owner || !self->owner->client) + { + G_FreeEdict(self); + return; + } + + if ((self->owner->health <= 0) || (self->owner->client->ctf_grapple != self)) + { + gclient_t *cl; + + cl = self->owner->client; + + if (cl->ctf_grapple == self) + { + cl->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; //sever codefix test + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), 1, ATTN_NORM, 0); + cl->ctf_grapple = NULL; + cl->ctf_grapplereleasetime = level.time; + cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + } + + G_FreeEdict(self); + + return; + } + //start of tut + if (level.time > self->delay) + self->prethink = CTFPlayerResetGrapple2; + else + { + if (self->owner->client->hook_touch) + { + edict_t *obj = self->owner->client->hook_touch; + + if (obj == g_edicts) + { + CTFPlayerResetGrapple2(self->owner); + botDebugPrint("resetting 1\n"); + return; + } + if (obj->inuse == false) + { + CTFPlayerResetGrapple2(self->owner); + botDebugPrint("resetting 2\n"); + return; + } + if (obj->deadflag == DEAD_DEAD) + { + CTFPlayerResetGrapple2(self->owner); + botDebugPrint("resetting 3\n"); + return; + } + } + self->nextthink = level.time + FRAMETIME; + } +} + +void CTFGrappleDrawCable2 (edict_t *self) +{ + vec3_t offset, start, end, f, r; + vec3_t dir; + float distance; + + AngleVectors (self->owner->client->v_angle, f, r, NULL); + VectorSet(offset, 16, 16, self->owner->viewheight-8); + P_ProjectSource (self->owner->client, self->owner->s.origin, offset, f, r, start); + + VectorSubtract(start, self->owner->s.origin, offset); + + VectorSubtract (start, self->s.origin, dir); + distance = VectorLength(dir); + // don't draw cable if close + if (distance < 64) + return; + +#if 0 + if (distance > 256) + return; + + // check for min/max pitch + vectoangles (dir, angles); + if (angles[0] < -180) + angles[0] += 360; + if (fabs(angles[0]) > 45) + return; + + trace_t tr; //!! + + tr = gi.trace (start, NULL, NULL, self->s.origin, self, MASK_SHOT); + if (tr.ent != self) { + CTFResetGrapple(self); + return; + } +#endif + + + VectorCopy (self->s.origin, end); + + gi.WriteByte (svc_temp_entity); +#if 1 //def USE_GRAPPLE_CABLE + gi.WriteByte (TE_GRAPPLE_CABLE); + gi.WriteShort (self->owner - g_edicts); + gi.WritePosition (self->owner->s.origin); + gi.WritePosition (end); + gi.WritePosition (offset); +#else + gi.WriteByte (TE_MEDIC_CABLE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (end); + gi.WritePosition (start); +#endif + gi.multicast (self->s.origin, MULTICAST_PVS); +} + + +void CTFFireGrapple2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *grapple; + trace_t tr; + +#ifdef USE_OLD_GRAPPLE + CTFFireGrapple (self, start, dir, damage, speed, effect); + return; +#endif + + VectorNormalize (dir); + + grapple = G_Spawn(); + VectorCopy (start, grapple->s.origin); + VectorCopy (start, grapple->s.old_origin); + vectoangles (dir, grapple->s.angles); + VectorScale (dir, speed, grapple->velocity); + VectorSet(grapple->avelocity, 0, 0, 500);//tut + grapple->classname = "hook";//tut + grapple->movetype = MOVETYPE_FLYMISSILE; + grapple->clipmask = MASK_SHOT; + grapple->svflags |= SVF_DEADMONSTER;//tut + grapple->solid = SOLID_BBOX; + grapple->s.effects |= effect; + VectorClear (grapple->mins); + VectorClear (grapple->maxs); + + grapple->s.modelindex = gi.modelindex ("models/weapons/grapple/hook/tris.md2"); + grapple->owner = self; + grapple->touch = CTFGrappleTouch2; + grapple->delay = level.time + DELAY_TIME;//tut +// grapple->nextthink = level.time;//tut + grapple->nextthink = level.time + FRAMETIME; + grapple->think = CTFGrappleThink2; + grapple->prethink = CTFGrappleDrawCable2; + grapple->health = 100;//tut + grapple->svflags = SVF_MONSTER;//tut + grapple->dmg = damage; + self->client->ctf_grapple = grapple; + self->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + + gi.linkentity (grapple); + + tr = gi.trace (self->s.origin, NULL, NULL, grapple->s.origin, grapple, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (grapple->s.origin, -10, dir, grapple->s.origin); + grapple->touch (grapple, tr.ent, NULL, NULL); + } + + +} +void CTFGrappleFire2 (edict_t *ent, vec3_t g_offset, int damage, int effect) +{ + vec3_t forward, right; + vec3_t start; + vec3_t offset; + float volume = 1.0; + + //Feign 5/99 + if (ent->client->pers.feign) + return; + +#ifdef USE_OLD_GRAPPLE + CTFGrappleFire (ent, g_offset, damage, effect); + return; +#endif + + if (ent->client->ctf_grapple) //tut s not needed? + { + return; + }//tut e + + if (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + return; // it's already out + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + VectorSet(offset, 24, 8, ent->viewheight-8+2); + VectorAdd (offset, g_offset, offset); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (ent->client->silencer_shots) + volume = 0.2; + + gi.sound (ent, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grfire.wav"), volume, ATTN_NORM, 0); + CTFFireGrapple2 (ent, start, forward, damage, CTF_GRAPPLE_SPEED, effect); + + ent->client->hook_touch = NULL;//tut + +#if 0 + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_BLASTER); + gi.multicast (ent->s.origin, MULTICAST_PVS); +#endif + + PlayerNoise(ent, start, PNOISE_WEAPON); + +//Eraser START: record this position, so we drop a grapple node here, rather than where the player is when they leave the ground + if (!ent->bot_client) + { botDebugPrint("Grapple pull\n"); + VectorCopy(ent->s.origin, ent->animate_org); + } +//ERASER END +} +void CTFWeapon_Grapple_Fire2 (edict_t *ent) +{ + int damage; + + damage = 10; + CTFGrappleFire2 (ent, vec3_origin, damage, 0); +// ent->client->ps.gunframe++;//COed by Gregg Reno +} + +void CTFWeapon_Grapple2 (edict_t *ent) +{ + static int pause_frames[] = {10, 18, 27, 0}; + static int fire_frames[] = {6, 0}; + int prevstate; + +#ifdef USE_OLD_GRAPPLE + CTFWeapon_Grapple (ent); + return; +#endif + + // if the the attack button is still down, stay in the firing frame + if ((ent->client->buttons & BUTTON_ATTACK) && + ent->client->weaponstate == WEAPON_FIRING && + ent->client->ctf_grapple) + { + botDebugPrint("firing\n"); + ent->client->ps.gunframe = 9; + } + if (!(ent->client->buttons & BUTTON_ATTACK) && ent->client->ctf_grapple) + { botDebugPrint("reset 3\n"); + CTFResetGrapple2(ent->client->ctf_grapple); + if (ent->client->weaponstate == WEAPON_FIRING) + ent->client->weaponstate = WEAPON_READY; + } + + + if (ent->client->newweapon && ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY && + ent->client->weaponstate == WEAPON_FIRING) + { botDebugPrint("state\n"); + // he wants to change weapons while grappled + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 32; + } + + prevstate = ent->client->weaponstate; + Weapon_Generic (ent, 5, 9, 31, 36, pause_frames, fire_frames,CTFWeapon_Grapple_Fire2); + + // if we just switched back to grapple, immediately go to fire frame + if (prevstate == WEAPON_ACTIVATING && + ent->client->weaponstate == WEAPON_READY && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + { + if (!(ent->client->buttons & BUTTON_ATTACK)) + ent->client->ps.gunframe = 9; + else + ent->client->ps.gunframe = 5; + ent->client->weaponstate = WEAPON_FIRING; + } +} +// ent is player +void CTFPlayerResetGrapple2(edict_t *ent) +{ + if (ent->client && ent->client->ctf_grapple) +// CTFResetGrapple(ent->client->ctf_grapple); + CTFResetGrapple2(ent->client->ctf_grapple); +} + +// self is grapple, not player +void CTFResetGrapple2(edict_t *self) +{ + float volume = 1.0; +// gclient_t *cl; + + + edict_t *owner = self->owner;//tut + gclient_t *client = self->owner->client;//tut replaces cl + edict_t *link = self->teamchain;//tut used for map ents push code + client->on_hook = false;//tut + client->hook_touch = NULL;//tut + botDebugPrint("SelfReset %s\n",self->classname); + + #ifdef USE_OLD_GRAPPLE + CTFResetGrapple(self); + return; +#endif + + + // if (self->owner->client->ctf_grapple) +// { + if (client->ctf_grapple != NULL)//tut section + { + + client->ctf_grapple = NULL;//replaced cl + VectorClear(client->oldvelocity); + self->think = NULL; + if (self->enemy) + { + self->enemy->mynoise2 = NULL; + } + //tut section end + + if (self->owner->client->silencer_shots) + volume = 0.2; + + if ((self->owner) && (self->owner->client)) + { + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), volume, ATTN_NORM, 0); +// cl = self->owner->client; +// client->ctf_grapple = NULL; + client->ctf_grapplereleasetime = level.time; + client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; //sever codefix test + }//tut + G_FreeEdict(self);//tut + }//tut + +} +void CTFGrapplePull2(edict_t *player) +{ + vec3_t hookDir ,v; + vec_t vlen; + edict_t *self = player->client->ctf_grapple; + +#ifdef USE_OLD_GRAPPLE + CTFGrapplePull(player); + return; +#endif + + if (strcmp(player->client->pers.weapon->classname, "weapon_grapple") == 0 && + !player->client->newweapon && + player->client->weaponstate != WEAPON_FIRING && + player->client->weaponstate != WEAPON_ACTIVATING) + { + CTFResetGrapple2(self); + return; + } + + if (self->enemy) + { + if (self->enemy->solid == SOLID_NOT) + { + CTFResetGrapple2(self); + return; + } + if (self->enemy->solid == SOLID_BBOX) + { + VectorScale(self->enemy->size, 0.5, v); + VectorAdd(v, self->enemy->s.origin, v); + VectorAdd(v, self->enemy->mins, self->s.origin); + gi.linkentity (self); + } else + VectorCopy(self->enemy->velocity, self->velocity); + if (self->enemy->takedamage && + !CheckTeamDamage (self->enemy, self->owner)) + { + float volume = 1.0; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + T_Damage (self->enemy, self, self->owner, self->velocity, self->s.origin, vec3_origin, 1, 1, 0, MOD_GRAPPLE); + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhurt.wav"), volume, ATTN_NORM, 0); + } + // he died /ERASER ADDED || self->enemy->deadflag + if (!self->enemy || self->enemy->deadflag) + { + CTFResetGrapple2(self); + return; + } + } + + VectorSubtract(self->s.origin, player->s.origin,hookDir); + vlen = VectorNormalize(hookDir); + //vector speeds + if (vlen < 64) + { + VectorScale(hookDir, 100,player->velocity); + } + else + { + VectorScale(hookDir, CTF_GRAPPLE_PULL_SPEED,player->velocity);//tut + } + VectorCopy(hookDir, player->movedir);//tut + + if (player->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + { + +//sever codefix test + /* vec3_t forward, up; + + AngleVectors (self->owner->client->v_angle, forward, NULL, up); + VectorCopy(self->owner->s.origin, v); + v[2] += self->owner->viewheight; + VectorSubtract (self->s.origin, v, hookDir);*/ +//server codefix end + + if (player->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL && + vlen < 64) { + float volume = 1.0; + + if (player->client->silencer_shots) + volume = 0.2; + + self->owner->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; //sever codefix test + gi.sound (player, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grhang.wav"), volume, ATTN_NORM, 0); + player->client->ctf_grapplestate = CTF_GRAPPLE_STATE_HANG; + } + } + /*To move the player off the ground just a bit + so he doesn't staystuck (version 3.17 bug)tut*/ + if (player->velocity[2] > 0) + { + vec3_t traceTo; + trace_t trace; + // find the point immediately above the player's origin + VectorCopy(player->s.origin, traceTo); + traceTo[2] += 1; + // trace to it + trace = gi.trace(traceTo, player->mins, player->maxs, traceTo, player, MASK_PLAYERSOLID); + // if there isn't a solid immediately above the player + if (!trace.startsolid) + { + player->s.origin[2] += 1; // make sure player off ground + } + } + + } + diff --git a/j_diesease.c b/j_diesease.c new file mode 100644 index 0000000..bde2138 --- /dev/null +++ b/j_diesease.c @@ -0,0 +1,158 @@ +#include "g_local.h" + +void Cure_Disease(edict_t *self) +{ + if (self->owner) + { + self->owner->disease = 0; + self->owner->s.effects &= ~EF_FLIES; + } + G_FreeEdict(self); +} + +void Disease_Think (edict_t *self) +{ + edict_t *ent; + vec3_t dir; + trace_t tr; + int damage; + float points; + vec3_t v; + float rnum; + rnum = random(); + + if (!self->owner) + { + Cure_Disease(self); + return; + } + + //stop disease after the person dies - GREGG + if(self->owner->health < 1) + { + Cure_Disease(self); + return; + } + + //If disease flag is clear, then they are cured + if(!self->owner->disease) + { + Cure_Disease(self); + return; + } + + //Move the entity to where the target is + VectorAdd (self->target_ent->mins, self->target_ent->maxs, v); + VectorMA (self->target_ent->s.origin, 0.5, v, v); + VectorSubtract (self->s.origin, v, v); + VectorSubtract (self->owner->s.origin, self->s.origin, dir); + VectorCopy(self->owner->s.origin,self->s.origin); + + //Random amount of damage + damage = rndnum (4,11); + points = damage - 0.5 * (VectorLength (v)); + if (points > wf_game.grenade_damage[GRENADE_TYPE_PLAGUE] ) points = wf_game.grenade_damage[GRENADE_TYPE_PLAGUE] ; + + T_Damage (self->owner, self, self->target_ent, dir, self->owner->s.origin,vec3_origin, damage, (int)points, DAMAGE_NO_KNOCKBACK, MOD_DISEASE); + + //Throw up 10% of the time //JR thought I should raise this chance then + if(rnum < 0.10) + { + ThrowUpNow(self->owner); + T_Damage (self->owner, self, self->target_ent, dir, self->owner->s.origin,vec3_origin, damage, (int)points, DAMAGE_NO_KNOCKBACK,MOD_DISEASE); + } + + self->nextthink = level.time + 1.5; + ent = NULL; + + //Infect others who are close + while ((ent = findradius(ent, self->s.origin, 128)) && ent != NULL) + { + if (!ent->client) //only infect other players + continue; + + if (self->owner == ent) //don't infect self + continue; + + //Don't go through walls + if (!visible(self, ent)) + continue; + + if (!ent->takedamage) + continue; + + if (!CanDamage (ent, self)) + continue; + + if (!CanDamage (ent, self->target_ent)) + continue; + + tr = gi.trace (self->s.origin, NULL, NULL, ent->s.origin, self, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + + infect_person(ent,self->target_ent); + } +} + +void infect_person(edict_t *target, edict_t *owner) +{ + edict_t *plague; + + //Valid entities? + if (!target) return; + if (!owner) return; + + //Ignore if either entity went away + if (!target->inuse) return; + if (!owner->inuse) return; + + //Only infect players + if (!target->client) + return; + if (!owner->client) + return; + + // ignore if on same team + if (target->wf_team == owner->wf_team) + { + return; + } + + //Don't disease again + if (target->disease) + { + return; + } + + //Can't infect a nurse + //Shouldn't test the class number. Instead, don't infect them + //if the can create a biosentry or healing depot + if ((target->client->player_special & SPECIAL_HEALING) || + (target->client->player_special & SPECIAL_BIOSENTRY) ) + return; + + safe_cprintf(target, PRINT_HIGH, "You've been infected by %s.\n", owner->client->pers.netname); + safe_cprintf(owner, PRINT_HIGH, "You have infected %s!\n",target->client->pers.netname); + + target->disease++; + target->diseased_by = owner; + target->s.effects |= EF_FLIES; + + plague = G_Spawn(); + plague->movetype = MOVETYPE_NOCLIP; + plague->clipmask = MASK_SHOT; + plague->solid = SOLID_NOT; + VectorClear (plague->mins); + VectorClear (plague->maxs); + plague->owner = target; + plague->target_ent = owner; + plague->s.modelindex = gi.modelindex ("sprites/s_bubble.sp2"); + plague->nextthink = level.time + 1.5; + plague->think = Disease_Think; + plague->s.effects |= EF_GIB; + plague->s.sound = gi.soundindex ("infantry/inflies1.wav"); + plague->classname = "Disease"; + VectorCopy(target->s.origin,plague->s.origin); + gi.linkentity (plague); +} diff --git a/j_fire.c b/j_fire.c new file mode 100644 index 0000000..d985e78 --- /dev/null +++ b/j_fire.c @@ -0,0 +1,264 @@ +#include "g_local.h" +void unfreeze_player(edict_t *ent); + + +//Remove all the flames attached to a player +void Remove_Player_Flames (edict_t *ent) +{ + edict_t *blip = NULL; + int i; + + if (!ent) return; + if (!ent->Flames) return; + + for (i=1, blip=g_edicts+i ; i < globals.num_edicts ; i++,blip++) + { + if (blip->owner == ent) + { + + //Flames + if (!strcmp(blip->classname, "fire") ) + { + //set damage to zero so flame think function will clean it up + blip->dmg = 0; + } + } + } + + ent->Flames = 0; +} + +void Remove_Flame(edict_t *ent) +{ + if (ent->owner) + { + ent->owner->Flames--; + if (ent->owner->Flames < 0) ent->owner->Flames = 0; + } + G_FreeEdict (ent); +} + +void Fire_Think (edict_t *self) +{ + vec3_t dir; + int damage; + float points; + vec3_t v; + int mod; + + //Must have an owner + if (!self->owner) + { + G_FreeEdict (self); + return; + } + + if (level.time > self->delay) + { + Remove_Flame(self); + return; + } + + if (self->dmg <= 0) //Something else wants this flame removed + { + Remove_Flame(self); + return; + } + + // ++TeT add check for freed or dead owners + if (!self->owner->inuse || (self->owner->Flames == 0)) + { + Remove_Flame(self); + return; + } + + + //If player or flame is in water + if (self->owner->waterlevel || self->waterlevel) + { + //Play this if in water + if ((self->owner) && (self->owner->client)) + gi.sound (self->owner, CHAN_WEAPON, gi.soundindex ("world/airhiss1.wav"), 1, ATTN_NORM, 0); + Remove_Flame(self); + return; + } + + damage = self->dmg; + mod = self->mod; + if (mod == 0) mod = MOD_WF_FLAME; + + VectorAdd (self->orb->mins, self->orb->maxs, v); + VectorMA (self->orb->s.origin, 0.5, v, v); + VectorSubtract (self->s.origin, v, v); + points = damage - 0.5 * (VectorLength (v)); + + if (points < 8) points = 8; + + VectorSubtract (self->owner->s.origin, self->s.origin, dir); + + VectorCopy(self->owner->s.origin,self->s.origin); + if (self->PlasmaDelay < level.time) + { + T_Damage (self->owner, self, self->orb, dir, self->owner->s.origin,vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, mod); + self->PlasmaDelay = level.time + 0.8; + } + self->nextthink = level.time + .2; +} + +void burn_person(edict_t *target, edict_t *owner, int damage, int mod) +{ + edict_t *flame; + + if (target->frozen) + unfreeze_player(target); //Thaw them out! + + if (!target->takedamage) return; + + if (target->wf_team == owner->wf_team) return; + + if (target->Flames > 1) return; + + //If they are not a client, only burn decoys and sentry guns + if (!target->client) + { + if ((strcmp(target->classname, "decoy") != 0) && + (strcmp(target->classname, "SentryGun") != 0)) + return; + } + + //Don't burn them if they have flame resistance + if ((target->client) && (target->client->player_special & SPECIAL_FLAME_RESISTANCE) ) + { + gi.sound (target, CHAN_WEAPON, gi.soundindex ("items/protect4.wav"), 1, ATTN_NORM, 0); + return; + } + + //burn em baby, but dead people don't scream (TeT) + if ((target->client) && (target->health > 0)) + gi.sound (target, CHAN_WEAPON, gi.soundindex ("scream.wav"), 1, ATTN_NORM, 0); + + target->Flames++; + flame = G_Spawn(); + flame->movetype = MOVETYPE_NOCLIP; + flame->clipmask = MASK_SHOT; + flame->solid = SOLID_NOT; + flame->s.effects |= EF_ANIM_ALLFAST|EF_BFG|EF_HYPERBLASTER;//|EF_GRENADE|EF_BLASTER; + flame->velocity[0] = target->velocity[0]; + flame->velocity[1] = target->velocity[1]; + flame->velocity[2] = target->velocity[2]; + + VectorClear (flame->mins); + VectorClear (flame->maxs); + flame->s.modelindex = gi.modelindex ("sprites/fire.sp2"); + flame->owner = target; + flame->orb = owner; + flame->delay = level.time + 10; //G.R. Made it larger to do more damage + flame->nextthink = level.time + .8; + flame->PlasmaDelay = level.time + 0.8; + flame->think = Fire_Think; + flame->SniperDamage = damage+2;//JR increased that + flame->classname = "fire"; + flame->s.sound = gi.soundindex ("weapons/bfg__l1a.wav"); + flame->dmg = damage; + flame->mod = mod; + gi.linkentity (flame); + + VectorCopy(target->s.origin,flame->s.origin); +} + +static void Napalm_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) + { + return; + } + return; +} + +void Napalm_Think(edict_t *self) +{ + edict_t *ent; + vec3_t v; + float points; + float dist; + ent = NULL; + while ((ent = findradius(ent, self->s.origin, 64)) != NULL) + { + if (!ent->takedamage) + continue; + if (!CanDamage (ent, self)) + continue; + if (!CanDamage (ent, self->owner)) + continue; + // ++TeT + if (ent->wf_team == self->wf_team) + 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; + burn_person(ent, self->owner, 4, MOD_NAPALMGRENADE); + + T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, 3, 25, DAMAGE_ENERGY,0); + } + self->nextthink = level.time + 0.8; + + if (self->delay think = G_FreeEdict; + if (self->waterlevel) + { + G_FreeEdict (self); + return; + } +} + +//Use Gregg's function for now +void fire_napalm2 (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|EF_BLASTER|*/EF_ANIM_ALLFAST|EF_BFG|EF_HYPERBLASTER;; + // VectorClear (grenade->mins); + // VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("sprites/fire.sp2"); + grenade->owner = self; + grenade->wf_team = self->wf_team; + grenade->touch = Napalm_Touch; + grenade->think = Napalm_Think; + grenade->nextthink = level.time + 0.1; + grenade->delay = level.time + timer; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "napalm"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -32, -32, -32); + VectorSet(grenade->maxs, 32, 32, 32); + grenade->mass = 5; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + gi.linkentity (grenade); +} diff --git a/j_kamikaze.c b/j_kamikaze.c new file mode 100644 index 0000000..73c7c98 --- /dev/null +++ b/j_kamikaze.c @@ -0,0 +1,76 @@ +#include "g_local.h" +/*Function: Start_Kamikaze_Mode + Places the edict passed to it into Kamikaze Mode + (probably best to pass a player to it) + Warns everyone that so and so is a kamikaze...*/ +void stuffcmd(edict_t *e, char *s); + +void Start_Kamikaze_Mode(edict_t *the_doomed_one) +{ + //jR The great bug fix + if (the_doomed_one->health<0) + { + //gi.bprintf (PRINT_HIGH, "%s tried to cheat or crash the server!\n But good old Cryect stopped him\n Cheaters never!!!\nByeBye!", the_doomed_one->client->pers.netname); + //stuffcmd(the_doomed_one, "alias kickme say I cheat;say I try to cheat and crash servers thank you all and tell everyone how all about me!;disconnect;echo Wow what a great server crasher!"); + //stuffcmd(the_doomed_one, "kickme"); + return; + } + + + /* see if we are already in kamikaze mode*/ + if (the_doomed_one->client->kamikaze_mode & 1) + { + safe_cprintf(the_doomed_one, PRINT_MEDIUM, "Already in Kamikaze Mode! Cancel to stop!"); + return; + } + /* dont run if in god mode */ + if (the_doomed_one->flags & FL_GODMODE) + { + safe_cprintf(the_doomed_one, PRINT_MEDIUM, "Can't Kamikaze in God Mode!"); + return; + } + /* not in kamikaze mode yet */ + the_doomed_one->client->kamikaze_mode = 1; + /* Give us only so long */ + the_doomed_one->client->kamikaze_timeleft = KAMIKAZE_BLOW_TIME; + the_doomed_one->client->kamikaze_framenum = level.framenum + the_doomed_one->client->kamikaze_timeleft; + /* Warn the World */ + //gi.sound( the_doomed_one, CHAN_WEAPON, gi.soundindex("makron/rail_up.wav"), 1, ATTN_NONE, 0 ); +//GREGG - use 10 second countdown instead + //JR - I like it Gregg + gi.sound( the_doomed_one, CHAN_WEAPON, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0 ); + + return; + } + +/* Function: Kamikaze_Active + Are we in Kamikaze Mode? + a helper function to see if we are running in Kamikaze Mode*/ +qboolean Kamikaze_Active(edict_t *the_doomed_one) +{ + return (the_doomed_one->client->kamikaze_mode); +} + +/* Function: Kamikaze_Cancel + Canceled for Some Reason Call if Player is killed before time is up*/ +void Kamikaze_Cancel(edict_t *the_spared_one) +{ + /* not in kamikaze mode yet */ + the_spared_one->client->kamikaze_mode = 0; + /* Give us only so long */ + the_spared_one->client->kamikaze_timeleft = 0; + the_spared_one->client->kamikaze_framenum = 0; + return; +} + +void Kamikaze_Explode(edict_t *the_doomed_one) +{ + /* BANG ! and show the clients */ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition(the_doomed_one -> s.origin); + gi.multicast (the_doomed_one->s.origin, MULTICAST_PVS); + + /* A whole Lotta Damage */ + T_RadiusDamage (the_doomed_one, the_doomed_one, KAMIKAZE_DAMAGE, NULL, KAMIKAZE_DAMAGE_RADUIS, MOD_KAMIKAZE); +} \ No newline at end of file diff --git a/kamikaze.h b/kamikaze.h new file mode 100644 index 0000000..7591a2f --- /dev/null +++ b/kamikaze.h @@ -0,0 +1,17 @@ +/* Amount of Damage caused */ +#define KAMIKAZE_DAMAGE 300 + +/* Radius of blast */ +#define KAMIKAZE_DAMAGE_RADUIS 400 +// Quake Units + +/* Count down time */ +//Gregg-Make it longer to match 10 second countdown +//#define KAMIKAZE_BLOW_TIME 30 // 1/10 seconds +#define KAMIKAZE_BLOW_TIME 110 // 1/10 seconds + +void Start_Kamikaze_Mode(edict_t *the_doomed_one); + // setup and start self destruct mode +qboolean Kamikaze_Active(edict_t *the_doomed_one); +void Kamikaze_Explode(edict_t *the_doomed_one); +void Kamikaze_Cancel(edict_t *the_spared_one); diff --git a/laser2.h b/laser2.h new file mode 100644 index 0000000..9084dad --- /dev/null +++ b/laser2.h @@ -0,0 +1,19 @@ +// my functions + void PlaceLaser (edict_t *ent); + void pre_target_laser_think (edict_t *self); + void pre_target_laser_def_think (edict_t *self); + // controlling parameters +// #define LASER_TIME 30 + #define LASER_TIME 90 + #define MAX_LASERS 5 + #define CELLS_FOR_LASER 20 + #define LASER_DAMAGE 100 + #define LASER_MOUNT_DAMAGE 50 + #define LASER_MOUNT_DAMAGE_RADIUS 64 + // In-built Quake2 routines + void target_laser_use (edict_t *self, edict_t *other, edict_t *activator); + void target_laser_think (edict_t *self); + void target_laser_def_think (edict_t *self); + void target_laser_on (edict_t *self); + void target_laser_def_on (edict_t *self); + void target_laser_off (edict_t *self); diff --git a/launcher.cfg b/launcher.cfg new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/launcher.cfg @@ -0,0 +1 @@ + diff --git a/m_flash.c b/m_flash.c new file mode 100644 index 0000000..8457639 --- /dev/null +++ b/m_flash.c @@ -0,0 +1,469 @@ +// m_flash.c + +#include "q_shared.h" + +// this file is included in both the game dll and quake2, +// the game needs it to source shot locations, the client +// needs it to position muzzle flashes +vec3_t monster_flash_offset [] = +{ +// flash 0 is not used + 0.0, 0.0, 0.0, + +// MZ2_TANK_BLASTER_1 1 + 20.7, -18.5, 28.7, +// MZ2_TANK_BLASTER_2 2 + 16.6, -21.5, 30.1, +// MZ2_TANK_BLASTER_3 3 + 11.8, -23.9, 32.1, +// MZ2_TANK_MACHINEGUN_1 4 + 22.9, -0.7, 25.3, +// MZ2_TANK_MACHINEGUN_2 5 + 22.2, 6.2, 22.3, +// MZ2_TANK_MACHINEGUN_3 6 + 19.4, 13.1, 18.6, +// MZ2_TANK_MACHINEGUN_4 7 + 19.4, 18.8, 18.6, +// MZ2_TANK_MACHINEGUN_5 8 + 17.9, 25.0, 18.6, +// MZ2_TANK_MACHINEGUN_6 9 + 14.1, 30.5, 20.6, +// MZ2_TANK_MACHINEGUN_7 10 + 9.3, 35.3, 22.1, +// MZ2_TANK_MACHINEGUN_8 11 + 4.7, 38.4, 22.1, +// MZ2_TANK_MACHINEGUN_9 12 + -1.1, 40.4, 24.1, +// MZ2_TANK_MACHINEGUN_10 13 + -6.5, 41.2, 24.1, +// MZ2_TANK_MACHINEGUN_11 14 + 3.2, 40.1, 24.7, +// MZ2_TANK_MACHINEGUN_12 15 + 11.7, 36.7, 26.0, +// MZ2_TANK_MACHINEGUN_13 16 + 18.9, 31.3, 26.0, +// MZ2_TANK_MACHINEGUN_14 17 + 24.4, 24.4, 26.4, +// MZ2_TANK_MACHINEGUN_15 18 + 27.1, 17.1, 27.2, +// MZ2_TANK_MACHINEGUN_16 19 + 28.5, 9.1, 28.0, +// MZ2_TANK_MACHINEGUN_17 20 + 27.1, 2.2, 28.0, +// MZ2_TANK_MACHINEGUN_18 21 + 24.9, -2.8, 28.0, +// MZ2_TANK_MACHINEGUN_19 22 + 21.6, -7.0, 26.4, +// MZ2_TANK_ROCKET_1 23 + 6.2, 29.1, 49.1, +// MZ2_TANK_ROCKET_2 24 + 6.9, 23.8, 49.1, +// MZ2_TANK_ROCKET_3 25 + 8.3, 17.8, 49.5, + +// MZ2_INFANTRY_MACHINEGUN_1 26 + 26.6, 7.1, 13.1, +// MZ2_INFANTRY_MACHINEGUN_2 27 + 18.2, 7.5, 15.4, +// MZ2_INFANTRY_MACHINEGUN_3 28 + 17.2, 10.3, 17.9, +// MZ2_INFANTRY_MACHINEGUN_4 29 + 17.0, 12.8, 20.1, +// MZ2_INFANTRY_MACHINEGUN_5 30 + 15.1, 14.1, 21.8, +// MZ2_INFANTRY_MACHINEGUN_6 31 + 11.8, 17.2, 23.1, +// MZ2_INFANTRY_MACHINEGUN_7 32 + 11.4, 20.2, 21.0, +// MZ2_INFANTRY_MACHINEGUN_8 33 + 9.0, 23.0, 18.9, +// MZ2_INFANTRY_MACHINEGUN_9 34 + 13.9, 18.6, 17.7, +// MZ2_INFANTRY_MACHINEGUN_10 35 + 15.4, 15.6, 15.8, +// MZ2_INFANTRY_MACHINEGUN_11 36 + 10.2, 15.2, 25.1, +// MZ2_INFANTRY_MACHINEGUN_12 37 + -1.9, 15.1, 28.2, +// MZ2_INFANTRY_MACHINEGUN_13 38 + -12.4, 13.0, 20.2, + +// MZ2_SOLDIER_BLASTER_1 39 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_BLASTER_2 40 + 21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, +// MZ2_SOLDIER_SHOTGUN_1 41 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_SHOTGUN_2 42 + 21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_1 43 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_2 44 + 21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, + +// MZ2_GUNNER_MACHINEGUN_1 45 + 30.1 * 1.15, 3.9 * 1.15, 19.6 * 1.15, +// MZ2_GUNNER_MACHINEGUN_2 46 + 29.1 * 1.15, 2.5 * 1.15, 20.7 * 1.15, +// MZ2_GUNNER_MACHINEGUN_3 47 + 28.2 * 1.15, 2.5 * 1.15, 22.2 * 1.15, +// MZ2_GUNNER_MACHINEGUN_4 48 + 28.2 * 1.15, 3.6 * 1.15, 22.0 * 1.15, +// MZ2_GUNNER_MACHINEGUN_5 49 + 26.9 * 1.15, 2.0 * 1.15, 23.4 * 1.15, +// MZ2_GUNNER_MACHINEGUN_6 50 + 26.5 * 1.15, 0.6 * 1.15, 20.8 * 1.15, +// MZ2_GUNNER_MACHINEGUN_7 51 + 26.9 * 1.15, 0.5 * 1.15, 21.5 * 1.15, +// MZ2_GUNNER_MACHINEGUN_8 52 + 29.0 * 1.15, 2.4 * 1.15, 19.5 * 1.15, +// MZ2_GUNNER_GRENADE_1 53 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, +// MZ2_GUNNER_GRENADE_2 54 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, +// MZ2_GUNNER_GRENADE_3 55 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, +// MZ2_GUNNER_GRENADE_4 56 + 4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15, + +// MZ2_CHICK_ROCKET_1 57 +// -24.8, -9.0, 39.0, + 24.8, -9.0, 39.0, // PGM - this was incorrect in Q2 + +// MZ2_FLYER_BLASTER_1 58 + 12.1, 13.4, -14.5, +// MZ2_FLYER_BLASTER_2 59 + 12.1, -7.4, -14.5, + +// MZ2_MEDIC_BLASTER_1 60 + 12.1, 5.4, 16.5, + +// MZ2_GLADIATOR_RAILGUN_1 61 + 30.0, 18.0, 28.0, + +// MZ2_HOVER_BLASTER_1 62 + 32.5, -0.8, 10.0, + +// MZ2_ACTOR_MACHINEGUN_1 63 + 18.4, 7.4, 9.6, + +// MZ2_SUPERTANK_MACHINEGUN_1 64 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_2 65 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_3 66 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_4 67 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_5 68 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_MACHINEGUN_6 69 + 30.0, 30.0, 88.5, +// MZ2_SUPERTANK_ROCKET_1 70 + 16.0, -22.5, 91.2, +// MZ2_SUPERTANK_ROCKET_2 71 + 16.0, -33.4, 86.7, +// MZ2_SUPERTANK_ROCKET_3 72 + 16.0, -42.8, 83.3, + +// --- Start Xian Stuff --- +// MZ2_BOSS2_MACHINEGUN_L1 73 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L2 74 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L3 75 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L4 76 + 32, -40, 70, +// MZ2_BOSS2_MACHINEGUN_L5 77 + 32, -40, 70, +// --- End Xian Stuff + +// MZ2_BOSS2_ROCKET_1 78 + 22.0, 16.0, 10.0, +// MZ2_BOSS2_ROCKET_2 79 + 22.0, 8.0, 10.0, +// MZ2_BOSS2_ROCKET_3 80 + 22.0, -8.0, 10.0, +// MZ2_BOSS2_ROCKET_4 81 + 22.0, -16.0, 10.0, + +// MZ2_FLOAT_BLASTER_1 82 + 32.5, -0.8, 10, + +// MZ2_SOLDIER_BLASTER_3 83 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_SHOTGUN_3 84 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_3 85 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_BLASTER_4 86 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_SHOTGUN_4 87 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_4 88 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_BLASTER_5 89 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_SHOTGUN_5 90 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_5 91 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_BLASTER_6 92 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_SHOTGUN_6 93 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_6 94 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_BLASTER_7 95 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_SHOTGUN_7 96 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_7 97 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_BLASTER_8 98 +// 34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2, + 31.5 * 1.2, 9.6 * 1.2, 10.1 * 1.2, +// MZ2_SOLDIER_SHOTGUN_8 99 + 34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2, +// MZ2_SOLDIER_MACHINEGUN_8 100 + 34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2, + +// --- Xian shit below --- +// MZ2_MAKRON_BFG 101 + 17, -19.5, 62.9, +// MZ2_MAKRON_BLASTER_1 102 + -3.6, -24.1, 59.5, +// MZ2_MAKRON_BLASTER_2 103 + -1.6, -19.3, 59.5, +// MZ2_MAKRON_BLASTER_3 104 + -0.1, -14.4, 59.5, +// MZ2_MAKRON_BLASTER_4 105 + 2.0, -7.6, 59.5, +// MZ2_MAKRON_BLASTER_5 106 + 3.4, 1.3, 59.5, +// MZ2_MAKRON_BLASTER_6 107 + 3.7, 11.1, 59.5, +// MZ2_MAKRON_BLASTER_7 108 + -0.3, 22.3, 59.5, +// MZ2_MAKRON_BLASTER_8 109 + -6, 33, 59.5, +// MZ2_MAKRON_BLASTER_9 110 + -9.3, 36.4, 59.5, +// MZ2_MAKRON_BLASTER_10 111 + -7, 35, 59.5, +// MZ2_MAKRON_BLASTER_11 112 + -2.1, 29, 59.5, +// MZ2_MAKRON_BLASTER_12 113 + 3.9, 17.3, 59.5, +// MZ2_MAKRON_BLASTER_13 114 + 6.1, 5.8, 59.5, +// MZ2_MAKRON_BLASTER_14 115 + 5.9, -4.4, 59.5, +// MZ2_MAKRON_BLASTER_15 116 + 4.2, -14.1, 59.5, +// MZ2_MAKRON_BLASTER_16 117 + 2.4, -18.8, 59.5, +// MZ2_MAKRON_BLASTER_17 118 + -1.8, -25.5, 59.5, +// MZ2_MAKRON_RAILGUN_1 119 + -17.3, 7.8, 72.4, + +// MZ2_JORG_MACHINEGUN_L1 120 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L2 121 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L3 122 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L4 123 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L5 124 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_L6 125 + 78.5, -47.1, 96, +// MZ2_JORG_MACHINEGUN_R1 126 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R2 127 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R3 128 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R4 129 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R5 130 + 78.5, 46.7, 96, +// MZ2_JORG_MACHINEGUN_R6 131 + 78.5, 46.7, 96, +// MZ2_JORG_BFG_1 132 + 6.3, -9, 111.2, + +// MZ2_BOSS2_MACHINEGUN_R1 73 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R2 74 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R3 75 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R4 76 + 32, 40, 70, +// MZ2_BOSS2_MACHINEGUN_R5 77 + 32, 40, 70, + +// --- End Xian Shit --- + +// ROGUE +// note that the above really ends at 137 +// carrier machineguns +// MZ2_CARRIER_MACHINEGUN_L1 + 56, -32, 32, +// MZ2_CARRIER_MACHINEGUN_R1 + 56, 32, 32, +// MZ2_CARRIER_GRENADE + 42, 24, 50, +// MZ2_TURRET_MACHINEGUN 141 + 16, 0, 0, +// MZ2_TURRET_ROCKET 142 + 16, 0, 0, +// MZ2_TURRET_BLASTER 143 + 16, 0, 0, +// MZ2_STALKER_BLASTER 144 + 24, 0, 6, +// MZ2_DAEDALUS_BLASTER 145 + 32.5, -0.8, 10.0, +// MZ2_MEDIC_BLASTER_2 146 + 12.1, 5.4, 16.5, +// MZ2_CARRIER_RAILGUN 147 + 32, 0, 6, +// MZ2_WIDOW_DISRUPTOR 148 + 57.72, 14.50, 88.81, +// MZ2_WIDOW_BLASTER 149 + 56, 32, 32, +// MZ2_WIDOW_RAIL 150 + 62, -20, 84, +// MZ2_WIDOW_PLASMABEAM 151 // PMM - not used! + 32, 0, 6, +// MZ2_CARRIER_MACHINEGUN_L2 152 + 61, -32, 12, +// MZ2_CARRIER_MACHINEGUN_R2 153 + 61, 32, 12, +// MZ2_WIDOW_RAIL_LEFT 154 + 17, -62, 91, +// MZ2_WIDOW_RAIL_RIGHT 155 + 68, 12, 86, +// MZ2_WIDOW_BLASTER_SWEEP1 156 pmm - the sweeps need to be in sequential order + 47.5, 56, 89, +// MZ2_WIDOW_BLASTER_SWEEP2 157 + 54, 52, 91, +// MZ2_WIDOW_BLASTER_SWEEP3 158 + 58, 40, 91, +// MZ2_WIDOW_BLASTER_SWEEP4 159 + 68, 30, 88, +// MZ2_WIDOW_BLASTER_SWEEP5 160 + 74, 20, 88, +// MZ2_WIDOW_BLASTER_SWEEP6 161 + 73, 11, 87, +// MZ2_WIDOW_BLASTER_SWEEP7 162 + 73, 3, 87, +// MZ2_WIDOW_BLASTER_SWEEP8 163 + 70, -12, 87, +// MZ2_WIDOW_BLASTER_SWEEP9 164 + 67, -20, 90, +// MZ2_WIDOW_BLASTER_100 165 + -20, 76, 90, +// MZ2_WIDOW_BLASTER_90 166 + -8, 74, 90, +// MZ2_WIDOW_BLASTER_80 167 + 0, 72, 90, +// MZ2_WIDOW_BLASTER_70 168 d06 + 10, 71, 89, +// MZ2_WIDOW_BLASTER_60 169 d07 + 23, 70, 87, +// MZ2_WIDOW_BLASTER_50 170 d08 + 32, 64, 85, +// MZ2_WIDOW_BLASTER_40 171 + 40, 58, 84, +// MZ2_WIDOW_BLASTER_30 172 d10 + 48, 50, 83, +// MZ2_WIDOW_BLASTER_20 173 + 54, 42, 82, +// MZ2_WIDOW_BLASTER_10 174 d12 + 56, 34, 82, +// MZ2_WIDOW_BLASTER_0 175 + 58, 26, 82, +// MZ2_WIDOW_BLASTER_10L 176 d14 + 60, 16, 82, +// MZ2_WIDOW_BLASTER_20L 177 + 59, 6, 81, +// MZ2_WIDOW_BLASTER_30L 178 d16 + 58, -2, 80, +// MZ2_WIDOW_BLASTER_40L 179 + 57, -10, 79, +// MZ2_WIDOW_BLASTER_50L 180 d18 + 54, -18, 78, +// MZ2_WIDOW_BLASTER_60L 181 + 42, -32, 80, +// MZ2_WIDOW_BLASTER_70L 182 d20 + 36, -40, 78, +// MZ2_WIDOW_RUN_1 183 + 68.4, 10.88, 82.08, +// MZ2_WIDOW_RUN_2 184 + 68.51, 8.64, 85.14, +// MZ2_WIDOW_RUN_3 185 + 68.66, 6.38, 88.78, +// MZ2_WIDOW_RUN_4 186 + 68.73, 5.1, 84.47, +// MZ2_WIDOW_RUN_5 187 + 68.82, 4.79, 80.52, +// MZ2_WIDOW_RUN_6 188 + 68.77, 6.11, 85.37, +// MZ2_WIDOW_RUN_7 189 + 68.67, 7.99, 90.24, +// MZ2_WIDOW_RUN_8 190 + 68.55, 9.54, 87.36, +// MZ2_CARRIER_ROCKET_1 191 + 0, 0, -5, +// MZ2_CARRIER_ROCKET_2 192 + 0, 0, -5, +// MZ2_CARRIER_ROCKET_3 193 + 0, 0, -5, +// MZ2_CARRIER_ROCKET_4 194 + 0, 0, -5, +// MZ2_WIDOW2_BEAMER_1 195 +// 72.13, -17.63, 93.77, + 69.00, -17.63, 93.77, +// MZ2_WIDOW2_BEAMER_2 196 +// 71.46, -17.08, 89.82, + 69.00, -17.08, 89.82, +// MZ2_WIDOW2_BEAMER_3 197 +// 71.47, -18.40, 90.70, + 69.00, -18.40, 90.70, +// MZ2_WIDOW2_BEAMER_4 198 +// 71.96, -18.34, 94.32, + 69.00, -18.34, 94.32, +// MZ2_WIDOW2_BEAMER_5 199 +// 72.25, -18.30, 97.98, + 69.00, -18.30, 97.98, +// MZ2_WIDOW2_BEAM_SWEEP_1 200 + 45.04, -59.02, 92.24, +// MZ2_WIDOW2_BEAM_SWEEP_2 201 + 50.68, -54.70, 91.96, +// MZ2_WIDOW2_BEAM_SWEEP_3 202 + 56.57, -47.72, 91.65, +// MZ2_WIDOW2_BEAM_SWEEP_4 203 + 61.75, -38.75, 91.38, +// MZ2_WIDOW2_BEAM_SWEEP_5 204 + 65.55, -28.76, 91.24, +// MZ2_WIDOW2_BEAM_SWEEP_6 205 + 67.79, -18.90, 91.22, +// MZ2_WIDOW2_BEAM_SWEEP_7 206 + 68.60, -9.52, 91.23, +// MZ2_WIDOW2_BEAM_SWEEP_8 207 + 68.08, 0.18, 91.32, +// MZ2_WIDOW2_BEAM_SWEEP_9 208 + 66.14, 9.79, 91.44, +// MZ2_WIDOW2_BEAM_SWEEP_10 209 + 62.77, 18.91, 91.65, +// MZ2_WIDOW2_BEAM_SWEEP_11 210 + 58.29, 27.11, 92.00, + +// end of table + 0.0, 0.0, 0.0 +}; diff --git a/m_move.c b/m_move.c new file mode 100644 index 0000000..adc2b0a --- /dev/null +++ b/m_move.c @@ -0,0 +1,541 @@ +// 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_PLAYERSOLID); +//WF24-34 USES MONSTERSOLID + if (trace.allsolid) + return false; + + if (trace.startsolid) + { + neworg[2] -= stepsize; + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_PLAYERSOLID); + if (trace.allsolid || trace.startsolid) + return false; + }//SAME HERE MASK + + + // 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 (ent->enemy && (skill->value > 1)) + speed *= skill->value;//24 OR ERASER FIXME?? + + 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..d78a826 --- /dev/null +++ b/m_player.h @@ -0,0 +1,206 @@ +// 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..061eb30 --- /dev/null +++ b/p_client.c @@ -0,0 +1,3624 @@ +/* p_client.c */ + +#include "g_local.h" +#include "m_player.h" +#include "stdlog.h" // StdLog - Mark Davies +#include "wf_classmgr.h"//acrid for WFCopyToBodyQue + +FILE *zbotfile = NULL; + +int deas = 10; + +//ERASER START +///Q2 Camera Begin +#include "camclient.h" +///Q2 Camera End +#include "p_trail.h" +#include "bot_procs.h" +#include +#include +#define OPTIMIZE_INTERVAL 0.1 +int num_clients = 0; +//ERASER END + +void ClientUserinfoChanged (edict_t *ent, char *userinfo); +void SP_misc_teleporter_dest (edict_t *ent); +void WFRemoveDisguise(edict_t *ent); +void Drop_General (edict_t *ent, gitem_t *item); +void HealPlayer(edict_t *ent); +void I_AM_A_ZBOT(edict_t *ent); + +qboolean ZbotCheck(edict_t *ent, usercmd_t *ucmd); + +//Utility function to fix player name so it doesnt have a '%' in it +void wfFixName(char *text) +{ + int j; + j = 0; + while (j < 150 && text[j] != 0) + { + if (text[j] == '%') text[j] = '_'; // don't allow formated characters in text + ++j; + } + +} + +// +// 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) || Q_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(Q_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(Q_stricmp(level.mapname, "security") == 0) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_CreateCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + +//ERASER START +edict_t *dm_spots[64]; +int num_dm_spots; +//ERASER END + +/*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); + + dm_spots[num_dm_spots++] = self;//ERASER +} + +/*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((Q_stricmp(level.mapname, "jail2") == 0) || + (Q_stricmp(level.mapname, "jail4") == 0) || + (Q_stricmp(level.mapname, "mine1") == 0) || + (Q_stricmp(level.mapname, "mine2") == 0) || + (Q_stricmp(level.mapname, "mine3") == 0) || + (Q_stricmp(level.mapname, "mine4") == 0) || + (Q_stricmp(level.mapname, "lab") == 0) || + (Q_stricmp(level.mapname, "boss1") == 0) || + (Q_stricmp(level.mapname, "fact3") == 0) || + (Q_stricmp(level.mapname, "biggun") == 0) || + (Q_stricmp(level.mapname, "space") == 0) || + (Q_stricmp(level.mapname, "command") == 0) || + (Q_stricmp(level.mapname, "power2") == 0) || + (Q_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) +{ +} + + +/*QUAKED info_position (1 0 1) (-16 -16 -24) (16 16 32) +Used to describe the location within a map +Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll' +*/ +void SP_info_position(edict_t *ent) +{ +/* + gi.dprintf("DEBUG (Spawned info_position) Classname = %s\n", ent->classname); + if (ent->message && ent->message[0]) + gi.dprintf("DEBUG (info_position) Message = %s\n", ent->message); +*/ + +} + +/*QUAKED item_flagreturn (1 0 1) (-16 -16 -24) (16 16 32) +Used to describe the location of the flag return base +*/ +void SP_item_flagreturn(edict_t *ent) +{ + +// gi.dprintf("--Spawned %s. Target = %s, ent = %d\n", ent->classname, ent->target, ent); + +} + +//======================================================================= + + +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) +{ + + if (!ent->client) + return false; + + if (ent->client->player_model == CLASS_MODEL_FEMALE) + return true; + else + return false; +} + +//WF +qboolean IsCyborg (edict_t *ent) +{ + + + if (!ent->client) + return false; + + if (ent->client->player_model == CLASS_MODEL_CYBORG) + return true; + else + return false; +} +//WF + +//Not female, male or cyborg? +qboolean IsNeutral (edict_t *ent) +{ + + if (!ent->client) + return false; + + if ((ent->client->player_model == CLASS_MODEL_FEMALE) || + (ent->client->player_model == CLASS_MODEL_MALE) || + (ent->client->player_model == CLASS_MODEL_CYBORG)) + return false; + else + return true; +} + +int PlayerChangeScore(edict_t *self, int points) +{ + if (wf_game.game_halted) + { + safe_cprintf(self, PRINT_HIGH, "No Scoring Allowed While Game Is Suspended\n"); + return false; + } + else + self->client->resp.score += points; + return true; +} + + +void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *atk) +{ + int mod; + char *message; + char *message2; + qboolean ff; + edict_t *attacker; + + qboolean special_message; + + special_message = false; + + attacker = atk; + + //If this is a death by sentry gun, then the attacker's owner + //is the real attacker + if ((atk->creator) &&(!atk->client) && (meansOfDeath == MOD_SENTRY || meansOfDeath == MOD_SENTRY_ROCKET || meansOfDeath == MOD_SHRAPNEL || meansOfDeath == MOD_PELLET) ) + attacker = atk->creator; + + if (coop->value && attacker->client) + meansOfDeath |= MOD_FRIENDLY_FIRE; + +//ERASER START //ACRID FIXME? teamplay + else if (deathmatch->value && teamplay->value && attacker->client && SameTeam(self, attacker)) + { + meansOfDeath |= MOD_FRIENDLY_FIRE; + } +//ERASER END + + if (deathmatch->value || coop->value) + { + ff = meansOfDeath & MOD_FRIENDLY_FIRE; + mod = meansOfDeath & ~MOD_FRIENDLY_FIRE; + message = NULL; + message2 = ""; + + switch (mod) + { + case MOD_SUICIDE: + message = "suicides"; + break; + case MOD_FALLING: + message = "cratered"; + break; + case MOD_CRUSH: + message = "was squished"; + 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_FEIGN: + message = "died while trying to feign"; + break; + case MOD_EXPLOSIVE: + case MOD_BARREL: + message = "blew up"; + break; + case MOD_EXIT: + message = "found a way out"; + break; +//TeT - Laser is not suicide +// 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: + 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; + case MOD_FEIGN: + message = "died while trying to feign"; + break; + default: + if (IsNeutral(self)) + message = "killed itself"; + else if (IsFemale(self)) + message = "killed herself"; + else + message = "killed himself"; + break; + } + } + if (message) + { + my_bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message); + if (deathmatch->value) + { + if (PlayerChangeScore(self,CTF_SUICIDE_POINTS)) + sl_LogScore( &gi, self->client->pers.netname, "", "Suicide",mod, CTF_SUICIDE_POINTS ); // StdLog + } + self->enemy = NULL; + + if (special_message) + { +// safe_cprintf(self, PRINT_HIGH, "[YOU KILLED YOURSELF: "); +// if ((inflictor) && (inflictor->classname)) +// safe_cprintf(self, PRINT_HIGH, " item=%s",inflictor->classname); +// safe_cprintf(self, PRINT_HIGH, "]\n"); + } + + +//if (wfdebug) gi.dprintf("diseased = %d. Diseased by = %d\n", self->disease, self->diseased_by); + + //If player is diseased, give credit to nurse who infected them + if(self->disease && self->diseased_by && self->diseased_by->client && self->diseased_by->inuse) + { + if (PlayerChangeScore(self->diseased_by,CTF_FRAG_POINTS)) + sl_LogScore( &gi, self->diseased_by->client->pers.netname, self->client->pers.netname, "Kill",mod, CTF_FRAG_POINTS ); // StdLog - Mark Davies + + my_bprintf (PRINT_MEDIUM,"%s gets a frag for %s's suicide while diseased.\n", self->diseased_by->client->pers.netname, self->client->pers.netname); + } + + return; + } + + self->enemy = attacker; + if (attacker && attacker->client) + { + switch (mod) + { + 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_MISSILE: + message = "was eliminated by"; + message2 = "'s missile launcher"; + break; + case MOD_HOMINGROCKET: + message = "was slammed"; + message2 = "'s homing 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; +//ZOID + case MOD_GRAPPLE: + message = "was caught by"; + message2 = "'s grapple"; + break; +//ZOID + +//WF + case MOD_WF_LASERBALL: + message = "was fried by"; + message2 = "'s laserball"; + break; + + case MOD_WF_GOODYEAR: + message = "was popped by"; + message2 = "'s goodyear grenade"; + break; + + case MOD_WF_PROXIMITY: + message = "swallowed"; + message2 = "'s proximity grenade"; + break; + + case MOD_WF_CLUSTER: + message = "was ripped by"; + message2 = "'s cluster grenade"; + break; + + case MOD_WF_PIPEBOMB: + message = "fell for"; + message2 = "'s pipebomb"; + break; + + case MOD_WF_EARTHQUAKE: + message = "was shaken by"; + message2 = "'s earthquake"; + break; + + case MOD_WF_FLAME: + message = "was burned by"; + message2 = "'s flame"; + break; + + case MOD_FLAMETHROWER: + message = "was charred by"; + message2 = "'s flame-thrower"; + break; + + case MOD_REVERSE_TELEFRAG: + message = "was reverse telefragged by"; + break; + case MOD_NAG: + message = "was NAGed to death by"; + break; + case MOD_SHRAPNEL: + message = "was pithed by"; + message2 = "'s shrapnel grenade"; + break; + case MOD_CLUSTERROCKET: + message = "was splattered by"; + message2 = "'s cluster rocket"; + break; + case MOD_DISEASE: + message = "died from"; + message2 = "'s disease"; + break; + + case MOD_SNIPERRIFLE: + message = "was slaughtered by"; + message2 = "'s sniper rifle"; + break; + case MOD_SNIPERRIFLE_LEG: + message = "had their legs shot out by"; + message2 = "'s sniper rifle"; + break; + case MOD_SNIPERRIFLE_HEAD: + message = "had his head blown off by"; + message2 = "'s sniper rifle"; + break; + + case MOD_SHC: + message = "burst into flames from"; + message2 = "'s SHC rifle"; + break; + + case MOD_NEEDLER: + message = "was needled by"; + break; + + case MOD_CONCUSSION: + message = "lost his head from"; + message2 = "'s concussion grenade"; + break; + + case MOD_ARMORDART: + message = "was pierced by"; + message2 = "'s dart"; + break; + + case MOD_INFECTEDDART: + message = "was infected by"; + message2 = "'s dart"; + break; + + case MOD_NAPALMROCKET: + message = "was smoked by"; + message2 = "'s napalm rocket"; + break; + case MOD_NAPALMGRENADE: + message = "got crispy from"; + message2 = "'s napalm grenade"; + break; + + case MOD_LIGHTNING: + message = "saw lightning from"; + message2 = "'s lightning gun"; + break; + + case MOD_TELSA: + message = "was exposed to"; + message2 = "'s telsa coil"; + break; + + case MOD_MAGNOTRON: + message = "was sucked into"; + message2 = "'s magnotron"; + break; + + case MOD_SHOCK: + message = "was shocked by"; + break; + + case MOD_PELLET: + message = "was pelted by"; + break; + + case MOD_FLAREGUN: + message = "was embarrassed to be toasted by"; + message2 = "'s flare gun"; + break; + + case MOD_FLARE: + message = "was snuffed by"; + message2 = "'s flare"; + break; + + case MOD_TRANQUILIZER: + message = "slowed to a stop from"; + message2 = "'s tranquilizer"; + break; + + case MOD_LRPROJECTILE: + message = "was blasted by"; + message2 = "'s LR Projectile Launcher"; + break; + + case MOD_BOLTEDBLASTER: + message = "expired from"; + message2 = "'s bolted blaster"; + break; + + case MOD_WF_TURRET: + message="caught Turret's Syndrome from"; + message2="'s Turret Grenade."; + break; + + case MOD_PLASMABOMB: + message = "was vaporized by"; + break; + case MOD_NAIL: + message = "was nailed by"; + //message2 = ""; + break; + case MOD_MBPC: + message = "was disintegrated by"; + message2 = "'s Pulse Cannon"; + break; + case MOD_SENTRY: + message = "was laid low by"; + message2 = "'s Sentry Gun"; + break; + case MOD_SENTRY_ROCKET: + message = "was liquidated by"; + message2 = "'s Sentry Gun Rocket"; + break; + case MOD_KAMIKAZE: + message = "didn't survive"; + message2 = "'s Kamikaze run"; + break; + case MOD_TRANQUILIZERDART: + message = "did not escape"; + message2 = "'s tranquilizer dart"; + break; + case MOD_DEPOT: + message = "was blown to bits by"; + message2 = "'s supply depot"; + break; + case MOD_DEPOT_EXPLODE: + message = "was in the way when"; + message2 = " blew up a supply depot"; + break; + case MOD_HEALINGDEPOT: + message = "was blown to bits by"; + message2 = "'s healing depot"; + break; + case MOD_MEGACHAINGUN: + message = "was cut to ribbons"; + message2 = "'s mega chaingun"; + break; + case MOD_SENTRYKILLER: + message = "got in the way of "; + message2 = "'s Sentry Killer Rocket"; + break; + + case MOD_KNIFE: + message = "was punctured by"; + message2 = "'s knife"; + break; + + case MOD_STINGER: + message = "was stopped by"; + message2 = "'s stinger"; + break; + + case MOD_KNIFEBACK: + message = "was stabbed in the back by"; + message2 = "'s knife"; + break; + + case MOD_FREEZER: + message = "was iced by";//acrid 3/99 death msg + message2 = "'s freezer"; + break; + + case MOD_LASERCUTTER: + message = "was sliced and diced by"; + message2 = "'s laser cutter grenade"; + break; + + case MOD_TESLA: + message = "was electrified by"; + message2 = "'s tesla grenade"; + break; + + case MOD_GASGRENADE: + message = "was fumigated by"; + message2 = "'s gas grenade"; + break; + + case MOD_AK47: + message = "became swiss cheese from"; + message2 = "'s AK47"; + break; + + case MOD_PISTOL: + message = "was assassinated by "; + message2 = "'s pistol"; + break; + + case MOD_TARGET_LASER: + message = "saw"; + message2 = "'s light"; + break; + + case MOD_CAMERA: + message = "smiled for"; + message2 = "'s camera"; + break; + + + } + if (message) + { + my_bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2); + if (deathmatch->value) + { + if (ff) + { + if (PlayerChangeScore(attacker,CTF_SUICIDE_POINTS)) + sl_LogScore( &gi, attacker->client->pers.netname, self->client->pers.netname, "Friendly Fire",mod, CTF_SUICIDE_POINTS ); // StdLog - Mark Davies + } + else + { + if (PlayerChangeScore(attacker,CTF_FRAG_POINTS)) + sl_LogScore( &gi, attacker->client->pers.netname, self->client->pers.netname, "Kill",mod, CTF_FRAG_POINTS ); // StdLog - Mark Davies + } + } + return; + } + } + } + + my_bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname); + +// safe_cprintf(self, PRINT_HIGH, "[YOU WERE KILLED WITH: "); +// if ((inflictor) && (inflictor->classname)) +// safe_cprintf(self, PRINT_HIGH, " %s",inflictor->classname); +// if ((attacker) && (attacker->client) && (attacker->client->pers.netname)) +// safe_cprintf(self, PRINT_HIGH, " BY %s",attacker->client->pers.netname); +// safe_cprintf(self, PRINT_HIGH, "]\n"); + + if (deathmatch->value) + PlayerChangeScore(self, -1); +} + + +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +void TossClientWeapon (edict_t *self) +{ + gitem_t *item; + edict_t *drop; + qboolean quad; + float spread; + + if (!deathmatch->value) + return; + +//WF If player classes are on, don't drop a weapon + if (self->client && (((int)wfflags->value & WF_NO_PLAYER_CLASSES) == 0)) + return; +//WF + + 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; + } + + 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; + + //Reset fov if autozoom was on + if (self->client->pers.autozoom) + { + self->client->ps.fov = 90; + self->PlayerSnipingZoom = 0; + } + + //Free laser sight + if ( self->lasersight ) + { + G_FreeEdict(self->lasersight); + self->lasersight = NULL; + } + + VectorClear (self->avelocity); + + self->takedamage = DAMAGE_YES; + self->movetype = MOVETYPE_TOSS; + + self->s.modelindex2 = 0; // remove linked weapon model +//ZOID + self->s.modelindex3 = 0; // remove linked ctf flag +//ZOID + + self->s.angles[0] = 0; + self->s.angles[2] = 0; + + self->s.sound = 0; + self->client->weapon_sound = 0; + + self->maxs[2] = -8; + +// self->solid = SOLID_NOT; + 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); + +//WF +/* + //Drop all the keys you are holding + for (n = 0; n < game.num_items; n++) + { + //If this is a key and I'm holding it, then drop it + if ((deathmatch->value) && (itemlist[n].flags & IT_KEY) ) + { + if (self->client->pers.inventory[n]) + { + Drop_General(self, &itemlist[n]); +//gi.dprintf("Dropping key: %s\n", itemlist[n].classname); + } + else + { +//gi.dprintf("Key not in inventory: %d: %s\n", n, itemlist[n].classname); + } + + } + } +*/ +//WF + WFPlayer_Die (self); + Kamikaze_Cancel(self); + +//ZOID + CTFFragBonuses(self, inflictor, attacker); +//ZOID + TossClientWeapon (self); +//ZOID + if (self->bot_client)//newgrap 4/99 + CTFPlayerResetGrapple(self); + else//newgrap 4/99 +// CTFPlayerResetGrapple(self); + CTFPlayerResetGrapple2(self);//newgrap 4/99 + + CTFDeadDropFlag(self); + CTFDeadDropTech(self); +//ZOID + if (deathmatch->value && !self->client->showscores) + Cmd_Help_f (self); // show scores + + // clear inventory + // this is kind of ugly, but it's how we want to handle keys in coop + for (n = 0; n < game.num_items; n++) + { + if (coop->value && itemlist[n].flags & IT_KEY) + self->client->resp.coop_respawn.inventory[n] = self->client->pers.inventory[n]; + self->client->pers.inventory[n] = 0; + } + + } + + // remove powerups + self->client->quad_framenum = 0; + self->client->invincible_framenum = 0; + self->client->breather_framenum = 0; + self->client->enviro_framenum = 0; + + // clear inventory +//3.20 memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory)); + +//WF & ATTILA begin + if ( Jet_Active(self) ) + { + Jet_BecomeExplosion( self, damage ); + /*stop jetting when dead*/ + self->client->Jet_framenum = 0; + } +//WF & ATTILA + + if (self->health < -40)//dont use its fixed || (self->client->player_special & SPECIAL_DISGUISE)) + { // 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); +//ZOID + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = 0; +//ZOID + self->takedamage = DAMAGE_NO; + } + else if (self->frozen && !self->deadflag)//acrid 3/99 frozen death animation + { + self->s.effects |= EF_COLOR_SHELL; + self->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + self->client->anim_priority = ANIM_DEATH; + self->s.frame = FRAME_stand01; + self->client->anim_end = FRAME_stand01; + self->frozenbody = 1; + gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0); + } + + 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; + } + gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0); + } + } + + self->deadflag = DEAD_DEAD; + self->frozen = 0;//acrid 3/99 + + + 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; + int i; + int HasVoted = client->pers.HasVoted; + int tmpGrenade[GRENADE_TYPE_COUNT + 1]; + int tmpSpecial[SPECIAL_COUNT + 1]; + qboolean homing_state = client->pers.homing_state; + int laseron = client->pers.laseron; + int feign = client->pers.feign; + int autozoom = client->pers.autozoom; + int fastaim = client->pers.fastaim; + int nospam_level = client->pers.nospam_level; + qboolean autoconfig = client->pers.autoconfig; + int player_class = client->pers.player_class; + int next_player_class = client->pers.next_player_class; + edict_t *friend_ent; + int hasfriends = client->pers.hasfriends; + int isbot = client->pers.i_am_a_bot; + + friend_ent = client->pers.friend_ent[0]; + + //Save grenade and special counts + for (i=1; i <= GRENADE_TYPE_COUNT; ++i) + { + tmpGrenade[i] = client->pers.active_grenades[i]; + } + + for (i=1; i <= SPECIAL_COUNT; ++i) + tmpSpecial[i] = client->pers.active_special[i]; + + memset (&client->pers, 0, sizeof(client->pers)); + + //Restore grenade and special counts + for (i=1; i <= GRENADE_TYPE_COUNT; ++i) + { + if (tmpGrenade[i] < 0) tmpGrenade[i] = 0; + client->pers.active_grenades[i] = tmpGrenade[i]; + } + + for (i=1; i <= SPECIAL_COUNT; ++i) + client->pers.active_special[i] = tmpSpecial[i]; + + + item = FindItem("Blaster"); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + + client->pers.weapon = item; +//ZOID + client->pers.lastweapon = item; +//ZOID + +//ERASER START + item = FindItem("Grapple"); +// if (grapple->value || ctf->value) + if (ctf->value) + {//E +//ZOID + client->pers.inventory[ITEM_INDEX(item)] = 1;//NORMAL LINE +//ZOID + }//E + else//E + {//E + client->pers.inventory[ITEM_INDEX(item)] = 0;//E + } +//ERASER END GRAPPLE TROUBLE HERE FIXME ACRID + + client->pers.i_am_a_bot = isbot; + + client->pers.health = 100; + client->pers.max_health = 100; + + client->pers.connected = true; + client->pers.friend_ent[0] = friend_ent; + + // WF & CCH: reset homing_state + client->pers.HasVoted = HasVoted; + + client->pers.homing_state = homing_state; + client->pers.laseron = laseron; + client->pers.feign = feign; + client->pers.autozoom = autozoom; + client->pers.fastaim = fastaim; + client->pers.nospam_level = nospam_level; + client->pers.autoconfig = autoconfig; + client->pers.player_class = player_class; + client->pers.next_player_class = next_player_class; + + client->pers.hasfriends = hasfriends; + + // Initialize grenade type + client->pers.grenade_num = 1; + client->pers.grenade_type = GRENADE_TYPE_NORMAL; + if (((int)wfflags->value & WF_NO_PLAYER_CLASSES) == 0) + client->pers.grenade_type = client->grenade_type1; + + // Scanner +// ClearScanner(client); + + // Scanner +// CompassOff(client);//5/99 compass + +} + + +qboolean is_bot=false;//ERASER +void InitClientResp (gclient_t *client, edict_t *ent) +{ +// int i; + int ctf_team; + + //K2:begin + qboolean inServer = client->resp.inServer; + //K2:End 3/99 botcam + + ctf_team = client->resp.ctf_team; + + memset (&client->resp, 0, sizeof(client->resp)); + + client->resp.ctf_team = ctf_team; + + //K2:begin + client->resp.inServer = inServer; + //K2:End 3/99 botcam + + client->resp.enterframe = level.framenum; + client->resp.coop_respawn = client->pers; + +//ZOID +// if (ctf->value && (client->resp.ctf_team < CTF_TEAM1)) + if (client->resp.ctf_team < CTF_TEAM1) + CTFAssignTeam(client, is_bot); +//ZOID +} + +/* +================== +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.savedFlags = (ent->flags & (FL_GODMODE|FL_NOTARGET|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; + ent->flags |= ent->client->pers.savedFlags; + 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 = 0; n < num_players; n++)//WF24 LINE IS for (n = 1; n <= maxclients->value; n++) + { + player = &g_edicts[n]; + */ + + for (n = 0; n < num_players; n++)//WF24 LINE IS for (n = 1; n <= maxclients->value; n++) + { + player = players[n];//ERASER ,WF USES LINE ABOVE THIS + + 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, spot_num;//ERASER + int selection; + float range, range1, range2; + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; +//WF USES THIS CO LINE +// while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + while (count < num_dm_spots) + { + spot = dm_spots[count];//ERASER + + 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; + spot_num = 0;//ERASER + do + { +//WF24 USES spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + spot = dm_spots[spot_num++];//ERASER + if (spot == spot1 || spot == spot2) + selection++; + } while(selection--); + + return spot; +} + +/* +================ +SelectFarthestDeathmatchSpawnPoint + +================ +*/ +edict_t *SelectFarthestDeathmatchSpawnPoint (void) +{ + edict_t *bestspot; + float bestdistance, bestplayerdistance; + edict_t *spot; + int spot_num;//ERASER + + + spot = NULL; + bestspot = NULL; + bestdistance = 0; + spot_num = 0;//ERASER +//WF24 USES while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + while (spot_num < num_dm_spots)//ERASER + { + spot = dm_spots[spot_num++];//ERASER + + 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 +//WF USES spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + spot = dm_spots[0];//ERASER + + 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; + + if (deathmatch->value) +//ZOID + if (ctf->value) + spot = SelectCTFSpawnPoint(ent); + else +//ZOID + spot = SelectDeathmatchSpawnPoint (); + else if (coop->value) + spot = SelectCoopSpawnPoint (ent); + + // find a single player start spot + if (!spot) + { +// gi.dprintf("DEBUG - No CTF Spawn Point.\n"); + 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) + { +// gi.dprintf("DEBUG - No Spawn Point Without Target.\n"); + 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"; + } +} + +//ERASER START +void Body_droptofloor(edict_t *ent) +{ + trace_t tr; + vec3_t dest; + float *v; + + v = tv(-15,-15,-24); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + ent->s.origin[2] += 32; + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + + VectorCopy(tr.endpos, ent->s.origin); + + gi.linkentity(ent); + + if (tr.ent) + ent->nextthink = level.time + 0.1; +} +//ERASER END + +void body_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health < -40) + { + 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); + // body->frozenbody =1;//acrid 3/99 this may be a solution if theres problems between the ent pointer and body + 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); + body->solid = ent->solid; + body->clipmask = ent->clipmask; + body->owner = ent->owner; +// body->s.effects = 0; +// body->s.renderfx = 0; + body->movetype = ent->movetype; + + body->die = body_die; + body->takedamage = DAMAGE_YES; + + gi.linkentity (body); +} + +void WFCopyToBodyQue (edict_t *ent) +{ + edict_t *body; + char modelname[64];//acrid + int classnum;//acrid + + // 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); + + +//new skin fix code + body->wf_team = ent->client->resp.ctf_team; + body->model = ent->model;//needed????? 3/99 + classnum = ent->client->pers.player_class; + sprintf(modelname, "wfactory/models/decoys/%s/tris.md2", classinfo[classnum].model_name); + body->s.modelindex = gi.modelindex (modelname); + body->s.skinnum = classinfo[classnum].decoyskin; + if (body->wf_team == CTF_TEAM1) //team 1 + { + ++body->s.skinnum; + } + else + { + //team 2 + } +//end new skin fix + + // body->s = ent->s; + VectorCopy(ent->s.origin, body->s.origin);//acrid + VectorCopy(ent->s.angles, body->s.angles);//acrid + VectorCopy(ent->s.old_origin, body->s.old_origin);//acrid + body->s.frame = ent->s.frame;//acrid + 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); + body->solid = ent->solid; + body->s.event = ent->s.event;//acrid + body->clipmask = ent->clipmask; + body->owner = ent->owner; + body->s.effects = ent->s.effects; + body->s.renderfx = ent->s.renderfx; + + //If they were carrying the flag, clear the glow - GREGG + if (body->s.effects & (EF_FLAG1 | EF_FLAG2)) + { + body->s.effects = 0; + body->s.renderfx = 0; + } + + + body->movetype = ent->movetype; + body->die = body_die; + body->takedamage = DAMAGE_YES; + + gi.linkentity (body); +} + +void respawn (edict_t *self) +{ + if (deathmatch->value || coop->value) + { + if (((int)wfflags->value & WF_NO_PLAYER_CLASSES) == 0) + { //for stopping instant gib creating a extra body//may need !self->frozenbody + if (self->health > -40 && !self->bot_client) + WFCopyToBodyQue (self); + //use normal que if instant gib + else + CopyToBodyQue (self); + } + //if wfflags use normal que + else + { + CopyToBodyQue (self); + } + + + + PutClientInServer (self); + + // 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"); +} + +//============================================================== + +// WF: Weapons Factory user setup */ +static void WFClientSetup (edict_t *ent) +{ + int i; + + //Set aliases required for mod + stuffcmd(ent,"alias +thrust \"cmd thrust on\"\n"); + stuffcmd(ent,"alias -thrust \"cmd thrust off\"\n"); + stuffcmd(ent,"alias +grapple \"+use\"\n");//newgrap 4/99 + stuffcmd(ent,"alias -grapple \"-use\"\n");//newgrap 4/99 + stuffcmd(ent,"alias +rzoom \"fov 30;+mlook\"\n"); + stuffcmd(ent,"alias -rzoom \"fov 90;-mlook\"\n"); + //stuffcmd(ent,"alias wfhelp \"cmd wfhelp\"\n"); + //stuffcmd(ent,"alias vote \"cmd vote\"\n"); + + //FORCE SOME BINDS +// stuffcmd(ent,"bind \";\" cmd special\n"); +// stuffcmd(ent,"bind v +thrust\n"); +// stuffcmd(ent,"bind b cmd grenade\n"); + + //note - removed mlook from rzoom command + + //Clear grenade and special item limits + for (i=1; i <= GRENADE_TYPE_COUNT; ++i) + ent->client->pers.active_grenades[i] = 0; + + for (i=1; i <= SPECIAL_COUNT; ++i) + ent->client->pers.active_special[i] = 0; + +} +//WF + +/* +=========== +PutClientInServer + +Called when a player connects to a server or respawns in +a deathmatch. +============ +*/ + +void ShowGun(edict_t *ent);//ERASER + +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 i; + client_persistant_t saved; + client_respawn_t resp; +//ERASER START + bot_team_t *team; + lag_trail_t *lag_trail; + lag_trail_t *lag_angles; + float latency; + + if (!the_client && !ent->bot_client && (num_players <= 1)) + the_client = ent; + + // 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; + + HealPlayer(ent); + + //Did class change? + if (ent->client->pers.player_class != ent->client->pers.next_player_class) + { + //Remove all their old stuff lying around +// gi.dprintf("Class changed-removing stuff. Old class=%d, New Class=%d\n",ent->client->pers.player_class, ent->client->pers.next_player_class); + WFPlayer_ChangeClassTeam(ent); + } + + ent->client->pers.player_class = ent->client->pers.next_player_class; + + // 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; +//ERASER START + team = client->team; + lag_trail = client->lag_trail; + lag_angles = client->lag_angles; + latency = client->latency; +//ERASER END + memset (client, 0, sizeof(*client)); + + client->pers = saved; + client->resp = resp; +//ERASER START + client->team = team; + client->lag_trail = lag_trail; + client->lag_angles = lag_angles; + client->latency = latency; + if (client->pers.health <= 0) + InitClientPersistant(client); +//ERASER END + // copy some data from the client to the entity + FetchClientEntData (ent); + + if (!ent->wf_team) ent->wf_team = client->resp.ctf_team; + + // clear entity values + ent->groundentity = NULL; + if (!ent->bot_client)//ERASER + 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"; + +//ERASER START + if (!ent->bot_client) + { + ent->pain = player_pain; + ent->die = player_die; + } +//ERASER END MIDDLE 2 LINES NORMAL WF24 CODE + + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags &= ~FL_NO_KNOCKBACK; + ent->svflags &= ~SVF_DEADMONSTER; + // acrid 3/99 start unfrozen + ent->frozen = 0; + ent->frozenbody = 0; + 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; +//ZOID + client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; +//ZOID + + if (!ent->bot_client)//ERASER + {//E + 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); + } + + + client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); + + // clear entity state values + ent->s.effects = 0; + // sknum is player num and weapon number + // weapon number will be added in changeweapon + ent->s.skinnum = ent - g_edicts - 1; + ent->s.modelindex = 255; // will use the skin specified model + ent->s.modelindex2 = 255; // custom gun model + if (ent->bot_client) + { + 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); + +//WF - for flash grenades + client->blindBase = 0; + client->blindTime = 0; + + //Clear disguise stuff + ent->disguised = 0; + ent->disguising = 0; + ent->disguisetime = 0; + ent->disguisingteam = 0; + ent->disguisedteam = 0; + ent->disguiseshots = 0; + + lasersight_off (ent); + + //Stop any disease + ent->disease = 0; + ent->cantmove = 0; + ent->Slower = 0; + + ent->client->weapon_damage = DAMAGE_BLASTER; //blaster is startup weapon + +//WF + //set the team of the entity + ent->wf_team = client->resp.ctf_team; + + //set respawn protection time + ent->client->protecttime = level.time + RESPAWN_PROTECT_TIME; + + //Set player classes + + //Fixup class if team is set but there is no next_player_class +/* + if ((client->resp.ctf_team > 0) && (ent->client->pers.next_player_class == 0)) + { + //Can we use current setting? + if (ent->client->pers.player_class) + { + ent->client->pers.next_player_class = ent->client->pers.player_class; + } + //Just assign them to the first class if all else fails + else + { + ent->client->pers.next_player_class = 1; + } + } +*/ +/* Old position of this code + //Did class change? + if (ent->client->pers.player_class != ent->client->pers.next_player_class) + { + //Remove all their old stuff lying around +// gi.dprintf("Class changed-removing stuff. Old class=%d, New Class=%d\n",ent->client->pers.player_class, ent->client->pers.next_player_class); + WFPlayer_ChangeClassTeam(ent); + } + + ent->client->pers.player_class = ent->client->pers.next_player_class; + +*/ + if ((((int)wfflags->value & WF_NO_PLAYER_CLASSES) == 0) && (ent->client->pers.player_class)) + { +// gi.dprintf("Player Classes Used\n"); + wf_InitPlayerClass(ent->client); + } +// else if ((int)wfflags->value & WF_RESPAWN_ARMED) +// { +// ent->client->pers.next_player_class = CLASS_SUPERMAN; +// ent->client->pers.player_class = CLASS_SUPERMAN; +// wf_InitPlayerClass(ent->client); +// } + else + { + ent->client->pers.next_player_class = 0; + ent->client->pers.player_class = 0; + } + Cmd_ShowClass(ent); + + //Pick the best weapon for the class + NoAmmoWeaponChange (ent); + + + +//ZOID + if (CTFStartClient(ent)) + return; +//ZOID + + + if (!KillBox (ent)) + { // could't spawn in? + } + + gi.linkentity (ent); +//ERASER START + if (!ent->map) + ent->map = G_CopyString(ent->client->pers.netname); +//ERASER END + + // force the current weapon up + client->newweapon = client->pers.weapon; + if (!ent->bot_client)//ERASER + ChangeWeapon (ent); + + + //If this is a zbot player, mess them up. + if (ent->client->pers.i_am_a_bot) + I_AM_A_ZBOT(ent); + +//ERASER START + ent->last_max_z = 32; + ent->jump_ent = NULL; + ent->duck_ent = NULL; + ent->last_trail_dropped = NULL; + + if (!ent->bot_client) + { + if (ent->client->lag_trail) + { + // init the lag_trail + for (i=0; i<10; i++) + { + VectorCopy(ent->s.origin, (*ent->client->lag_trail)[i]); + VectorCopy(ent->client->v_angle, (*ent->client->lag_angles)[i]); + } + } + + if ((bot_calc_nodes->value) && (trail[0]->timestamp)) + { // find a trail node to start checking from + if ((i = ClosestNodeToEnt(ent, false, true)) > -1) + { + ent->last_trail_dropped = trail[i]; + } + } + } +//ERASER END +} + +/* Anti-ZBot Support Code */ +void wf_strdate(char *s); +void wf_strtime(char *s); + +void zbotFileOpen( ) +{ + char path[100]; + + zbotfile = NULL; + + //Is zbot detection on? + if (((int)wfflags->value & WF_ZBOT_DETECT) == 0) + return; + + strcpy(path, gamedir->string); + +#if defined(_WIN32) || defined(WIN32) + strcat(path,"\\zbot.log"); +#else + strcat(path,"/zbot.log"); +#endif + + if((zbotfile = fopen(path,"a")) != NULL) + { + fprintf(zbotfile,"\nSERVER RESTART\n"); + gi.dprintf("ZBot Protection Enabled\n"); + } +} + +void zbotLogAttack(edict_t *ent, int type) +{ + char tDate[50]; + char tTime[50]; + char *stype; + + if (type == 2) stype = "@zbot"; + else if (type == 3) stype = "#zbot"; + else stype = "!zbot"; + + if (!zbotfile) return; + + //value = Info_ValueForKey (userinfo, "ip"); + wf_strdate( tDate ); + wf_strtime( tTime ); + + fprintf(zbotfile,"%s %s: Name = %s, Type = %s\n", + tDate, tTime, ent->client->pers.netname, stype); + fflush(zbotfile); + +} + +void zbotFileClose() +{ + if (zbotfile) fclose(zbotfile); +} + +/* +===================== +ClientBeginDeathmatch + +A client has just connected to the server in +deathmatch mode, so clear everything out before starting them. +===================== +*/ +void ClientBeginDeathmatch (edict_t *ent) +{ + + G_InitEdict (ent); +//ERASER START + if (ctf->value) + ent->client->team = NULL; +//ERASER END + InitClientResp (ent->client, ent);//WF34 ADDED ENT + if (!ent->wf_team) ent->wf_team = ent->client->resp.ctf_team; + + + // locate ent at a spawn point + PutClientInServer (ent); + ent->client->pers.autozoom = 1; //default sniper rifle to auto-zoom +//WF34 START + // -- ANTI-ZBOT STARTS -- // + //ent->client->resp.bot_start = level.time + 5 + (rand() % 5); + //ent->client->resp.bot_end = 0; + //ent->client->resp.bot_end2 = 0; + //ent->client->resp.bot_end3 = 0; + + //Send these commands. User must respond back with all 3 + // between 5 and 9 seconds later. + /* C.O. For Lithium Test + if ((int)wfflags->value & WF_ZBOT_DETECT) + { + stuffcmd(ent, "!zbot\n"); + ent->client->resp.bot_end = level.time + 4; + stuffcmd(ent, "@zbot\n"); + ent->client->resp.bot_end2 = level.time + 4; + stuffcmd(ent, "#zbot\n"); + ent->client->resp.bot_end3 = level.time + 4; + + ent->client->resp.bot_retries = 0; + ent->client->resp.bot_retries = 2; + ent->client->resp.bot_retries = 3; + } + C.O. For Lithium Test */ + // -- ANTI-ZBOT ENDS -- // + + //Enforce cl_forwardspeed and cl_sidespeed 5/99 acrid + if (!ent->bot_client) + { + stuffcmd(ent,"cl_forwardspeed 200\n"); + stuffcmd(ent,"cl_sidespeed 200\n"); + } + +//ERASER START + ///Q2 Camera Begin + EntityListAdd(ent); + ///Q2 Camera End +//ERASER END +//WF34 SE + // Voting + ent->client->pers.HasVoted = false; + + // Homing + ent->client->pers.homing_state = 1; + + // Feign + ent->client->pers.feign = 0; + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + {//WF34 E + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + }//WF34 + my_bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + +//WF24 S Setup user + WFClientSetup(ent); + sl_LogPlayerConnect( &gi, level, ent ); // StdLog - Mark Davies +//WF24 E + + // 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. +============ +*///WF24 IS A BIT DIF +void botAddPlayer(edict_t *ent); +void ClientBegin (edict_t *ent) +{ + int i; + + //Check if player in cam mode botcam + if(ent->client->bIsCamera) + { + ent->client->bIsCamera = false; + botAddPlayer(ent); + } + + ent->client = game.clients + (ent - g_edicts - 1); +//ERASER START + players[num_players++] = ent; + num_clients++; + + + if (!ctf->value) + { // make sure grapple is removed if CTF has been disabled + ent->client->pers.inventory[ITEM_INDEX(FindItem("Grapple"))] = 0; + } +//ERASER END + if (deathmatch->value) + { +//ERASER START (TeT - commented out to prevent overflows) +// if (bot_show_connect_info->value) +// gi.centerprintf(ent, "\n\n=====================================\nThe Eraser Bot v%1.3f\nby Ryan Feltrin (aka Ridah)\n\nRead the readme.txt file\nlocated in the Eraser directory!\n\nVisit http://impact.frag.com/\nfor Eraser news\n\n-------------------------------------\n", ERASER_VERSION, maxclients->value); +// +// if (teamplay->value && !ctf->value) +// safe_cprintf(ent, PRINT_HIGH, "\n\n=====================================\nServer has enabled TEAMPLAY!\n\nType: \"cmd teams\" to see the list of teams\nType: \"cmd join \" to join a team\n\n"); +//ERASER END + 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, ent);//WF34 ADDED ENT + PutClientInServer (ent); + } + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { + // send effect if in a multiplayer game + if (game.maxclients > 1) + { + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + my_bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + } + } +//WF24 S Setup user + WFClientSetup(ent); +//WF24 E + + // 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; + 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"); + + //Remove any "%" from name + wfFixName(s); + + // start - mdavies + // Has the player got a name + if( strlen(ent->client->pers.netname) ) + { + // has the name changed + if( strcmp( ent->client->pers.netname, s ) ) + { + //int iTimeInSeconds = level.time; + + // log player rename + sl_LogPlayerRename( &gi, + ent->client->pers.netname, + s); + } + } + // end - mdavies + strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1); + + // set skin + s = Info_ValueForKey (userinfo, "skin"); + + playernum = ent-g_edicts-1; + + // combine name and skin into a configstring +//ZOID +// if (ctf->value) + CTFAssignSkin(ent, s); +// else +//ZOID +// gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) ); + + // 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; + } + + // handedness + s = Info_ValueForKey (userinfo, "hand"); + if (strlen(s)) + { + ent->client->pers.hand = atoi(s); + } + + // save off the userinfo in case we want to check something later + strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1); + +} + + +/* +=========== +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; + char *tmp; + int i; + + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + + // drop port # + tmp = value; +if (!ent->bot_client) + while (tmp) + { + if (*(++tmp) == ':') + { + *tmp = (char) NULL; + break; + } + } + + if (IsBanned(value)) + { + gi.dprintf("*** Banned IP %s tried to connect!\n", value); + Info_SetValueForKey(userinfo, "rejmsg", "You are banned."); + return false; + } + + // check for a password + if (!ent->bot_client)//ERASER + { + value = Info_ValueForKey (userinfo, "password"); + if ((wfpassword) &&(strcmp(wfpassword->string, value) != 0)) + return false; + } + + // they can connect + ent->client = game.clients + (ent - g_edicts - 1); + + // 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 +//ZOID -- force team join + ent->client->resp.ctf_team = -1; +//ZOID + is_bot = ent->bot_client;//ERASER// make sure bot's join a team + InitClientResp (ent->client, ent);//WF34 ADDED ENT + is_bot = false;//ERASER + + if (!game.autosaved || !ent->client->pers.weapon) + InitClientPersistant (ent->client); + if (!ent->wf_team) ent->wf_team = ent->client->resp.ctf_team; + } +//ERASER START + // do real client specific stuff + if (!ent->bot_client) + { + int i; + + ent->client->team = NULL; + + ent->client->lag_trail = gi.TagMalloc(sizeof(lag_trail_t), TAG_GAME); + ent->client->lag_angles = gi.TagMalloc(sizeof(lag_trail_t), TAG_GAME); + + // init the lag_trail + for (i=0; i<10; i++) + { + VectorCopy(ent->s.origin, (*ent->client->lag_trail)[i]); + VectorCopy(ent->client->v_angle, (*ent->client->lag_angles)[i]); + } + } +//ERASER END + + // Clear active grenade and special item counts +//gi.dprintf("Debug: clear grenade counts. After =\n"); + for (i=1; i <= GRENADE_TYPE_COUNT; ++i) + ent->client->pers.active_grenades[i] = 0; + + for (i=1; i <= SPECIAL_COUNT; ++i) + ent->client->pers.active_special[i] = 0; + + ClientUserinfoChanged (ent, userinfo); + + if (game.maxclients > 1) + gi.dprintf ("%s connected\n", ent->client->pers.netname); + + //make sure they don't start with a old players class 5/99 + if (!ent->bot_client) + { + if (ent->client->pers.player_class != 0) + { + ent->client->pers.player_class = 0; + ent->client->pers.next_player_class = 0; + // gi.dprintf("Connected with a class\n"); + } + } + + ent->svflags = 0; // make sure we start with known default + ent->client->pers.connected = true; + return true; +} + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. +============ +*/ +void botRemovePlayer(edict_t *self);//ERASER + +void ClientDisconnect (edict_t *ent) +{ + int playernum; + //K2 + int i; + edict_t *other; + //K2 + + if (!ent->client) + return; + + //See if it's the ref + if (wf_game.ref_ent == ent) + wf_game.ref_ent = NULL; + +//ERASER START + ///Q2 Camera Begin + EntityListRemove(ent); + ///Q2 Camera End + + //K2: If player was in cammode, he was already removed 3/99 + if(!ent->client->bIsCamera) + botRemovePlayer(ent); + +//ERASER END + my_bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname); + + // update chase cam if being followed + for (i = 0; i <= num_players; i++) { + + other = players[i]; + + if(!other || other->bot_client) + continue; + + if (other->inuse && other->client->chase_target == ent) + { + other->client->chase_target->inuse = false; + UpdateChaseCam(other); + } + } + //K2:End //3/99 botcam acrid + + ent->client->resp.inServer = false;//3/99 botcam + + + //WF + sl_LogPlayerDisconnect( &gi, level, ent ); // StdLog - Mark Davies + WFPlayer_ChangeClassTeam(ent); + +//WF + +//ZOID + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); +//ZOID + + // 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, ""); +} + + +//============================================================== + + +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. +============== +*/ +int last_bot=0;//ERASER +#define BOT_THINK_TIME 0.03//ERASER// never do bot thinks for more than this time + +void ClientThink (edict_t *ent, usercmd_t *ucmd) +{ + gclient_t *client; + edict_t *other; + int i, j; + pmove_t pm; + qboolean jetting; +//ERASER START + int count=0, start; + clock_t start_time, now; + + int playernum; + char userinfo[MAX_INFO_STRING];//acrid 3/99 freeze + + //ZBOT DETECTION FROM LITHIUM + if ((int)wfflags->value & WF_ZBOT_DETECT) + { + if(ZbotCheck(ent, ucmd)) + { + zbotLogAttack(ent, 1); + //my_bprintf (PRINT_MEDIUM,"SYSTEM MESSAGE: %s has been kicked for using a ZBot.\n", ent->client->pers.netname); + safe_cprintf(ent, PRINT_HIGH, "You have been kicked for using a ZBot.\n"); + stuffcmd(ent, "disconnect\n"); // kick out zbots. + my_bprintf(PRINT_HIGH, "%s is using a ZBot!\n",ent->client->pers.netname); + I_AM_A_ZBOT(ent); + } + } + + //ZBOT DETECTION FROM LITHIUM + + if (paused) + { + gi.centerprintf(ent, "PAUSED\n\n(type \"botpause\" to resume)"); + ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + return; + } + +//ERASER END +//Acrid freeze 3/99 + if (ent->frozen) + { + if( level.time < ent->frozentime ) + { ent->client->ps.pmove.pm_type = PM_DEAD; + + if (ent->client->buttons & BUTTON_ATTACK)//4/99 acrid + { + ent->client->buttons &= ~BUTTON_ATTACK; + } + + return; + } + else + { + playernum = ent - g_edicts - 1; + strcpy( userinfo, ent->client->pers.userinfo ); + ent->frozen = 0; + Info_SetValueForKey( userinfo, "skin", ent->oldskin ); + ClientUserinfoChanged( ent, userinfo ); + } + } + + 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; + } + + + //Feign Acrid + if(client->pers.feign) + { + if (ent->health <=0) + client->pers.feign = 0; +//oldfeign else + // return; + } + +//ERASER START + ///Q2 Camera Begin + if (ent->client->bIsCamera) + { + CameraThink(ent,ucmd); + return; + } + ///Q2 Camera End + + if (ent->bot_client) + return; + + if (client->on_hook == true)//newgrap 4/99 + { + CTFGrapplePull2(ent); + client->ps.pmove.gravity = 0; + } + else + { + client->ps.pmove.gravity = sv_gravity->value; + } +//gi.dprintf("f: %i, s: %i, u: %i\n", ucmd->forwardmove, ucmd->sidemove, ucmd->upmove); +//gi.dprintf("%i, %i\n", ucmd->buttons, client->ps.pmove.pm_flags); +//ERASER END 2 LINES ALREADY CO + pm_passent = ent; + +//ZOID + if (ent->client->chase_target) { + 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]); + return; + } +//ZOID + + // 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; + + //Enforce cl_forwardspeed and cl_sidespeed 5/99 acrid + if (!ent->bot_client) + { + if (ucmd->forwardmove > 400 || ucmd->sidemove > 400) + { + stuffcmd(ent,"cl_forwardspeed 200\n"); + stuffcmd(ent,"cl_sidespeed 200\n"); + } + } + +// client->ps.pmove.gravity = sv_gravity->value;newgrap 4/99 +//WF24 S + if (ent->flags & FL_BOOTS) + client->ps.pmove.gravity = sv_gravity->value * 0.25; + if(ent->Slower>level.time) + { + ucmd->forwardmove *= 0.5; + ucmd->sidemove *= 0.5; + ucmd->upmove *= 0.5; + } + //WF & ATTILA begin + //Turn off thrusting state if the WF_NO_FLYING flag is set + //This is set here so the feature can be turned off mid-game + //Some classes can't fly + if (ent->client && + (((int)wfflags->value & WF_NO_FLYING) || + ((ent->client->player_special & SPECIAL_JETPACK) == 0))) + { + ent->client->thrusting = 0; + } + + //Thrust if you are not all the way in the water + if ((ent->client->thrusting) && (ent->waterlevel < 3)) + // if ( Jet_Active(ent) ) + Jet_ApplyJet( ent, ucmd );//FIXME NODES +//WF24 E & ATTILA end + + 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; + } +//WF34 START + if(ent->superslow) + { +// ucmd->forwardmove *= 0.4; +// ucmd->sidemove *= 0.4; +// ucmd->upmove *= 0.4; + ucmd->forwardmove *= 0.75; + ucmd->sidemove *= 0.75; + ucmd->upmove *= 0.75; + } + if(ent->lame) + { + ucmd->forwardmove *= 0.5; + ucmd->sidemove *= 0.5; + ucmd->upmove *= 0.5; + } +//WF34 END + //Feign Acrid + if(client->pers.feign)//newfeign 3/99 + { + ucmd->forwardmove *= 0.0; + ucmd->sidemove *= 0.0; + ucmd->upmove *= 0.0; + } + 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); + + // save results of pmove + client->ps.pmove = pm.s; + client->old_pmove = pm.s; + + 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; + } + + jetting = Jet_Active(ent); + for (i=0 ; i<3 ; i++) + { + if ( !jetting + || (jetting + && (fabs((float)pm.s.velocity[i]*0.125) < fabs(ent->velocity[i]))) ) + { + ent->velocity[i] = pm.s.velocity[i]*0.125; + } + } + + //FIXME NODES? dont remember //newfeign 3/99 + //Feign Acrid + if(!client->pers.feign) + { + 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]); + +//WF24 S & ATTILA begin + if (jetting) + if( pm.groundentity ) //are we on ground + if ( Jet_AvoidGround(ent) ) //then lift us if possible + pm.groundentity = NULL; //FIXME NODES //now we are no longer on ground +//WF24 E & ATTILA end + + + if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0)) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); + } + + //Feign Acrid + if(!client->pers.feign)//newfeign 3/99 + 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; + } + //Feign Acrid + else if(!client->pers.feign)//newfeign 3/99 + { + VectorCopy (pm.viewangles, client->v_angle); + VectorCopy (pm.viewangles, client->ps.viewangles); + } + + +//ZOID + if (ent->bot_client)//newgrap 4/99 + { + if (client->ctf_grapple) + CTFGrapplePull(client->ctf_grapple); + } +//ZOID + + + // handle cloaking ability + if (ent->client->cloaking) + { + if (ucmd->forwardmove != 0 || ucmd->sidemove != 0) + { + ent->svflags &= ~SVF_NOCLIENT; + ent->client->cloaking = false; + } + else + { + if (ent->svflags & SVF_NOCLIENT) + { + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] >= CLOAK_AMMO) + { + ent->client->cloakdrain ++; + if (ent->client->cloakdrain == CLOAK_DRAIN) + { + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= CLOAK_AMMO; + ent->client->cloakdrain = 0; + } + } + else + { + ent->svflags &= ~SVF_NOCLIENT; + ent->client->cloaking = false; + } + } + else + { + if (ent->client->cloaking) + { + if (level.time > ent->client->cloaktime) + { + ent->svflags |= SVF_NOCLIENT; + ent->client->cloakdrain = 0; + } + } + else + { + ent->client->cloaktime = level.time + CLOAK_ACTIVATE_TIME; + ent->client->cloaking = true; + } + } + } + } + + + gi.linkentity (ent); + + if (ent->movetype != MOVETYPE_NOCLIP) + G_TouchTriggers (ent); + + // touch other objects + for (i=0 ; itouch) + continue; +//acrid 3/99 + if (other->frozen && other->client ) + { + // only touch-unfreeze if we have the same skin acrid3 + if( !strcmp( Info_ValueForKey( ent->client->pers.userinfo, "skin" ), other->oldskin)) + {botDebugPrint("frozen and have same skin \n"); + // find the player number + playernum = other - g_edicts - 1; + // get the userinfo + strcpy( userinfo, other->client->pers.userinfo ); + // unfreeze me and restore skin + other->frozen = 0; + Info_SetValueForKey( userinfo, "skin", other->oldskin ); + ClientUserinfoChanged( other, userinfo ); + } + } + // ****************** + + other->touch (other, ent, NULL, NULL); + } +//ERASER START + if ((client->latency > 0) && !(client->buttons & BUTTON_ATTACK) && (ucmd->buttons & BUTTON_ATTACK)) + { + if ((level.time - client->firing_delay) > 0.1) + client->firing_delay = level.time + (client->latency/1000); + + if (client->firing_delay > level.time) // turn it off + { + ucmd->buttons &= ~BUTTON_ATTACK; + if (ucmd->buttons >= BUTTON_ANY) + ucmd->buttons -= BUTTON_ANY; + } + } + + if ((client->latency > 0) && !(ucmd->buttons & BUTTON_ATTACK) && (client->firing_delay > (level.time - 0.1)) && (client->firing_delay <= level.time)) + { // remember the button after it was released when simulating lag + ucmd->buttons |= BUTTON_ATTACK; + } +//ERASER END + 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 +//ZOID + && ent->movetype != MOVETYPE_NOCLIP +//ZOID + ) + { + if (!client->weapon_thunk) + { + client->weapon_thunk = true; + Think_Weapon (ent); + } + } +//WF24 S ST + if ((ent->client->kamikaze_mode & 1) && (ent->client->kamikaze_framenum <= level.framenum)) + Kamikaze_Explode(ent); +//WF24 E ST + +//ZOID +//regen tech + CTFApplyRegeneration(ent); +//ZOID + +//ZOID + //K2: Changed to use playerlist botcam + // update chase cam if being followed +// for (i = 1; i <= maxclients->value; i++) { +// other = g_edicts + i; + for (i = 0; i <= num_players; i++) { + + other = players[i]; + + if (!other || other->bot_client) + continue; + if (other->inuse && other->client->chase_target == ent)//orig from here + UpdateChaseCam(other); + } +//ZOID +//WF34 START + //WF + if(ent->cantmove) + VectorCopy(ent->LockedPosition,ent->s.origin); +//WF34 END + +//ERASER START + // ========================================================= + // check for a bots to think + if ((num_players > 2) && (num_clients == 1)) + { + int save=-1, loop_count=0; + vec3_t org; + + start = last_bot; + start_time = clock(); + now = start_time; + + // go to the next bot + last_bot++; + if (last_bot >= num_players) + last_bot = 0; + + VectorCopy(ent->s.origin, org); + org[2] += ent->viewheight; + + while ( (count < 5) && (((double) (now - start_time)) < 50)) + { + // don't do client's, or bot's in this client's view + while ( (players[last_bot]->last_think_time == level.time) + || (!players[last_bot]->bot_client) + || ( (gi.inPVS(org, players[last_bot]->s.origin)) + && ( (players[last_bot]->nextthink >= level.time) + || (players[last_bot]->nextthink = level.time + 0.1) + ) + ) // this makes the bot think by itself (smooth movement) + ) + { + last_bot++; + if (last_bot == num_players) + last_bot = 0; + loop_count++; + + if (players[last_bot]->bot_client && (last_bot == start)) // we've done all bots + goto done_all_bots; + + if (loop_count > num_players) + goto done_all_bots; + } + + if (start == 0) + start = last_bot; + + if ((level.time - players[last_bot]->last_think_time) >= 0.1) + { + players[last_bot]->think(players[last_bot]); + players[last_bot]->nextthink = -1; // don't think by themselves + count++; + + save = last_bot; + } + + if (last_bot == start) // we've done all bots + break; + + last_bot++; + if (last_bot == num_players) + last_bot = 0; + loop_count++; + if (loop_count > num_players) + goto done_all_bots; + + now = clock(); + + } + + } + +done_all_bots: +/* + // check for firing between server frames + for (i=0; ibot_client) + && (players[i]->dmg) +// && ( (timebuffer.millitm < players[i]->lastattack_time.millitm) +// || ((timebuffer.millitm - players[i]->lastattack_time.millitm) >= 50) +// ) + ) + { + bot_FireWeapon(players[i]); + players[i]->dmg = 0; + } + } +*/ + if (!dedicated->value) + OptimizeRouteCache();//crash??? + + // ========================================================= + // Check to see if player pressing the "use" key newgrap 4/99 + if (ent->client->buttons & BUTTON_USE && !ent->deadflag && + client->hook_frame <= level.framenum) + { + if((ent->client->player_special & SPECIAL_GRAPPLE) == 0) + { + safe_cprintf(ent, PRINT_HIGH, "Sorry - This class cannot use the grapple.\n"); + return; + } + else if (ent->movetype == MOVETYPE_NOCLIP) + { + return; + } + else + { + int damage = 10; + CTFGrappleFire2 (ent, vec3_origin, damage, 0); + // CTFWeapon_Grapple_Fire2 (ent); + } + } + if (Ended_Grappling (client) && !ent->deadflag && client->ctf_grapple) + { + CTFPlayerResetGrapple2(ent); + } + +} + +//ERASER END + +/* +============== +ClientBeginServerFrame + +This will be called once for each server frame, before running +any other entities in the world. +============== +*/ +//ERASER START +void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator); +void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator); +void CTFFlagThink(edict_t *ent); +//ERASER END +void VectorRotate(vec3_t in, vec3_t angles, vec3_t out); //WF34 + + +void ClientBeginServerFrame (edict_t *ent) +{ + gclient_t *client; + int i;//ERASER + int buttonMask; +// qboolean noise = false; +// vec3_t temp,new;//WF34 +// float volume = 1.0; +// int blood;//WF34 + + if (ent->bot_client)//ERASER + return;//E + + if (level.intermissiontime) + return; +//ERASER START + ///Q2 Camera Begin + if (ent->client->bIsCamera) + { + return; + } + ///Q2 Camera End +//ERASER END + client = ent->client; + + /* C.O. For Lithium Test + // -- ANTI-ZBOT STARTS -- // + if (ent->client->resp.bot_start > 0 && ent->client->resp.bot_start <= level.time) + { + //ent->client->resp.bot_start = 0; + //stuffcmd(ent, "!zbot\n"); + //ent->client->resp.bot_end = level.time + 4; + //stuffcmd(ent, "@zbot\n"); + //ent->client->resp.bot_end2 = level.time + 4; + //stuffcmd(ent, "#zbot\n"); + //ent->client->resp.bot_end3 = level.time + 4; + } + + if (ent->client->resp.bot_end > 0 && ent->client->resp.bot_end <= level.time) + { + //If the player ping is too high, try again later + if (ent->client->resp.bot_retries <= 4) + { + ent->client->resp.bot_start = level.time + 5 + (rand() % 5); + ent->client->resp.bot_end = 0; + ++ent->client->resp.bot_retries; + } + else + { + ent->client->resp.bot_end = 0; + zbotLogAttack(ent, 1); + //my_bprintf (PRINT_MEDIUM,"SYSTEM MESSAGE: %s has been kicked for using a ZBot.\n", ent->client->pers.netname); + safe_cprintf(ent, PRINT_HIGH, "You have been kicked for using a ZBot (or you have a bad connection).\n"); + stuffcmd(ent, "disconnect\n"); // kick out zbots. + } + } + + if (ent->client->resp.bot_end2 > 0 && ent->client->resp.bot_end2 <= level.time) + { + //If the player ping is too high, try again later + if (ent->client->resp.bot_retries2 <= 4) + { + ent->client->resp.bot_start = level.time + 5 + (rand() % 5); + ent->client->resp.bot_end2 = 0; + ++ent->client->resp.bot_retries2; + } + else + { + ent->client->resp.bot_end2 = 0; + zbotLogAttack(ent, 2); + //my_bprintf (PRINT_MEDIUM,"SYSTEM MESSAGE: %s has been kicked for using a ZBot.\n", ent->client->pers.netname); + safe_cprintf(ent, PRINT_HIGH, "You have been kicked for using a ZBot (or you have a bad connection).\n"); + stuffcmd(ent, "disconnect\n"); // kick out zbots. + } + } + + if (ent->client->resp.bot_end3 > 0 && ent->client->resp.bot_end3 <= level.time) + { + //If the player ping is too high, try again later + if (ent->client->resp.bot_retries3 <= 4) + { + ent->client->resp.bot_start = level.time + 5 + (rand() % 5); + ent->client->resp.bot_end3 = 0; + ++ent->client->resp.bot_retries3; + } + else + { + ent->client->resp.bot_end3 = 0; + zbotLogAttack(ent, 3); + //my_bprintf (PRINT_MEDIUM,"SYSTEM MESSAGE: %s has been kicked for using a ZBot.\n", ent->client->pers.netname); + safe_cprintf(ent, PRINT_HIGH, "You have been kicked for using a ZBot (or you have a bad connection).\n"); + stuffcmd(ent, "disconnect\n"); // kick out zbots. + } + } + + + C.O. For Lithium Test + + */ + // -- ANTI-ZBOT ENDS -- // + + // run weapon animations if it hasn't been done by a ucmd_t + if (!client->weapon_thunk +//ZOID + && ent->movetype != MOVETYPE_NOCLIP +//ZOID + ) + Think_Weapon (ent); + else + client->weapon_thunk = false; + +//ERASER START + if (client->latency > 0) + { + // -- save the location in the lag_trail + // filter the trails down one position + for (i=9; i>0; i--) + { + VectorCopy((*client->lag_trail)[i-1], (*client->lag_trail)[i]); + VectorCopy((*client->lag_angles)[i-1], (*client->lag_angles)[i]); + } + + VectorCopy(ent->s.origin, (*client->lag_trail)[0]); + VectorCopy(ent->client->v_angle, (*client->lag_angles)[0]); + // -- done. + } +//ERASER END + + if (ent->deadflag) + { + // wait for any button just going down + if ( level.time > client->respawn_time) + { + // 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 ((int)wfflags->value & WF_DECOY_PURSUE) + + if (!visible (ent, PlayerTrail_LastSpot() ) ) +// PlayerTrail_Add (ent->s.old_origin);//WF24 LINE//ACRID FIXME MAJOR TROUBLE + PlayerTrail_Add (ent, ent->s.old_origin, NULL, false, false, NODE_NORMAL); +*///FIXME NODES + //WF24 E + client->latched_buttons = 0; +//WF34 START + + if ((ent->disguised ||ent->disguisedteam) && (ent->disguiseshots==0)) + { + WFRemoveDisguise(ent); + } + + //See if menu is up + if (ent->client->menu) + { + //check timeout value of menu + if ((client->menu->MenuTimeout) && (level.time > client->menu->MenuTimeout)) + { + client->menu->MenuTimeout = 0; + PMenu_Close(ent); + } + } + + +//ERASER START +// if (gi.pointcontents(ent->s.origin) & CONTENTS_LADDER) + + // check for new node(s) + if (bot_calc_nodes->value) + { + CheckMoveForNodes(ent); + } + +//if (ent->groundentity) +//gi.dprintf("%s\n", ent->groundentity->classname); + + if (ent->flags & FL_SHOWPATH) + { + // set the target location + if (!ent->goalentity) // spawn it + { + ent->goalentity = G_Spawn(); + ent->goalentity->classname = "player goal"; + VectorCopy(ent->mins, ent->goalentity->mins); + VectorCopy(ent->maxs, ent->goalentity->maxs); + + VectorCopy(ent->s.origin, ent->goalentity->s.origin); + ent->goalentity->s.modelindex = 255; + gi.linkentity(ent->goalentity); + } + + if (ent->client->buttons & BUTTON_ATTACK) + { + VectorCopy(ent->s.origin, ent->goalentity->s.origin); + ent->goalentity->s.modelindex = 255; + gi.linkentity(ent->goalentity); + } + + Debug_ShowPathToGoal(ent, ent->goalentity); + } + +//gi.dprintf("%i\n", ent->waterlevel); + + // HACK, send bots to us if we have the flag, and also summon some helper bots + if (ctf->value && CarryingFlag(ent))// && +// (!ent->movetarget || !ent->movetarget->item || (ent->movetarget->item->pickup != CTFPickup_Flag))) + { + edict_t *flag, *enemy_flag, *plyr, *self; + int i=0, count=0, ideal; + static float last_checkhelp=0; + + self = ent; + + if (self->client->resp.ctf_team == CTF_TEAM1) + { + flag = flag1_ent; + enemy_flag = flag2_ent; + } + else + { + flag = flag2_ent; + enemy_flag = flag1_ent; + } + + // look for some helpers + if (last_checkhelp < (level.time - 0.5)) + { + for (i=0; iclient->resp.ctf_team != self->client->resp.ctf_team) + { + if ( (plyr->enemy != self) + && (!plyr->target_ent || + (plyr->target_ent->think != CTFFlagThink) || + (entdist(plyr, plyr->target_ent) > 1000)) + && (entdist(plyr, self) < 2000)) + { // send this enemy to us + plyr->enemy = self; + } +// continue; + } + + if ((plyr != self) && (plyr->target_ent == self)) + count++; + } + + ideal = ((int)ceil((1.0*(float)num_players)/4.0)); + + if (count < ideal) + { + for (i=0; (iclient->resp.ctf_team != self->client->resp.ctf_team) + continue; + + if (plyr->target_ent == self) + continue; + + if (entdist(plyr, self) > 700) + continue; + + if (!gi.inPVS(plyr->s.origin, self->s.origin)) + continue; + + plyr->target_ent = self; + if (++count >= ideal) + break; + } + } + else if (count > ideal) // release a defender + { + for (i=0; (iclient->resp.ctf_team != self->client->resp.ctf_team) + continue; + + if (plyr->target_ent != self) + continue; + + plyr->target_ent = NULL; + break; + } + } + + last_checkhelp = level.time + random()*0.5; + } + + } + + if (ent->flags & FL_SHOW_FLAGPATHS) + { + edict_t *trav; + + // show lines between alternate routes + trav = NULL; + while (trav = G_Find(trav, FOFS(classname), "flag_path_src")) + { + if (!trav->last_goal || !trav->target_ent) + continue; // not complete, don't save + + trav->s.modelindex = gi.modelindex ("models/objects/gibs/chest/tris.md2"); + + if (trav->last_goal) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (trav->s.origin); + gi.WritePosition (trav->last_goal->s.origin); + gi.multicast (trav->s.origin, MULTICAST_PVS); + } + if (trav->target_ent) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (trav->s.origin); + gi.WritePosition (trav->target_ent->s.origin); + gi.multicast (trav->s.origin, MULTICAST_PHS); + } + + } + } + +} +//ERASER END \ No newline at end of file diff --git a/p_hud.c b/p_hud.c new file mode 100644 index 0000000..57a0c42 --- /dev/null +++ b/p_hud.c @@ -0,0 +1,769 @@ +#include "g_local.h" + + + +/* +====================================================================== + +INTERMISSION + +====================================================================== +*/ + +void MoveClientToIntermission (edict_t *ent) +{ +//ERASER START + gclient_t *client; + + client = ent->client; +//ERASER END + if (deathmatch->value || coop->value) + ent->client->showscores = true; + VectorCopy (level.intermission_origin, ent->s.origin); + ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8; + ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8; + ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8; + VectorCopy (level.intermission_angle, ent->client->ps.viewangles); + ent->client->ps.pmove.pm_type = PM_FREEZE; + ent->client->ps.gunindex = 0; + ent->client->ps.blend[3] = 0; + ent->client->ps.rdflags &= ~RDF_UNDERWATER; + + // clean up powerup info + ent->client->quad_framenum = 0; + ent->client->invincible_framenum = 0; + ent->client->breather_framenum = 0; + ent->client->enviro_framenum = 0; + ent->client->grenade_blew_up = false; + ent->client->grenade_time = 0; + + ent->viewheight = 0; + ent->s.modelindex = 0; + ent->s.modelindex2 = 0; + ent->s.modelindex3 = 0; + ent->s.modelindex = 0; + ent->s.effects = 0; + ent->s.sound = 0; + ent->solid = SOLID_NOT; + + // add the layout + + if (!ent->bot_client && (deathmatch->value || coop->value))//ERASER ADDED (!ent->bot_client && + { + DeathmatchScoreboardMessage (ent, NULL); + gi.unicast (ent, true); + } + +} + +void BeginIntermission (edict_t *targ) +{ + int i, n; + edict_t *ent, *client; + + if (level.intermissiontime) + return; // allready activated + +//ZOID + if (deathmatch->value && ctf->value) + CTFCalcScores(); +//ZOID + + game.autosaved = false; + + // respawn any dead clients + for (i=0 ; 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); + } +} + +//ERASER START FOR CUSTOM TEAMS PLAY +void TeamplayScoreboardMessage (edict_t *ent, edict_t *killer) +{ + char entry[1024]; + char string[1400]; + int stringlength; + int i, j, k, t; + int sorted[MAX_CLIENTS]; + int sortedscores[MAX_CLIENTS]; + + bot_team_t *sortedteams[MAX_TEAMS]; + int doneteam[MAX_TEAMS]; + int numteams, best, bestscore; + + int score, total; + int x, y; + gclient_t *cl; + edict_t *cl_ent; + + // clear the doneteam flags + memset(doneteam, 0, sizeof(int) * MAX_TEAMS); + + numteams = 0; // incremented each time we add a team to the list + + // sort the teams + for (i=0; iingame) + continue; + + bestscore = -999999; + best = -1; + + // find the highest scoring team + for (j=0; jingame) + continue; + + if (bot_teams[j]->score > bestscore) + { + best = j; + bestscore = bot_teams[j]->score; + } + } + + if (best > -1) + { + doneteam[best] = true; + sortedteams[numteams] = bot_teams[best]; + numteams++; + } + else // must be done + { + break; + } + } + + string[0] = 0; + stringlength = strlen(string); + + for (t=0; t 3) // only print the top 4 teams + break; + + // sort the clients by score + total = 0; + for (i=0 ; iinuse) + continue; + + if (cl_ent->client->team != sortedteams[t]) + 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++; + } + + if (total > 4) + total = 4; + + x = 160; + y = (t * 48); + + if (ent->client->team == sortedteams[t]) + { + Com_sprintf (entry, sizeof(entry), + "xv %i yv %i picn tag1 ", 32, y); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + // Send team info + Com_sprintf (entry, sizeof(entry), + "xv %i yv %i string \"%s\" ", 70, y+6, sortedteams[t]->teamname); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + + // Send team score info + Com_sprintf (entry, sizeof(entry), + "xv %i yv %i string \"%i\" ", 80, y + 20, sortedteams[t]->score); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + + for (i=0 ; iresp.score, cl->pers.netname); + } + else + { + Com_sprintf (entry, sizeof(entry), + "xv %i yv %i string2 \"%3i %s\" ", + x, y, cl->resp.score, cl->pers.netname); + } + + j = strlen(entry); + if (stringlength + j > 1024) + break; + + strcpy (string + stringlength, entry); + stringlength += j; + } + } + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} +//ERASER END + +/* +================== +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; + +//WF - Scanner +// if (ent->client->showscores || ent->client->showinventory) +// if (ent->client->pers.scanner_active) +// ent->client->pers.scanner_active=2; +//WF + + //WF - added following 2 lines + if (ent->client->showscores) + { + +//ZOID + if (ctf->value) + { + CTFScoreboardMessage (ent, killer); + return; + } +//ZOID + +//ERASER START + if (teamplay->value && ent->client->team) + { + TeamplayScoreboardMessage(ent, killer); + return; + } +//ERASER END + + + // 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; + } + +//ERASER START + if (cl_ent->bot_client) + { + cl->ping = (int) cl_ent->bot_stats->avg_ping + ((random() * 2) - 1) * 80; + if (cl->ping < 0) + cl->ping = 0; + } +//ERASER END + + // 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; + } + }//WF - added rest of stuff here including this line + else + *string=0; + + //WF Scanner active ? + //if (ent->client->pers.scanner_active) + // ShowScanner(ent,string); + + //WF END + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + + +/* +================== +DeathmatchScoreboard + +Draw instead of help message. +Note that it isn't that hard to overflow the 1400 byte message limit! +================== +*/ +void DeathmatchScoreboard (edict_t *ent) +{ + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Score_f + +Display the scoreboard +================== +*/ +void Cmd_Score_f (edict_t *ent) +{ + ent->client->showinventory = false; + ent->client->showhelp = false; +//ZOID + if (ent->client->menu) + PMenu_Close(ent); +//ZOID + + if (!deathmatch->value && !coop->value) + return; + + if (ent->client->showscores) + { + ent->client->showscores = false; + ent->client->update_chase = true; + return; + } + + ent->client->showscores = true; + + DeathmatchScoreboard (ent); +} + + +/* +================== +HelpComputer + +Draw help computer. +================== +*/ +void HelpComputer (edict_t *ent) +{ + char string[1024]; +/* + char *sk; + + if (skill->value == 0) + sk = "easy"; + else if (skill->value == 1) + sk = "medium"; + else if (skill->value == 2) + sk = "hard"; + else + sk = "hard+"; +*/ + // send the layout + Com_sprintf (string, sizeof(string), + "xv 32 yv 8 picn help " // background + "xv 0 yv 24 cstring2 \"%s\" " // level name + "xv 0 yv 54 cstring2 \"%s\" " // help 1 + "xv 0 yv 110 cstring2 \"%s\" ", // help 2 + level.level_name, + game.helpmessage1, + game.helpmessage2); + + 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); +} + +// TeT Using a menu to display map help - see g_ctf.c +//void Cmd_MapHelp_f (edict_t *ent) +//{ +// ent->client->showinventory = false; +// ent->client->showscores = false; + +// if (ent->client->showhelp && (ent->client->resp.game_helpchanged == game.helpchanged)) +// { +// ent->client->showhelp = false; +// return; +// } + +// ent->client->showhelp = true; +// ent->client->resp.helpchanged = 0; +// HelpComputer (ent); +//} + +//======================================================================= + +/* +=============== +G_SetStats +=============== +*/ +void G_SetStats (edict_t *ent) +{ + gitem_t *item; + int index; + int power_armor_type; + int cells = 0; + + // + // health + // + ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health; + ent->client->ps.stats[STAT_HEALTH] = ent->health; + + // + // weapon damage + // + ent->client->ps.stats[STAT_DAMAGE_ICON] = level.pic_damage; + ent->client->ps.stats[STAT_DAMAGE] = ent->client->weapon_damage; + + // + // ref called timeout? + // + if (wf_game.game_halted) + ent->client->ps.stats[STAT_TIMEOUT_ICON] = level.pic_timeout; + else + ent->client->ps.stats[STAT_TIMEOUT_ICON] = 0; + + + // + // ammo + // + if (!ent->client->ammo_index /* ||!ent->client->pers.inventory[ent->client->ammo_index] */) + { + ent->client->ps.stats[STAT_AMMO_ICON] = 0; + ent->client->ps.stats[STAT_AMMO] = 0; + } + else + { + item = &itemlist[ent->client->ammo_index]; + + ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex(item->icon); + ent->client->ps.stats[STAT_AMMO] = + ent->client->pers.inventory[ent->client->ammo_index]; + } + + + // + // armor + // + power_armor_type = PowerArmorType (ent); + if (power_armor_type) + { + cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))]; + if (cells == 0) + { // ran out of cells for power armor + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + power_armor_type = 0;; + } + } + + index = ArmorIndex (ent); + if (power_armor_type && (!index || (level.framenum & 8) ) ) + { // flash between power armor and other armor icon + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield"); + ent->client->ps.stats[STAT_ARMOR] = cells; + } + else if (index) + { + item = GetItemByIndex (index); + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index]; + } + else + { + ent->client->ps.stats[STAT_ARMOR_ICON] = 0; + ent->client->ps.stats[STAT_ARMOR] = 0; + } + + // + // pickup message + // + if (level.time > ent->client->pickup_msg_time) + { + ent->client->ps.stats[STAT_PICKUP_ICON] = 0; + ent->client->ps.stats[STAT_PICKUP_STRING] = 0; + } + + // + // timers + // + if (ent->client->quad_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10; + } + else if (ent->client->invincible_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10; + } + else if (ent->client->enviro_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10; + } + else if (ent->client->breather_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10; + } + else + { + ent->client->ps.stats[STAT_TIMER_ICON] = 0; + ent->client->ps.stats[STAT_TIMER] = 0; + } + + // + // selected item + // + if (ent->client->pers.selected_item == -1) + ent->client->ps.stats[STAT_SELECTED_ICON] = 0; + else + ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon); + + ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item; + + // + // layouts + // + ent->client->ps.stats[STAT_LAYOUTS] = 0; + + if (deathmatch->value) + { +//WF - scanner + if (ent->client->pers.health <= 0 || level.intermissiontime + || ent->client->showscores || ent->client->pers.scanner_active + || ent->client->showhelp) // TeT, added showhelp, needed for maphelp + ent->client->ps.stats[STAT_LAYOUTS] |= 1; +//WF + + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + else + { + if (ent->client->showscores || ent->client->showhelp) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + + // + // frags + // + ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score; + + // + // help icon / current weapon if not shown + // + if (ent->client->resp.helpchanged && (level.framenum&8) ) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help"); + else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91) + && ent->client->pers.weapon) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon); + else + ent->client->ps.stats[STAT_HELPICON] = 0; + +//ZOID + SetCTFStats(ent); +//ZOID +} diff --git a/p_menu.c b/p_menu.c new file mode 100644 index 0000000..b3c97d6 --- /dev/null +++ b/p_menu.c @@ -0,0 +1,278 @@ +#include "g_local.h" + +//#define DEBUG_NT 1 +//#define DEBUG_UNIX 2 + +#ifdef DEBUG_NT +#include +#endif + +void PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num, qboolean usekeys, qboolean showbackground) +{ + 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 = malloc(sizeof(*hnd)); + + hnd->entries = entries; + hnd->num = num; + hnd->MenuTimeout = 0; //Default to no timeout + hnd->UseNumberKeys = usekeys; //Can select items with number keys? + hnd->ShowBackground = showbackground; + + 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; + + free(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->health<1) + { + PMenu_Close(ent); + return; + } + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + //JR Disabled this to slow down overflows//Nope added it back since it didn't work right then + + if (hnd->ShowBackground) + strcpy(string, "xv 32 yv 8 picn inventory "); + else + strcpy(string, ""); + + 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++; + } + + //WF - made inventory screen larger + sprintf(string + strlen(string), "yv %d ", 32 + i * 8); + if (p->align == PMENU_ALIGN_CENTER) + x = 196/2 - strlen(t)*4 + 64; +// x = 240/2 - strlen(t)*4 + 64; + else if (p->align == PMENU_ALIGN_RIGHT) +// x = 64 + (196 - strlen(t)*8); + x = 64 + (240 - 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); + +//gi.dprintf("%s\n", 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); +} + +int WFMenuFromNumberKey(edict_t *ent, int slot) +{ + pmenuhnd_t *hnd; + pmenu_t *p; + int i; + int pos; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return 0; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return 0; // no selectable entries + + + for (i = 0, pos = 0; (i < hnd->num) && (ent->client->menu); ++i) + { +//DEBUGGING MEMORY PROBLEM-GREGG +#ifdef DEBUG_NT + if (_CrtIsValidPointer(hnd, sizeof(pmenuhnd_t), true) != true) + { + gi.dprintf("Bad Menu Handle in p_menu (2)\n"); + return 0; + } +#endif +//DEBUGGING MEMORY PROBLEM-GREGG + + if (!ent->client->menu) return 0; + + //Pick next menu item + p = hnd->entries + i; + + //Increment position for selectable items + if ( (p) && p->SelectFunc) + { + ++pos; + + //Did we find it? + if (pos == slot) + { + //Execute the function + p->SelectFunc(ent, p); + return 1; + } + } + if (!ent->client->menu) return 0; + +//DEBUGGING MEMORY PROBLEM-GREGG +#ifdef TARGET_NT + if (_CrtIsValidPointer(hnd, sizeof(pmenuhnd_t), 1) != 1) + { + gi.dprintf("Bad Menu Handle in p_menu (2)\n"); + return 0; + } +#endif +//DEBUGGING MEMORY PROBLEM-GREGG + + } + return 1; +} diff --git a/p_menu.h b/p_menu.h new file mode 100644 index 0000000..8446884 --- /dev/null +++ b/p_menu.h @@ -0,0 +1,31 @@ + +enum { + PMENU_ALIGN_LEFT, + PMENU_ALIGN_CENTER, + PMENU_ALIGN_RIGHT +}; + +typedef struct pmenuhnd_s { + struct pmenu_s *entries; + int cur; + int num; + qboolean UseNumberKeys; //If true, number keys will select a menu item + float MenuTimeout; //If set, menu will time out and be removed after a set amount of time + qboolean ShowBackground; //Set if background should be shown +} pmenuhnd_t; + +typedef struct pmenu_s { + char *text; + int align; +// void *arg; + int 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, qboolean usekeys, qboolean showbackground); +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); +int WFMenuFromNumberKey(edict_t *ent, int slot); diff --git a/p_trail.h b/p_trail.h new file mode 100644 index 0000000..a9ae1a4 --- /dev/null +++ b/p_trail.h @@ -0,0 +1,53 @@ +void OptimizeRouteCache(); +void CalcRoutes(int node_index); +int ClosestNodeToEnt(edict_t *self, int check_fullbox, int check_all_nodes); +float PathToEnt(edict_t *self, edict_t *target, int check_fullbox, int check_all_nodes); +edict_t *PathToEnt_Node; // self's goal node, to get to target, from last PathToEnt() call +edict_t *PathToEnt_TargetNode; // target's goal node, to get FROM self, from last PathToEnt() call +void Debug_ShowPathToGoal(edict_t *self, edict_t *goalent); + +edict_t *matching_trail(vec3_t spot); +void AddTrailToPortals(edict_t *trail); +int GetGridPortal(float pos); + +void NodeDebug(char *fmt, ...); +void CheckMoveForNodes(edict_t *ent); +void CalcItemPaths(edict_t *ent); + +int trail_head, last_head; + +int dropped_trail; +float last_optimize; +int optimize_marker; + +#define NODE_NORMAL 0 +#define NODE_PLAT 1 +#define NODE_LANDING 2 // jump destination node (cannot be seen from any other nodes other than the jumping node) +#define NODE_BUTTON 3 +#define NODE_TELEPORT 4 +#define NODE_GRAPPLE 5 // set when this jump node is a grapple start + +#define TRAIL_PORTAL_SUBDIVISION 24 +#define MAX_TRAILS_PER_PORTAL 196 +#define MAX_MAP_AXIS 5000 + +int trail_portals[TRAIL_PORTAL_SUBDIVISION+1][TRAIL_PORTAL_SUBDIVISION+1][MAX_TRAILS_PER_PORTAL]; +// contains a list of trails grouped by grid blocks, in the X/Y plane, with +// size 512x512 (covers the entire map) + +// each trail can be a member of up to 4 portals, so as to somewhat "blur" the +// line between nodes, so that selection of a ClosestNodeToEnt is less likely +// to be restricted by being close to a boundary + +int num_trail_portals[TRAIL_PORTAL_SUBDIVISION+1][TRAIL_PORTAL_SUBDIVISION+1]; +// keep track of the total nodes in each trail_portal + +typedef struct ctf_item_s +{ + char classname[64]; + vec3_t origin; + vec3_t angles; + struct ctf_item_s *next; +} ctf_item_t; + +ctf_item_t *ctf_item_head; \ No newline at end of file diff --git a/p_trail.o.sav b/p_trail.o.sav new file mode 100644 index 0000000..1f2d358 Binary files /dev/null and b/p_trail.o.sav differ diff --git a/p_trail.obj b/p_trail.obj new file mode 100644 index 0000000..21c4baf Binary files /dev/null and b/p_trail.obj differ diff --git a/p_view.c b/p_view.c new file mode 100644 index 0000000..b115eca --- /dev/null +++ b/p_view.c @@ -0,0 +1,1423 @@ +#include "g_local.h" +#include "m_player.h" + +void ShowCloseFriend_f (edict_t *ent); + +//WF24 S ST +// JDB: Define variables for lowlight vision effect 4/4/98 +#define LLV_R 0.7 +#define LLV_G 0.0 +#define LLV_B 0.0 +#define LLV_A 0.7 +int bubble =0; +int YDirection; +int RDirection; +int PDirection; +float lava_blend =0.5; +int lava = 0; +int guncamx=0; +int guncamy=0; +int guncamz=0; + +//WF24 E ST + +edict_t *current_player; +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) +//WF34 START +//Gas COlors +float GasR[9] = { + 0.4, + 0.2, + 0.7, + 0.2, + 0.1, + 0.7, + 0.8, + 0.4, + 0.34 + }; +float GasG[9] = { + 0.7, + 0.1, + 0.9, + 0.3, + 0.2, + 0.3, + 0.6, + 0.6, + 0.7 + }; +float GasB[9] = { + 0.1, + 0.75, + 0.2, + 0.4, + 0.54, + 0.68, + 0.2, + 0.3, + 0.94 + }; +//WF34 END +/* +=============== +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->pers.feign ==0) + 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; + +//WF24 S ST + edict_t *Bub; + vec3_t offset; + trace_t tr; + +//WF24 E ST +//=================================== + + // 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; +//WF24 S STWF - Concusion grenade + //add angles based on drunk + if (ent->DrunkTime > level.time) + { + bubble = bubble + 1; + if (bubble == 10) + { + Bub = G_Spawn(); + Bub->movetype = MOVETYPE_NOCLIP; + Bub->clipmask = MASK_SHOT; + Bub->solid = SOLID_NOT; + VectorClear (Bub->mins); + VectorClear (Bub->maxs); + VectorSet (Bub->velocity,0,0,10); + Bub->s.modelindex = gi.modelindex ("sprites/s_bubble.sp2"); + Bub->nextthink = level.time + 2; + Bub->think = G_FreeEdict; + VectorCopy(ent->s.origin,Bub->s.origin); + gi.linkentity(Bub); + bubble = 0; + } + ent->client->ps.rdflags |= RDF_UNDERWATER; + if(YDirection) + ent->DizzyYaw += random()*6; + else + ent->DizzyYaw -= random()*6; + if(PDirection) + ent->DizzyPitch += random()*5; + else + ent->DizzyPitch -= random()*5; + if(RDirection) + ent->DizzyRoll += random()*3; + else + ent->DizzyRoll -= random()*3; + if (ent->DizzyYaw < -35) + YDirection = 1; + if (ent->DizzyYaw > 35) + YDirection = 0; + if (ent->DizzyPitch < -25) + PDirection = 1; + if (ent->DizzyPitch > 25) + PDirection = 0; + if (ent->DizzyRoll < -25) + RDirection = 1; + if (ent->DizzyRoll > 25) + RDirection = 0; + angles[YAW] += ent->DizzyYaw; + angles[PITCH] += ent->DizzyPitch; + angles[ROLL] += ent->DizzyRoll; + } +//WF24 E ST + } +//WF34 START + if (ent->client->Gas_Time>level.time) + { + angles[YAW] += ent->client->GasViewOffset[YAW]; + angles[PITCH] += ent->client->GasViewOffset[PITCH]; + angles[ROLL] += ent->client->GasViewOffset[ROLL]; + } +//WF34 END +//=================================== + + // 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 +//WF24 S ST + + //Remote Camera View + if (ent->client->remotetoggle) + { + if (ent->remotecam) + { + VectorSet (v, 0, 0, 0); + ent->client->ps.gunframe = 0; + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->ps.pmove.origin[0] = ent->remotecam->camposition[0]*8; + ent->client->ps.pmove.origin[1] = ent->remotecam->camposition[1]*8; + ent->client->ps.pmove.origin[2] = ent->remotecam->camposition[2]*8; + VectorCopy(ent->s.angles,ent->remotecam->s.angles); +/*db ent->client->oldplayer->s.frame = ent->s.frame; + + VectorCopy(ent->s.origin,ent->client->oldplayer->s.origin); + VectorCopy(ent->s.angles,ent->client->oldplayer->s.angles); + VectorCopy (ent->velocity, ent->client->oldplayer->velocity); + + ent->client->oldplayer->s.modelindex = ent->s.modelindex; + ent->client->oldplayer->s.modelindex2 = ent->s.modelindex2; + ent->client->oldplayer->s.modelindex3 = ent->s.modelindex3;*/ + VectorCopy (v, ent->client->ps.viewoffset); + + } + + } + + //Normal View + else if (ent->client->chasetoggle == 0) + { +//WF24 E + 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; + guncamz = 0;//WF24 + VectorCopy (v, ent->client->ps.viewoffset); +} +//WF S + //Chase Camera View + else + { + + VectorSet(offset,ent->s.origin[0]+forward[0]*-80,ent->s.origin[1]+forward[1]*-80,36+ent->s.origin[2]+forward[2]*-40); + tr = gi.trace (ent->s.origin, NULL, NULL, offset, ent, MASK_SHOT); + + ent->client->ps.pmove.origin[0] = (tr.endpos[0]+forward[0]*10)*8; + ent->client->ps.pmove.origin[1] =(tr.endpos[1]+forward[1]*10)*8; + ent->client->ps.pmove.origin[2] =(tr.endpos[2]+forward[2]*10)*8; +/*db ent->client->oldplayer->s.frame = ent->s.frame; + + VectorCopy(ent->s.origin,ent->client->oldplayer->s.origin); + VectorCopy(ent->s.angles,ent->client->oldplayer->s.angles); + VectorCopy (ent->velocity, ent->client->oldplayer->velocity); + + ent->client->oldplayer->s.modelindex = ent->s.modelindex; + ent->client->oldplayer->s.modelindex2 = ent->s.modelindex2; + ent->client->oldplayer->s.modelindex3 = ent->s.modelindex3;*/ + VectorSet (v, 0, 0, 0); + VectorCopy (v, ent->client->ps.viewoffset); + } +} +//WF E ST +/* +============== +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; + short number;//WF34 + vec3_t vieworg; + int remaining; + + 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); +//WF24 S ST - screen turns orange if on fire + if(ent->Flames) + SV_AddBlend (1.0, 0.5, 0.0, 0.4, ent->client->ps.blend); + //ST + + //WF - Flash grenade + //Are we done with flash yet? + if (ent->client->blindTime > 0) + { + + float alpha = ent->client->blindTime / ent->client->blindBase; + if (alpha > 1) alpha = 1; + SV_AddBlend (1, 1, 1, alpha, ent->client->ps.blend); + ent->client->blindTime -= 1; + } +//WF34 START +//WF + //WF Disguise Stuff + if(ent->disguiseshots>2) + SV_AddBlend (0.0, 1.0, 0.0, 0.3, ent->client->ps.blend); + else if (ent->disguiseshots==2) + SV_AddBlend (1.0, 1.0, 0.0, 0.3, ent->client->ps.blend); + else if (ent->disguiseshots==1) + SV_AddBlend (0.0, 0.5, 0.0, 0.3, ent->client->ps.blend); + + // NEW CODE: cloaked - darken vision + if (ent->client->cloaking && (ent->svflags & SVF_NOCLIENT)) + SV_AddBlend (-1, -1, -1, 0.3, 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); + } +//WF24 S ST + else if (ent->client->kamikaze_framenum > level.framenum) + { + remaining = ent->client->kamikaze_framenum - level.framenum; + ent->client->kamikaze_timeleft = remaining; +// if (remaining == 20) +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4)) + SV_AddBlend (0, 0, 1, 0.08, ent->client->ps.blend); + } +//WF24 E ST + 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); +//WF34 START + if (ent->client->Gas_Time>level.time) + { + if(ent->client->Gas_Delayclient->Gas_Delay=(float)((int)(1000*random())%25)/10+level.time; + number=(int)(random()*10)%9; + ent->client->GasR=GasR[number]; + ent->client->GasG=GasG[number]; + ent->client->GasB=GasB[number]; + ent->client->GasViewOffset[0]=(float)((int)(random()*1000)%400-200)/10; + ent->client->GasViewOffset[1]=(float)((int)(random()*1000)%400-200)/10; + ent->client->GasViewOffset[2]=(float)((int)(random()*1000)%400-200)/10; + ent->client->GasFOV=(int)(random()*1000)%100+41; + } + ent->client->ps.fov=ent->client->GasFOV; + if (ent->client->Gas_Timeclient->ps.fov=90; + SV_AddBlend (ent->client->GasR, ent->client->GasG, ent->client->GasB, 0.65, ent->client->ps.blend); + } +//WF34 END + // 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; + + 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]; + } + delta = delta*delta * 0.0001; + + // TeT The game stopped them cold, don't take damage for it + if (ent->cantmove) + { + return; + } + // TeT The jet pack can cause massive velocity changes + if (Jet_Active(ent)) + { + return; + } + +//ZOID + // never take damage if just release grapple or on grapple + if (level.time - ent->client->ctf_grapplereleasetime <= FRAMETIME * 2 || + (ent->client->ctf_grapple && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)) + return; +//ZOID + + // 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; + + if (delta < 15) + { + 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 + ent->s.event = EV_FALL; + } + ent->pain_debounce_time = level.time; // no normal pain sound + damage = (delta-30)/2; + if (damage < 1) + damage = 1; + 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); + } + else + { + ent->s.event = EV_FALLSHORT; + return; + } +} + + + +/* +============= +P_WorldEffects +============= +*/ +void P_WorldEffects (void) +{ + qboolean breather; + qboolean envirosuit; + int waterlevel, old_waterlevel; + //ERASER ADDED || (CURRENT + if ((current_player->movetype == MOVETYPE_NOCLIP) || (current_player->health <= 0)) + { + current_player->air_finished = level.time + 12; // don't need air + return; + } + + //Give air time if frozen 4/99 Acrid + if (current_player->frozen) + current_player->air_finished = level.time + 6; + + 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 (current_client->player_items & ITEM_REBREATHER) + breather = 1;//WF34 2 LINES + // + // 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; + if (ent->health > 0) + { + ent->s.effects = 0; + ent->s.renderfx = 0; + }//acrid3 to stop skipped freeze effect on dead bodies + + + 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; + } + } + +//ZOID + CTFEffects(ent); +//ZOID +//WF24 S + //Add effects for respawn protection + if (ent->client->protecttime > level.time) + ent->s.effects |= EF_TELEPORTER; //set it + else + ent->s.effects &= ~EF_TELEPORTER; //clear it +//WF24 E + if (ent->client->quad_framenum > level.framenum +//ZOID + && (level.framenum & 8) +//ZOID + ) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_QUAD; + } + + if (ent->client->invincible_framenum > level.framenum +//ZOID + && (level.framenum & 8) +//ZOID + ) + { + 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); + } + if (ent->disease) + ent->s.effects |= EF_FLIES;//WF34 2 LINES + //acrid 3/99 + if (ent->frozen)//white shell + { + ent->client->ps.rdflags |= RDF_UNDERWATER; + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + SV_AddBlend (0.75, 0.75, 0.75, 0.6, ent->client->ps.blend); + } +} + + +/* +=============== +G_SetClientEvent +=============== +*/ +void G_SetClientEvent (edict_t *ent) +{ + if (ent->s.event) + return; + + if ( ent->groundentity && xyspeed > 225) + { + if ( (int)(current_client->bobtime+bobmove) != bobcycle ) + 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; +} + +//WF24 S + void decoy_stand (edict_t *self); +//WF24 E + +/* +=============== +G_SetClientFrame +=============== +*/ +void G_SetClientFrame (edict_t *ent) +{ + gclient_t *client; + qboolean duck, run; + + if( ent->frozen ) //acrid3 might not be needed + return; + + 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; + + if (client->pers.feign) + { + if (ent->s.frame < client->anim_end) + { + ent->s.frame++; + } + return; + } + + // 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; + + if (ent->s.frame < client->anim_end) + { // continue an animation + ent->s.frame++; + return; + } + + if (client->pers.feign) + 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) + { +//ZOID: if on grapple, don't go into jump frame, go into standing +//frame + if (client->ctf_grapple) { + ent->s.frame = FRAME_stand01; + client->anim_end = FRAME_stand40; + } else { +//ZOID + 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; +//WF24 S - put decoy back to standing + if (ent->decoy) decoy_stand(ent->decoy); +//WF24 E + } + } +} + + +/* +================= +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; + edict_t *e;//ERASER + + current_player = ent; + current_client = ent->client; + + // + // 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) + { + 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); + + // 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); + +//ZOID + if (!ent->client->chase_target) +//ZOID + G_SetStats (ent); + +//ZOID +//FIXME ACRID WF24 USES THIS +//update chasecam follower stats +// for (i = 1; i <= maxclients->value; i++) { +// edict_t *e = g_edicts + i; +//FIXME ACRID WF24 USES THIS + +// ERASER START, use player list + for (i=0; iinuse || + e->client->chase_target != ent)//crash here was changing maps + continue; + memcpy(e->client->ps.stats, + ent->client->ps.stats, + sizeof(ent->client->ps.stats)); + e->client->ps.stats[STAT_LAYOUTS] = 1; + break; + } +//ZOID + + G_SetClientEvent (ent); + + G_SetClientEffects (ent); + + G_SetClientSound (ent); + + G_SetClientFrame (ent); + + //See if there are any friends in the area + ShowCloseFriend_f (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); + +//WF24 S - SCANNER +// if (ent->client->showscores && !(level.framenum & 31) )//NORMAL LINE +// if (((ent->client->showscores || ent->client->pers.scanner_active) && deathmatch->value && +// !(level.framenum & SCANNER_UPDATE_FREQ)) || (ent->client->pers.scanner_active & 2)) + if ( ((ent->client->showscores) && !(level.framenum & 31)) || + ((ent->client->pers.scanner_active) && !(level.framenum & 7)) ) +//WF34 2 LINES +//WF24 E + { +//ZOID + if (ent->client->menu) + { +//WF CO PMenu_Update(ent);//NORMAL LINE WF24 USES BELOW SECTION + ent->menutime++;//JR Added this so it updates less since it was always updating not good + if(ent->menutime>85)//WF34 WAS 55 + { + PMenu_Update(ent); + ent->menutime = 0; + } + } else +//ZOID + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, false); +//WF24 S - scanner + ent->client->pers.scanner_active &= ~2; +//WF24 E + } +} diff --git a/p_weapon.c b/p_weapon.c new file mode 100644 index 0000000..e6197f3 --- /dev/null +++ b/p_weapon.c @@ -0,0 +1,2161 @@ +// g_weapon.c + +#include "g_local.h" +//ERASER START +#include "m_player.h" +#include "bot_procs.h" +void botPickBestWeapon(edict_t *self); +//ERASER END + +void fire_slowgrenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_plasmabeam (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick); +void fire_tesla (edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed); +void fire_gasgrenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_blaster_wf (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper); +void weapon_knife_fire (edict_t *ent); +int wfWeaponDamage(gitem_t *it); + + +//static qboolean is_quad; +qboolean is_quad; +byte is_silenced; + + +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.hand == LEFT_HANDED) + _distance[1] *= -1; + 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) + { + //Dont decrease # of silenced shots if the class + //is assigned it as perminent + if ((who->client) && (who->client->player_items & ITEM_SILENCER)) + who->client->silencer_shots = 999; + else + 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); +} + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other) +{ + int index; + gitem_t *ammo; + gclient_t *client;//ERASER +//ERASER START + if (other->client) + client = other->client; + else + return false; +//ERASER END + + 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 + } + + 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; + } + } + + 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; + +//ERASER START + // check for bot change weapon//FIXME ACRID THIS IS THE ONLY WORKING ONE + if (other->bot_client) + { + botPickBestWeapon(other); + } + return true; +} +void ShowGun(edict_t *ent)//eraser vwep 3.20 support +{ + int i; + // set visible model for bots vwep + if (ent->s.modelindex == 255) + { + if (ent->client->pers.weapon) + { + i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8); + } + else + { + i = 0; + } + ent->s.skinnum = (ent - g_edicts - 1) | i; + } +} +/* +=============== +ChangeWeapon + +The old weapon has been dropped all the way, so make the new one +current +=============== +*/ + +void ChangeWeapon (edict_t *ent) +{ + int i; + int weapon; + + 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; + } + + ent->client->pers.lastweapon = ent->client->pers.weapon; + ent->client->pers.weapon = ent->client->newweapon; + ent->client->newweapon = NULL; + ent->client->machinegun_shots = 0; + + // set visible model + if (ent->s.modelindex == 255) { + if (ent->client->pers.weapon) + { + i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8); + } + else + { + i = 0; + } + ent->s.skinnum = (ent - g_edicts - 1) | i; + } + + 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) + { // dead + ent->client->ps.gunindex = 0; + return; + } + + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->ps.gunframe = 0; + ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); + + 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; + + } + + // Update weapon damage icon + ent->client->weapon_damage = wfWeaponDamage(ent->client->pers.weapon); + + //special case for grenades and grapple + if (Q_stricmp("ammo_grenades", ent->client->pers.weapon->classname ) == 0) + { + ent->client->weapon_damage = wf_game.grenade_damage[ent->client->pers.grenade_type]; + } + else if (Q_stricmp("ammo_grenades2", ent->client->pers.weapon->classname ) == 0) + { + ent->client->weapon_damage = wf_game.grenade_damage[ent->client->pers.grenade_type]; + } + else if (Q_stricmp("ammo_grenades3", ent->client->pers.weapon->classname ) == 0) + { + ent->client->weapon_damage = wf_game.grenade_damage[ent->client->pers.grenade_type]; + } + + + //Turn on/off laser sight if needed. Not on if homing is not on + weapon = ent->client->pers.weapon->tag; + if ((weapon == WEAPON_NAPALMMISSLE) && ( ent->client->pers.homing_state) ) + lasersight_on (ent); + else + lasersight_off (ent); + + +} + +/* +================= +NoAmmoWeaponChange +================= +*/ +void NoAmmoWeaponChange (edict_t *ent) +{ +//ERASER????? FIXME + gclient_t *client; + + if (ent->client) + client = ent->client; + else + return; +//ERASER???? END + + lasersight_off (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); + + //WF +// if (ent->client->player_items & ITEM_SILENCER) +// is_silenced = MZ_SILENCED; //Always silenced! + } +} + + +/* +================ +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; + + //K2:Camera crash fix acrid 3/99 botcam + if (ent->client->bIsCamera) + return; + + // see if we're already using it + if (item == ent->client->pers.weapon) + return; + + if (item->ammo && !g_select_empty->value && !(item->flags & IT_AMMO)) + { + ammo_item = FindItem(item->ammo); + if (ammo_item == 0) + { + safe_cprintf (ent, PRINT_HIGH, "Ammo %s does not exist for %s.\n", item->ammo, item->pickup_name); + return; + } + 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; +} + +//WF - Special Use function for grapple +void Use_Grapple (edict_t *ent, gitem_t *item) +{ + //Has server turned off grappling on server? + if ((int)wfflags->value & WF_NO_GRAPPLE) + { + safe_cprintf(ent, PRINT_HIGH, "Sorry - The grapple is turned off on this server.\n"); + return; + } + + //Is this class capable of using the grapple? + if ((ent->client) && ((ent->client->player_special & SPECIAL_GRAPPLE) == 0)) + { + safe_cprintf(ent, PRINT_HIGH, "Sorry - This class cannot use the grapple.\n"); + return; + } + + Use_Weapon(ent, item); +} + + +/* +================ +Drop_Weapon +================ +*/ +void Drop_Weapon (edict_t *ent, gitem_t *item) +{ + int index; + + safe_cprintf (ent, PRINT_HIGH, "Sorry, you can't drop a weapon in WF!\n"); + return; + + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + return; + + index = ITEM_INDEX(item); + // see if we're already using it + 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; + } + + Drop_Item (ent, item); + ent->client->pers.inventory[index]--; +} + + +/* +================ +Weapon_Generic + +A generic function to handle the basics of weapon thinking +================ +*/ +#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1) +#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1) +#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1) + +/* +================ +Weapon_Generic + +A generic function to handle the basics of weapon thinking +================ +*/ +#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1) +#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1) +#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1) + +static void Weapon_Generic2 (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) +{ + int n; + int i; + vec3_t oldorg, oldangles; +// char *s; +//WF34 START + if(ent->deadflag || ent->s.modelindex != 255) // VWep animations screw up corpses + { + return; + } +//WF34 END + if (ent->client->weaponstate == WEAPON_DROPPING) + { + if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST) + { + ChangeWeapon (ent); + return; + } +//WF34 START + 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; + + } + } +//WF34 END + ent->client->ps.gunframe++; + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { +//WF24 S +// if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST) + if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST || fastswitch) +//WF24 E + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + ent->client->ps.gunframe++; + return; + } + + if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING)) + { + ent->client->weaponstate = WEAPON_DROPPING; +//WF24 S +// ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + if (fastswitch) + ent->client->ps.gunframe = FRAME_DEACTIVATE_LAST; + else + ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; +//WF24 E +//WF34 START 3.20 + 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; + + } + } +//WF34 END 3.20 + + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { +//WF24 S & K2:Begin + //If respawn protected, remove it because player pressed fire/attack + if (K2_IsProtected(ent)) + ent->client->protecttime = 0; +//WF24 E & K2:End + + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if ((!ent->client->ammo_index) || + ( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity)) + { + 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 (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 + { + 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++; + return; + } + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + for (n = 0; fire_frames[n]; n++) + { + if (ent->client->ps.gunframe == fire_frames[n]) + { +//ERASER START + if (ent->client->latency > 0) + { // simulate lag + + // find the best lag_trail location to use + i = (int) (ent->client->latency/100); + if (i > 9) + i = 9; + + VectorCopy(ent->s.origin, oldorg); + VectorCopy(ent->client->v_angle, oldangles); + + VectorCopy((*ent->client->lag_trail)[i], ent->s.origin); + VectorCopy((*ent->client->lag_angles)[i], ent->client->v_angle); + gi.linkentity(ent); + } +//ERASER END +//ZOID + if (!CTFApplyStrengthSound(ent)) +//ZOID + if (ent->client->quad_framenum > level.framenum) + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); +//ZOID + CTFApplyHasteSound(ent); +//ZOID + if (ent->disguiseshots && fire != weapon_knife_fire)//WF34 + { + ent->disguiseshots--;//WF34 + } + + if (ent->client->latency > 0) + { // simulate lag + fire (ent);//ERASER START + + VectorCopy(oldorg, ent->s.origin); + VectorCopy(oldangles, ent->client->v_angle); + + gi.linkentity(ent); + + break; + } + else + { + + fire (ent); + break; + }//ERASER END + } + } + + if (!fire_frames[n]) + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1) + ent->client->weaponstate = WEAPON_READY; + } +} + +//ZOID +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) +{ + int oldstate = ent->client->weaponstate; + + if(ent->SlowerFrameShot++;//WF34 2 LINES + + // run the weapon frame again if hasted + if (stricmp(ent->client->pers.weapon->pickup_name, "Grapple") == 0 && + ent->client->weaponstate == WEAPON_FIRING) + return; + + if ((CTFApplyHaste(ent) || + (Q_stricmp(ent->client->pers.weapon->pickup_name, "Grapple") == 0 + && ent->client->weaponstate != WEAPON_FIRING)) + && oldstate == ent->client->weaponstate) + { + if(ent->SlowerFrameShot++;//WF34 2 LINES + }//WF34 START + if (ent->Slower>level.time) + { + if(ent->FrameShot>1) + { + ent->FrameShot=0; + Weapon_Generic2 (ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, fire_frames, fire); + }//WF34 END + } +} +//ZOID + +/* +====================================================================== + +GRENADE + +====================================================================== +*/ + +#define GRENADE_TIMER 3.0 +#define GRENADE_MINSPEED 400 +#define GRENADE_MAXSPEED 800 + +int fireButtonOn(edict_t *ent) +{ + if ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) + return 1; + else + return 0; +} + +char *getWeaponState(edict_t *ent) +{ + if (ent->client->weaponstate == WEAPON_READY) return "WEAPON_READY"; + else if (ent->client->weaponstate == WEAPON_ACTIVATING ) return "WEAPON_ACTIVATING"; + else if (ent->client->weaponstate == WEAPON_DROPPING ) return "WEAPON_DROPPING"; + else if (ent->client->weaponstate == WEAPON_FIRING ) return "WEAPON_FIRING"; + else return "(unknown)"; +} + +void weapon_grenade_fire (edict_t *ent, qboolean held) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; +// char *s; + int damage = wf_game.grenade_damage[GRENADE_TYPE_NORMAL]; +//WF + int lbdamage = 15; //reduce damage +//WF + float timer; + int speed; + float radius; + +//if (wfdebug) +//{ +//gi.dprintf("Grenade fire: held=%d, frame = %d, %s, fire=%d, grenadetype=%d\n", +// held, ent->client->ps.gunframe, getWeaponState(ent), +// fireButtonOn(ent), ent->client->pers.grenade_type); +//} + //Remove respawn protection and disguise + if (ent->disguised) + { + WFRemoveDisguise(ent); + } + + if (K2_IsProtected(ent)) + ent->client->protecttime = 0; + + + 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); + speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((wf_game.grenade_speed[ent->client->pers.grenade_type] - GRENADE_MINSPEED) / GRENADE_TIMER); + + if (speed < 0) speed = 200; + +//WF - throw the right kind of grenade + switch( ent->client->pers.grenade_type ) + { + case GRENADE_TYPE_LASERBALL: + fire_laserball (ent, start, forward, lbdamage, speed, timer, radius); + break; + case GRENADE_TYPE_GOODYEAR: + fire_goodyear (ent, start, forward, damage, speed, timer, radius); + break; + case GRENADE_TYPE_NORMAL: + fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held); + break; + case GRENADE_TYPE_FLASH: + fire_flash (ent, start, forward, damage, speed, timer, radius); + break; + case GRENADE_TYPE_PROXIMITY: + fire_proximity (ent, start, forward, damage, speed, timer, radius, PROXIMITY_TYPE_GRENADE); + break; + case GRENADE_TYPE_CLUSTER: + fire_cluster (ent, start, forward, damage, speed, timer, radius); + break; + case GRENADE_TYPE_PIPEBOMB: + fire_pipebomb (ent, start, forward, damage, speed, timer, radius); + break; + case GRENADE_TYPE_EARTHQUAKE: + fire_earthquake_grenade (ent, start, forward, damage, speed, timer, radius); + break; + case GRENADE_TYPE_TURRET: + fire_turret_grenade (ent, start, forward, damage, speed, timer, radius, held); + break; + case GRENADE_TYPE_NAPALM: + fire_napalm (ent, start, forward, damage, speed, timer, radius); + break; + case GRENADE_TYPE_CONCUSSION: + fire_concussiongrenade (ent, start, forward, damage, speed, 2.5, radius); + break; +// case GRENADE_TYPE_NARCOTIC: +// fire_napalm (ent, start, forward, damage, speed, timer, radius); +// break; + case GRENADE_TYPE_PLAGUE: + fire_diseasegrenade (ent, start, forward, damage, speed, 2.5, radius); + break; + case GRENADE_TYPE_MAGNOTRON: + fire_magnogrenade (ent, start, forward, damage, speed, 2.5, radius); + break; + case GRENADE_TYPE_SHOCK: + fire_shockgrenade (ent, start, forward, damage, speed, 2.5, radius); + break; + case GRENADE_TYPE_SHRAPNEL: + fire_bulletgrenade (ent, start, forward, damage, speed, 2.5, radius); + break; + case GRENADE_TYPE_FLARE: + fire_flare (ent, start, forward, damage, speed, 2.5, radius); + break; + case GRENADE_TYPE_SLOW: + fire_slowgrenade (ent, start, forward, damage, speed, timer, radius); + break; + case GRENADE_TYPE_LASERCUTTER: + fire_laser_grenade (ent, start, forward, damage, speed, 2.5, radius, held); + break; + case GRENADE_TYPE_GAS: + fire_gasgrenade (ent, start, forward, damage, speed, 2.5, radius); + break; + case GRENADE_TYPE_TESLA: + fire_tesla (ent, start, forward, damage, speed); + break; + default : + fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held); + safe_cprintf (ent, PRINT_HIGH, "Unknown Type, using Normal Grenade\n"); + break; + } + +// fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held); +//WF + + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + { + if (ent->client->pers.grenade_type == GRENADE_TYPE_EARTHQUAKE) + ent->client->pers.inventory[ent->client->ammo_index] = + ent->client->pers.inventory[ent->client->ammo_index] - EARTHQUAKE_GRENADES; + + // turret grenades are taken out somewhere else + else if (ent->client->pers.grenade_type != GRENADE_TYPE_TURRET) + ent->client->pers.inventory[ent->client->ammo_index]--; + + } + + //Don't allow negative grenades + else if (ent->client->pers.inventory[ent->client->ammo_index] < 0) + ent->client->pers.inventory[ent->client->ammo_index] = 0; + + ent->client->grenade_time = level.time + 1.0; + + if(ent->deadflag || ent->s.modelindex != 255) // VWep animations screw up corpses + { + return; + } + + if (ent->health <= 0) + return; + + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->client->anim_priority = ANIM_ATTACK; + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak3; + } + else + { + ent->client->anim_priority = ANIM_REVERSE; + ent->s.frame = FRAME_wave08; + ent->client->anim_end = FRAME_wave01; + } +} + +void Weapon_Grenade (edict_t *ent) +{ + qboolean ok_to_throw; + int max; + char *str; + int count; + + str = NULL; + + 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; +//WF +// wf_show_grenade_type(ent); +//WF + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + +//WF + //make sure we don't have too many active grenades of each grenade type + ok_to_throw = true; + count = ent->client->pers.active_grenades[ent->client->pers.grenade_type]; + switch( ent->client->pers.grenade_type ) + { + case GRENADE_TYPE_LASERBALL: + max = MAX_TYPE_LASERBALL; + str = "Laserball"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_GOODYEAR: + max = MAX_TYPE_GOODYEAR; + str = "Goodyear"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_CONCUSSION: + max = MAX_TYPE_CONCUSSION; + str = "Concussion"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_MAGNOTRON: + max = MAX_TYPE_MAGNOTRON; + str = "Magnotron"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_PIPEBOMB: + max = MAX_TYPE_PIPEBOMB; + str = "Pipebomb"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_SLOW: + max = MAX_TYPE_SLOW; + str = "Slow"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_PLAGUE: + max = MAX_TYPE_PLAGUE; + str = "Plague"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_NORMAL: + max = MAX_TYPE_NORMAL; + str = "Normal"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_FLASH: + max = MAX_TYPE_FLASH; + str = "Flash"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_PROXIMITY: + max = MAX_TYPE_PROXIMITY; + str = "Proximity"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_CLUSTER: + max = MAX_TYPE_CLUSTER; + str = "Cluster"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_EARTHQUAKE: + max = MAX_TYPE_EARTHQUAKE; + str = "Earthquake"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_TURRET: + max = MAX_TYPE_TURRET; + str = "Turret"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_SHOCK: + max = MAX_TYPE_SHOCK; + str = "Shock"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_NAPALM: + max = MAX_TYPE_NAPALM; + str = "Napalm"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_LASERCUTTER: + max = MAX_TYPE_LASERCUTTER; + str = "Laser cutter"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_TESLA: + max = MAX_TYPE_TESLA; + str = "Tesla"; + if (count >= max) ok_to_throw = false; + break; + case GRENADE_TYPE_GAS: + max = MAX_TYPE_GAS; + str = "Gas"; + if (count >= max) ok_to_throw = false; + break; + } + if (ok_to_throw == false) + { + safe_cprintf(ent, PRINT_HIGH, "You can only have %d active %s type grenades. \n",max,str); + return; + } + + //Is this grenade banned? +/* + if ((bangrenade->string) && (str) && (strcmp(str, bangrenade->string) == 0)) + { + safe_cprintf(ent, PRINT_HIGH, "Sorry, the %s grenade has been disabled.\n", str); + return; + } +*/ + + + //for laserball, make sure they have cells + if ( ent->client->pers.grenade_type == GRENADE_TYPE_LASERBALL) + { + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < LASERBALL_CELLS) + { + safe_cprintf(ent, PRINT_HIGH, "Not enough cells for laserball.\n"); + return; + } + } + //for earthquake grenade, make sure they have enough grenades + if ( ent->client->pers.grenade_type == GRENADE_TYPE_EARTHQUAKE) + { + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Grenades"))] < EARTHQUAKE_GRENADES) + { + safe_cprintf(ent, PRINT_HIGH, "Not enough grenades. You need %d for an earthquake.\n",EARTHQUAKE_GRENADES); + return; + } + } +//WF + 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) + { +//WF - Speed up grenade throw + if (ent->client->ps.gunframe < 11) + ent->client->ps.gunframe = 11; +//WF + 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"); + } + + // 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 = wf_game.weapon_damage[WEAPON_GRENADELAUNCHER]; + 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; + +// if (ent->client->player_class == CLASS_CYBORG) +// fire_cluster (ent, start, forward, damage, 600, 2.5, radius); +// else + fire_grenade (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_GRENADELAUNCHER], 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, 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); + damage = wf_game.weapon_damage[WEAPON_ROCKETLAUNCHER]; + radius_damage = 105; + damage_radius = 105; + 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); + + //WF - increase speed of rocket +// fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage, MOD_ROCKET); + fire_rocket (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_ROCKETLAUNCHER], damage_radius, radius_damage, MOD_ROCKET); + + // 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]--; + + //If this is a homing rocket, take an extra one away. (Homing cost 2 rockets) + //If that was their last rocket, give them a freebee + if ((ent->client) && (ent->client->pers.homing_state) && + (ent->client->pers.inventory[ent->client->ammo_index] > 0)) + { + 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, 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_wf (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_BLASTER], 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; + + damage = wf_game.weapon_damage[WEAPON_BLASTER]; + 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, pause_frames, fire_frames, Weapon_Blaster_Fire); +} +/* +=================================== +New HyperBlaster code - PlasmaBeam +by Fireball +=================================== +*/ +void Weapon_PlasmaBeam_Fire (edict_t *ent) +{ + float rotation; + vec3_t offset; + int effect; + int damage; + vec3_t start; + vec3_t forward, right;//, up; +// vec3_t angles; + //vec3_t end; + + 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; +*/ + effect = EF_HYPERBLASTER; + if (deathmatch->value) + damage = 15; + else + damage = 20; + + if (is_quad) + damage *= 4; + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorSet(offset, 0, 7, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_plasmabeam (ent, start, forward, damage, 200); + + // 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); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + 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->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_Fire_WF (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; + damage = wf_game.weapon_damage[WEAPON_HYPERBLASTER]; + + //WF JR LAG fixing in case lag gets high + //Hopefully this will help during a fierce battle and the + //ping times rocket up + ent->ShotNumber++; + if (ent->client->ping >400) + { + if (ent->ShotNumber>1) + { + ent->ShotNumber = 0; + Blaster_Fire (ent, offset, damage*2, true, effect); + } + } + else + { + ent->ShotNumber = 0; + Blaster_Fire (ent, offset, damage, true, effect); + } + //end lag code + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + 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->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_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; + effect = EF_IONRIPPER; + 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->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->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) +{ +/* 3.4 beta 2 version of hyperblaster + static int pause_frames[] = {0}; + static int fire_frames[] = {6, 7, 8, 9, 10, 11, 0}; + + Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_PlasmaBeam_Fire); // Changed to Weapon_PlasmaBeam_Fire by Fireball +*/ + static int pause_frames[] = {0}; + static int fire_frames[] = {6, 7, 8, 9, 10, 11, 0}; + + //Orig id type + //Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire); + + //3.3 version of hyperblaster + Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire_WF); + +} + +/* +====================================================================== + +MACHINEGUN / CHAINGUN + +====================================================================== +*/ + +void Machinegun_Fire (edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = wf_game.weapon_damage[WEAPON_MACHINEGUN]; + 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); + //WF JR LAG fixing in case lag gets high + //Hopefully this will help during a fierce battle and the + //ping times rocket up + ent->ShotNumber++; + if (ent->client->ping >400) + { + if (ent->ShotNumber>1) + { + ent->ShotNumber = 0; + fire_bullet (ent, start, forward, damage*2, kick*2, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); + } + } + else + { + ent->ShotNumber = 0; + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); + } +//end lag code + 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]--; + + 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; + } +} + +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, 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 = wf_game.weapon_damage[WEAPON_CHAINGUN]; + 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"); + } + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (ent->client->ps.gunframe & 1); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (ent->client->ps.gunframe & 1); + ent->client->anim_end = FRAME_attack8; + } + + 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); + //WF JR LAG fixing in case lag gets high + //Hopefully this will help during a fierce battle and the + //ping times rocket up + ent->ShotNumber++; + if (ent->client->ping >400) + { + if (ent->ShotNumber>1) + { + ent->ShotNumber=0; + fire_bullet (ent, start, forward, damage*2, kick*2, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN); + } + } + else + { + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN); + ent->ShotNumber=0; + } + //end lag + } +//WF + //Send a tracer bullet + if ((shots == 3) && (random() < .16)) + { + fire_blaster (ent, start, forward, 0, wf_game.weapon_speed[WEAPON_BLASTER], EF_HYPERBLASTER, true); + } + +//WF + // 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, 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 = wf_game.weapon_damage[WEAPON_SHOTGUN]; + 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, 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 = wf_game.weapon_damage[WEAPON_SUPERSHOTGUN]; + 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, 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; + + damage = wf_game.weapon_damage[WEAPON_RAILGUN]; + kick = 200; + + 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, MOD_RAILGUN); + + // 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, 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; + + damage = wf_game.weapon_damage[WEAPON_BFG]; + + 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, wf_game.weapon_speed[WEAPON_BFG], 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, pause_frames, fire_frames, weapon_bfg_fire); +} + + +//====================================================================== diff --git a/q_devels.c b/q_devels.c new file mode 100644 index 0000000..ba5878a --- /dev/null +++ b/q_devels.c @@ -0,0 +1,160 @@ +// +// Q_DEVELS.C - cool support functions for Quake II development +// Version 1.5 (last updated Jan 29, 1998) +// +// Published at http://www.planetquake.com/qdevels +// Code by various authors, released by SumFuka@planetquake.com +// +// Please use this code in your mods... if you distribute it, +// KEEP THE AUTHOR'S NAMES with the code. They deserve the credit. +// + +// +// INSTRUCTIONS : +// +// To use the functions in this file, first add the file to your project. +// Then add '#include "q_devels.h"' to the top of every source file you +// need to call the functions from, or even better, put that same line +// in g_local.h. +// +// Compile away, rock and roll. +// + + +#include "g_local.h" +#include "q_devels.h" + + +// 1. FOR_EACH_PLAYER by SumFuka +// +// Use this macro to quickly apply code to each player in the game +// ! you must supply arguments of type (edict_t *) and (int) ! +// e.g. int i; edict_t *joe_bloggs; +// for_each_player(joe_bloggs,i) { joe_bloggs->client->pers.health = 0; } + +// ---=== The code is #define'd in header file qdevels.h ===--- + + +// 2. STUFFCMD (author unkown...) +// +// Use this function to send a command string to a CLIENT. +// E.g. stuffcmd(player, "alias ready \"cmd ready\"\n"); + +//void stuffcmd(edict_t *e, char *s) { +// gi.WriteByte (11); +// gi.WriteString (s); +// gi.unicast (e, true); +//} + + +// 3. ENT_BY_NAME by Kevin Sullivan (Ignitor) +// +// Here is a nice little function I wrote to find a player's +// entity structure by his name. + +edict_t *ent_by_name (char *target) +{ + int i; + edict_t *targ=NULL; + for (i=0;;i++) + { + if (i > globals.num_edicts) + return (NULL); + + targ = G_Find (targ, FOFS(classname), "player"); + + if (strcmp(targ->client->pers.netname, target) == 0) + return (targ); + } +} + + + +// 4. CENTERPRINT_ALL by SumFuka +// +// Use this function to centerprint to all players. +// e.g. centerprint_all("---<<< FIGHT ! >>>---\n"); + +void centerprint_all (char *msg) +{ + int i; + edict_t *joe_bloggs; + + for_each_player(joe_bloggs,i) + { + gi.centerprintf (joe_bloggs, msg); + } +} + + + +// 5. RNDNUM by RoJoMo7 +// +// Returns a random number between "y" and "z". +// y = lower limit of number to generate. +// z = upper limit of number to generate. +// e.g. rndnum (10,20); -- returns a random number between 10 an 20. + +// ---=== The code is #define'd in header file qdevels.h ===--- + + + +// 6. RANDOM_PLAYER by SumFuka +// +// Select a random player, supply NULL for any player, +// or supply a player to exclude the player from being chosen. +// +// e.g. gi.centerprintf (random_player(NULL), "You're it !!!"); +// e.g. gi.centerprintf (random_player(ent), "You're it !!!"); + +edict_t *random_player (edict_t *notme) +{ + int i; + int count; + int random_player; + edict_t *joe_bloggs; + + // count the number of players + count = 0; + for_each_player(joe_bloggs,i) + { + if (joe_bloggs != notme) + count++; + } + + // no players ? + if (count == 0) + { + gi.dprintf("ERROR: tried to select a random player when none are available.\n"); + return NULL; + } + + // select a random player + random_player = rand() % count; + + // find the randomly selected player + count = 0; + for_each_player(joe_bloggs,i) + { + if (joe_bloggs != notme) + { + if (count == random_player) + return joe_bloggs; + else + count++; + } + } +} + + +// 7. Item lists by smith57@airmail.net +// +// These are the indexes into the entity->client->pers->inventory and +// the itemlist array. You could use ITEM_INDEX( FindItem( value;INDEX++) \ + if ((JOE_BLOGGS=&g_edicts[i]) && JOE_BLOGGS->inuse) + +#define rndnum(y,z) ((random()*((z)-((y)+1)))+(y)) + +void stuffcmd(edict_t *e, char *s); +edict_t *ent_by_name (char *target); +void centerprint_all (char *msg); + + + +// 7. Item lists by smith57@airmail.net + +#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_BLASTER 7 +#define ITEMLIST_SHOTGUN 8 +#define ITEMLIST_SUPERSHOTGUN 9 +#define ITEMLIST_MACHINEGUN 10 +#define ITEMLIST_CHAINGUN 11 +#define ITEMLIST_GRENADELAUNCHER 12 +#define ITEMLIST_ROCKETLAUNCHER 13 +#define ITEMLIST_HYPERBLASTER 14 +#define ITEMLIST_RAILGUN 15 +#define ITEMLIST_BFG10K 16 +#define ITEMLIST_SHELLS 17 +#define ITEMLIST_BULLETS 18 +#define ITEMLIST_CELLS 19 +#define ITEMLIST_GRENADES 20 +#define ITEMLIST_ROCKETS 21 +#define ITEMLIST_SLUGS 22 +#define ITEMLIST_QUADDAMAGE 23 +#define ITEMLIST_INVULNERABILITY 24 +#define ITEMLIST_SILENCER 25 +#define ITEMLIST_REBREATHER 26 +#define ITEMLIST_ENVIRONMENTSUIT 27 +#define ITEMLIST_ANCIENTHEAD 28 +#define ITEMLIST_ADRENALINE 29 +#define ITEMLIST_BANDOLIER 30 +#define ITEMLIST_AMMOPACK 31 +#define ITEMLIST_DATACD 32 +#define ITEMLIST_POWERCUBE 33 +#define ITEMLIST_PYRAMIDKEY 34 +#define ITEMLIST_DATASPINNER 35 +#define ITEMLIST_SECURITYPASS 36 +#define ITEMLIST_BLUEKEY 37 +#define ITEMLIST_REDKEY 38 +#define ITEMLIST_COMMANDERSHEAD 39 +#define ITEMLIST_AIRSTRIKEMARKER 40 +#define ITEMLIST_HEALTH 41 diff --git a/q_shared.c b/q_shared.c new file mode 100644 index 0000000..ad498a8 --- /dev/null +++ b/q_shared.c @@ -0,0 +1,1400 @@ +#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 +================== +*/ +#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..09b0414 --- /dev/null +++ b/q_shared.h @@ -0,0 +1,1269 @@ + +// 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 + +#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; +typedef enum {false, true} qboolean; + + +#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 + + +// game print flags +#define PRINT_LOW 0 // pickup messages +#define PRINT_MEDIUM 1 // death messages +#define PRINT_HIGH 2 // 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) +#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_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; + +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 + +// 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 + +// 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 +#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 + +// +// 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 +// 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 +#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 +#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, + 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 +} 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 +#define STAT_CHASE 16 +#define STAT_SPECTATOR 17 + +#define MAX_STATS 32 + + +// dmflags->value flags +#define DF_NO_HEALTH 0x00000001 // 1 +#define DF_NO_ITEMS 0x00000002 // 2 +#define DF_WEAPONS_STAY 0x00000004 // 4 +#define DF_NO_FALLING 0x00000008 // 8 +#define DF_INSTANT_ITEMS 0x00000010 // 16 +#define DF_SAME_LEVEL 0x00000020 // 32 +#define DF_SKINTEAMS 0x00000040 // 64 +#define DF_MODELTEAMS 0x00000080 // 128 +#define DF_NO_FRIENDLY_FIRE 0x00000100 // 256 +#define DF_SPAWN_FARTHEST 0x00000200 // 512 +#define DF_FORCE_RESPAWN 0x00000400 // 1024 +#define DF_NO_ARMOR 0x00000800 // 2048 +#define DF_ALLOW_EXIT 0x00001000 // 4096 +#define DF_INFINITE_AMMO 0x00002000 // 8192 +#define DF_QUAD_DROP 0x00004000 // 16384 +#define DF_FIXED_FOV 0x00008000 // 32768 + +// 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 + +/* +ROGUE - VERSIONS +1234 08/13/1998 Activision +1235 08/14/1998 Id Software +1236 08/15/1998 Steve Tietze +1237 08/15/1998 Phil Dobranski +1238 08/15/1998 John Sheley +1239 08/17/1998 Barrett Alexander +1230 08/17/1998 Brandon Fish +1245 08/17/1998 Don MacAskill +1246 08/17/1998 David "Zoid" Kirsch +1247 08/17/1998 Manu Smith +1248 08/17/1998 Geoff Scully +1249 08/17/1998 Andy Van Fossen +1240 08/20/1998 Activision Build 2 +1256 08/20/1998 Ranger Clan +1257 08/20/1998 Ensemble Studios +1258 08/21/1998 Robert Duffy +1259 08/21/1998 Stephen Seachord +1250 08/21/1998 Stephen Heaslip +1267 08/21/1998 Samir Sandesara +1268 08/21/1998 Oliver Wyman +1269 08/21/1998 Steven Marchegiano +1260 08/21/1998 Build #2 for Nihilistic +1278 08/21/1998 Build #2 for Ensemble + +9999 08/20/1998 Internal Use +*/ +#define ROGUE_VERSION_ID 1278 + +#define ROGUE_VERSION_STRING "08/21/1998 Beta 2 for Ensemble" + +// ROGUE + +//WF Weapons Factory Game Flags +// wfflags->value flags +#define WF_ALLOW_FRIENDLY_FIRE 1 +#define WF_FRAG_LOGGING 2 +#define WF_NO_FORT_RESPAWN 4 +#define WF_NO_HOMING 8 +#define WF_NO_FLYING 16 +#define WF_DECOY_PURSUE 32 +#define WF_NO_DECOYS 64 +//#define WF_NO_RAILGUN_EFFECT 64 //no longer used +#define WF_NO_TURRET 128 +#define WF_NO_EARTHQUAKES 256 +#define WF_NO_GRAPPLE 512 +#define WF_MAP_VOTE 1024 +#define WF_ANARCHY 2048 +#define WF_ZOID_FLAGCAP 4096 +#define WF_NO_PLAYER_CLASSES 8192 +#define WF_ZBOT_DETECT 16384 +#define WF_AUTO_TEAM_BALANCE 32768 +#define WF_SPECIAL_LIGHTS 131072 +#define WF_TMP5 262144 +#define WF_TMP6 524288 + + +/* Explaination of WF flags: + +DF_ALLOW_FRIENDLY_FIRE - If set, this will allow homing rockets, + decoys, laserballs, proximity bombs to target teammates + +DF_NO_FAST_WEAPONS_SWITCH - If set, this will turn OFF fast + weapons switching + +WF_NO_FORT_RESPAWN - Turns off spawning in own fort + +WF_NO_HOMING - Turn off homing rockets + +WF_NO_FLYING - Turns off ability to fly + +WF_PLAYER_CLASSES - (no longer used. see WF_NO_PLAYER_CLASSES) + +WF_DECOY_PURSUE - will let the decoy chase after an enemy + +WF_NO_RAILGUN_EFFECT - turns off the spiral effect of the railgun. It + will make it much harder to see snipers with this option set. + +WF_VWEP_ON - Turns on vwep (visible weapons) support for all players + +WF_NO_EARTHQUAKES - disable earthquake grenades + +WF_NO_DECOYS - disabled the use of decoys + +WF_NO_TURRET - Disables turret grenade + +WF_NO_GRAPPLE - Disables the use of the grappling hook + +WF_RESPAWN_ARMED - If player classes are not used, setting this flag will + give the player all weapons except bfg on respawn. + +WF_MAP_VOTE - Allow players to vote for the next map + +WF_STD_LOG - Write to standard log output (like GibStats) + +WF_ZOID_FLAGCAP - Use standard CTF flag capture instead of TF style + +WF_NO_PLAYER_CLASSES - If set, this will turn OFF player classes. If + NOT set, all weapons will be removed from maps and players only get + what comes with their class. + +WF_FRAG_LOGGING - If set, it will turn on frag logging (gslog format) + +WF_ZBOT_DETECT - Turn on zbot detection and log it + +WF_AUTO_TEAM_BALANCE - If set, it will modify damage amounts based on score +*/ + +//The following flags are reserved for wfadmin +#define WF_ADMIN_MAP_LIST 1 + +/* +WF_ADMIN_MAP_LIST - Use a map list for map rotation? RESERVED + by L. Allan Campbell (Geist) +*/ +//WF + + +/* +========================================================== + + 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 + +#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) +#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, + 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; + unsigned int effects; // PGM - we're filling it, so it needs to be unsigned + 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; + + +// ================== +// PGM +#define VIDREF_GL 1 +#define VIDREF_SOFT 2 +#define VIDREF_OTHER 3 + +extern int vidref_val; +// PGM +// ================== diff --git a/r_weap.c b/r_weap.c new file mode 100644 index 0000000..e26de4e --- /dev/null +++ b/r_weap.c @@ -0,0 +1,2246 @@ +#include "g_local.h" + +#define INCLUDE_ETF_RIFLE 1 +#define INCLUDE_PROX 1 +#define INCLUDE_FLAMETHROWER 1 +#define INCLUDE_INCENDIARY 1 +#define INCLUDE_NUKE 1 +#define INCLUDE_MELEE 1 +#define INCLUDE_TESLA 1 +#define INCLUDE_BEAMS 1 + +extern void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed); +extern void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); +extern void droptofloor (edict_t *ent); +extern void Grenade_Explode (edict_t *ent); + +extern void drawbbox (edict_t *ent); + +#define DAMAGE_FIRE 5 + +void vectoangles2 (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 + { + // PMM - fixed to correct for pitch of 0 + if (value1[0]) + yaw = (atan2(value1[1], value1[0]) * 180 / M_PI); + else if (value1[1] > 0) + yaw = 90; + else + yaw = 270; + + if (yaw < 0) + yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + +void G_ProjectSource2 (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t up, vec3_t result) +{ + result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1] + up[0] * distance[2]; + result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1] + up[1] * distance[2]; + result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + up[2] * distance[2]; +} +static void P_ProjectSource2 (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, + vec3_t right, vec3_t up, vec3_t result) +{ + vec3_t _distance; + + VectorCopy (distance, _distance); + if (client->pers.hand == LEFT_HANDED) + _distance[1] *= -1; + else if (client->pers.hand == CENTER_HANDED) + _distance[1] = 0; + G_ProjectSource2 (point, _distance, forward, right, up, result); +} + +#ifdef INCLUDE_ETF_RIFLE +/* +======================== +fire_flechette +======================== +*/ +void flechette_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t dir; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { +//gi.dprintf("t_damage %s\n", other->classname); + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, + self->dmg, self->dmg_radius, 0, MOD_ETF_RIFLE); + } + else + { + if(!plane) + VectorClear (dir); + else + VectorScale (plane->normal, 256, dir); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_FLECHETTE); + gi.WritePosition (self->s.origin); + gi.WriteDir (dir); + gi.multicast (self->s.origin, MULTICAST_PVS); + +// T_RadiusDamage(self, self->owner, 24, self, 48, MOD_ETF_RIFLE); + } + + G_FreeEdict (self); +} + +void fire_flechette (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int kick) +{ + edict_t *flechette; + + VectorNormalize (dir); + + flechette = G_Spawn(); + VectorCopy (start, flechette->s.origin); + VectorCopy (start, flechette->s.old_origin); + vectoangles2 (dir, flechette->s.angles); + + VectorScale (dir, speed, flechette->velocity); + flechette->movetype = MOVETYPE_FLYMISSILE; + flechette->clipmask = MASK_SHOT; + flechette->solid = SOLID_BBOX; + flechette->s.renderfx = RF_FULLBRIGHT; + VectorClear (flechette->mins); + VectorClear (flechette->maxs); + + flechette->s.modelindex = gi.modelindex ("models/proj/flechette/tris.md2"); + +// flechette->s.sound = gi.soundindex (""); // FIXME - correct sound! + flechette->owner = self; + flechette->touch = flechette_touch; + flechette->nextthink = level.time + 8000/speed; + flechette->think = G_FreeEdict; + flechette->dmg = damage; + flechette->dmg_radius = kick; + + gi.linkentity (flechette); + + if (self->client) + check_dodge (self, flechette->s.origin, dir, speed); +} +#endif + + +// ************************* +// FLAMETHROWER +// ************************* + +#ifdef INCLUDE_FLAMETHROWER +#define FLAMETHROWER_RADIUS 8 + +void fire_remove (edict_t *ent) +{ + if(ent == ent->owner->teamchain) + ent->owner->teamchain = NULL; + + G_FreeEdict(ent); +} + +void fire_flame_new (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed) +{ + edict_t *flame; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + + flame = G_Spawn(); + + // the origin is the first control point, put it speed forward. + VectorMA(start, speed, forward, flame->s.origin); + + // record that velocity + VectorScale (aimdir, speed, flame->velocity); + + VectorCopy (dir, flame->s.angles); + flame->movetype = MOVETYPE_NONE; + flame->solid = SOLID_NOT; + + VectorSet(flame->mins, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS); + VectorSet(flame->maxs, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS); + + flame->s.sound = gi.soundindex ("weapons/flame.wav"); + flame->owner = self; + flame->dmg = damage; + flame->classname = "flame"; + + // clear control points and velocities + VectorCopy (flame->s.origin, flame->flameinfo.pos1); + VectorCopy (flame->velocity, flame->flameinfo.vel1); + VectorCopy (flame->s.origin, flame->flameinfo.pos2); + VectorCopy (flame->velocity, flame->flameinfo.vel2); + VectorCopy (flame->s.origin, flame->flameinfo.pos3); + VectorCopy (flame->velocity, flame->flameinfo.vel3); + VectorCopy (flame->s.origin, flame->flameinfo.pos4); + + // hook flame stream to owner + self->teamchain = flame; + + gi.linkentity (flame); +} + +// fixme - change to use start location, not entity origin +void fire_maintain (edict_t *ent, edict_t *flame, vec3_t start, vec3_t aimdir, int damage, int speed) +{ + trace_t tr; + + // move the control points out the appropriate direction and velocity + VectorAdd(flame->flameinfo.pos3, flame->flameinfo.vel3, flame->flameinfo.pos4); + VectorAdd(flame->flameinfo.pos2, flame->flameinfo.vel2, flame->flameinfo.pos3); + VectorAdd(flame->flameinfo.pos1, flame->flameinfo.vel1, flame->flameinfo.pos2); + VectorAdd(flame->s.origin, flame->velocity, flame->flameinfo.pos1); + + // move the velocities for the control points + VectorCopy(flame->flameinfo.vel2, flame->flameinfo.vel3); + VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel2); + VectorCopy(flame->velocity, flame->flameinfo.vel1); + + // set velocity and location for new control point 0. + VectorMA(start, speed, aimdir, flame->s.origin); + VectorScale(aimdir, speed, flame->velocity); + + // + // does it hit a wall? if so, when? + // + + // player fire point to flame origin. + tr = gi.trace(start, flame->mins, flame->maxs, + flame->s.origin, flame, MASK_SHOT); + if(tr.fraction == 1.0) + { + // origin to point 1 + tr = gi.trace(flame->s.origin, flame->mins, flame->maxs, + flame->flameinfo.pos1, flame, MASK_SHOT); + if(tr.fraction == 1.0) + { + // point 1 to point 2 + tr = gi.trace(flame->flameinfo.pos1, flame->mins, flame->maxs, + flame->flameinfo.pos2, flame, MASK_SHOT); + if(tr.fraction == 1.0) + { + // point 2 to point 3 + tr = gi.trace(flame->flameinfo.pos2, flame->mins, flame->maxs, + flame->flameinfo.pos3, flame, MASK_SHOT); + if(tr.fraction == 1.0) + { + // point 3 to point 4, point 3 valid + tr = gi.trace(flame->flameinfo.pos3, flame->mins, flame->maxs, + flame->flameinfo.pos4, flame, MASK_SHOT); + if(tr.fraction < 1.0) // point 4 blocked + { + VectorCopy(tr.endpos, flame->flameinfo.pos4); + } + } + else // point 3 blocked, point 2 valid + { + VectorCopy(flame->flameinfo.vel2, flame->flameinfo.vel3); + VectorCopy(tr.endpos, flame->flameinfo.pos3); + VectorCopy(tr.endpos, flame->flameinfo.pos4); + } + } + else // point 2 blocked, point 1 valid + { + VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel2); + VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel3); + VectorCopy(tr.endpos, flame->flameinfo.pos2); + VectorCopy(tr.endpos, flame->flameinfo.pos3); + VectorCopy(tr.endpos, flame->flameinfo.pos4); + } + } + else // point 1 blocked, origin valid + { + VectorCopy(flame->velocity, flame->flameinfo.vel1); + VectorCopy(flame->velocity, flame->flameinfo.vel2); + VectorCopy(flame->velocity, flame->flameinfo.vel3); + VectorCopy(tr.endpos, flame->flameinfo.pos1); + VectorCopy(tr.endpos, flame->flameinfo.pos2); + VectorCopy(tr.endpos, flame->flameinfo.pos3); + VectorCopy(tr.endpos, flame->flameinfo.pos4); + } + } + else // origin blocked! + { +// gi.dprintf("point 2 blocked\n"); + VectorCopy(flame->velocity, flame->flameinfo.vel1); + VectorCopy(flame->velocity, flame->flameinfo.vel2); + VectorCopy(flame->velocity, flame->flameinfo.vel3); + VectorCopy(tr.endpos, flame->s.origin); + VectorCopy(tr.endpos, flame->flameinfo.pos1); + VectorCopy(tr.endpos, flame->flameinfo.pos2); + VectorCopy(tr.endpos, flame->flameinfo.pos3); + VectorCopy(tr.endpos, flame->flameinfo.pos4); + } + + if(tr.fraction < 1.0 && tr.ent->takedamage) + { + T_Damage (tr.ent, flame, ent, flame->velocity, tr.endpos, tr.plane.normal, + damage, 0, DAMAGE_NO_KNOCKBACK | DAMAGE_ENERGY | DAMAGE_FIRE, MOD_FLAMETHROWER); + } + + gi.linkentity(flame); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_FLAME); + gi.WriteShort(ent - g_edicts); + gi.WriteShort(6); + gi.WritePosition (start); + gi.WritePosition (flame->s.origin); + gi.WritePosition (flame->flameinfo.pos1); + gi.WritePosition (flame->flameinfo.pos2); + gi.WritePosition (flame->flameinfo.pos3); + gi.WritePosition (flame->flameinfo.pos4); + gi.multicast (flame->s.origin, MULTICAST_PVS); +} + +/*QUAKED trap_flameshooter (1 0 0) (-8 -8 -8) (8 8 8) +*/ +#define FLAMESHOOTER_VELOCITY 50 +#define FLAMESHOOTER_DAMAGE 20 +#define FLAMESHOOTER_BURST_VELOCITY 300 +#define FLAMESHOOTER_BURST_DAMAGE 30 + +//#define FLAMESHOOTER_PUFF 1 +#define FLAMESHOOTER_STREAM 1 + +void flameshooter_think (edict_t *self) +{ + vec3_t forward, right, up; + edict_t *flame; + + if(self->delay) + { + if(self->teamchain) + fire_remove (self->teamchain); + return; + } + + self->s.angles[1] += self->speed; + if(self->s.angles[1] > 135 || self->s.angles[1] < 45) + self->speed = -self->speed; + + AngleVectors (self->s.angles, forward, right, up); + +#ifdef FLAMESHOOTER_STREAM + flame = self->teamchain; + if(!self->teamchain) + fire_flame_new (self, self->s.origin, forward, FLAMESHOOTER_DAMAGE, FLAMESHOOTER_VELOCITY); + else + fire_maintain (self, flame, self->s.origin, forward, FLAMESHOOTER_DAMAGE, FLAMESHOOTER_VELOCITY); + + self->think = flameshooter_think; + self->nextthink = level.time + 0.05; +#else + fire_burst (self, self->s.origin, forward, FLAMESHOOTER_BURST_DAMAGE, FLAMESHOOTER_BURST_VELOCITY); + + self->think = flameshooter_think; + self->nextthink = level.time + 0.1; +#endif +} + +void flameshooter_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if(self->delay) + { + self->delay = 0; + self->think = flameshooter_think; + self->nextthink = level.time + 0.1; + } + else + self->delay = 1; +} + +void SP_trap_flameshooter(edict_t *self) +{ + vec3_t tempAngles; + + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NONE; + + self->delay = 0; + + self->use = flameshooter_use; + if(self->delay == 0) + { + self->think = flameshooter_think; + self->nextthink = level.time + 0.1; + } + +// self->flags |= FL_NOCLIENT; + + self->speed = 10; + +// self->speed = 0; // FIXME this stops the spraying + + VectorCopy(self->s.angles, tempAngles); + + if (!VectorCompare(self->s.angles, vec3_origin)) + G_SetMovedir (self->s.angles, self->movedir); + + VectorCopy(tempAngles, self->s.angles); + +// gi.setmodel (self, self->model); + gi.linkentity (self); +} + +// ************************* +// fire_burst +// ************************* + +#define FLAME_BURST_MAX_SIZE 64 +#define FLAME_BURST_FRAMES 20 +#define FLAME_BURST_MIDPOINT 10 + +void fire_burst_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int powerunits; + int damage, radius; + vec3_t origin; + + if (surf && (surf->flags & SURF_SKY)) + { +// gi.dprintf("Hit sky. Removed\n"); + G_FreeEdict (ent); + return; + } + + if(other == ent->owner || ent == other) + return; + + // don't let flame puffs blow each other up + if(other->classname && !strcmp(other->classname, ent->classname)) + return; + + if(ent->waterlevel) + { +// gi.dprintf("Hit water. Removed\n"); + G_FreeEdict(ent); + } + + if(!(other->svflags & SVF_MONSTER) && !other->client) + { + powerunits = FLAME_BURST_FRAMES - ent->s.frame; + damage = powerunits * 6; + radius = powerunits * 4; + +// T_RadiusDamage (inflictor, attacker, damage, ignore, radius) + T_RadiusDamage(ent, ent->owner, damage, ent, radius, DAMAGE_FIRE); + +// gi.dprintf("Hit world: %d pts, %d rad\n", damage, radius); + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PLAIN_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + G_FreeEdict (ent); + } +} + +void fire_burst_think (edict_t *self) +{ + int current_radius; + + if(self->waterlevel) + { + G_FreeEdict(self); + return; + } + + self->s.frame++; + if(self->s.frame >= FLAME_BURST_FRAMES) + { + G_FreeEdict(self); + return; + } + + else if(self->s.frame < FLAME_BURST_MIDPOINT) + { + current_radius = (FLAME_BURST_MAX_SIZE / FLAME_BURST_MIDPOINT) * self->s.frame; + } + else + { + current_radius = (FLAME_BURST_MAX_SIZE / FLAME_BURST_MIDPOINT) * (FLAME_BURST_FRAMES - self->s.frame); + } + + if(self->s.frame == 3) + self->s.skinnum = 1; + else if (self->s.frame == 7) + self->s.skinnum = 2; + else if (self->s.frame == 10) + self->s.skinnum = 3; + else if (self->s.frame == 13) + self->s.skinnum = 4; + else if (self->s.frame == 16) + self->s.skinnum = 5; + else if (self->s.frame == 19) + self->s.skinnum = 6; + + if(current_radius < 8) + current_radius = 8; + else if(current_radius > FLAME_BURST_MAX_SIZE) + current_radius = FLAME_BURST_MAX_SIZE; + + T_RadiusDamage(self, self->owner, self->dmg, self, current_radius, DAMAGE_FIRE); + + self->think = fire_burst_think; + self->nextthink = level.time + 0.1; +} + +void fire_burst (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed) +{ + edict_t *flame; + vec3_t dir; + vec3_t baseVel; + vec3_t forward, right, up; + + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + + flame = G_Spawn(); + VectorCopy(start, flame->s.origin); +// VectorScale (aimdir, speed, flame->velocity); + + // scale down so only 30% of player's velocity is taken into account. + VectorScale (self->velocity, 0.3, baseVel); + VectorMA(baseVel, speed, aimdir, flame->velocity); + + VectorCopy (dir, flame->s.angles); + flame->movetype = MOVETYPE_FLY; + flame->solid = SOLID_TRIGGER; + + VectorSet(flame->mins, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS); + VectorSet(flame->maxs, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS); + + flame->s.sound = gi.soundindex ("weapons/flame.wav"); + flame->s.modelindex = gi.modelindex ("models/projectiles/puff/tris.md2"); + flame->owner = self; + flame->touch = fire_burst_touch; + flame->think = fire_burst_think; + flame->nextthink = level.time + 0.1; + flame->dmg = damage; + flame->classname = "flameburst"; +// flame->s.effects = EF_FIRE_PUFF; //gar + + gi.linkentity (flame); +} +#endif + +// ************************* +// INCENDIARY GRENADES +// ************************* + +#ifdef INCLUDE_INCENDIARY +void FireThink (edict_t *ent) +{ + if(level.time > ent->wait) + G_FreeEdict(ent); + else + { + ent->s.frame++; + if(ent->s.frame>10) + ent->s.frame = 0; + ent->nextthink = level.time + 0.05; + ent->think = FireThink; + } +} + +#define FIRE_HEIGHT 64 +#define FIRE_RADIUS 64 +#define FIRE_DAMAGE 3 +#define FIRE_DURATION 15 + +edict_t *StartFire(edict_t *fireOwner, vec3_t fireOrigin, float fireDuration, float fireDamage) +{ + edict_t *fire; + + fire = G_Spawn(); + VectorCopy (fireOrigin, fire->s.origin); + fire->movetype = MOVETYPE_TOSS; + fire->solid = SOLID_TRIGGER; + VectorSet(fire->mins, -FIRE_RADIUS, -FIRE_RADIUS, 0); + VectorSet(fire->maxs, FIRE_RADIUS, FIRE_RADIUS, FIRE_HEIGHT); + + fire->s.sound = gi.soundindex ("weapons/incend.wav"); + fire->s.modelindex = gi.modelindex ("models/objects/fire/tris.md2"); + + fire->owner = fireOwner; + fire->touch = hurt_touch; + fire->nextthink = level.time + 0.05; + fire->wait = level.time + fireDuration; + fire->think = FireThink; +// fire->nextthink = level.time + fireDuration; +// fire->think = G_FreeEdict; + fire->dmg = fireDamage; + fire->classname = "incendiary_fire"; + + gi.linkentity (fire); + +// gi.sound (fire, CHAN_VOICE, gi.soundindex ("weapons/incend.wav"), 1, ATTN_NORM, 0); + return fire; +} + +static void Incendiary_Explode (edict_t *ent) +{ + vec3_t origin; + + 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? + T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, DAMAGE_FIRE); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + gi.WriteByte (svc_temp_entity); + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + StartFire(ent->owner, ent->s.origin, FIRE_DURATION, FIRE_DAMAGE); + + G_FreeEdict (ent); + +} + +static void Incendiary_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->svflags & SVF_MONSTER) && !(ent->client)) +// if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb2b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + Incendiary_Explode (ent); +} + +void fire_incendiary_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; + + vectoangles2 (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; +// if (self->client) +// grenade->s.effects &= ~EF_TELEPORT; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/projectiles/incend/tris.md2"); + grenade->owner = self; + grenade->touch = Incendiary_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Incendiary_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "incendiary_grenade"; + + gi.linkentity (grenade); +} +#endif + +// ************************* +// NUKE +// ************************* + +#ifdef INCLUDE_NUKE +#define NUKE_DELAY 4 +#define NUKE_TIME_TO_LIVE 6 +//#define NUKE_TIME_TO_LIVE 40 +#define NUKE_RADIUS 512 +#define NUKE_DAMAGE 400 +#define NUKE_QUAKE_TIME 3 +#define NUKE_QUAKE_STRENGTH 100 + +void Nuke_Quake (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, 0.75, 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; + else + G_FreeEdict (self); +} + + +static void Nuke_Explode (edict_t *ent) +{ +// vec3_t origin; + +// nuke_framenum = level.framenum + 20; + + if (ent->teammaster->client) + PlayerNoise(ent->teammaster, ent->s.origin, PNOISE_IMPACT); + +// T_RadiusNukeDamage(ent, ent->teammaster, ent->dmg, ent, ent->dmg_radius, MOD_NUKE); + T_RadiusDamage(ent, ent->teammaster, ent->dmg, ent, ent->dmg_radius, MOD_NUKE); + +// VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + if (ent->dmg > NUKE_DAMAGE) + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/grenlx1a.wav"), 1, ATTN_NONE, 0); +/* + gi.WriteByte (svc_temp_entity); + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); +*/ + + // BecomeExplosion1(ent); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1_BIG); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_NUKEBLAST); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_ALL); + + // become a quake + ent->svflags |= SVF_NOCLIENT; + ent->noise_index = gi.soundindex ("world/rumble.wav"); + ent->think = Nuke_Quake; + ent->speed = NUKE_QUAKE_STRENGTH; + ent->timestamp = level.time + NUKE_QUAKE_TIME; + ent->nextthink = level.time + FRAMETIME; + ent->last_move_time = 0; +} + +void nuke_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + if ((attacker) && !(strcmp(attacker->classname, "nuke"))) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("nuke nuked by a nuke, not nuking\n"); + G_FreeEdict (self); + return; + } + Nuke_Explode(self); +} + +void Nuke_Think(edict_t *ent) +{ + float attenuation, default_atten = 1.8; + int damage_multiplier, muzzleflash; + +// gi.dprintf ("player range: %2.2f damage radius: %2.2f\n", realrange (ent, ent->teammaster), ent->dmg_radius*2); + + damage_multiplier = ent->dmg/NUKE_DAMAGE; + switch (damage_multiplier) + { + case 1: + attenuation = default_atten/1.4; + muzzleflash = MZ_NUKE1; + break; + case 2: + attenuation = default_atten/2.0; + muzzleflash = MZ_NUKE2; + break; + case 4: + attenuation = default_atten/3.0; + muzzleflash = MZ_NUKE4; + break; + case 8: + attenuation = default_atten/5.0; + muzzleflash = MZ_NUKE8; + break; + default: +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("default attenuation used for nuke!\n"); + attenuation = default_atten; + muzzleflash = MZ_NUKE1; + break; + } + + if(ent->wait < level.time) + Nuke_Explode(ent); + else if (level.time >= (ent->wait - NUKE_TIME_TO_LIVE)) + { + ent->s.frame++; +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("nuke frame %d\n", ent->s.frame); + if(ent->s.frame > 11) + ent->s.frame = 6; + + if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + Nuke_Explode (ent); + return; + } + + ent->think = Nuke_Think; + ent->nextthink = level.time + 0.1; + ent->health = 1; + ent->owner = NULL; + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (muzzleflash); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + if (ent->timestamp <= level.time) + { +/* gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn.wav"), 1, ATTN_NORM, 0); + ent->timestamp += 10.0; + } +*/ + + if ((ent->wait - level.time) <= (NUKE_TIME_TO_LIVE/2.0)) + { +// ent->s.sound = gi.soundindex ("weapons/nukewarn.wav"); +// gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0); + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0); +// gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0); +// gi.dprintf ("time %2.2f\n", ent->wait-level.time); + ent->timestamp = level.time + 0.3; + } + else + { + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0); +// gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0); + ent->timestamp = level.time + 0.5; +// gi.dprintf ("time %2.2f\n", ent->wait-level.time); + } + } + } + else + { + if (ent->timestamp <= level.time) + { + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0); +// gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0); +// gi.dprintf ("time %2.2f\n", ent->wait-level.time); + ent->timestamp = level.time + 1.0; + } + ent->nextthink = level.time + FRAMETIME; + } +} + +void nuke_bounce (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + 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); +} + + +//======== +//ROGUE +byte P_DamageModifier(edict_t *ent) +{ + int damage_multiplier; + + is_quad = 0; + damage_multiplier = 1; + + if(ent->client->quad_framenum > level.framenum) + { + damage_multiplier *= 4; + is_quad = 1; + + // if we're quad and DF_NO_STACK_DOUBLE is on, return now. + if(((int)(dmflags->value) & DF_NO_STACK_DOUBLE)) + return damage_multiplier; + } + if(ent->client->double_framenum > level.framenum) + { + if ((deathmatch->value) || (damage_multiplier == 1)) + { + damage_multiplier *= 2; + is_quad = 1; + } + } + + return damage_multiplier; +} +//ROGUE +//======== + +void fire_nuke (edict_t *self, vec3_t start, vec3_t aimdir, int speed) +{ + edict_t *nuke; + vec3_t dir; + vec3_t forward, right, up; + int damage_modifier; + + damage_modifier = (int) P_DamageModifier (self); + + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + + nuke = G_Spawn(); + VectorCopy (start, nuke->s.origin); + VectorScale (aimdir, speed, nuke->velocity); + + VectorMA (nuke->velocity, 200 + crandom() * 10.0, up, nuke->velocity); + VectorMA (nuke->velocity, crandom() * 10.0, right, nuke->velocity); + VectorClear (nuke->avelocity); + VectorClear (nuke->s.angles); + nuke->movetype = MOVETYPE_BOUNCE; + nuke->clipmask = MASK_SHOT; + nuke->solid = SOLID_BBOX; + nuke->s.effects |= EF_GRENADE; + nuke->s.renderfx |= RF_IR_VISIBLE; + VectorSet (nuke->mins, -8, -8, 0); + VectorSet (nuke->maxs, 8, 8, 16); + nuke->s.modelindex = gi.modelindex ("models/weapons/g_nuke/tris.md2"); + nuke->owner = self; + nuke->teammaster = self; + nuke->nextthink = level.time + FRAMETIME; + nuke->wait = level.time + NUKE_DELAY + NUKE_TIME_TO_LIVE; + nuke->think = Nuke_Think; + nuke->touch = nuke_bounce; + + nuke->health = 10000; + nuke->takedamage = DAMAGE_YES; +// nuke->svflags |= SVF_DAMAGEABLE; //gar + nuke->dmg = NUKE_DAMAGE * damage_modifier; + if (damage_modifier == 1) + nuke->dmg_radius = NUKE_RADIUS; + else + nuke->dmg_radius = NUKE_RADIUS + NUKE_RADIUS*(0.25*(float)damage_modifier); + // this yields 1.0, 1.5, 2.0, 3.0 times radius + +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("nuke modifier = %d, damage = %d, radius = %f\n", damage_modifier, nuke->dmg, nuke->dmg_radius); + + nuke->classname = "nuke"; + nuke->die = nuke_die; + + gi.linkentity (nuke); +} +#endif + +// ************************* +// TESLA +// ************************* + +#ifdef INCLUDE_TESLA +#define TESLA_TIME_TO_LIVE 30 +#define TESLA_DAMAGE_RADIUS 128 +#define TESLA_DAMAGE DAMAGE_TESLA +#define TESLA_KNOCKBACK 8 + +#define TESLA_ACTIVATE_TIME 3 + +#define TESLA_EXPLOSION_DAMAGE_MULT 50 // this is the amount the damage is multiplied by for underwater explosions +#define TESLA_EXPLOSION_RADIUS 200 + +void tesla_remove (edict_t *self) +{ + edict_t *cur, *next; + + self->takedamage = DAMAGE_NO; + if(self->teamchain) + { + cur = self->teamchain; + while(cur) + { + next = cur->teamchain; + G_FreeEdict ( cur ); + cur = next; + } + } + else if (self->air_finished) + gi.dprintf ("tesla without a field!\n"); + + self->owner = self->teammaster; // Going away, set the owner correctly. + // PGM - grenade explode does damage to self->enemy + self->enemy = NULL; + + // play quad sound if quadded and an underwater explosion + if ((self->dmg_radius) && (self->dmg > (TESLA_DAMAGE*TESLA_EXPLOSION_DAMAGE_MULT))) + gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + Grenade_Explode(self); +} + +void tesla_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ +// gi.dprintf("tesla killed\n"); + tesla_remove(self); +} + +void tesla_blow (edict_t *self) +{ +// T_RadiusDamage(self, self->owner, TESLA_EXPLOSION_DAMAGE, NULL, TESLA_EXPLOSION_RADIUS, MOD_TESLA); + self->dmg = self->dmg * TESLA_EXPLOSION_DAMAGE_MULT; + self->dmg_radius = TESLA_EXPLOSION_RADIUS; + tesla_remove(self); +} + + +void tesla_zap (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ +} + +void tesla_think_active (edict_t *self) +{ + int i,num; + edict_t *touch[MAX_EDICTS], *hit; + vec3_t dir, start; + trace_t tr; + + if(level.time > self->air_finished) + { + tesla_remove(self); + return; + } + + VectorCopy(self->s.origin, start); + start[2] += 16; + + num = gi.BoxEdicts(self->teamchain->absmin, self->teamchain->absmax, touch, MAX_EDICTS, AREA_SOLID); + for(i=0;iinuse)) + break; + + hit=touch[i]; + if(!hit->inuse) + continue; + if(hit == self) + continue; + if(hit->health < 1) + continue; + // don't hit clients in single-player or coop +/* + if(hit->client) + if (coop->value || !deathmatch->value) + continue; +//GAR if(!(hit->svflags & (SVF_MONSTER | SVF_DAMAGEABLE)) && !hit->client) + if(!(hit->svflags & (SVF_MONSTER )) && !hit->client) + continue; +*/ + tr = gi.trace(start, vec3_origin, vec3_origin, hit->s.origin, self, MASK_SHOT); + if(tr.fraction==1 || tr.ent==hit)// || tr.ent->client || (tr.ent->svflags & (SVF_MONSTER | SVF_DAMAGEABLE))) + { + VectorSubtract(hit->s.origin, start, dir); + + // PMM - play quad sound if it's above the "normal" damage + if (self->dmg > TESLA_DAMAGE) + gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); + + // PGM - don't do knockback to walking monsters + if((hit->svflags & SVF_MONSTER) && !(hit->flags & (FL_FLY|FL_SWIM))) + T_Damage (hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal, + self->dmg, 0, 0, MOD_TESLA); + else + T_Damage (hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal, + self->dmg, TESLA_KNOCKBACK, 0, MOD_TESLA); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LIGHTNING); + gi.WriteShort (hit - g_edicts); // destination entity + gi.WriteShort (self - g_edicts); // source entity + gi.WritePosition (tr.endpos); + gi.WritePosition (start); + gi.multicast (start, MULTICAST_PVS); + } + } + + if(self->inuse) + { + self->think = tesla_think_active; + self->nextthink = level.time + FRAMETIME; + } +} + +void tesla_activate (edict_t *self) +{ + edict_t *trigger; + edict_t *search; + + if (gi.pointcontents (self->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WATER)) + { + tesla_blow (self); + return; + } + + // only check for spawn points in deathmatch + if (deathmatch->value) + { + search = NULL; + while ((search = findradius(search, self->s.origin, 1.5*TESLA_DAMAGE_RADIUS)) != NULL) + { + //if (!search->takedamage) + // continue; + // if it's a monster or player with health > 0 + // or it's a deathmatch start point + // and we can see it + // blow up + if(search->classname) + { + if ( ( (!strcmp(search->classname, "info_player_deathmatch")) + || (!strcmp(search->classname, "info_player_start")) + || (!strcmp(search->classname, "info_player_coop")) + || (!strcmp(search->classname, "misc_teleporter_dest")) + ) + && (visible (search, self)) + ) + { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("Tesla to close to %s, removing!\n", search->classname); + tesla_remove (self); + return; + } + } + } + } + + trigger = G_Spawn(); +// if (trigger->nextthink) +// { +// if ((g_showlogic) && (g_showlogic->value)) +// gi.dprintf ("tesla_activate: fixing nextthink\n"); +// trigger->nextthink = 0; +// } + VectorCopy (self->s.origin, trigger->s.origin); + VectorSet (trigger->mins, -TESLA_DAMAGE_RADIUS, -TESLA_DAMAGE_RADIUS, self->mins[2]); + VectorSet (trigger->maxs, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS); + trigger->movetype = MOVETYPE_NONE; + trigger->solid = SOLID_TRIGGER; + trigger->owner = self; + trigger->touch = tesla_zap; + trigger->classname = "tesla trigger"; + // doesn't need to be marked as a teamslave since the move code for bounce looks for teamchains + gi.linkentity (trigger); + + VectorClear (self->s.angles); + // clear the owner if in deathmatch + if (deathmatch->value) + self->owner = NULL; + self->teamchain = trigger; + self->think = tesla_think_active; + self->nextthink = level.time + FRAMETIME; + self->air_finished = level.time + TESLA_TIME_TO_LIVE; +} + +void tesla_think (edict_t *ent) +{ + if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + tesla_remove (ent); + return; + } + VectorClear (ent->s.angles); + + if(!(ent->s.frame)) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/teslaopen.wav"), 1, ATTN_NORM, 0); + + ent->s.frame++; + if(ent->s.frame > 14) + { + ent->s.frame = 14; + ent->think = tesla_activate; + ent->nextthink = level.time + 0.1; + } + else + { + if(ent->s.frame > 9) + { + if(ent->s.frame == 10) + { + if (ent->owner && ent->owner->client) + { + PlayerNoise(ent->owner, ent->s.origin, PNOISE_WEAPON); // PGM + } + ent->s.skinnum = 1; + } + else if(ent->s.frame == 12) + ent->s.skinnum = 2; + else if(ent->s.frame == 14) + ent->s.skinnum = 3; + } + ent->think = tesla_think; + ent->nextthink = level.time + 0.1; + } +} + +void tesla_lava (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t land_point; + + if (plane->normal) + { + VectorMA (ent->s.origin, -20.0, plane->normal, land_point); + if (gi.pointcontents (land_point) & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + tesla_blow (ent); + return; + } + } + 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); +} + +void fire_tesla (edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed) +{ + edict_t *tesla; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + + tesla = G_Spawn(); + VectorCopy (start, tesla->s.origin); + VectorScale (aimdir, speed, tesla->velocity); + VectorMA (tesla->velocity, 200 + crandom() * 10.0, up, tesla->velocity); + VectorMA (tesla->velocity, crandom() * 10.0, right, tesla->velocity); +// VectorCopy (dir, tesla->s.angles); + VectorClear (tesla->s.angles); + tesla->movetype = MOVETYPE_BOUNCE; + tesla->solid = SOLID_BBOX; + tesla->s.effects |= EF_GRENADE; + tesla->s.renderfx |= RF_IR_VISIBLE; +// VectorClear (tesla->mins); +// VectorClear (tesla->maxs); + VectorSet (tesla->mins, -12, -12, 0); + VectorSet (tesla->maxs, 12, 12, 20); + +// tesla->s.modelindex = gi.modelindex ("models/weapons/g_tesla/tris.md2"); + tesla->s.modelindex = gi.modelindex (GRTESLA_MODEL); + tesla->s.skinnum = GRTESLA_SKIN; + + tesla->owner = self; // PGM - we don't want it owned by self YET. + tesla->teammaster = self; + + tesla->wait = level.time + TESLA_TIME_TO_LIVE; +//GAR tesla->think = tesla_think; + tesla->think = tesla_activate; +//GAR tesla->nextthink = level.time + TESLA_ACTIVATE_TIME; + tesla->nextthink = level.time + 3.0; + + // blow up on contact with lava & slime code + tesla->touch = tesla_lava; + + if(deathmatch->value) + // PMM - lowered from 50 - 7/29/1998 + tesla->health = 20; + else + tesla->health = 30; // FIXME - change depending on skill? + + tesla->takedamage = DAMAGE_YES; + tesla->die = tesla_die; + tesla->dmg = TESLA_DAMAGE*damage_multiplier; +// tesla->dmg = 0; + tesla->classname = "tesla"; +// tesla->svflags |= SVF_DAMAGEABLE; + tesla->clipmask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + tesla->flags |= FL_MECHANICAL; + + gi.linkentity (tesla); +} +#endif + +// ************************* +// HEATBEAM +// ************************* + +#ifdef INCLUDE_BEAMS +static void fire_beams (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, int te_beam, int te_impact, int mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + vec3_t water_start, endpoint; + qboolean water = false, underwater = false; + int content_mask = MASK_SHOT | MASK_WATER; + vec3_t beam_endpt; + +// tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); +// if (!(tr.fraction < 1.0)) +// { + vectoangles2 (aimdir, dir); + AngleVectors (dir, forward, right, up); + + VectorMA (start, 8192, forward, end); + + if (gi.pointcontents (start) & MASK_WATER) + { +// gi.dprintf ("Heat beam under water\n"); + underwater = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_HEATBEAM_SPARKS); +// gi.WriteByte (50); + gi.WritePosition (water_start); + gi.WriteDir (tr.plane.normal); +// gi.WriteByte (8); +// gi.WriteShort (60); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + VectorCopy (tr.endpos, endpoint); +// } + + // halve the damage if target underwater + if (water) + { + damage = damage /2; + } + + // 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_ENERGY, mod); + } + else + { + if ((!water) && (strncmp (tr.surface->name, "sky", 3))) + { + // This is the truncated steam entry - uses 1+1+2 extra bytes of data + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_HEATBEAM_STEAM); +// gi.WriteByte (20); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); +// gi.WriteByte (0xe0); +// gi.WriteShort (60); + 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) || (underwater)) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL2); +// gi.WriteByte (8); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } + + if ((!underwater) && (!water)) + { + VectorCopy (tr.endpos, beam_endpt); + } + else + { + VectorCopy (endpoint, beam_endpt); + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_beam); + gi.WriteShort (self - g_edicts); + gi.WritePosition (start); + gi.WritePosition (beam_endpt); + gi.multicast (self->s.origin, MULTICAST_ALL); + +} + + +/* +================= +fire_heat + +Fires a single heat beam. Zap. +================= +*/ +void fire_heat (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, qboolean monster) +{ + if (monster) + fire_beams (self, start, aimdir, offset, damage, kick, TE_MONSTER_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM); + else + fire_beams (self, start, aimdir, offset, damage, kick, TE_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM); +} + +#endif + + +// ************************* +// BLASTER 2 +// ************************* + +/* +================= +fire_blaster3 + +Fires a single green blaster bolt. Used by monsters, generally. +================= +*/ +void blaster3_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int mod; + int damagestat; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner && self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + // the only time players will be firing blaster3 bolts will be from the + // defender sphere. + if(self->owner->client) + mod = MOD_DEFENDER_SPHERE; + else + mod = MOD_BLASTER2; + + if (self->owner) + { + damagestat = self->owner->takedamage; + self->owner->takedamage = DAMAGE_NO; + if (self->dmg >= 5) + T_RadiusDamage(self, self->owner, self->dmg*3, other, self->dmg_radius, 0); + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); + self->owner->takedamage = damagestat; + } + else + { + if (self->dmg >= 5) + T_RadiusDamage(self, self->owner, self->dmg*3, other, self->dmg_radius, 0); + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); + } + } + else + { + //PMM - yeowch this will get expensive + if (self->dmg >= 5) + T_RadiusDamage(self, self->owner, self->dmg*3, self->owner, self->dmg_radius, 0); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER2); + 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_blaster3 (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(); + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles2 (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); + + if (effect) + bolt->s.effects |= EF_TRACKER; + bolt->dmg_radius = 128; + bolt->s.modelindex = gi.modelindex ("models/proj/laser2/tris.md2"); + bolt->touch = blaster3_touch; + + bolt->owner = self; + bolt->nextthink = level.time + 2; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + +// ************************* +// tracker +// ************************* + +/* +void tracker_boom_think (edict_t *self) +{ + self->s.frame--; + if(self->s.frame < 0) + G_FreeEdict(self); + else + self->nextthink = level.time + 0.1; +} + +void tracker_boom_spawn (vec3_t origin) +{ + edict_t *boom; + + boom = G_Spawn(); + VectorCopy (origin, boom->s.origin); + boom->s.modelindex = gi.modelindex ("models/items/spawngro/tris.md2"); + boom->s.skinnum = 1; + boom->s.frame = 2; + boom->classname = "tracker boom"; + gi.linkentity (boom); + + boom->think = tracker_boom_think; + boom->nextthink = level.time + 0.1; + //PMM +// boom->s.renderfx |= RF_TRANSLUCENT; + boom->s.effects |= EF_SPHERETRANS; + //pmm +} +*/ + +#define TRACKER_DAMAGE_FLAGS (DAMAGE_ENERGY | DAMAGE_NO_KNOCKBACK) +#define TRACKER_IMPACT_FLAGS (DAMAGE_ENERGY) +//#define TRACKER_DAMAGE_FLAGS (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY | DAMAGE_NO_KNOCKBACK) +//#define TRACKER_IMPACT_FLAGS (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY) + +#define TRACKER_DAMAGE_TIME 0.5 // seconds + +void tracker_pain_daemon_think (edict_t *self) +{ + static vec3_t pain_normal = { 0, 0, 1 }; + int hurt; + + if(!self->inuse) + return; + + if((level.time - self->timestamp) > TRACKER_DAMAGE_TIME) + { + if(!self->enemy->client) + self->enemy->s.effects &= ~EF_TRACKERTRAIL; + G_FreeEdict (self); + } + else + { + if(self->enemy->health > 0) + { +// gi.dprintf("ouch %x\n", self); + T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin, pain_normal, +//gar self->dmg, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER); + self->dmg, 0, 0, MOD_TRACKER); + + // if we kill the player, we'll be removed. + if(self->inuse) + { + // if we killed a monster, gib them. + if (self->enemy->health < 1) + { + if(self->enemy->gib_health) + hurt = - self->enemy->gib_health; + else + hurt = 500; + +// gi.dprintf("non-player killed. ensuring gib! %d\n", hurt); + T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin, + pain_normal, hurt, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER); + } + + if(self->enemy->client) + self->enemy->client->tracker_pain_framenum = level.framenum + 1; + else + self->enemy->s.effects |= EF_TRACKERTRAIL; + + self->nextthink = level.time + FRAMETIME; + } + } + else + { + if(!self->enemy->client) + self->enemy->s.effects &= ~EF_TRACKERTRAIL; + G_FreeEdict (self); + } + } +} + +void tracker_pain_daemon_spawn (edict_t *owner, edict_t *enemy, int damage) +{ + edict_t *daemon; + + if(enemy == NULL) + return; + + daemon = G_Spawn(); + daemon->classname = "pain daemon"; + daemon->think = tracker_pain_daemon_think; + daemon->nextthink = level.time + FRAMETIME; + daemon->timestamp = level.time; + daemon->owner = owner; + daemon->enemy = enemy; + daemon->dmg = damage; +} + +void tracker_explode (edict_t *self, cplane_t *plane) +{ + vec3_t dir; + + if(!plane) + VectorClear (dir); + else + VectorScale (plane->normal, 256, dir); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_TRACKER_EXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + +// gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/disrupthit.wav"), 1, ATTN_NORM, 0); +// tracker_boom_spawn(self->s.origin); + + G_FreeEdict (self); +} + +void tracker_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + float damagetime; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + if((other->svflags & SVF_MONSTER) || other->client) + { + if(other->health > 0) // knockback only for living creatures + { + // PMM - kickback was times 4 .. reduced to 3 + // now this does no damage, just knockback + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, + /* self->dmg */ 0, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER); + + if (!(other->flags & (FL_FLY|FL_SWIM))) + other->velocity[2] += 140; + + damagetime = ((float)self->dmg)*FRAMETIME; + damagetime = damagetime / TRACKER_DAMAGE_TIME; +// gi.dprintf ("damage is %f\n", damagetime); + + tracker_pain_daemon_spawn (self->owner, other, (int)damagetime); + } + else // lots of damage (almost autogib) for dead bodies + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, + self->dmg*4, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER); + } + } + else // full damage in one shot for inanimate objects + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, + self->dmg, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER); + } + } + + tracker_explode (self, plane); + return; +} + +void tracker_fly (edict_t *self) +{ + vec3_t dest; + vec3_t dir; + vec3_t center; + + if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->health < 1)) + { + tracker_explode (self, NULL); + return; + } +/* + VectorCopy (self->enemy->s.origin, dest); + if(self->enemy->client) + dest[2] += self->enemy->viewheight; +*/ + // PMM - try to hunt for center of enemy, if possible and not client + if(self->enemy->client) + { + VectorCopy (self->enemy->s.origin, dest); + dest[2] += self->enemy->viewheight; + } + // paranoia + else if (VectorCompare(self->enemy->absmin, vec3_origin) || VectorCompare(self->enemy->absmax, vec3_origin)) + { + VectorCopy (self->enemy->s.origin, dest); + } + else + { + VectorMA (vec3_origin, 0.5, self->enemy->absmin, center); + VectorMA (center, 0.5, self->enemy->absmax, center); + VectorCopy (center, dest); + } + + VectorSubtract (dest, self->s.origin, dir); + VectorNormalize (dir); + vectoangles2 (dir, self->s.angles); + VectorScale (dir, self->speed, self->velocity); + VectorCopy(dest, self->monsterinfo.saved_goal); + + self->nextthink = level.time + 0.1; +} + +void fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles2 (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->speed = speed; + bolt->s.effects = EF_TRACKER; + bolt->s.sound = gi.soundindex ("weapons/disrupt.wav"); + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + + bolt->s.modelindex = gi.modelindex ("models/proj/disintegrator/tris.md2"); + bolt->touch = tracker_touch; + bolt->enemy = enemy; + bolt->owner = self; + bolt->dmg = damage; + bolt->classname = "tracker"; + gi.linkentity (bolt); + + if(enemy) + { + bolt->nextthink = level.time + 0.1; + bolt->think = tracker_fly; + } + else + { + bolt->nextthink = level.time + 10; + bolt->think = G_FreeEdict; + } + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + +// +// Disintegrator +// + +void weapon_tracker_fire (edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t end; + vec3_t offset; + edict_t *enemy; + trace_t tr; + int damage; + vec3_t mins, maxs; + + // PMM - felt a little high at 25 + damage = wf_game.weapon_damage[WEAPON_DISRUPTOR]; + +// if (is_quad) +// damage *= damage_multiplier; //pgm + + VectorSet(mins, -16, -16, -16); + VectorSet(maxs, 16, 16, 16); + AngleVectors (self->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 8, self->viewheight-8); + P_ProjectSource (self->client, self->s.origin, offset, forward, right, start); + + // FIXME - can we shorten this? do we need to? + VectorMA (start, 8192, forward, end); + enemy = NULL; + //PMM - doing two traces .. one point and one box. + tr = gi.trace (start, vec3_origin, vec3_origin, end, self, MASK_SHOT); + if(tr.ent != world) + { + if(tr.ent->svflags & SVF_MONSTER || tr.ent->client ) + { + if(tr.ent->health > 0) + enemy = tr.ent; + } + } + else + { + tr = gi.trace (start, mins, maxs, end, self, MASK_SHOT); + if(tr.ent != world) + { + if(tr.ent->svflags & SVF_MONSTER || tr.ent->client) + { + if(tr.ent->health > 0) + enemy = tr.ent; + } + } + } + + VectorScale (forward, -2, self->client->kick_origin); + self->client->kick_angles[0] = -1; + + fire_tracker (self, start, forward, damage, wf_game.weapon_speed[WEAPON_DISRUPTOR], enemy); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (self-g_edicts); + gi.WriteByte (MZ_TRACKER); + gi.multicast (self->s.origin, MULTICAST_PVS); + + PlayerNoise(self, start, PNOISE_WEAPON); + + self->client->ps.gunframe++; + self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity; +} + +void Weapon_Disintegrator (edict_t *ent) +{ + static int pause_frames[] = {14, 19, 23, 0}; +// static int fire_frames[] = {7, 0}; + static int fire_frames[] = {5, 0}; + +// Weapon_Generic (ent, 4, 9, 29, 34, pause_frames, fire_frames, weapon_tracker_fire); + Weapon_Generic (ent, 4, 12, 32, 37, pause_frames, fire_frames, weapon_tracker_fire); +} + + +/* +====================================================================== + +ETF RIFLE + +====================================================================== +*/ +void weapon_etf_rifle_fire (edict_t *ent) +{ + vec3_t forward, right, up; + vec3_t start, tempPt; + int damage; + int kick = 3; + int i; + vec3_t angles; + vec3_t offset; + + damage = wf_game.weapon_damage[WEAPON_ETF_RIFLE]; + + // PGM - adjusted to use the quantity entry in the weapon structure. + if(ent->client->pers.inventory[ent->client->ammo_index] < ent->client->pers.weapon->quantity) + { + VectorClear (ent->client->kick_origin); + VectorClear (ent->client->kick_angles); + ent->client->ps.gunframe = 8; + + 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 *= damage_multiplier; +// kick *= damage_multiplier; + } + + for(i=0;i<3;i++) + { + ent->client->kick_origin[i] = crandom() * 0.85; + ent->client->kick_angles[i] = crandom() * 0.85; + } + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); +// AngleVectors (angles, forward, right, NULL); +// gi.dprintf("v_angle: %s\n", vtos(ent->client->v_angle)); + AngleVectors (ent->client->v_angle, forward, right, up); + + // FIXME - set correct frames for different offsets. + + if(ent->client->ps.gunframe == 6) // right barrel + { +// gi.dprintf("right\n"); + VectorSet(offset, 15, 8, -8); + } + else // left barrel + { +// gi.dprintf("left\n"); + VectorSet(offset, 15, 6, -8); + } + + VectorCopy (ent->s.origin, tempPt); + tempPt[2] += ent->viewheight; + P_ProjectSource2 (ent->client, tempPt, offset, forward, right, up, start); +// gi.dprintf("start: %s\n", vtos(start)); + fire_flechette (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_ETF_RIFLE], kick); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_ETF_RIFLE); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + ent->client->ps.gunframe++; + ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity; + + 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; + } + +} + +void Weapon_ETF_Rifle (edict_t *ent) +{ + static int pause_frames[] = {18, 28, 0}; + static int fire_frames[] = {6, 7, 0}; +// static int idle_seq; + + // note - if you change the fire frame number, fix the offset in weapon_etf_rifle_fire. + +// if (!(ent->client->buttons & BUTTON_ATTACK)) +// ent->client->machinegun_shots = 0; + + if (ent->client->weaponstate == WEAPON_FIRING) + { + if (ent->client->pers.inventory[ent->client->ammo_index] <= 0) + ent->client->ps.gunframe = 8; + } + + Weapon_Generic (ent, 4, 7, 37, 41, pause_frames, fire_frames, weapon_etf_rifle_fire); + + if(ent->client->ps.gunframe == 8 && (ent->client->buttons & BUTTON_ATTACK)) + ent->client->ps.gunframe = 6; + +// gi.dprintf("etf rifle %d\n", ent->client->ps.gunframe); +} + +// pgm - this now uses ent->client->pers.weapon->quantity like all the other weapons +//#define HEATBEAM_AMMO_USE 2 +#define HEATBEAM_DM_DMG 15 +#define HEATBEAM_SP_DMG 15 + +void Heatbeam_Fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t offset; + int damage; + int kick; + + // for comparison, the hyperblaster is 15/20 + // jim requested more damage, so try 15/15 --- PGM 07/23/98 + if (deathmatch->value) + damage = HEATBEAM_DM_DMG; + else + damage = HEATBEAM_SP_DMG; + + if (deathmatch->value) // really knock 'em around in deathmatch + kick = 75; + else + kick = 30; + +// if(ent->client->pers.inventory[ent->client->ammo_index] < HEATBEAM_AMMO_USE) +// { +// NoAmmoWeaponChange (ent); +// return; +// } + + ent->client->ps.gunframe++; + ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer2/tris.md2"); + +/* + if (is_quad) + { + damage *= damage_multiplier; + kick *= damage_multiplier; + } +*/ + + VectorClear (ent->client->kick_origin); + VectorClear (ent->client->kick_angles); + + // get start / end positions + AngleVectors (ent->client->v_angle, forward, right, up); + +// This offset is the "view" offset for the beam start (used by trace) + + VectorSet(offset, 7, 2, ent->viewheight-3); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + // This offset is the entity offset + VectorSet(offset, 2, 7, -3); + + fire_heat (ent, start, forward, offset, damage, kick, false); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_HEATBEAM | 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] -= ent->client->pers.weapon->quantity; + + 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; + } + +} + +void Weapon_Heatbeam (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}; + static int pause_frames[] = {35, 0}; +// static int fire_frames[] = {9, 0}; + static int fire_frames[] = {9, 10, 11, 12, 0}; +// static int attack_index; +// static int off_model, on_model; + +// if ((g_showlogic) && (g_showlogic->value)) { +// gi.dprintf ("Frame %d, skin %d\n", ent->client->ps.gunframe, ent->client->ps.gunskin); +// } + +// if (!attack_index) +// { +// attack_index = gi.soundindex ("weapons/bfg__l1a.wav"); +// off_model = gi.modelindex ("models/weapons/v_beamer/tris.md2"); +// on_model = gi.modelindex ("models/weapons/v_beamer2/tris.md2"); + //ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); +// } + + if (ent->client->weaponstate == WEAPON_FIRING) + { +// ent->client->weapon_sound = attack_index; + ent->client->weapon_sound = gi.soundindex ("weapons/bfg__l1a.wav"); + if ((ent->client->pers.inventory[ent->client->ammo_index] >= 2) && ((ent->client->buttons) & BUTTON_ATTACK)) + { +// if(ent->client->ps.gunframe >= 9 && ((ent->client->buttons) & BUTTON_ATTACK)) +// if(ent->client->ps.gunframe >= 12 && ((ent->client->buttons) & BUTTON_ATTACK)) + if(ent->client->ps.gunframe >= 13) + { + ent->client->ps.gunframe = 9; +// ent->client->ps.gunframe = 8; +// ent->client->ps.gunskin = 0; +// ent->client->ps.gunindex = on_model; + ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer2/tris.md2"); + } + else + { +// ent->client->ps.gunskin = 1; +// ent->client->ps.gunindex = on_model; + ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer2/tris.md2"); + } + } + else + { +// ent->client->ps.gunframe = 10; + ent->client->ps.gunframe = 13; +// ent->client->ps.gunskin = 1; +// ent->client->ps.gunindex = off_model; + ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer/tris.md2"); + } + } + else + { +// ent->client->ps.gunskin = 1; +// ent->client->ps.gunindex = off_model; + ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer/tris.md2"); + ent->client->weapon_sound = 0; + } + +// Weapon_Generic (ent, 8, 9, 39, 44, pause_frames, fire_frames, Heatbeam_Fire); + Weapon_Generic (ent, 8, 12, 39, 44, pause_frames, fire_frames, Heatbeam_Fire); +} diff --git a/remotecam.c b/remotecam.c new file mode 100644 index 0000000..8dd4bad --- /dev/null +++ b/remotecam.c @@ -0,0 +1,289 @@ +#include "g_local.h" +void T_Radius2Damage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int kickback, int mod); + +//ent = the remote camera +void remote_remove(edict_t *ent) +{ + if (!ent) return; + + ent->takedamage = DAMAGE_NO; + + //Should it blow up? + if (ent->dmg) + { + T_Radius2Damage(ent, ent->owner, ent->dmg, NULL, 200, 30, MOD_CAMERA); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + } + + if ((ent->owner) && (ent->owner->client)) + { + ent->owner->client->remotetoggle = 0; + ent->owner->remotecam = NULL; + } + + //Remove camera + G_FreeEdict (ent); +} + +void remote_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + +if (wfdebug) gi.dprintf("remote_die\n"); +// T_RadiusDamage (self, self->owner, 20, NULL, 10,0); + + // BANG ! + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition(self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = remote_remove; +} + +void remotecam_think(edict_t *ent) +{ + if(ent->owner->client->remotetoggle) + { + /* Remove cell requirements for now - GAR + if (ent->owner->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < 1) + { + safe_cprintf(ent->owner, PRINT_HIGH, "Not enough cells for the Remote Camera to stay\n"); + + T_RadiusDamage (ent, ent->owner, 10, NULL, 10,0); + + // BANG ! + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition(ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + remote_remove(ent); + return; + } + */ + } + /* Remove cell requirements for now - GAR + if(ent->owner->client->remotetoggle) + ent->owner->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= 1; + */ + ent->nextthink = level.time + 0.5; +} + +void place_remotecam (edict_t *ent) +{ + + vec3_t forward, + wallp, offset; + + trace_t tr; + + // valid ent ? + if ((!ent->client) || (ent->health<=0)) + return; + + //If there already is a camera, remove it + if (ent->remotecam) + { + //If damage is set to zero, just remove it + if (ent->remotecam->dmg == 0) + { + remote_remove(ent->remotecam); + safe_cprintf (ent, PRINT_HIGH, "Camera removed.\n"); + return; + } + + //Otherwise, detonate it + if (ent->remotecam->delay > level.time) + { + safe_cprintf (ent, PRINT_HIGH, "You can't blow up camera for another %d seconds.\n", + (int)(ent->remotecam->delay - level.time)); + return; + } + safe_cprintf (ent, PRINT_HIGH, "Remote cam will detonate in 5 seconds.\n"); + ent->remotecam->dmg = 400; + ent->remotecam->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + ent->remotecam->think = remote_remove; + ent->remotecam->nextthink = level.time + 5; + //remote_remove(ent->remotecam); + return; + } + + // cells for camera + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < 25) + { + safe_cprintf(ent, PRINT_HIGH, "You need 25 cells to place camera\n"); + return; + } + + // Setup "little look" to close wall + VectorCopy(ent->s.origin,wallp); + + // Cast along view angle + AngleVectors (ent->client->v_angle, forward, NULL, NULL); + + // Setup end point + wallp[0]=ent->s.origin[0]+forward[0]*50; + wallp[1]=ent->s.origin[1]+forward[1]*50; + wallp[2]=ent->s.origin[2]+forward[2]*50; + + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, wallp, ent, MASK_SOLID); + + // Line complete ? (ie. no collision) + if (tr.fraction == 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Too far from wall.\n"); + return; + } + + // Hit sky ? + if (tr.surface) + { + if (tr.surface->flags & SURF_SKY) + return; + } + + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= 25; + + if (ent->remotecam) + { + safe_cprintf (ent, PRINT_HIGH, "Remote cam off.\n"); + remote_remove(ent->remotecam); + return; + } + + safe_cprintf (ent, PRINT_HIGH, "Remote cam on.\n"); + + ent->remotecam = G_Spawn(); + VectorClear (ent->remotecam->mins); + VectorClear (ent->remotecam->maxs); + VectorCopy (tr.endpos, ent->remotecam->s.origin); + vectoangles(tr.plane.normal,ent->remotecam -> s.angles); + ent->remotecam -> movetype = MOVETYPE_NONE; + ent->remotecam -> clipmask = MASK_SHOT; + //grenade -> solid = SOLID_NOT; + ent->remotecam->solid = SOLID_BBOX; + VectorSet(ent->remotecam->mins, -3, -3, 0); + VectorSet(ent->remotecam->maxs, 3, 3, 6); + ent->remotecam->classname="camera"; + ent->remotecam->takedamage=DAMAGE_YES; + ent->remotecam -> s.modelindex = gi.modelindex ("models/objects/camera/tris.md2"); + ent->remotecam -> owner = ent; + ent->remotecam->think = remotecam_think; + ent->remotecam->nextthink = level.time + 0.5; + ent->remotecam->die = remote_die; + ent->remotecam->health= 60; + ent->remotecam->max_health = 60; + ent->remotecam->mass = 2; + ent->remotecam->delay = level.time + 10; //can't blow up for this many seconds + ent->remotecam->dmg = 20; + offset[0]=forward[0]*-10; + offset[1]=forward[1]*-10; + offset[2]=forward[2]*-10; + VectorAdd(offset,tr.endpos,offset); + VectorCopy(offset,ent->remotecam->camposition); + gi.linkentity (ent->remotecam); + +/*db if(ent->client->remotetoggle) + { + ent->client->oldplayer = G_Spawn(); + ent->client->oldplayer->s.frame = ent->s.frame; + VectorCopy (ent->s.origin, ent->client->oldplayer->s.origin); + VectorCopy (ent->velocity, ent->client->oldplayer->velocity); + VectorCopy (ent->s.angles, ent->client->oldplayer->s.angles); + ent->client->oldplayer->s.modelindex = ent->s.modelindex; + ent->client->oldplayer->s.modelindex2 = ent->s.modelindex2; + gi.linkentity (ent->client->oldplayer); + }*/ +} + +//Remote Camera Commands +void cmd_CameraPlace(edict_t *ent) +{ + place_remotecam (ent); +} + +void cmd_CameraToggle(edict_t *ent) +{ + if (ent->remotecam == NULL) + { + safe_cprintf (ent, PRINT_HIGH, "Remote camera does not exist!\n"); + return; + } + + if (ent->client->remotetoggle) + { + ent->client->remotetoggle = 0; +//db G_FreeEdict(ent->client->oldplayer); + } + else + { + ent->client->remotetoggle =1; +/*db ent->client->oldplayer = G_Spawn(); + ent->client->oldplayer->s.frame = ent->s.frame; + VectorCopy (ent->s.origin, ent->client->oldplayer->s.origin); + VectorCopy (ent->velocity, ent->client->oldplayer->velocity); + VectorCopy (ent->s.angles, ent->client->oldplayer->s.angles); + ent->client->oldplayer->s.modelindex = ent->s.modelindex; + ent->client->oldplayer->s.modelindex2 = ent->s.modelindex2; + gi.linkentity (ent->client->oldplayer);*/ + } +} + +// Command line handling for camera +void cmd_Camera(edict_t *ent) +{ + char *string; + int time = 0; + + string = gi.args(); + + if (!ent->client) return; + + //argument = "build", "detonate", "remove", "toggle" + if (Q_stricmp ( string, "build") == 0) + { + if (ent->remotecam) + safe_cprintf(ent, PRINT_HIGH, "You already have an active camera\n"); + else + place_remotecam (ent); + } + else if (Q_stricmp ( string, "toggle") == 0) + { + if (!ent->remotecam) + safe_cprintf(ent, PRINT_HIGH, "You don't have an active camera!\n"); + else + cmd_CameraToggle(ent); + } + else if (Q_stricmp ( string, "detonate") == 0) + { + if (!ent->remotecam) + safe_cprintf(ent, PRINT_HIGH, "You don't have an active camera!\n"); + else + place_remotecam (ent); + } + else if (Q_stricmp ( string, "remove") == 0) + { + if (!ent->remotecam) + safe_cprintf(ent, PRINT_HIGH, "You don't have an active camera!\n"); + else + { + ent->remotecam->dmg = 0; + place_remotecam (ent); + } + } + + else + { + safe_cprintf(ent->owner, PRINT_HIGH, "Incomplete command: add BUILD, DETONATE, REMOVE or TOGGLE\n"); + } + +} + diff --git a/stdlog.c b/stdlog.c new file mode 100644 index 0000000..e2ea22f --- /dev/null +++ b/stdlog.c @@ -0,0 +1,360 @@ +#include "g_local.h" +#include "stdlog.h" +#include + +struct mod_to_weapon +{ + char *wname; + int wid; +}; + +struct mod_to_weapon m2w[] = { + + {"Unknown", 0}, + {"Blaster", 1}, + {"Shotgun", 2}, + {"Super Shotgun", 3}, + {"Machine Gun", 4}, + {"Chain Gun", 5}, + {"Grenade", 6}, + {"Grenade", 7}, + {"Rocket", 8}, + {"Rocket", 9}, + {"Hyperblaster", 10}, + {"Rail Gun", 11}, + {"BFG", 12}, + {"BFG", 13}, + {"BFG", 14}, + {"Hand Grenade", 15}, + {"Hand Grenade", 16}, + {"Water", 17}, + {"Slime", 18}, + {"Lava", 19}, + {"Crush", 20}, + {"Telefrag", 21}, + {"Falling", 22}, + {"Suicide", 23}, + {"Held Grenade", 24}, + {"Explosive", 25}, + {"Barrel", 26}, + {"Bomb", 27}, + {"Exit", 28}, + {"Splash", 29}, + {"Target Laser", 30}, + {"Trigger Hurt", 31}, + {"Hit", 32}, + {"Target Blaster", 33}, + {"Grapple", 34}, + {"Laserball", 35}, + {"Goodyear Grenade", 36}, + {"Proximity Grenade", 37}, + {"Cluster Grenade", 38}, + {"Pipe Bomb", 39}, + {"Earth Quake", 40}, + {"Reverse Telefrag", 41}, + {"Turret Grenade", 42}, + {"Flame", 43}, + {"NAG Rifle", 44}, + {"Pulse Cannon", 45}, + {"Shrapnel Grenade", 46}, + {"Cluster Rocket", 47}, + {"Plasma Bomb", 48}, + {"Disease", 49}, + {"Sniper Rifle", 50}, + {"Nail Gun", 51}, + {"Spon Human Comb", 52}, + {"Needler", 53}, + {"Concusion Grenade", 54}, + {"Armor Piercing Dart", 55}, + {"Infected Dart", 56}, + {"Napalm Rocket", 57}, + {"Lightning Gun", 58}, + {"Telsa Coil", 59}, + {"Magnotron", 60}, + {"Shock Grenade", 61}, + {"Pellet Grenade", 62}, + {"Flare Gun", 64}, + {"Tranquilizer", 65}, + {"Bolted Blaster", 66}, + {"Sentry", 67}, + {"Long Range Projectile", 68}, + {"Flare", 69}, + {"Kamikazi", 70}, + {"Depot", 71}, + {"Sentry Killer", 72}, + {"Mega Chaingun", 73}, + {"Homing Rocket", 74}, + {"Tranquilizer Dart", 75}, + {"Sniper Rifle - Leg Shot", 76}, + {"Sniper Rifle - Head Shot", 77}, + {"Sentry Rocket", 78}, + {"Knife",79}, + {"Knife In The Back",80}, + {"Healing Depot", 81}, + {"Laser Cutter",82}, + {"Napalm Grenade", 83}, + {"Flamethrower",84}, + {"Bio Sentry",85}, + {"AK47", 86}, + {"Pistol", 87}, + {"Ion Ripper",88}, + {"Phalanx",89}, + {"ETF Rifle",90}, + {"Tesla",91}, + {"Heat Beam",92}, + {"Defender Sphere",93}, + {"Blaster2",94}, + {"Tracker",95}, + {"Nuke",96}, + {"Laser Defense",97}, + {"Gas Grenade", 98}, + {"Stinger", 99}, + {"Missile Launcher", 100}, + {"Camera", 101}, + {"Feign", 102}, + {"Freezer", 103},//acrid 3/99 + {"",9999}, +}; + +void wf_strdate(char *s) +{ + time_t t; + struct tm *tmptr; + + time(&t); //get the date/time + tmptr = localtime(&t); + + //format the date + sprintf(s, "%2d/%2d/%2d", tmptr->tm_mon + 1, tmptr->tm_mday, tmptr->tm_year); +} + +void wf_strtime(char *s) +{ + time_t t; + struct tm *tmptr; + + time(&t); //get the date/time + tmptr = localtime(&t); + + //format the time + sprintf(s, "%2d:%2d:%2d", tmptr->tm_hour, tmptr->tm_min, tmptr->tm_sec); +} + +char *sl_FindWeapon(int mod) +{ + int i; + int len; + + if (mod < 0) mod = 0; + + len = sizeof(m2w); + for (i = 0; i < len; ++i) + { + if (mod == m2w[i].wid) + return (m2w[i].wname); + } + //return ("UNKNOWN"); + return (NULL); +} + +FILE *logfile = NULL; + +char *strip9(char *str) +{ + char *p = str; + if (str == 0) return ""; + while(*p) + { + if(*p==9) + *p = '_'; + p++; + } + return str; +} + +void sl_LogPlayerName( game_import_t *gi, + char *pPlayerName, + char *pTeamName) +{} + +void sl_Logging( game_import_t *gi, char *pPatchName ) +{ + char path[100]; + char str[20]; + char fname[60]; + char fullpath[100]; + + logfile = NULL; + + //if (wfflags == NULL) return; + + //Is frag logging on? + if (((int)wfflags->value & WF_FRAG_LOGGING) == 0) + return; + + strcpy(path, gamedir->string); + + strcpy(fname, wf_game.stdlog_name); + + if (fname == NULL || fname[0] == 0) strcpy(fname, "std.log"); + +#if defined(_WIN32) || defined(WIN32) + //strcat(path,"\\std.log"); + sprintf(fullpath, "%s\\%s", path, fname); +#else + //strcat(path,"/std.log"); + sprintf(fullpath, "%s/%s", path, fname); +#endif + + if( logfile = fopen(fullpath,"a") ) + { + fprintf(logfile,"\x9\x9StdLog\x9 1.22\n"); + fprintf(logfile,"\x9\x9PatchName\x9%s\n",strip9(pPatchName)); + wf_strdate( str ); + fprintf(logfile,"\x9\x9LogDate\x9%s\n",str); + wf_strtime( str ); + fprintf(logfile,"\x9\x9LogTime\x9%s\n",str); + fflush(logfile); + } +} + + +void sl_GameStart( game_import_t *gi,level_locals_t level ) +{ + if( logfile ) + { + fprintf(logfile,"\x9\x9LogDeathFlags\x9%d\n",dmflags); + fprintf(logfile,"\x9\x9Map\x9%s\n",strip9(level.level_name)); + fprintf(logfile,"\x9\x9GameStart\x9\x9\x9%f\n",level.time); + fflush(logfile); + } +} + +void sl_GameEnd( game_import_t *gi,level_locals_t level ) +{ + if( logfile ) + { + fprintf(logfile,"\x9\x9GameEnd\x9\x9\x9%f\n",level.time); + fflush(logfile); + } +} + +void sl_LogPlayerConnect( game_import_t *gi,level_locals_t level, edict_t *ent ) +{ + if( logfile ) + { + fprintf(logfile,"\x9\x9PlayerConnect\x9%s\x9%s\x9%f\n", + strip9(ent->client->pers.netname), + CTFTeamName(ent->client->resp.ctf_team), + level.time); + fflush(logfile); + } +} + +void sl_LogPlayerDisconnect( game_import_t *gi,level_locals_t level, edict_t *ent ) +{ + if( logfile ) + { + fprintf(logfile,"\x9\x9PlayerDisonnect\x9%s\x9\x9%f\n", + strip9(ent->client->pers.netname), + level.time); + fflush(logfile); + } +} + +void sl_LogPlayerTeamChange( game_import_t *gi, char *pPlayerName, char *pTeamName) +{ + if( logfile ) + { + fprintf(logfile,"\x9\x9PlayerTeamChange\x9%s\x9%s\x9%f\n", + strip9(pPlayerName), + strip9(pTeamName), + level.time); + fflush(logfile); + } +} + +void sl_LogPlayerRename( game_import_t *gi, char *pPlayerName, char *pNewName) +{ + if( logfile ) + { + fprintf(logfile,"\x9\x9PlayerRename\x9%s\x9%s\x9%f\n", + strip9(pPlayerName), + strip9(pNewName), + level.time); + fflush(logfile); + } +} + + + +void sl_LogScore( game_import_t *gi, + char *pKillerName, + char *pTargetName, + char *pScoreType, + int mod, + int iScore) +{ + char *pWeaponName; + + if( !logfile ) return; + + //Check for suicide + if (strcmp(pScoreType, "Suicide") == 0) + { + pWeaponName = sl_FindWeapon(mod); //Start with these + switch (mod) + { + case MOD_FALLING: + pWeaponName = "Fell"; + break; + case MOD_CRUSH: + pWeaponName = "Crushed"; + break; + case MOD_WATER: + pWeaponName = "Drowned"; + break; + case MOD_SLIME: + pWeaponName = "Melted"; + break; + case MOD_LAVA: + pWeaponName = "Lava"; + break; + case MOD_BOMB: + case MOD_EXPLOSIVE: + case MOD_BARREL: + pWeaponName = "Explosion"; + break; + case MOD_TARGET_LASER: + pWeaponName = "Lasered"; + break; + case MOD_TARGET_BLASTER: + pWeaponName = "Blasted"; + break; + } + fprintf(logfile,"%s\x9%s\x9%s\x9%s\x9%d\x9%f\n", + strip9(pKillerName), + strip9(pTargetName), + strip9(pScoreType), + pWeaponName, + iScore, + level.time ); + fflush(logfile); + return; + } + + + + //Not suicide - do normal kill + if( logfile ) + { + fprintf(logfile,"%s\x9%s\x9%s\x9%s\x9%d\x9%f\n", + strip9(pKillerName), + strip9(pTargetName), + strip9(pScoreType), + strip9(sl_FindWeapon(mod)), + iScore, + level.time ); + fflush(logfile); + } +} diff --git a/stdlog.h b/stdlog.h new file mode 100644 index 0000000..336de0c --- /dev/null +++ b/stdlog.h @@ -0,0 +1,71 @@ +/* + * GibStats Logging functions + * + * + * $Id: stdlog.h 1.4 1998/03/14 13:19:09 mdavies Exp mdavies $ + */ + +#ifndef __STDLOG_H__ +#define __STDLOG_H__ + +#define _USE_LOGGING_ +#ifdef _USE_LOGGING_ + +//#define __STDLOG_ID__ "$Id: stdlog.h 1.4 1998/03/14 13:19:09 mdavies Exp $" +#define __STDLOG_ID__ "$Id: stdlog.h 1.7 1998/04/06 09:24:23 mdavies Exp $" + +/* + * NEW API + * + */ + +extern void sl_LogPlayerTeamChange( game_import_t *gi, + char *pPlayerName, + char *pTeamName); + +extern void sl_LogPlayerRename( game_import_t *gi, + char *pOldPlayerName, + char *pNewPlayerName); + +extern void sl_Logging( game_import_t *gi, + char *pPatchName ); + +extern void sl_GameStart( game_import_t *gi, + level_locals_t level ); + +extern void sl_GameEnd( game_import_t *gi, + level_locals_t level ); + +void sl_LogScore( game_import_t *gi, + char *pKillerName, + char *pTargetName, + char *pScoreType, + int mod, + int iScore); + +extern void sl_LogPlayerConnect( game_import_t *gi, + level_locals_t level, + edict_t *ent ); + +extern void sl_LogPlayerDisconnect( game_import_t *gi, + level_locals_t level, + edict_t *ent ); + +extern void sl_LogPlayerName( game_import_t *gi, + char *pPlayerName, + char *pTeamName ); +#else +#define sl_LogPlayerTeamChange( gi, pPlayerName, pTeamName) +#define sl_LogPlayerRename( gi, OldPlayerName, pNewPlayerName) +#define sl_Logging( gi, pPatchName ) +#define sl_GameStart(gi, level ) +#define sl_GameEnd( gi, level ) +#define sl_LogScore( gi, pKillerName, pTargetName, pScoreType, pWeaponName, iScore) +#define sl_LogPlayerConnect( gi, level, ent ) +#define sl_LogPlayerDisconnect( gi, level, ent ) +#define sl_LogPlayerName( gi, pPlayerName, pTeamName) +#endif + +#endif + +/* end of file */ diff --git a/throwup.c b/throwup.c new file mode 100644 index 0000000..d3aa706 --- /dev/null +++ b/throwup.c @@ -0,0 +1,82 @@ +//throwup.c +//1/5/98 JR + +#include "g_local.h" +#include "throwup.h" + +//this function makes you throw up +void ThrowUpNow(edict_t *self) +{ + //variables + vec3_t forward, right; + vec3_t mouth_pos, spew_vector; + float rnum; + + int i; + //set spew vector based on client's view angle + if (self->client) + AngleVectors (self->client->v_angle, forward, right, NULL); + else + AngleVectors (self->s.angles, forward, right, NULL); + + //Make it originate from the mouth + VectorScale (forward, 24, mouth_pos); + VectorAdd (mouth_pos, self->s.origin, mouth_pos); + mouth_pos[2] += self->viewheight; + //Make come foward from the mouth + VectorScale (forward, 24, spew_vector); + //Blood + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLOOD); + gi.WritePosition (mouth_pos); + gi.WriteDir (spew_vector); + gi.multicast (mouth_pos, MULTICAST_PVS); + //say something +/* G.R. - Removed for now - too many messages + rnum = random(); + if (rnum < 0.2) + gi.bprintf (PRINT_MEDIUM, "Retch !\n"); + else if (rnum < 0.4) + gi.bprintf (PRINT_MEDIUM, "...Vomit...\n"); + else if (rnum < 0.6) + gi.bprintf (PRINT_MEDIUM, "Chunder.\n"); + else if (rnum < 0.8) + gi.bprintf (PRINT_MEDIUM, "Chuck. chuck. chuck.\n"); + else + gi.bprintf (PRINT_MEDIUM, "Hmmmmff hmmmf hhhuuuuuuurrrrrllll.\n"); +*/ + +// make a painful sound + rnum = random(); + if (self->client) + { + if (rnum < 0.125) + gi.sound (self, CHAN_BODY, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0); + else if (rnum < 0.25) + gi.sound (self, CHAN_BODY, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0); + else if (rnum < 0.375) + gi.sound (self, CHAN_BODY, gi.soundindex("*pain50_1.wav"), 1, ATTN_NORM, 0); + else if (rnum < 0.5) + gi.sound (self, CHAN_BODY, gi.soundindex("*pain50_2.wav"), 1, ATTN_NORM, 0); + else if (rnum < 0.625) + gi.sound (self, CHAN_BODY, gi.soundindex("*pain75_1.wav"), 1, ATTN_NORM, 0); + else if (rnum < 0.75) + gi.sound (self, CHAN_BODY, gi.soundindex("*pain75_2.wav"), 1, ATTN_NORM, 0); + else if (rnum < 0.875) + gi.sound (self, CHAN_BODY, gi.soundindex("*pain100_1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_BODY, gi.soundindex("*pain100_2.wav"), 1, ATTN_NORM, 0); + } + // also do a spewing sound + gi.sound (self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + // cough up some gibs. + for (i = 0; i<5; i++) { + ThrowVomit (self, mouth_pos, forward, right, self->velocity); + } + // every now and again, cough up MEGA vomit + if (random() < 0.1) { + for (i = 0; i<10; i++) { + ThrowVomit (self, mouth_pos, forward, right, self->velocity); + } + } +} \ No newline at end of file diff --git a/throwup.h b/throwup.h new file mode 100644 index 0000000..99af9d4 --- /dev/null +++ b/throwup.h @@ -0,0 +1,8 @@ +//throup.h +//1/5/98 JR + +//main function + +void ThrowUpNow (edict_t *self); +//utility in g_misc.c +void ThrowVomit (edict_t *ent, vec3_t mouth_pos, vec3_t forward, vec3_t right, vec3_t player_vel); \ No newline at end of file diff --git a/unzip32.dll b/unzip32.dll new file mode 100644 index 0000000..4e7e505 Binary files /dev/null and b/unzip32.dll differ diff --git a/w_ak47.c b/w_ak47.c new file mode 100644 index 0000000..4bdec11 --- /dev/null +++ b/w_ak47.c @@ -0,0 +1,105 @@ +#include "g_local.h" + +#define DEFAULT_AK47_HSPREAD 200 //narrower spread than machine gun +#define DEFAULT_AK47_VSPREAD 300 + +/* +====================================================================== + +MACHINEGUN AK-47 +by Fireball + +====================================================================== +*/ + +void AK47_Fire (edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = wf_game.weapon_damage[WEAPON_AK47]; // AK-47 is better than Q2's machinegun :) + int kick = 3; + 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; + } +// Cardinal + /* + if ((ent->client->pers.inventory[ent->client->ammo_index] - 1) % 30 == 0 && ent->client->pers.inventory[ent->client->ammo_index] > 1) + { + ent->client->weaponstate = WEAPON_MACHINEGUNREARMING; + ent->client->machinegun_shots = 0; + ent->client->ps.gunframe = 6; + } + */ +// Cardinal + 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; + + // don't 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_AK47_HSPREAD, DEFAULT_AK47_VSPREAD, MOD_AK47); + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); +// gi.WriteByte (MZ_MACHINEGUN| is_silenced); + gi.WriteByte (MZ_ETF_RIFLE| 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_AK47 (edict_t *ent) +{ + static int pause_frames[] = {23, 45, 0}; + static int fire_frames[] = {4, 5, 0}; + + Weapon_Generic (ent, 3, 5, 45, 49, pause_frames, fire_frames, AK47_Fire); +} \ No newline at end of file diff --git a/w_armordart.c b/w_armordart.c new file mode 100644 index 0000000..d570497 --- /dev/null +++ b/w_armordart.c @@ -0,0 +1,168 @@ +#include "g_local.h" +void dart_prethink (edict_t *ent); +void stick(edict_t *projectile, edict_t *object); +void WFAddArmor (edict_t *other, int armorval); +void AddArmorToSentry(edict_t *ent, int amt); + +/* +====================== +Armor Piercing Dart Launcher +====================== +*/ + +void armordart_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ +// int index; + + if (other == self->owner) + return; + + //Special case if its a sentry gun - upgrade health + if ((strcmp(other->classname, "SentryGun") == 0) && (other->wf_team == self->wf_team)) + { + AddArmorToSentry(other, 25); + } + + if (!other->client) //only damage players + 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) + { + //Increase armor of person if you hit someone on the same team + if (other->wf_team == self->wf_team) + { + WFAddArmor(other, 25); + } + else + { + if (other->s.origin[2]-self->s.origin[2] > self->viewheight-6) + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg*5, 2, DAMAGE_ENERGY, MOD_ARMORDART); + else + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 2, DAMAGE_ENERGY, MOD_ARMORDART); + gi.sound (other, CHAN_VOICE, gi.soundindex ("darthit.wav"), 1, ATTN_NORM, 0); + } + } + 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); + } + + self->think = G_FreeEdict; + self->nextthink = level.time + 3 + 8 * crandom(); + + stick(self, other); +} + +void fire_armordart (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->wf_team = self->wf_team; + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->velocity[2] += 100; + bolt->movetype = MOVETYPE_TOSS; //JR 1/4/98 changed so it bounces + bolt->clipmask = MASK_SHOT; + bolt->prethink = dart_prethink; // Keeps the arrow aligned, so it arcs through the air nicely. + bolt->gravity = 0.4; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorSet (bolt->mins,-1,-1,-3.5); + VectorSet (bolt->maxs,1,1,2.5); + bolt->s.modelindex = gi.modelindex ("models/dart/tris.md2"); + bolt->s.skinnum = 1; + bolt->owner = self; + bolt->touch = armordart_touch; +// bolt->touch = infecteddart_touch; + bolt->nextthink = level.time + 1.7; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "armor dart"; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + +void weapon_armordartlauncher_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage; + + damage = wf_game.weapon_damage[WEAPON_ARMORDART]; + + if (is_quad) + { + damage *= 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); + //start[0]=ent->s.origin[0] + forward[0]*1+right[0]*6; + //start[1]=ent->s.origin[1] + forward[1]*1+right[1]*6; + //start[2]=ent->s.origin[2] + forward[2]*1+right[2]*6+ent->viewheight-8; + + /* new stuff to fix left handedness */ + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + /* new stuff to fix left handedness */ + + fire_armordart (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_ARMORDART], EF_GREENGIB); + + // 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] -= ent->client->pers.weapon->quantity; +} + + +void Weapon_ArmorDartLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_armordartlauncher_fire); +} \ No newline at end of file diff --git a/w_boltedblaster.c b/w_boltedblaster.c new file mode 100644 index 0000000..a248cce --- /dev/null +++ b/w_boltedblaster.c @@ -0,0 +1,139 @@ + +#include "g_local.h" + +/* +=========================== +Magneticlly Bolted Blaster +=========================== +*/ + +void blaster2_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); + + if (other->takedamage) + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, MOD_BOLTEDBLASTER); + else + { + //JR 1/4/98 added return + return; + //COde below not used + 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_blaster2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->wf_team = self->wf_team; + + VectorCopy (start, bolt->s.origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + VectorAdd(start,bolt->velocity,bolt->s.old_origin); + bolt->movetype = MOVETYPE_FLYRICOCHET; //JR 1/4/98 changed so it bounces + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.renderfx |= RF_BEAM|RF_TRANSLUCENT; + //bolt->s.effects |= effect; + VectorSet (bolt->mins,-8,-8,-8); + VectorSet (bolt->maxs,8,8,8); + bolt->s.modelindex = 1; + bolt->s.frame = 2; + if (self->wf_team == CTF_TEAM1) // on red team + bolt->s.skinnum = 0xf2f2f0f0; // red + else // on blue team + bolt->s.skinnum = 0xf3f3f1f1; // sorta blue + + //bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2"); + bolt->s.sound = gi.soundindex ("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster2_touch; + bolt->nextthink = level.time + 4; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + + +void BoltedBlaster_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_blaster2 (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_MAGBOLTED], effect); + + // 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_BoltedBlaster_Fire (edict_t *ent) +{ + int damage; + + damage = wf_game.weapon_damage[WEAPON_MAGBOLTED]; + BoltedBlaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER); + ent->client->ps.gunframe++; +} + +void Weapon_BoltedBlaster (edict_t *ent) +{ + static int pause_frames[] = {19, 32, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_BoltedBlaster_Fire); +} diff --git a/w_cgprojectilelauncher.c b/w_cgprojectilelauncher.c new file mode 100644 index 0000000..38f3b35 --- /dev/null +++ b/w_cgprojectilelauncher.c @@ -0,0 +1,183 @@ +#include "g_local.h" +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)); +/* +===================================================== +Computer Guided Projectile Launcher http://haydon.niehs.nih.gov/maplist2.htm +===================================================== +*/ + +void FireComputerGuidedProjectile(edict_t *ent,int damage,int kick) +{ + vec3_t forward, right, target, dir,start; + vec3_t check; + trace_t tr; + edict_t *blip, *targ; + if(ent->light_level<1) + return; + ent->light_level--; + AngleVectors (ent->client->v_angle, forward, right, NULL); + + + check[0]=ent->s.origin[0] + forward[0]*750; + check[1]=ent->s.origin[1] + forward[1]*750; + check[2]=ent->s.origin[2] + forward[2]*750; + targ=NULL; + blip = NULL; + while (blip = findradius (blip, check, 375)) + { + + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + continue; + if (blip->health <= 0) + continue; + + tr = gi.trace (ent->s.origin, NULL, NULL, blip->s.origin, ent, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + targ = blip; + } + if(!targ) + { + check[0]=ent->s.origin[0] + forward[0]*500; + check[1]=ent->s.origin[1] + forward[1]*500; + check[2]=ent->s.origin[2] + forward[2]*500; + while (blip = findradius (blip, check, 175)) + { + + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + continue; + if (blip->health <= 0) + continue; + + + tr = gi.trace (ent->s.origin, NULL, NULL, blip->s.origin, ent, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + targ = blip; + } + } + if(!targ) + { + check[0]=ent->s.origin[0] + forward[0]*900; + check[1]=ent->s.origin[1] + forward[1]*900; + check[2]=ent->s.origin[2] + forward[2]*900; + while (blip = findradius (blip, check, 250)) + { + + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + continue; + if (blip->health <= 0) + continue; + + tr = gi.trace (ent->s.origin, NULL, NULL, blip->s.origin, ent, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + targ = blip; + } + } + if(!targ) + { + check[0]=ent->s.origin[0] + forward[0]*1050; + check[1]=ent->s.origin[1] + forward[1]*1050; + check[2]=ent->s.origin[2] + forward[2]*1050; + while (blip = findradius (blip, check, 175)) + { + + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + continue; + if (blip->health <= 0) + continue; + + tr = gi.trace (ent->s.origin, NULL, NULL, blip->s.origin, ent, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + targ = blip; + } + } + if(!targ) + { + check[0]=ent->s.origin[0] + forward[0]*1125; + check[1]=ent->s.origin[1] + forward[1]*1125; + check[2]=ent->s.origin[2] + forward[2]*1125; + while (blip = findradius (blip, check, 75)) + { + + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + continue; + if (blip->health <= 0) + continue; + + tr = gi.trace (ent->s.origin, NULL, NULL, blip->s.origin, ent, MASK_SOLID); + if (tr.fraction != 1.0) + continue; + targ = blip; + } + } + if(targ) + { + // calc direction to where we targted + VectorMA (targ->s.origin, -0.2, targ->velocity, target); + + //Adjust for height + target[2] -= targ->viewheight/1.5; + + VectorSubtract (target, ent->s.origin, dir); + VectorNormalize (dir); + safe_cprintf (ent, PRINT_HIGH, "Target Acquired by Computer!\n"); + + VectorCopy(dir,forward); + + } + + start[0] = ent->s.origin[0]+forward[0]*3; + start[1] = ent->s.origin[1]+forward[1]*3; + + start[2] = ent->s.origin[2]+forward[2]*3+ent->viewheight; + //gi.error("JOhn"); + //fire bullet + + fire_bullet (ent, start, forward, damage, kick, 75, 75,0); + + // send muzzle flash +/* gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SHOTGUN); + gi.multicast (ent->s.origin, MULTICAST_PVS);*/ +} + +void weapon_ComputerGuidedProjectileLauncher_fire (edict_t *ent) +{ + vec3_t start; + int damage = 8; + int kick = 80; + if ((ent->client->buttons & BUTTON_ATTACK) && (ent->client->ps.gunframe == 11)) + { + ent->client->ps.gunframe=8; + return; + } + /* + if (is_quad) + { + damage *= 4; + kick *= 4; + }*/ + + + FireComputerGuidedProjectile(ent, damage, kick); + + + 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_ComputerGuidedProjectileLauncher (edict_t *ent) +{ + static int pause_frames[] = {22, 28, 34, 0}; + static int fire_frames[] = { 9,11, 0}; + + Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_ComputerGuidedProjectileLauncher_fire); +} \ No newline at end of file diff --git a/w_clustermissiles.c b/w_clustermissiles.c new file mode 100644 index 0000000..d4fbc18 --- /dev/null +++ b/w_clustermissiles.c @@ -0,0 +1,179 @@ +#include "g_local.h" +/* +====================== +Cluster Missiles +====================== +*/ +void rocketcluster_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + int n; +// int i; +// float spd; +// vec3_t org; + + //Sean added these 4 vectors + vec3_t grenade1; + vec3_t grenade2; + vec3_t grenade3; + vec3_t grenade4; + + + 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_CLUSTERROCKET); + } + else + { + // don't throw any debris in net games + if (!deathmatch->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_CLUSTERROCKET); + + 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_PVS); + // SumFuka did this bit : give grenades up/outwards velocities + VectorSet(grenade1,20,20,40); + VectorSet(grenade2,20,-20,40); + VectorSet(grenade3,-20,20,40); + VectorSet(grenade4,-20,-20,40); + + // Sean : explode the four grenades outwards + fire_grenade2(ent->owner, origin, grenade1, DAMAGE_CLUSTERROCKET, 1, 1.0, 120, false); + fire_grenade2(ent->owner, origin, grenade2, DAMAGE_CLUSTERROCKET, 1, 1.0, 120, false); + fire_grenade2(ent->owner, origin, grenade3, DAMAGE_CLUSTERROCKET, 1, 1.0, 120, false); + fire_grenade2(ent->owner, origin, grenade4, DAMAGE_CLUSTERROCKET, 1, 1.0, 120, false); + + // make some glowing shrapnel + //spd = 15.0 * ent->dmg / 200; +/* for (i = 0; i < 3; i++) + { + org[0] = ent->s.origin[0] + crandom() * ent->size[0]; + org[1] = ent->s.origin[1] + crandom() * ent->size[1]; + org[2] = ent->s.origin[2] + crandom() * ent->size[2]; + ThrowShrapnel4 (ent, "models/objects/debris2/tris.md2", spd, org); + } + make_debris (ent); + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius,MOD_CLUSTERROCKET); + T_ShockItems(ent); + T_ShockWave(ent, 255, 1024);*/ + BecomeNewExplosion (ent); +} + +void fire_clusterrocket (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(); + rocket->wf_team = self->wf_team; + + 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 = rocketcluster_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"; + // CCH: a few more attributes to let the rocket 'die' + VectorSet(rocket->mins, -10, -3, 0); + VectorSet(rocket->maxs, 10, 3, 6); + rocket->mass = 10;+ rocket->health = 10; + rocket->die = Rocket_Die; + rocket->takedamage = DAMAGE_NO; + rocket->monsterinfo.aiflags = AI_NOSTEP; + if (self->client) + check_dodge (self, rocket->s.origin, dir, speed); + + gi.linkentity (rocket); +} + +void Weapon_RocketClusterLauncher_Fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + + + int damage; + float damage_radius; + int radius_damage; + + damage = wf_game.weapon_damage[WEAPON_CLUSTERROCKET]; + radius_damage = 20; + damage_radius = 20; + 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; + //First rocket + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_clusterrocket (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_CLUSTERROCKET], 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] -= ent->client->pers.weapon->quantity; +} +void Weapon_RocketClusterLauncher (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, pause_frames, fire_frames, Weapon_RocketClusterLauncher_Fire); +} diff --git a/w_concussion.c b/w_concussion.c new file mode 100644 index 0000000..cbbf191 --- /dev/null +++ b/w_concussion.c @@ -0,0 +1,155 @@ +#include "g_local.h" + +/* +=========================== +Concussion Grenades +=========================== +*/ + + +void Concussion_Explode (edict_t *ent) + { + vec3_t offset,v; + edict_t *target; + float Distance, DrunkTimeAdd; + // Move it off the ground so people are sure to see it + VectorSet(offset, 0, 0, 10); + VectorAdd(ent->s.origin, offset, ent->s.origin); + + if (ent->owner->client) + { + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + } + + target = NULL; + while ((target = findradius(target, ent->s.origin, 520)) != NULL) + { + if (!target->client) + continue; // It's not a player + + if (target->wf_team == ent->wf_team) //don't attack if on same team + continue; + + if (!visible(ent, target)) + continue; // The grenade can't see it + + if (target->DrunkTime > level.time) + continue; //don't make them more drunk (Gregg) + + // Find distance + VectorSubtract(ent->s.origin, target->s.origin, v); + Distance = VectorLength(v); + // Calculate drunk factor + if(Distance < 520/10) + DrunkTimeAdd = 20; //completely drunk + else + DrunkTimeAdd = 1.5 * 20 * ( 1 / ( ( Distance - 520*2 ) / (520*2) - 2 ) + 1 ); //partially drunk + if ( DrunkTimeAdd < 0 ) + DrunkTimeAdd = 0; // Do not make drunk at all. + + //GREGG - decrease drunk time + DrunkTimeAdd *= .3; + + // Increment the drunk time + if(target->DrunkTime < level.time) + target->DrunkTime = DrunkTimeAdd+level.time; + else + target->DrunkTime += DrunkTimeAdd; + } + + // Blow up the grenade + BecomeExplosion1(ent); +} + +void Concussion_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = Concussion_Explode; +} + + +void Concussion_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + // Dont blow up if on same team + if (other->wf_team == ent->wf_team) + return; + + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + //ent->enemy = other; was causing damage to thrower? + Concussion_Explode (ent); +} + + +void fire_concussiongrenade (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; + + ++self->client->pers.active_grenades[GRENADE_TYPE_CONCUSSION]; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + grenade->grenade_index = GRENADE_TYPE_CONCUSSION; + grenade->wf_team = self->wf_team; + + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 30.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 (GRCONCUSSION_MODEL); + grenade->s.skinnum = GRCONCUSSION_SKIN; + grenade->owner = self; + grenade->touch = Concussion_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Concussion_Explode; + grenade->dmg = wf_game.grenade_damage[GRENADE_TYPE_CONCUSSION]; + grenade->dmg_radius = damage_radius; + grenade->classname = "concussion"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = Concussion_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + gi.linkentity (grenade); +} diff --git a/w_flamethrower.c b/w_flamethrower.c new file mode 100644 index 0000000..26ffb5c --- /dev/null +++ b/w_flamethrower.c @@ -0,0 +1,134 @@ +#include "g_local.h" +/* +=============== +Flamethrower +=============== +*/ +void flame_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + + if (other == self->owner) + return; + + if (!other->takedamage) return; + + // clean up laser entities + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 6, 0, 0,MOD_WF_FLAME); + // core explosion - prevents firing it into the wall/floor + if (other->health) + { + burn_person(other, self->owner, self->SniperDamage, MOD_FLAMETHROWER); + } + G_FreeEdict (self); +} + +void Flame_IncreaseFrame(edict_t *self) +{ + self->s.frame++; + if(self->s.frame>4) + self->s.frame =0; + if(self->delaynextthink=level.time+0.1; +} + + +void fire_flamethrower(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius) +{ + edict_t *flame; + + flame = G_Spawn(); + flame->wf_team = self->wf_team; + VectorCopy (start, flame->s.origin); + VectorCopy (dir, flame->movedir); + vectoangles (dir, flame->s.angles); + VectorScale (dir, speed, flame->velocity); + flame->movetype = MOVETYPE_BOUNCE; + flame->clipmask = MASK_SHOT; + flame->solid = SOLID_BBOX; + flame->s.effects |= EF_PLASMA|EF_DOUBLE;//EF_ANIM_ALLFAST|EF_BFG|EF_HYPERBLASTER;//EF_BLASTER|EF_GRENADE; + VectorSet (flame->mins,-20,-20,-20); + VectorSet (flame->maxs,20,20,20); + flame->s.modelindex = gi.modelindex ("models/fire/tris.md2");//("sprites/fire.sp2"); + flame->gravity=0.2; + flame->s.frame=0; + flame->owner = self; + flame->touch = flame_touch; + flame->delay = level.time + 0.8; + flame->nextthink=level.time+0.1; + flame->think = Flame_IncreaseFrame; + //flame->think = G_FreeEdict; + flame->radius_dmg = damage; + flame->SniperDamage = damage; + flame->dmg_radius = damage_radius; + flame->classname = "flame"; + flame->s.sound = gi.soundindex ("weapons/bfg__l1a.wav"); + + if (self->client) + check_dodge (self, flame->s.origin, dir, speed); + + gi.linkentity (flame); +} + +void weapon_flamethrower_fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage = wf_game.weapon_damage[WEAPON_FLAMETHROWER] ; + float damage_radius = 1200; + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe = 31; + return; + } + else if(ent->client->ps.gunframe == 30) + ent->client->ps.gunframe = 9; + + // 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); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (is_quad) + damage *= 4; + ent->client->v_angle[2]+=5; + AngleVectors (ent->client->v_angle, forward, right, NULL); + ent->client->v_angle[2]-=5; + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (ent->waterlevel < 2) + { + fire_flamethrower (ent, start, forward, wf_game.weapon_damage[WEAPON_FLAMETHROWER], 450 , 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] -= ent->client->pers.weapon->quantity; +} + +void Weapon_FlameThrower (edict_t *ent) +{ + static int pause_frames[] = {39, 45, 50, 55, 0}; + static int fire_frames[] = {12, 15, 18, 24, 27, 30, 0}; + Weapon_Generic (ent, 8, 32, 55, 58, pause_frames, fire_frames, weapon_flamethrower_fire); +} diff --git a/w_flare.c b/w_flare.c new file mode 100644 index 0000000..9bf8990 --- /dev/null +++ b/w_flare.c @@ -0,0 +1,278 @@ +#include "g_local.h" +void stick(edict_t *projectile, edict_t *object); + +/* +======================= +Flares(light up stuff) +======================= +*/ +void Flare_Explode (edict_t *ent) +{ + vec3_t origin; + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + 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); +} + +void Flare_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = Flare_Explode; +} + +void Flare_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + //ent->enemy = other; + Flare_Explode (ent); +} + +void fire_flare (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(); + grenade->wf_team = self->wf_team; + 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_BLASTER|EF_BFG|EF_TELEPORTER; + grenade->s.renderfx |= RF_SHELL_GREEN; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex (GRFLARE_MODEL); + grenade->s.skinnum = GRFLARE_SKIN; + grenade->owner = self; + grenade->touch = Flare_Touch; + grenade->nextthink = level.time + timer + 35; + grenade->think = Flare_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = Flare_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + gi.linkentity (grenade); +} + +void weapon_flarelauncher_fire (edict_t *ent) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 12; + 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_flare (ent, start, forward, damage, 400, 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] -= ent->client->pers.weapon->quantity; +} + +void Weapon_FlareLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_flarelauncher_fire); +} +void Sticky_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //gi.dprintf("Cluster: Touch\n"); + + if (other == ent->owner) + return; + + // Dont blow up if on same team + //if (other->wf_team == ent->wf_team) + // return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + stick(ent, other); + + //Cluster_Explode (ent); +} +void fire_Stickyflare (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_BLASTER|EF_BFG|EF_TELEPORTER; + grenade->s.renderfx |= RF_SHELL_GREEN; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2"); + grenade->owner = self; + grenade->touch = Sticky_Touch; + grenade->nextthink = level.time + timer + 35; + grenade->think = Flare_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = Flare_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + gi.linkentity (grenade); +} + +void weapon_stickyflarelauncher_fire (edict_t *ent) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 12; + 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_Stickyflare (ent, start, forward, damage, 400, 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); + + ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity; +} + +void Weapon_StickyFlareLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_stickyflarelauncher_fire); +} diff --git a/w_flaregun.c b/w_flaregun.c new file mode 100644 index 0000000..324d367 --- /dev/null +++ b/w_flaregun.c @@ -0,0 +1,212 @@ +#include "g_local.h" +void stick(edict_t *projectile, edict_t *object); +/*Shelton: Enemy disguised spies are purposely not safe from fire damage. Flare ammo cost +not changed, but should be, also, flare limit not invoked, but should be discussed (Im thinking +3 or 4). Flares last roughly 5 seconds from fire time to burnout. */ + +/* +================ +Flare Gun +================ +*/ + +void flare2_explode (edict_t *ent) +{ + G_FreeEdict (ent); + //Shelton: remove flare entity, no explosion, just burn out. +} + +void flaregun2_sticky_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //Shelton: Sticky code taken from Snipers Flare. + + if (other == ent->owner) + return; + + if (other->wf_team == ent->wf_team) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + //Shelton: Flare set to bounce off of owner & team. Remove if hits sky. + + stick(ent, other); + //Shelton: otherwise stick it. + + ent->s.effects |= EF_TELEPORTER; + + if (other->takedamage) + { + burn_person(other, ent->owner, 10, MOD_FLAREGUN); + ent->think = flare2_explode; + } + /*Shelton: Direct hit on enemy, light em up. Flare continues to be stuck in victim + until nextthink occurs. During that time, other enemies may be burned too */ + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + //Shelton: stick to wall, play sound.. etc. +} + +static void mod_Flare2Think(edict_t *ent) +{ + edict_t *blip = NULL; + + if(ent->delay < level.time) + { + ent->think = flare2_explode; + } + //Shelton: flare timeout occurs, destroy flare. + while ((blip = findradius(blip, ent->s.origin, 100)) != NULL) + { + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + continue; + if (!visible(ent, blip)) + continue; + if (blip->health <= 0) + continue; + if (!blip->takedamage) + continue; + if (blip->wf_team != ent->wf_team) + burn_person(blip, ent->owner, 3, MOD_FLAREGUN); + continue; + if (blip->wf_team == ent->wf_team) + continue; + break; + } + //Shelton: blip for enemies to burn in radius, ignore team. Burn spies. + + ent->nextthink = level.time + .1; +} + +void fire_burningflare2(edict_t *self, vec3_t start, vec3_t dir, vec3_t los, int damage, int speed, +float damage_radius, int radius_damage) +{ + edict_t *rocket; + + rocket = G_Spawn(); + + VectorCopy (los, rocket->pos1); /*Added: Save the line of sight of the center rocket. This is + the axis around which the other two rockets rotate*/ + VectorCopy (start, rocket->pos2); /*Added: Save the start position of the rocket (Not sure if + this is necessary. This info might already be somewhere else)?*/ + + VectorCopy (start, rocket->s.origin); + VectorCopy (dir, rocket->movedir); + vectoangles (dir, rocket->s.angles); + VectorScale (dir, speed, rocket->velocity); + rocket->movetype = MOVETYPE_TOSS; + 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/flarep/tris.md2"); + rocket->owner = self; + rocket->touch = flaregun2_sticky_touch; + rocket->gravity = .4; + rocket->nextthink = level.time + 3; + rocket->think = mod_Flare2Think; + rocket->dmg = 0; + rocket->delay = level.time + 5; + rocket->radius_dmg = 0; + rocket->dmg_radius = 0; + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->wf_team = self->wf_team; + if (self->client) + check_dodge (self, rocket->s.origin, dir, speed); + + gi.linkentity (rocket); +} + +/*Shelton: rocket settings slighly altered. Old flare firing method changed to straight shot. +However, it's current trajectory is a bit off, and I don't know how to fix that */ + +void Weapon_FlareGun2_Fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + vec3_t traj_angle, lineofsight; /*Added: Trajectory angle of rocket and line of sight for the center rocket*/ + int startspeed; /*Added: Start speed of the rockets*/ + + int damage; + float damage_radius; + int radius_damage; + + startspeed = wf_game.weapon_speed[WEAPON_FLAREGUN]; + + damage = 0; + radius_damage = 0; + damage_radius = 0; + + //Shelton: removed direct damage. All damage comes from burn_person(). + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorScale (forward, 0, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + /*Added: Start of modified launcher code*/ + VectorCopy(forward, lineofsight); /*our line of sight is the same as our forward view vector*/ + VectorCopy(ent->client->v_angle, traj_angle); /*get the trajectory angles for later modification*/ + /*This is the original launch code*/ + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + /*Added: Now, modify the trajectory of the second rocket to 5 degrees off of the center axis*/ + traj_angle[1] = ent->client->v_angle[1]+5; + AngleVectors (traj_angle, forward, right, NULL); + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_burningflare2 (ent, start, forward, lineofsight, damage, startspeed, 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++; + // ### Hentai ### BEGIN +/* + 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; + } + + // ### Hentai ### END +*/ + PlayerNoise(ent, start, PNOISE_WEAPON); + + //Reduce ammo + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 6; +} +void Weapon_FlareGun2 (edict_t *ent) +{ + static int pause_frames[] = {9,32, 0}; + static int fire_frames[] = {9, 0}; + + Weapon_Generic (ent,5, 9, 31, 36, pause_frames, fire_frames, Weapon_FlareGun2_Fire); +} \ No newline at end of file diff --git a/w_gasgrenade.c b/w_gasgrenade.c new file mode 100644 index 0000000..ef23e0b --- /dev/null +++ b/w_gasgrenade.c @@ -0,0 +1,277 @@ +#include "g_local.h" + +/* +=========================== +Gas Grenades +=========================== +*/ + +void Gas_Explode (edict_t *ent); + +void Gas_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = Gas_Explode; +} + +void Gas_Explode (edict_t *ent) +{ + vec3_t origin,v; + edict_t *target; + float Distance, DrunkTimeAdd; + + if (ent->owner->client) + { + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + } + + 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_PVS); + + target = NULL; + while ((target = findradius(target, ent->s.origin, 80)) != NULL) + { + if (!target->client) + continue; // It's not a player + + if (target->wf_team == ent->wf_team) //don't attack if on same team + continue; + + if (!visible(ent, target)) + continue; // The grenade can't see it + + // Find distance + VectorSubtract(ent->s.origin, target->s.origin, v); + Distance = VectorLength(v); + // Calculate drunk factor + if(Distance < 80/10) + DrunkTimeAdd = 10; //completely drunk + else + DrunkTimeAdd = 1.5 * 10 * ( 1 / (( Distance - 80 ) / (80*2) - 2 ) + 2 ); //partially drunk + if ( DrunkTimeAdd < 0 ) + DrunkTimeAdd = 0; // Do not make drunk at all. + + + // Increment the drunk time + if(target->client->Gas_Time < level.time) + target->client->Gas_Time = DrunkTimeAdd+level.time; + else + target->client->Gas_Time += DrunkTimeAdd/3; + } + + // shake view + T_ShockWave(ent, 255, 1024); + // explode and destroy grenade + BecomeNewExplosion (ent); +} + +void Gas_think(edict_t *ent) +{ + vec3_t v; + edict_t *target; + float Distance,DrunkTimeAdd; + vec3_t forward, right, up; + ent->s.angles[0]+=10*random()-5; + ent->s.angles[1]+=10*random()-5; + ent->s.angles[2]+=10*random()-5; + AngleVectors (ent->s.angles, forward, right, up); + ent->movetype = MOVETYPE_FLYRICOCHET; + VectorSet (ent->avelocity, 150, 150, 150); + //VectorMA (ent->velocity, 200 + crandom() * 10.0, up, ent->velocity); + //VectorMA (ent->velocity, crandom() * 10.0, right, ent->velocity); + + target = NULL; + while ((target = findradius(target, ent->s.origin, 220)) != NULL) + { + if (!target->client) + continue; // It's not a player + + if (target->wf_team == ent->wf_team) //don't attack if on same team + continue; + + if (!visible(ent, target)) + continue; // The grenade can't see it + + + // Find distance + VectorSubtract(ent->s.origin, target->s.origin, v); + Distance = VectorLength(v); + // Calculate drunk factor + if(Distance < 220/10) + DrunkTimeAdd = 10; //completely drunk + else + DrunkTimeAdd = 1.5 * 10 * ( 1 / (( Distance - 220 ) / (220*2) - 2 ) + 2 ); //partially drunk + if ( DrunkTimeAdd < 0 ) + DrunkTimeAdd = 0; // Do not make drunk at all. + if ( DrunkTimeAdd > 10 ) + DrunkTimeAdd = 10; // Do not make drunk at all. + + + // Increment the drunk time + if(target->client->Gas_Time < level.time) + target->client->Gas_Time = DrunkTimeAdd+level.time; + else + target->client->Gas_Time += DrunkTimeAdd/3; + } + //Spray out some sparks at random + if(random()<0.30) + { + //Gregg add in some type of air sound to be played + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SHIELD_SPARKS); + gi.WritePosition (ent->s.origin); + gi.WriteDir (ent->s.angles); + gi.multicast (ent->s.origin, MULTICAST_PVS); + //The gas coming out pushes the grenade around + VectorScale (forward, 50+((int)(random()*1000)%150), ent->velocity); + } + //Some more sparks + if(random()>0.25) + { + //Gregg add in some type of air sound to be played + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SCREEN_SPARKS); + gi.WritePosition (ent->s.origin); + gi.WriteDir (ent->s.angles); + gi.multicast (ent->s.origin, MULTICAST_PVS); + //The gas coming out pushes the grenade around + VectorScale (forward, 40+((int)(random()*1000)%160), ent->velocity); + } + //Some really wierd sparks but sprays out a lot more of the plague + if(random()<0.15) + { + //Gregg add in some type of air sound to be played + /*gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + */ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WIDOWBEAMOUT); + gi.WriteShort (2000); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + target = NULL; + while ((target = findradius(target, ent->s.origin, 80)) != NULL) + { + if (!target->client) + continue; // It's not a player + + if (target->wf_team == ent->wf_team) //don't attack if on same team + continue; + + if (!visible(ent, target)) + continue; // The grenade can't see it + + + // Find distance + VectorSubtract(ent->s.origin, target->s.origin, v); + Distance = VectorLength(v); + // Calculate drunk factor + if(Distance < 80/10) + DrunkTimeAdd = 10; //completely drunk + else + DrunkTimeAdd = 1.5 * 10 * ( 1 / (( Distance - 80 ) / (80*2) - 2 ) + 2 ); //partially drunk + if ( DrunkTimeAdd < 0 ) + DrunkTimeAdd = 0; // Do not make drunk at all. + + + // Increment the drunk time + if(target->client->Gas_Time < level.time) + target->client->Gas_Time = DrunkTimeAdd+level.time; + else + target->client->Gas_Time += DrunkTimeAdd/3; + } + } + + if(ent->delaythink=Gas_Explode; + ent->nextthink=level.time + 0.5; +} +void Gas_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + // Dont blow up if on same team + if (other->wf_team == ent->wf_team) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (other->takedamage) + { + //ent->enemy = other; + Gas_Explode(ent); + } +} + + + +void fire_gasgrenade (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; + + ++self->client->pers.active_grenades[GRENADE_TYPE_GAS]; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + grenade->grenade_index = GRENADE_TYPE_GAS; + grenade->wf_team = self->wf_team; + 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_FLYRICOCHET; + 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 = Gas_Touch; + grenade->nextthink = level.time + 1; + grenade->delay = level.time + 10; + grenade->think = Gas_think; + grenade->dmg = (damage/3); + grenade->dmg_radius = damage_radius; + grenade->classname = "gas"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = Gas_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + gi.linkentity (grenade); +} diff --git a/w_infectdartlauncher.c b/w_infectdartlauncher.c new file mode 100644 index 0000000..0798f15 --- /dev/null +++ b/w_infectdartlauncher.c @@ -0,0 +1,319 @@ +#include "g_local.h" +void Remove_Player_Flames (edict_t *ent); +void HealPlayer(edict_t *ent); + +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; +} + +void VectorUnrotate(vec3_t in, vec3_t angles, vec3_t out) { + float cv, sv, angle, tv; + + VectorCopy(in, out); + + 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; + + 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[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; +} + +void stuck_prethink (edict_t *self) +{ + vec3_t temp, new; + edict_t *other; + + other = self->goalentity; + + if (!other->inuse) { + } + + VectorRotate(self->pos1, other->s.angles, temp); + VectorRotate(self->pos2, other->s.angles, new); + + VectorAdd(other->s.origin, temp, self->s.origin); + VectorSubtract(new, temp, new); + vectoangles(new, self->s.angles); +} + +void Calc_StuckOffset(edict_t *self, edict_t *other) +{ + vec3_t forward; + + VectorSubtract(self->s.origin, other->s.origin, forward); + VectorUnrotate(forward, other->s.angles, self->pos1); + + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorMA(self->s.origin, 64, forward, forward); + VectorSubtract(forward, other->s.origin, forward); + VectorUnrotate(forward, other->s.angles, self->pos2); +} + +void stick(edict_t *projectile, edict_t *object) +{ + projectile->solid = SOLID_NOT; + projectile->movetype = MOVETYPE_FLY; + VectorClear(projectile->velocity); + VectorClear(projectile->avelocity); + + if (object != g_edicts) { + Calc_StuckOffset(projectile, object); + projectile->goalentity = object; + projectile->prethink = stuck_prethink; + } else + projectile->prethink = NULL; +} + +/* +================= +dart_prethink + +This is a support routine for keeping an +object velocity-aligned, for I.E. arrows. +================= +*/ +void dart_prethink (edict_t *ent) +{ + vec3_t move; + vectoangles(ent->velocity, move); + VectorSubtract(move, ent->s.angles, move); + move[0] = fmod((move[0] + 180), 360) - 180; + move[1] = fmod((move[1] + 180), 360) - 180; + move[2] = fmod((move[2] + 180), 360) - 180; + VectorScale(move, 1/FRAMETIME, ent->avelocity); +} + +/* +====================== +Infected Dart Launcher +====================== +*/ +void infecteddart_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + return; + + if (!other->client) //only infect players + 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 (!Q_stricmp("SentryGun", other->classname)) + { + 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); + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 2, DAMAGE_ENERGY, self->mod); + } + //Heal person if you hit someone on the same team + else if (other->wf_team == self->wf_team) + { + if (other->disease) + { + safe_cprintf(other, PRINT_HIGH, "You've been cured!"); + } + + gi.sound (other, CHAN_VOICE, gi.soundindex ("items/pkup.wav"), 1, ATTN_NORM, 0); + + HealPlayer(other); + + if (other->client) + { + other->client->blindBase = 0; //stop blindness + other->client->blindTime = 0; + } + other->DrunkTime=level.time - 1; //stop concusion + + if (other->health < other->max_health) + { + other->health += 25; + if (other->health > other->max_health) + other->health = other->max_health; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_EXPLOSION); + gi.WritePosition (other->s.origin); + gi.multicast (other->s.origin, MULTICAST_PVS); + if (self->mod == MOD_BIOSENTRY) + gi.sound(other, CHAN_VOICE, gi.soundindex("weapons/biosentry/biofriendlyhit.wav"), 1, ATTN_NORM, 0); + else + gi.sound(other, CHAN_VOICE, gi.soundindex("ctf/tech4.wav"), 1, ATTN_NORM, 0); + } + } + else + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 2, DAMAGE_ENERGY, self->mod); + infect_person(other,self->owner); + if (self->mod == MOD_BIOSENTRY) + gi.sound(other, CHAN_VOICE, gi.soundindex("weapons/biosentry/bioenemyhit.wav"), 1, ATTN_NORM, 0); + else + gi.sound (other, CHAN_VOICE, gi.soundindex ("darthit.wav"), 1, ATTN_NORM, 0); + } + } + 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); + } + + self->think = G_FreeEdict; + self->nextthink = level.time + 3 + 8 * crandom(); + + stick(self, other); +} + +void fire_infecteddart (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int mod) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->mod = mod; + bolt->wf_team = self->wf_team; + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->velocity[2] += 90; + bolt->movetype = MOVETYPE_TOSS; //JR 1/4/98 changed so it bounces + bolt->clipmask = MASK_SHOT; + bolt->prethink = dart_prethink; // Keeps the arrow aligned, so it arcs through the air nicely. + bolt->gravity = 0.2; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorSet (bolt->mins,-1,-1,-2.5); + VectorSet (bolt->maxs,1,1,4); + bolt->s.modelindex = gi.modelindex ("models/dart/tris.md2"); + bolt->s.skinnum = 0; + bolt->owner = self; + bolt->touch = infecteddart_touch; + bolt->nextthink = level.time + 3; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "infected dart"; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + +void weapon_infecteddartlauncher_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage; + int speed; + + damage = wf_game.weapon_damage[WEAPON_INFECTEDDART]; + + if (is_quad) + { + damage *= 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); + //start[0]=ent->s.origin[0] + forward[0]*1+right[0]*6; + //start[1]=ent->s.origin[1] + forward[1]*1+right[1]*6; + //start[2]=ent->s.origin[2] + forward[2]*1+right[2]*6+ent->viewheight-8; + /* new stuff to fix left handedness */ + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + /* new stuff to fix left handedness */ + + speed = wf_game.weapon_speed[WEAPON_INFECTEDDART]; + fire_infecteddart (ent, start, forward, damage, speed, EF_GIB, MOD_INFECTEDDART); + + // 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] -= ent->client->pers.weapon->quantity; +} + + +void Weapon_InfectedDartLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64 ,pause_frames, fire_frames, weapon_infecteddartlauncher_fire); +} diff --git a/w_laser.h b/w_laser.h new file mode 100644 index 0000000..0cae827 --- /dev/null +++ b/w_laser.h @@ -0,0 +1,15 @@ +// my functions + void PlaceLaser (edict_t *ent); + void pre_target_laser_think (edict_t *self); + // controlling parameters + #define LASER_TIME 120 + #define MAX_LASERS 5 + #define CELLS_FOR_LASER 20 + #define LASER_DAMAGE 100 + #define LASER_MOUNT_DAMAGE 50 + #define LASER_MOUNT_DAMAGE_RADIUS 64 + // In-built Quake2 routines + void target_laser_use (edict_t *self, edict_t *other, edict_t *activator); + void target_laser_think (edict_t *self); + void target_laser_on (edict_t *self); + void target_laser_off (edict_t *self); diff --git a/w_lasercutter.c b/w_lasercutter.c new file mode 100644 index 0000000..2f6cf99 --- /dev/null +++ b/w_lasercutter.c @@ -0,0 +1,351 @@ +//The grenade turrets hit the ceiling and stick out halfway. +//this makes it so they drop down a bit, eliminating this. +#include "g_local.h" +void grenlaser_think2 (edict_t *ent); +void laser_Explode (edict_t *ent); + +void grenlaser_think4 (edict_t *ent) +{ + vec3_t down; + int speed; + + down[0]=0; //We're going DOWN!!! + down[1]=0; + down[2]=-100; + + VectorNormalize(down); + VectorCopy(down, ent->movedir); + speed=75; + VectorScale(down, speed, ent->velocity); + + ent->nextthink=level.time + .3; + ent->think=grenlaser_think2; +} + +void laser_Explode (edict_t *ent) +{ + vec3_t origin; + + T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, MOD_LASERCUTTER); + + 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); +} + +// Second think function for da grenade turrets +//Initially this was a homing think function from qdevels www.planetquake.com/qdevels +//Imp was here (duh) +//MUST go before grenturret_think1 so that think1 can set ent->think to grenturret_think2 + +//Note 3/22: Putting think2 before 1 isn't necessary anymore, since I +//prototyped it in g_local.h, but what the hell... + +void grenlaser_think2 (edict_t *ent) +{ + edict_t *target = NULL; + edict_t *blip = NULL; +// vec3_t start; +// vec3_t point; +// vec3_t dir; + trace_t tr; + vec3_t end,right,forward; + AngleVectors (ent->s.angles, forward, right, NULL); + VectorScale(ent->movedir, 12, ent->velocity); //Keep speed at 25 + + if (((int) (ent->turrettime * 10)) % 10==0) + ent->movedir[2]*=-1; + end[0]=right[0]*8000; + end[1]=right[1]*8000; + end[2]=right[2]*8000; + ent->s.angles[1]+=5; + if (ent->s.angles[1]>360) + ent->s.angles[1]-=360; + tr = gi.trace (ent->s.origin, NULL, NULL, end, ent, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + //Sparks + if ((tr.ent != ent->owner) && (tr.ent->takedamage)) + T_Damage (tr.ent, ent, ent->owner, forward, tr.endpos, tr.plane.normal, + wf_game.grenade_damage[GRENADE_TYPE_LASERCUTTER], 0, 0, MOD_LASERCUTTER); + else if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + // hit a brush, send clients + // a light flash and sparks temp entity. + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (ent->s.origin); + gi.WritePosition (tr.endpos); + gi.multicast (ent->s.origin, MULTICAST_PHS); + end[0]=right[0]*-8000; + end[1]=right[1]*-8000; + end[2]=right[2]*-8000; + tr = gi.trace (ent->s.origin, NULL, NULL, end, ent, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + //Laser sparks + if ((tr.ent != ent->owner) && (tr.ent->takedamage)) + T_Damage (tr.ent, ent, ent->owner, forward, tr.endpos, tr.plane.normal, + 5, 0, 0, MOD_LASERCUTTER); + else if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { // hit a brush, send clients + // a light flash and sparks temp entity. + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (ent->s.origin); + gi.WritePosition (tr.endpos); + gi.multicast (ent->s.origin, MULTICAST_PHS); + end[0]=forward[0]*8000; + end[1]=forward[1]*8000; + end[2]=forward[2]*8000; + tr = gi.trace (ent->s.origin, NULL, NULL, end, ent, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + //Laser sparks + if ((tr.ent != ent->owner) && (tr.ent->takedamage)) + T_Damage (tr.ent, ent, ent->owner, forward, tr.endpos, tr.plane.normal, 5, 0, 0, MOD_LASERCUTTER); + else if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { // hit a brush, send clients + // a light flash and sparks temp entity. + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (ent->s.origin); + gi.WritePosition (tr.endpos); + gi.multicast (ent->s.origin, MULTICAST_PHS); + end[0]=forward[0]*-8000; + end[1]=forward[1]*-8000; + end[2]=forward[2]*-8000; + tr = gi.trace (ent->s.origin, NULL, NULL, end, ent, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + //Laser sparks + if ((tr.ent != ent->owner) && (tr.ent->takedamage)) + T_Damage (tr.ent, ent, ent->owner, forward, tr.endpos, tr.plane.normal, + 5, 0, 0, MOD_LASERCUTTER); + else if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { // hit a brush, send clients + // a light flash and sparks temp entity. + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (ent->s.origin); + gi.WritePosition (tr.endpos); + gi.multicast (ent->s.origin, MULTICAST_PHS); +//If our turret is out of ammo or has been too long, kill it +if (level.time>=ent->turretdie) + { + ent->think=laser_Explode; //Drops to the ground and explodes + ent->nextthink=level.time+2; //Looks better than just disappearing + ent->movetype=MOVETYPE_BOUNCE; + + //FIXME: This is ugly... is it possible to subtract effects + //instead of totally redefining the effects? + ent->s.effects = EF_GRENADE; //Lights out!!! + ent->s.renderfx = 0; //Goodbye shell :( + } +ent->turrettime+=.1; + ent->nextthink=level.time +0.1; +} + +void grenlaser_think1 (edict_t *ent) +{ + vec3_t up; + vec3_t right; + int speed; + + //Take out gravity + ent->movetype=MOVETYPE_FLYMISSILE; + + up[0]=0; //We're going UP!!! + up[1]=0; + up[2]=5;//Waist height + + right[0]=100; //we're pointing right... ugly hack + right[1]=0; + right[2]=0; + + ent->s.effects |= EF_HYPERBLASTER; //Lots of fun with green lights + ent->s.effects |= EF_COLOR_SHELL; //Green shell... fun! + + // ent->s.renderfx |= RF_SHELL_GREEN; //It's a GREEN shell!!! + + if (ent->wf_team == CTF_TEAM1) //team 1 is red + ent->s.renderfx |= RF_SHELL_RED; + else + ent->s.renderfx |= RF_SHELL_BLUE; + + VectorNormalize(up); + VectorCopy(up, ent->movedir); + speed=20; + VectorScale(up, speed, ent->velocity); + + ent->avelocity[0]=0; + ent->avelocity[1]=360*5; + ent->avelocity[2]=0; + + vectoangles(right, ent->s.angles); + + ent->nextthink=level.time+.5; + ent->think=grenlaser_think4; + ent->turrettime=1; +} + +static void laser_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + laser_Explode (ent); + return; + } + + if (!other->takedamage) + { + if (ent->think==grenlaser_think4) //Move the grenade away from + ent->nextthink=level.time; //the ceiling when it hits it + + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + //ent->enemy = other; + //laser_Explode (ent); +} +void Laser_Grenade_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = laser_Explode; +} + +void fire_laser_grenade (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; + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + { + if ( self->client->pers.inventory[ITEM_INDEX(FindItem("Grenades"))] >= TURRET_GRENADES + && self->client->pers.inventory[ITEM_INDEX(FindItem("Slugs"))] >= TURRET_SLUGS) + { + self->client->pers.inventory[ITEM_INDEX(FindItem("Grenades"))] -= TURRET_GRENADES; + self->client->pers.inventory[ITEM_INDEX(FindItem("Slugs"))] -= TURRET_SLUGS; + } + else + { + safe_cprintf(self, PRINT_HIGH, "You need %d Grenades and %d Slugs for Laser Cutter\n",TURRET_GRENADES,TURRET_SLUGS); + return; + } + } + + ++self->client->pers.active_grenades[GRENADE_TYPE_LASERCUTTER]; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + grenade->grenade_index = GRENADE_TYPE_LASERCUTTER; + 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->clipmask = MASK_PLAYERSOLID; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex (GRLASERCUTTER_MODEL); + grenade->s.skinnum = GRLASERCUTTER_SKIN; + grenade->owner = self; +// grenade->touch = laser_Touch; + grenade->wf_team = self->client->resp.ctf_team; + + // A few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -10, -10, 0); + VectorSet(grenade->maxs, 10, 10, 10); + grenade->mass = 40; + grenade->health = 10; + grenade->die = Laser_Grenade_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + grenade->nextthink = level.time + timer; + grenade->think = grenlaser_think1; + grenade->turrettime=0; + grenade->turretdie=level.time+4.10 + timer; + grenade->turretammo=6; + + grenade->dmg = DAMAGE_LASERCUTTER; + grenade->dmg_radius = damage_radius; + grenade->classname = "lasercutter"; + if (held) + grenade->spawnflags = 3; + else + grenade->spawnflags = 1; + + //set team + grenade->wf_team = self->client->resp.ctf_team; + + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + //if (timer <= 0.0) + //laser_Explode (grenade); + //else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} + + diff --git a/w_lasersight.c b/w_lasersight.c new file mode 100644 index 0000000..6c4edb7 --- /dev/null +++ b/w_lasersight.c @@ -0,0 +1,111 @@ +//This code just handles the laser sight +#include "g_local.h" + +void LaserSightThink (edict_t *self); + +#define lss ent->lasersight + +void lasersight_on (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + +//if (wfdebug) gi.dprintf(" LASERON - "); + + if (!lss) // Create it + { +//if (wfdebug) gi.dprintf(" CREATE\n"); + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(end,100 , 0, 0); + G_ProjectSource (ent->s.origin, end, forward, right, start); + lss = G_Spawn (); + lss->owner = ent; + lss->movetype = MOVETYPE_NOCLIP; + lss->solid = SOLID_NOT; + lss->classname = "lasersight"; + lss->s.modelindex = gi.modelindex ("models/sight/tris.md2"); + lss->s.skinnum = 0; + lss->s.renderfx |= RF_FULLBRIGHT; + lss->think = LaserSightThink; + lss->nextthink = level.time + 0.1; + } + else + { +//if (wfdebug) gi.dprintf(" NOTHING\n"); + } +} + +void lasersight_off (edict_t *ent) +{ +//if (wfdebug) gi.dprintf(" LASEROFF -"); + if (lss) //Remove it + { + G_FreeEdict(lss); + lss = NULL; +//if (wfdebug) gi.dprintf(" CREATE\n"); + } + else + { +//if (wfdebug) gi.dprintf(" NOTHING\n"); + } +} + +void LaserSightThink (edict_t *self) +{ + vec3_t start,end,endp,offset; + vec3_t forward,right,up; + trace_t tr; + + AngleVectors (self->owner->client->v_angle, forward, right, up); + VectorSet(offset,24 , 6, self->owner->viewheight-7); + G_ProjectSource (self->owner->s.origin, offset, forward, right, start); + VectorMA(start,8192,forward,end); + tr = gi.trace (start,NULL,NULL, end,self->owner,CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + if (tr.fraction != 1) + { + VectorMA(tr.endpos,-4,forward,endp); + VectorCopy(endp,tr.endpos); + } +//if (tr.ent->client->ps.pmove.pm_flags & PMF_DUCKED) + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + { + if ((tr.ent->takedamage) && (tr.ent != self->owner)) + { + if (tr.endpos[2] > ((tr.ent->s.origin[2] + 20))) + { + // Headshot Confirmed: Opted to go with switching skins instead of models + self->s.skinnum = 1; + } + + /*Acrid 5/99 , note: BBox on crouched players differs + from standing so +8 won't work, even though it + should be correct ie, look how high you can aim + over a standing players head. I also suggest + 17-18 + for standing players above and g_weapon.c:remove this note */ + else if ( (tr.ent->client && tr.ent->client->ps.pmove.pm_flags & PMF_DUCKED) && (tr.endpos[2] > ((tr.ent->s.origin[2] + 1 ))) ) + { + self->s.skinnum = 1; + } + //else your not aiming at the head anymore //Acrid + else self->s.skinnum = 0; + } + } + else //else this isnt a valid object //Acrid + self->s.skinnum = 0; + + //The following line will make the meatball change angles + //based on the the meatball is touching + //vectoangles(tr.plane.normal,self->s.angles); + + //Instead, these line will face the meatball the same + //direction as the player + self->s.angles[PITCH] = self->owner->s.angles[PITCH]; + self->s.angles[YAW] = self->owner->s.angles[YAW]; + self->s.angles[ROLL] = self->owner->s.angles[ROLL]; + + + VectorCopy(tr.endpos,self->s.origin); + gi.linkentity (self); + self->nextthink = level.time + 0.1; +} diff --git a/w_laserweapons.c b/w_laserweapons.c new file mode 100644 index 0000000..1672278 --- /dev/null +++ b/w_laserweapons.c @@ -0,0 +1,455 @@ +#include "g_local.h" +#include "laser2.h" + +int laser_colour[] = { + 0xf3f3f1f1, //0 blue + 0xf2f2f0f0, //1 red +// 0xf2f2f0f0, //0 red +// 0xf3f3f1f1, //1 blue + 0xf3f3f1f1, //2 blue +// 0xd0d1d2d3, //2 green + 0xdcdddedf, //3 yellow + 0xe0e1e2e3, //4 bitty yellow strobe + 0x80818283, //5 JR brownish purple I think + 0x70717273, //6 JR light blue + 0x90919293, //7 JR type of green + 0xb0b1b2b3, //8 JR another purple + 0x40414243, //9 JR a reddish color + 0xe2e5e3e6, //10 JR another orange + 0xd0f1d3f3, //11 JR mixture of color + 0xf2f3f0f1, //12 JR red outer blue inner + 0xf3f2f1f0, //13 JR blue outer red inner + 0xdad0dcd2, //14 JR yellow outer green inner + 0xd0dad2dc //15 JR green outer yellow inner + }; + +//By setting the color for each, players can tell the difference +#define LASER_DEFENSE_COLOR 4 +#define LASER_TRIPBOMB_COLOR 12 + + +/* +===================== +Laser Defense +===================== +*/ +void laser_cleanup(edict_t *self) +{ + vec3_t origin; + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + T_RadiusDamage(self, self->owner, self->dmg, NULL, self->dmg_radius, MOD_LASER_DEFENSE); + + VectorMA (self->s.origin, -0.02, self->velocity, origin); + gi.WriteByte (svc_temp_entity); + if (self->waterlevel) + { + if (self->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + } + else + { + if (self->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + } + gi.WritePosition (origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + + //reduce # active laser defenses +// TeT++ + + //Remove laser + if (self->creator) + G_FreeEdict (self->creator); +// TeT-- + + //Remove grenade + G_FreeEdict (self); + +} + +void laser_defense_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ +// TeT++ + if (self->creator) + { + self->creator->delay = level.time + 0.1; + } +// TeT-- + + laser_cleanup(self); + +} + +//Laser Defense +// TeT++ +void PlaceLaser (edict_t *ent) +{ + edict_t *laser, + *grenade; + vec3_t forward, + wallp, + start, + end; + trace_t tr; + trace_t endTrace; + + // valid ent ? + if ((!ent->client) || (ent->health<=0)) + return; + + // cells for laser ? + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < CELLS_FOR_LASER) + { + safe_cprintf(ent, PRINT_HIGH, "Not enough cells for laser.\n"); + return; + } + + //Are there too many laser defense systems now? + if (ent->client->pers.active_special[ITEM_SPECIAL_LASER_DEFENSE] >= MAX_SPECIAL_LASER_DEFENSE) + { + safe_cprintf(ent, PRINT_HIGH, "You can only have %d active Laser Defense Systems.\n",MAX_SPECIAL_LASER_DEFENSE ); + return; + } + + // Setup "little look" to close wall + VectorCopy(ent->s.origin,wallp); + + // Cast along view angle + AngleVectors (ent->client->v_angle, forward, NULL, NULL); + + // Setup end point + wallp[0]=ent->s.origin[0]+forward[0]*50; + wallp[1]=ent->s.origin[1]+forward[1]*50; + wallp[2]=ent->s.origin[2]+forward[2]*50; + + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, wallp, ent, MASK_SOLID); + + // Line complete ? (ie. no collision) + if (tr.fraction == 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Too far from wall.\n"); + return; + } + + // Hit sky ? + if (tr.surface) + if (tr.surface->flags & SURF_SKY) + return; + + // Ok, lets stick one on then ... + safe_cprintf (ent, PRINT_HIGH, "Laser attached.\n"); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= CELLS_FOR_LASER; + + ++ent->client->pers.active_special[ITEM_SPECIAL_LASER_DEFENSE]; + + // get enties for both objects + grenade = G_Spawn(); + laser = G_Spawn(); + grenade->special_index = ITEM_SPECIAL_LASER_DEFENSE; + + // setup the Grenade + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + VectorCopy (tr.endpos, grenade->s.origin); + vectoangles(tr.plane.normal, grenade->s.angles); + + grenade->special_index = ITEM_SPECIAL_LASER_DEFENSE; + grenade->wf_team = ent->wf_team; + grenade -> movetype = MOVETYPE_NONE; + grenade -> clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade -> takedamage = DAMAGE_YES; + grenade -> die = laser_defense_die; + grenade -> s.modelindex = gi.modelindex (GRNORMAL_MODEL); + grenade -> owner = ent; + grenade -> creator = laser; + grenade -> monsterinfo.aiflags = AI_NOSTEP; + grenade -> classname = "laser_defense_gr"; + grenade -> nextthink = level.time + LASER_TIME; + grenade -> think = laser_cleanup; + grenade -> health = 10; + grenade -> max_health = 10; + + + // Now lets find the other end of the laser + // by starting at the grenade position + VectorCopy (grenade->s.origin, start); + + // setup laser movedir (projection of laser) + G_SetMovedir (grenade->s.angles, laser->movedir); + VectorMA (start, 2048, laser->movedir, end); + + endTrace = gi.trace (start, NULL, NULL, end, ent, MASK_SOLID); + + // ----------- + // Setup laser + // ----------- + laser -> wf_team = ent->wf_team; + + laser -> movetype = MOVETYPE_NONE; + laser -> solid = SOLID_NOT; + laser -> s.renderfx = RF_BEAM|RF_TRANSLUCENT; + laser -> s.modelindex = 1; // must be non-zero + laser -> s.sound = gi.soundindex ("world/laser.wav"); + laser -> classname = "laser_defense"; + laser -> s.frame = 2; // beam diameter + laser -> owner = NULL; + laser -> s.skinnum = laser_colour[ent->wf_team]; + laser -> dmg = LASER_DAMAGE; + laser -> think = pre_target_laser_def_think; + laser -> delay = level.time + LASER_TIME; + laser -> creator = grenade; + laser -> activator = ent; + + // start off ... + target_laser_off (laser); + VectorCopy (endTrace.endpos, laser->s.old_origin); + + // ... but make automatically come on + laser -> nextthink = level.time + 2; + + // Set orgin of laser to point of contact with wall + VectorCopy(endTrace.endpos,laser->s.origin); + + // convert normal at point of contact to laser angles + vectoangles(tr.plane.normal,laser->s.angles); + + // setup laser movedir (projection of laser) + G_SetMovedir (laser->s.angles, laser->movedir); + + VectorSet (laser->mins, -8, -8, -8); + VectorSet (laser->maxs, 8, 8, 8); + +// link to world + gi.linkentity (laser); + gi.linkentity (grenade); +} + +void pre_target_laser_def_think (edict_t *self) +{ + target_laser_on (self); + self->think = target_laser_def_think; +} +void pre_target_laser_think (edict_t *self) +{ + target_laser_on (self); + self->think = target_laser_think; +} + +void pre_target_laserb_think (edict_t *self); + +/* +=============== +Laser Tripbombs +=============== +*/ +/* +for them to work need some code I will do later +*/ + +void laser_trip_cleanup(edict_t *self) +{ + + //Remove laser + if (self->owner) + G_FreeEdict (self->owner); + + //Remove grenade + G_FreeEdict (self); + +} + + +void laser_tripbomb_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->owner->delay = level.time + 0.1; + + laser_trip_cleanup(self); + +} + +//Laser Tripbomb +void PlaceLaserb (edict_t *ent) +{ + edict_t *self, + *grenade; + + vec3_t forward, + wallp; + + trace_t tr; + + + // valid ent ? + if ((!ent->client) || (ent->health<=0)) + return; + + // cells for laser ? + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < 100) + { + safe_cprintf(ent, PRINT_HIGH, "Not enough cells for laser bomb.\n"); + return; + } + + //Are there too many laser defense systems now? + if (ent->client->pers.active_special[ITEM_SPECIAL_TRIPBOMB] >= MAX_SPECIAL_TRIPBOMB) + { + safe_cprintf(ent, PRINT_HIGH, "You can only have %d active Trip Bombs.\n",MAX_SPECIAL_TRIPBOMB ); + return; + } + + // Setup "little look" to close wall + VectorCopy(ent->s.origin,wallp); + + // Cast along view angle + AngleVectors (ent->client->v_angle, forward, NULL, NULL); + + // Setup end point + wallp[0]=ent->s.origin[0]+forward[0]*50; + wallp[1]=ent->s.origin[1]+forward[1]*50; + wallp[2]=ent->s.origin[2]+forward[2]*50; + + // trace + tr = gi.trace (ent->s.origin, NULL, NULL, wallp, ent, MASK_SOLID); + + // Line complete ? (ie. no collision) + if (tr.fraction == 1.0) + { + safe_cprintf (ent, PRINT_HIGH, "Too far from wall.\n"); + return; + } + + // Hit sky ? + if (tr.surface) + if (tr.surface->flags & SURF_SKY) + return; + + // Ok, lets stick one on then ... + safe_cprintf (ent, PRINT_HIGH, "Laser attached.\n"); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= 100; + + ++ent->client->pers.active_special[ITEM_SPECIAL_TRIPBOMB]; + + // ----------- + // Setup laser + // ----------- + self = G_Spawn(); + self->wf_team = ent->wf_team; + self->special_index = ITEM_SPECIAL_TRIPBOMB; + + + self -> movetype = MOVETYPE_NONE; + self -> solid = SOLID_NOT; + self -> s.renderfx = RF_BEAM|RF_TRANSLUCENT; + self -> s.modelindex = 1; // must be non-zero + self -> s.sound = gi.soundindex ("world/laser.wav"); + self -> classname = "lb"; + self -> s.frame = 2; // beam diameter + self -> owner = ent; +//GREGG +// self -> s.skinnum = laser_colour[((int) (random() * 1000)) % 16]; + self -> s.skinnum = laser_colour[LASER_TRIPBOMB_COLOR]; + self -> dmg = LASER_DAMAGE; + self -> think = pre_target_laserb_think; + self -> delay = level.time + LASER_TIME; + //add a laser to the amount +// ent->LaserBomb++; + // Set orgin of laser to point of contact with wall + VectorCopy(tr.endpos,self->s.origin); + + // convert normal at point of contact to laser angles + vectoangles(tr.plane.normal,self -> s.angles); + + // setup laser movedir (projection of laser) + G_SetMovedir (self->s.angles, self->movedir); + + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + + // link to world + gi.linkentity (self); + + // start off ... + target_laser_off (self); + + // ... but make automatically come on + self -> nextthink = level.time + 2; + grenade = G_Spawn(); + grenade->wf_team = ent->wf_team; + + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + VectorCopy (tr.endpos, grenade->s.origin); + vectoangles(tr.plane.normal,grenade -> s.angles); + grenade -> movetype = MOVETYPE_NONE; + grenade -> clipmask = MASK_SHOT; + //grenade -> solid = SOLID_NOT; + grenade->solid = SOLID_BBOX; + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->takedamage=DAMAGE_YES; + grenade->die = laser_tripbomb_die; + grenade -> s.modelindex = gi.modelindex (GRNORMAL_MODEL); + grenade -> owner = self; + grenade -> nextthink = level.time + LASER_TIME; + grenade -> think = laser_trip_cleanup; + grenade->health= 15; + grenade->max_health =15; + + gi.linkentity (grenade); +} + +void pre_target_laserb_think (edict_t *self) +{ + target_laser_on (self); + + self->think = target_laser_think; +} + +void cmd_LaserDefense(edict_t *ent) +{ +// if (ent->LaserOrbs > MAX_LASERS -1) +// safe_cprintf(ent, PRINT_HIGH, "Max Lasers Already Reached.\n"); +// else + PlaceLaser (ent); +} + +void cmd_TripBomb(edict_t *ent) +{ +// if (ent->LaserBomb > 4 -1) +// safe_cprintf(ent, PRINT_HIGH, "Max Laser Trip Bombs Already Reached.\n"); +// else + PlaceLaserb (ent); +} + + +//Remove all laser defenses for this entity +void cmd_RemoveLaserDefense(edict_t *ent) +{ + edict_t *blip = NULL; + + while ((blip = findradius(blip, ent->s.origin, 1000)) != NULL) + { + if (!strcmp(blip->classname, "laser_defense_gr") && blip->owner == ent) + { + blip->think = laser_cleanup; + blip->nextthink = level.time + .1; + } + } + +} diff --git a/w_lightninggun.c b/w_lightninggun.c new file mode 100644 index 0000000..11c690f --- /dev/null +++ b/w_lightninggun.c @@ -0,0 +1,141 @@ +#include "g_local.h" +/* +================ +lightning Gun +================ +*/ + +void lightning_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); + + if (other->takedamage) + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, self->kick, DAMAGE_BULLET,MOD_LIGHTNING); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (20); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.WriteByte (0xd8dad9db); //Whiteish yellow looking I think + gi.multicast (self->s.origin, MULTICAST_PVS); + } + else + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (20); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.WriteByte (0xd8dad9db); //Whiteish yellow looking I think + gi.multicast (self->s.origin, MULTICAST_PVS); + } + + G_FreeEdict (self); +} + +void fire_lightning (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int kick) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + gi.sound(self, CHAN_VOICE, gi.soundindex("lshoot.wav"), 1, ATTN_NORM, 0); + + bolt = G_Spawn(); + bolt->wf_team = self->wf_team; + 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; //JR 1/4/98 changed so it bounces + bolt->clipmask = MASK_SHOT; + bolt->s.effects |= EF_TELEPORTER|EF_ANIM01|EF_BLASTER; + bolt->solid = SOLID_BBOX; + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + bolt->s.modelindex = gi.modelindex ("models/bolt/tris.md2"); //Need to change this + bolt->s.sound = gi.soundindex ("electric.wav"); + bolt->owner = self; + bolt->think = G_FreeEdict; + bolt->nextthink = level.time + 4; + bolt->touch = lightning_touch; + bolt->dmg = damage; + bolt->kick = kick; + gi.linkentity (bolt); + +// if (self->client) +// check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + +void weapon_lightninggun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage; + int kick; + int speed; + + speed = wf_game.weapon_speed[WEAPON_LIGHTNING]; + damage = wf_game.weapon_damage[WEAPON_LIGHTNING]; + kick = 165+ (((int)(random()*1000)) % 30) ; + + 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_lightning (ent,ent->s.origin, forward, damage, speed, 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_Lightninggun (edict_t *ent) +{ + static int pause_frames[] = {56, 0}; + static int fire_frames[] = {4, 0}; + + Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_lightninggun_fire); +} \ No newline at end of file diff --git a/w_magnotron.c b/w_magnotron.c new file mode 100644 index 0000000..b197eba --- /dev/null +++ b/w_magnotron.c @@ -0,0 +1,258 @@ +#include "g_local.h" + + +/* +================= +Magnotron Grenade +================= +*/ +static void MagnoGrenade_Explode (edict_t *ent) +{ + vec3_t origin; +// int i; + vec3_t offset; + + + if (ent->owner->client) + { + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + } + + VectorSet(offset,0,0,0.5); + VectorAdd(offset,ent->s.origin,offset); + VectorCopy (offset, ent->s.origin); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, MOD_MAGNOTRON); + + VectorMA (ent->s.origin, -.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_PVS); + T_ShockWave(ent, 255, 1024); + // explode and destroy grenade + BecomeNewExplosion (ent); +} + + +static void MagnoGrenade_Timer (edict_t *self) +{ + edict_t *ent; +// vec3_t velo; + vec3_t dir,start,end; + + ent = NULL; + + if (level.time > self->delay) + { + self->think = MagnoGrenade_Explode; +// self->nextthink = level.time + 0.1; + self->nextthink = level.time + 0.2; + return; + } + + + //gi.dprintf("Magno Timer.\n"); + while ((ent = findradius(ent, self->s.origin, 512)) != NULL) + { + if (ent == self) + continue; + + if (!ent->client) + continue; + + if (ent == self->owner) + continue; + + //Don't go through walls + if (!visible(self, ent)) + continue; + + if (ent->wf_team == self->wf_team) + continue; + + if (!ent->takedamage) + continue; + + if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) + continue; + +/* + VectorSubtract(ent->s.origin,self->s.origin, velo); + velo[0] *= 0.45; + velo[1] *= 0.45; + velo[2] *= 0.45; + VectorAdd(ent->velocity,velo,ent->velocity); +*/ + VectorCopy(ent->s.origin, start); + VectorCopy(self->s.origin, end); + VectorSubtract(end, start, dir); + VectorNormalize(dir); + VectorScale(dir,500, ent->velocity); + VectorCopy(dir, ent->movedir); + } +// if(self->volumethink =MagnoGrenade_Explode; +// self->nextthink = level.time + 0.1; + self->nextthink = level.time + 0.2; +} + +static void MagnoGrenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + //BulletGrenade_Explode (ent); +} + +void Magnogrenade_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = MagnoGrenade_Explode; +} + +void fire_magnogrenade (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; + + ++self->client->pers.active_grenades[GRENADE_TYPE_MAGNOTRON]; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + + if ((int)wfflags->value & WF_ANARCHY) + grenade->wf_team = 0; //fire at anybody + else + grenade->wf_team = self->wf_team; + + 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->grenade_index = GRENADE_TYPE_MAGNOTRON; + + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + // +// grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2"); + grenade->s.modelindex = gi.modelindex (GRMAGNOTRON_MODEL); + grenade->s.skinnum = GRMAGNOTRON_SKIN; + + grenade->s.effects |= EF_GRENADE; + +// grenade->s.modelindex = gi.modelindex ("models/weapons/wfGrenade/tris.md2"); +// grenade->s.effects |= EF_GRENADE | EF_ANIM_ALLFAST; + + grenade->owner = self; + grenade->touch = MagnoGrenade_Touch; //Stuff for cluster grenades when they explode +// grenade->nextthink = level.time + timer; +// grenade->volume = level.time + timer + 0.5; + grenade->nextthink = level.time + 2.0; //start sucking in 1 second + grenade->delay = level.time + 3.5; //explode in 3.5 seconds + +if (wfdebug) grenade->delay = level.time + 20; //for testing magno die + + grenade->think = MagnoGrenade_Timer; //stuff for cluster grenades exploding + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "magnotron"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 30; + grenade->die = Magnogrenade_Die; + grenade->takedamage = DAMAGE_YES; +// grenade->takedamage = DAMAGE_NO; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + + gi.linkentity (grenade); +} + +void weapon_magnogrenadelauncher_fire (edict_t *ent) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = wf_game.grenade_damage[GRENADE_TYPE_MAGNOTRON]; + 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_magnogrenade (ent, start, forward, damage, 400, 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] -= ent->client->pers.weapon->quantity; +} + +void Weapon_MagnoGrenadeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_magnogrenadelauncher_fire); +} \ No newline at end of file diff --git a/w_mbpc.c b/w_mbpc.c new file mode 100644 index 0000000..0e8a688 --- /dev/null +++ b/w_mbpc.c @@ -0,0 +1,364 @@ +#include "g_local.h" +/* +=========================== +Multi Barreled Pulse Cannon +=========================== +*/ +void fire_pulse (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + //int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + /*Doesn't need to splash + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + }*/ + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (start, MULTICAST_PVS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PVS); + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod); + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + /*gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (rndnum(2,4)); + gi.WritePosition (self->s.origin); + + gi.WriteDir (tr.plane.normal); + gi.WriteByte (SPLASH_SLIME); + gi.multicast (self->s.origin, MULTICAST_PVS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (rndnum(5,10)); + gi.WritePosition (tr.endpos); + + gi.WriteDir (tr.plane.normal); + gi.WriteByte (SPLASH_SLIME); + gi.multicast (tr.endpos, MULTICAST_PVS);*/ + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + /*gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS);*/ + } +} + +void plasma_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 2, DAMAGE_ENERGY,MOD_MBPC); + T_RadiusDamage(self, self->owner, 10, NULL, 200,MOD_MBPC); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (rndnum(5,28)); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.WriteByte (SPLASH_SLIME); + gi.multicast (self->s.origin, MULTICAST_PVS); + G_FreeEdict (self); + +} +static void PlasmaThink(edict_t *ent) +{ +vec3_t newpos, pos; + +VectorSubtract(ent->s.origin, ent->pos2, pos); /*translate current position*/ +RotatePointAroundVector(newpos, ent->pos1, pos, 20); /*So we can rotate around our original line +of sight*/ +VectorAdd(ent->pos2, newpos, ent->s.origin); /*then translate the current position back*/ + +RotatePointAroundVector(ent->velocity, ent->pos1, ent->velocity, 20); /*The velocity vector must be +rotated also*/ +ent->nextthink = level.time + .1; //Need to think again in .1 seconds +} + +void fire_plasmaball(edict_t *self, vec3_t start, vec3_t dir, vec3_t los, int damage, int speed, +float damage_radius, int radius_damage) +{ +edict_t *rocket; + +rocket = G_Spawn(); +rocket->wf_team = self->wf_team; + +VectorCopy (los, rocket->pos1); /*Added: Save the line of sight of the center rocket. This is +the axis around which the other two rockets rotate*/ +VectorCopy (start, rocket->pos2); /*Added: Save the start position of the rocket (Not sure if +this is necessary. This info might already be somewhere else)?*/ + +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_BLASTER; +VectorClear (rocket->mins); +VectorClear (rocket->maxs); +rocket->s.modelindex = gi.modelindex ("models/ball/tris.md2"); +rocket->owner = self; +rocket->touch = plasma_touch; +rocket->nextthink = level.time + .1; +rocket->think = PlasmaThink; +rocket->dmg =4; +rocket->radius_dmg = 128; +rocket->dmg_radius =128; +rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + +if (self->client) +check_dodge (self, rocket->s.origin, dir, speed); + +gi.linkentity (rocket); +} +void ChainMBPC_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 = 8; + + damage = wf_game.weapon_damage[WEAPON_PULSE]; + + 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; + start[2] += ent->viewheight-8; + VectorSet(offset, 0, r, u + ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + //fire_rail (ent, start, forward, damage, kick); + //fire_plasmaball(ent, start, forward, forward, damage, 600, 100, 100); + ent->ShotNumber++; + if (ent->client->ping >500) + { + if (ent->ShotNumber>3) + { + fire_pulse (ent, start, forward, damage*4, kick*4, 0, 250, 250, MOD_MBPC); + ent->ShotNumber =0; + } + } + else + { + if (ent->ShotNumber>1) + { + fire_pulse (ent, start, forward, damage*2, kick*2, 0, 250, 250, MOD_MBPC); + ent->ShotNumber =0; + } + } + } + + // 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_ChainMBPC (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, pause_frames, fire_frames, ChainMBPC_Fire); +} \ No newline at end of file diff --git a/w_megachaingun.c b/w_megachaingun.c new file mode 100644 index 0000000..da7d96a --- /dev/null +++ b/w_megachaingun.c @@ -0,0 +1,260 @@ +#include "g_local.h" +/* +========================== +Mega Chaingun +========================== +*/ +/* + +Gregg put this code in client think At the very end +if(ent->cantmove) + VectorCopy(ent->LockedPosition,ent->s.origin); + + Alright also make this use shells +*/ +void fire_ChainBlast(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float ran; + float u; + float adjustedkick; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + vec3_t distance; + int dist; + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + //Push or kick is dependant on distance + distance[0] = tr.ent->s.origin[0] - self->s.origin[0]; + distance[1] = tr.ent->s.origin[1] - self->s.origin[1]; + distance[2] = tr.ent->s.origin[2] - self->s.origin[2]; + dist=VectorLength(distance); + + if (dist < 200) + adjustedkick = kick; + else if (dist < 350) + adjustedkick = 10; + else + adjustedkick = 5; + + + if (tr.ent->wf_team == self->wf_team) + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, damage, DAMAGE_BULLET, MOD_MEGACHAINGUN); + else + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, (int)adjustedkick, DAMAGE_BULLET, MOD_MEGACHAINGUN); + } +// else +// { +// if (strncmp (tr.surface->name, "sky", 3) != 0) +// { +// if (self->client) +// PlayerNoise(self, tr.endpos, PNOISE_IMPACT); +// } +// } + } + } + + ran = random(); + if(ran<0.1)//Well this is for shots that somehow misfire + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (start, MULTICAST_PVS); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_GUNSHOT); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + } + +} +void MegaChaingun_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 = 20; + + if (!ent->groundentity && !ent->cantmove) + { + ent->client->ps.gunframe = 32; + + safe_cprintf (ent, PRINT_HIGH, "Must be on the ground to fire. \n"); + ent->cantmove = 0; + ent->client->weapon_sound = 0; + return; + } + + damage = wf_game.weapon_damage[WEAPON_MEGACHAINGUN]; + + if (ent->client->ps.gunframe == 5) + { + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0); + VectorCopy(ent->s.origin,ent->LockedPosition); + ent->cantmove=1; + } + + if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe = 32; + ent->cantmove = 0; + 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; + ent->cantmove = 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 = 1; + else + shots = 1; + } + else + shots = 2; + + 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); + //WF JR LAG fixing in case lag gets high + //Hopefully this will help during a fierce battle and the + //ping times rocket up + if (ent->client->ping >550) + { + fire_ChainBlast(ent, start, forward, damage*2, kick, 450, 450); + fire_ChainBlast(ent, start, forward, damage*2, kick, 450, 450); + } + else + { + fire_ChainBlast(ent, start, forward, damage, kick, 500, 500); + fire_ChainBlast(ent, start, forward, damage, kick, 500, 500); + fire_ChainBlast(ent, start, forward, damage, kick, 500, 500); + fire_ChainBlast(ent, start, forward, damage, kick, 500, 500); + } + //end lag + } +//WF + //Send a tracer bullet + //if ((shots == 3) && (random() < .16)) + //{ + // fire_blaster (ent, start, forward, 0, 1000, EF_HYPERBLASTER, true); + //} +//WF + // 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 ) ) + { + // TeT we do fire four bullets per shot + //ent->client->pers.inventory[ent->client->ammo_index] -= (shots * 3); + ent->client->pers.inventory[ent->client->ammo_index] -= (int)(shots * 1.5); + } +} + + +void Weapon_MegaChaingun (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, pause_frames, fire_frames, MegaChaingun_Fire); +} \ No newline at end of file diff --git a/w_nag.c b/w_nag.c new file mode 100644 index 0000000..72b39c0 --- /dev/null +++ b/w_nag.c @@ -0,0 +1,142 @@ +#include "g_local.h" +/* +========================== +NAG(Nervous Accelator Gun) +========================== +*/ +void nag_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + float rnum; + rnum = random(); + 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) + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg+1, 2, DAMAGE_ENERGY,MOD_NAG); + if(rnum < 0.3333) + { + ThrowUpNow(other); + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 12, 4, DAMAGE_ENERGY,MOD_NAG); + } + } + 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_nag (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->wf_team = self->wf_team; + 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; //JR 1/4/98 changed so it bounces + 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 = nag_touch; + bolt->nextthink = level.time + 3; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "nag"; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + +void Nag_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_nag (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_NAG], effect); + + // 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_Nag_Fire (edict_t *ent) +{ + int damage; + //JR 1/4/98 + + + damage = wf_game.weapon_damage[WEAPON_NAG]; + Nag_Fire (ent, vec3_origin, damage, false, EF_BLASTER); + + ent->client->ps.gunframe++; + + ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity; + +} + +void Weapon_Nag (edict_t *ent) +{ + static int pause_frames[] = {19, 32, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Nag_Fire); +} diff --git a/w_nailgun.c b/w_nailgun.c new file mode 100644 index 0000000..17aab12 --- /dev/null +++ b/w_nailgun.c @@ -0,0 +1,144 @@ +#include "g_local.h" +/* +================ +Nail Gun +================ +*/ +void nail_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); + + if (other->takedamage) + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 2, DAMAGE_ENERGY,MOD_NAIL); + } + 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_nail (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->wf_team = self->wf_team; + + 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; + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + bolt->s.modelindex = gi.modelindex ("models/spike/tris.md2"); + bolt->owner = self; + bolt->touch = nail_touch; + bolt->nextthink = level.time + 8; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "nail"; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} +void weapon_nailgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage = wf_game.weapon_damage[WEAPON_NAILGUN]; + int kick = 8; + int speed = wf_game.weapon_speed[WEAPON_NAILGUN]; + if (!ent->client->pers.inventory[ent->client->ammo_index]) + ent->client->ps.gunframe = 12; + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 0, 0, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + ent->ShotNumber++; + //fire_nail (ent, start, forward, damage, speed); + if (ent->client->ping >500) + { + if (ent->ShotNumber>1) + { + fire_bullet (ent, start, forward, damage*2, 6*2, 25, 25,MOD_NAIL); + ent->ShotNumber =0; + } + } + else + { + fire_bullet (ent, start, forward, damage, 6, 25, 25,MOD_NAIL); + ent->ShotNumber =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); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (ent->client->ps.gunframe == 5 && (ent->client->buttons & BUTTON_ATTACK)) + ent->client->ps.gunframe = 4; + else + ent->client->ps.gunframe++; + //VWEAP STUFF + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - 1 + (ent->client->ps.gunframe % 3); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - 1 + (ent->client->ps.gunframe % 3); + ent->client->anim_end = FRAME_attack8; + } + + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity; +} + +void Weapon_NailGun (edict_t *ent) +{ + static int pause_frames[] = {15, 0}; + static int fire_frames[] = {4, 5, 6,7,8,9,10,11,0}; + + Weapon_Generic (ent, 3, 11, 14, 18, pause_frames, fire_frames, weapon_nailgun_fire); +} \ No newline at end of file diff --git a/w_napalm.c b/w_napalm.c new file mode 100644 index 0000000..74ed108 --- /dev/null +++ b/w_napalm.c @@ -0,0 +1,186 @@ +#include "g_local.h" +/* +================ +Napalm Grenades +================ +*/ +static void Napalm_Explode (edict_t *ent) +{ + vec3_t origin; + //Sean added these 4 vectors + vec3_t grenade1; + vec3_t grenade2; + vec3_t grenade3; + vec3_t grenade4; + vec3_t grenade5; + vec3_t grenade6; + vec3_t grenade7; + vec3_t grenade8; + 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? + T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius,MOD_WF_FLAME); + + 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_PVS); + + // SumFuka did this bit : give grenades up/outwards velocities + VectorSet(grenade1,20,20,40); + VectorSet(grenade2,20,-20,40); + VectorSet(grenade3,-20,20,40); + VectorSet(grenade4,-20,-20,40); + VectorSet(grenade5,10,10,40); + VectorSet(grenade6,10,-10,40); + VectorSet(grenade7,-10,10,40); + VectorSet(grenade8,-10,-10,40); + // Sean : explode the four grenades outwards +/*GREGG + fire_napalm(ent->owner, origin, grenade1, 120, 10, 8.0, 120); + fire_napalm(ent->owner, origin, grenade2, 120, 10, 8.0, 120); + fire_napalm(ent->owner, origin, grenade3, 120, 10, 8.0, 120); + fire_napalm(ent->owner, origin, grenade4, 120, 10, 8.0, 120); + fire_napalm(ent->owner, origin, grenade5, 120, 10, 8.0, 120); + fire_napalm(ent->owner, origin, grenade6, 120, 10, 8.0, 120); + fire_napalm(ent->owner, origin, grenade7, 120, 10, 8.0, 120); + fire_napalm(ent->owner, origin, grenade8, 120, 10, 8.0, 120); +*/ //Gregg + fire_flame(ent->owner, origin, grenade1, 120, 10, 8.0, 120, false); +// fire_flame(ent->owner, origin, grenade2, 120, 10, 8.0, 120, false); + fire_flame(ent->owner, origin, grenade3, 120, 10, 8.0, 120, false); + fire_flame(ent->owner, origin, grenade4, 120, 10, 8.0, 120, false); +// fire_flame(ent->owner, origin, grenade5, 120, 10, 8.0, 120, false); + fire_flame(ent->owner, origin, grenade6, 120, 10, 8.0, 120, false); + fire_flame(ent->owner, origin, grenade7, 120, 10, 8.0, 120, false); +// fire_flame(ent->owner, origin, grenade8, 120, 10, 8.0, 120, false); + + + G_FreeEdict (ent); +} +static void NapalmGrenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + Napalm_Explode (ent); +} +void fire_napalmgrenade (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(); + grenade->wf_team = self->wf_team; + 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->s.skinnum = 6; + grenade->owner = self; + grenade->touch = NapalmGrenade_Touch; //Stuff for cluster grenades when they explode + grenade->nextthink = level.time + timer; + grenade->think = Napalm_Explode; //stuff for cluster grenades exploding + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = GenericGrenade_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + gi.linkentity (grenade); +} +void weapon_napalmgrenadelauncher_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_napalmgrenade (ent, start, forward, damage, 400, 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] -= ent->client->pers.weapon->quantity; +} + +void Weapon_NapalmGrenadeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_napalmgrenadelauncher_fire); +} \ No newline at end of file diff --git a/w_napalmmissiles.c b/w_napalmmissiles.c new file mode 100644 index 0000000..fdc5b06 --- /dev/null +++ b/w_napalmmissiles.c @@ -0,0 +1,208 @@ +#include "g_local.h" +/* +====================== +Napalm Missiles +====================== +*/ + +// Napalm rockets will home in on the laser target if it exists +void rocketnapalm_think (edict_t *ent) +{ + edict_t *target = NULL; + vec3_t targetdir; + vec_t speed; + + if ((ent->owner) && (ent->owner->client->pers.homing_state == 0)) + { + ent->nextthink = level.time + .3; + return; + } + + if ((ent->owner) && (ent->owner->lasersight)) + { + target = ent->owner->lasersight; + VectorSubtract(target->s.origin, ent->s.origin, targetdir); + targetdir[2] += 16; + VectorNormalize(targetdir); + VectorScale(targetdir, 0.135, targetdir); + VectorAdd(targetdir, ent->movedir, targetdir); + VectorNormalize(targetdir); + VectorCopy(targetdir, ent->movedir); + vectoangles(targetdir, ent->s.angles); + speed = VectorLength(ent->velocity); + VectorScale(targetdir, speed, ent->velocity); + } + + ent->nextthink = level.time + .1; +} + +void rocketnapalm_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ +// int i; +// vec3_t org; +// float spd; + + vec3_t origin; + int n; + edict_t *blip = NULL; + + 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_NAPALMROCKET); + } + else + { + // don't throw any debris in net games + if (!deathmatch->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_NAPALMROCKET); + + 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_PVS); + + //See if there is anything to catch on fire + while ((blip = findradius(blip, ent->s.origin, 150)) != NULL) + { + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + continue; + if (blip == ent->owner) + continue; + //dont attack same team + if (blip->wf_team == ent->wf_team) + continue; + if (!blip->takedamage) + continue; + if (blip->health <= 0) + continue; + if (!visible(ent, blip)) + continue; + + burn_person(blip, ent->owner, 6, MOD_NAPALMROCKET); + break; + } + + T_ShockWave(ent, 255, 1024); + BecomeNewExplosion (ent); +} + +void fire_napalmrocket (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(); + rocket->wf_team = self->wf_team; + 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; + rocket->s.effects |= EF_BFG | EF_HYPERBLASTER; + VectorClear (rocket->mins); + VectorClear (rocket->maxs); + rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2"); + rocket->owner = self; + rocket->touch = rocketnapalm_touch; +// rocket->nextthink = level.time + 8000/speed; +// rocket->think = G_FreeEdict; + rocket->nextthink = level.time + .1; + rocket->think = rocketnapalm_think; + + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->classname = "rocket"; + // CCH: a few more attributes to let the rocket 'die' + VectorSet(rocket->mins, -10, -3, 0); + VectorSet(rocket->maxs, 10, 3, 6); + rocket->mass = 10;+ rocket->health = 10; + rocket->die = Rocket_Die; + rocket->takedamage = DAMAGE_NO; + rocket->monsterinfo.aiflags = AI_NOSTEP; + if (self->client) + check_dodge (self, rocket->s.origin, dir, speed); + + gi.linkentity (rocket); +} + +void Weapon_RocketNapalmLauncher_Fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + + + int damage; + float damage_radius; + int radius_damage; + + damage = wf_game.weapon_damage[WEAPON_NAPALMMISSLE]; + radius_damage = 20; + damage_radius = 20; + 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; + //First rocket + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_napalmrocket (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_NAPALMMISSLE], 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] -= ent->client->pers.weapon->quantity; +// ent->client->pers.inventory[ent->client->ammo_index] -= 2; //it costs 2 rockets +} +void Weapon_RocketNapalmLauncher (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, pause_frames, fire_frames, Weapon_RocketNapalmLauncher_Fire); +} \ No newline at end of file diff --git a/w_needler.c b/w_needler.c new file mode 100644 index 0000000..e28484f --- /dev/null +++ b/w_needler.c @@ -0,0 +1,243 @@ +#include "g_local.h" + +/* +============================== +Needler +============================== +*/ + +int needles=1; +void fire_needle(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + //int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + //Doesn't need to splash does it really + /*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 (7); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + }*/ + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, MOD_NEEDLER); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + gi.WriteByte (svc_temp_entity); + if (needles) + { + gi.WriteByte (TE_BUBBLETRAIL2); + needles=0; + } + else + { + gi.WriteByte (TE_BUBBLETRAIL); + needles=1; + } + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (start, MULTICAST_PVS); + // if went through water, determine where the end and make a bubble trail + //JR This will just slow it down not needed + /*if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + }*/ + gi.sound(self, CHAN_VOICE, gi.soundindex("needle.wav"), 1, ATTN_NORM, 0); + +} + +void Weapon_Needler_Fire (edict_t *ent) +{ + vec3_t forward, right,start; + vec3_t offset; + int damage,kick; + int xspread; + int yspread; + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe++; + return; + } + if (!(ent->client->pers.inventory[ent->client->ammo_index])) + { + ent->client->ps.gunframe++; + return; + } + + damage = wf_game.weapon_damage[WEAPON_NEEDLER]; + xspread = 125; + yspread = 125; + kick = 2 + (((int)(random()*1000)) % 8); + + if (is_quad) + { + damage *= 4; + xspread *=2.5; + yspread *=2.5; + kick *=4; + } + + 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); + //VWEAP STUFF + + 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; + } + //VWEAP STUFF + + + ent->ShotNumber++; + if (ent->client->ping >500) + { + if (ent->ShotNumber>1) + { + ent->ShotNumber=0; + fire_needle(ent, start, forward, damage*2, kick*2, TE_BLASTER, xspread, yspread); + } + } + else + { + ent->ShotNumber=0; + fire_needle(ent, start, forward, damage, kick, TE_BLASTER, xspread, yspread); + } + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity; +} + +void Weapon_Needler (edict_t *ent) +{ + static int pause_frames[] = {19, 32, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Needler_Fire); +} \ No newline at end of file diff --git a/w_other.c b/w_other.c new file mode 100644 index 0000000..6db4215 --- /dev/null +++ b/w_other.c @@ -0,0 +1,152 @@ +#include "g_local.h" + +//Other weapon functions + +void fire_timednuke (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(); + grenade->wf_team = self->wf_team; + 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 = TimedNuke_Touch; //Stuff for cluster grenades when they explode + grenade->nextthink = level.time + 4; + grenade->think = Grenade_Explode; //stuff for cluster grenades exploding + grenade->dmg = damage +500; + grenade->dmg_radius = damage_radius + 500; + grenade->classname = "grenade"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = GenericGrenade_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + gi.linkentity (grenade); +} + + +void TimedNuke_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; + } + + // ignore if on same team + if (other->wf_team == ent->wf_team) + return; + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + return; +} + + +void weapon_timednukelauncher_fire (edict_t *ent) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 1200; + float radius; + + radius = damage+400; + 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_timednuke (ent, start, forward, damage, 400, 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] -= ent->client->pers.weapon->quantity; +} + +void Weapon_TimedNukeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_timednukelauncher_fire); +} + +void gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); + +void ThrowVomit (edict_t *ent, vec3_t mouth_pos, vec3_t forward, vec3_t right, vec3_t player_vel) +{ + edict_t *gib; + gib = G_Spawn(); + gi.setmodel (gib, "models/objects/gibs/sm_meat/tris.md2"); + gib->solid = SOLID_NOT; gib->s.effects |= EF_GIB; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + gib->movetype = MOVETYPE_TOSS; + gib->touch = gib_touch; + // start the gib from out mouth, moving at a forwards velocity + VectorCopy ( mouth_pos, gib->s.origin); + VectorScale (forward, 120 + crandom()*40, gib->velocity); + VectorAdd (player_vel, gib->velocity, gib->velocity); + // add a random left-right component to the vomit velocity + VectorScale (right, crandom()*20, right); + VectorAdd (right, gib->velocity, gib->velocity); + gib->avelocity[0] = random()*500; + gib->avelocity[1] = random()*500; + gib->avelocity[2] = random()*500; + gib->think = G_FreeEdict; + gib->nextthink = level.time + 10 + random()*5; + gi.linkentity (gib); +} diff --git a/w_pelletmissile.c b/w_pelletmissile.c new file mode 100644 index 0000000..2b9027c --- /dev/null +++ b/w_pelletmissile.c @@ -0,0 +1,219 @@ +#include "g_local.h" + +/* +===================== +Pellet Missile +===================== +*/ +void rocket_shot_think (edict_t *ent) +{ +vec3_t origin; + //Sean added these 4 vectors + vec3_t grenade1; + vec3_t grenade2; + vec3_t grenade3; + vec3_t grenade4; + vec3_t grenade5; + vec3_t grenade6; + vec3_t grenade7; + vec3_t grenade8; + int i; + vec3_t offset; + + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + VectorSet(offset,0,0,32); + VectorAdd(offset,ent->s.origin,offset); + VectorCopy (offset, ent->s.origin); + VectorSet(grenade1,20,20,0); + VectorSet(grenade2,20,-20,0); + VectorSet(grenade3,-20,20,0); + VectorSet(grenade4,-20,-20,0); + VectorSet(grenade5,0,20,0); + VectorSet(grenade6,0,-20,0); + VectorSet(grenade7,-20,0,0); + VectorSet(grenade8,20,0,0); + + // Increased damage and decreased # bullets to reduce lag + for (i = 0; i < 4; i++) +// for (i = 0; i < 15; i++) + { + fire_bullet (ent, offset, grenade1, 12, 2, 15000, 15000,MOD_PELLET); + fire_bullet (ent, offset, grenade2, 12, 2, 15000, 15000,MOD_PELLET); + fire_bullet (ent, offset, grenade3, 12, 2, 15000, 15000,MOD_PELLET); + fire_bullet (ent, offset, grenade4, 12, 2, 15000, 15000,MOD_PELLET); + fire_bullet (ent, offset, grenade5, 12, 2, 15000, 15000,MOD_PELLET); + fire_bullet (ent, offset, grenade6, 12, 2, 15000, 15000,MOD_PELLET); + fire_bullet (ent, offset, grenade7, 12, 2, 15000, 15000,MOD_PELLET); + fire_bullet (ent, offset, grenade8, 12, 2, 15000, 15000,MOD_PELLET); + } + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, MOD_PELLET); + + VectorMA (ent->s.origin, -.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_PVS); + // SumFuka did this bit : give grenades up/outwards velocities + + G_FreeEdict (ent); +} + +void rocket_pellet_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + int n; + void rocket_shot_think (ent); + + 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_PELLET); + } + else + { + // don't throw any debris in net games + if (!deathmatch->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_PELLET); + + 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_PVS); + + G_FreeEdict (ent); +} + +void fire_pellet_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(); + rocket->wf_team = self->wf_team; + 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->creator = self; + rocket->touch = rocket_pellet_touch; + rocket->nextthink = level.time + .7; + rocket->think = rocket_shot_think; + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->classname = "rocket"; + // CCH: a few more attributes to let the rocket 'die' + VectorSet(rocket->mins, -10, -3, 0); + VectorSet(rocket->maxs, 10, 3, 6); + rocket->mass = 10;+ rocket->health = 10; + rocket->die = Rocket_Die; +// rocket->takedamage = DAMAGE_YES; + rocket->takedamage = DAMAGE_NO; //to fix self damage bug? + rocket->monsterinfo.aiflags = AI_NOSTEP; + if (self->client) + check_dodge (self, rocket->s.origin, dir, speed); + + gi.linkentity (rocket); +} + +void Weapon_PelletRocketLauncher_Fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + + + int damage; + float damage_radius; + int radius_damage; + + damage = wf_game.weapon_damage[WEAPON_PELLET]; + 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; + //First rocket + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_pellet_rocket (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_PELLET], 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] -= ent->client->pers.weapon->quantity; +} + +void Weapon_PelletRocketLauncher (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, pause_frames, fire_frames, Weapon_PelletRocketLauncher_Fire); +} \ No newline at end of file diff --git a/w_pistol.c b/w_pistol.c new file mode 100644 index 0000000..b466e71 --- /dev/null +++ b/w_pistol.c @@ -0,0 +1,67 @@ +#include "g_local.h" + +/* +====================================================================== + +PISTOL Gurza +by Fireball + +====================================================================== +*/ + + +void Weapon_Pistol_Fire (edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = wf_game.weapon_damage[WEAPON_PISTOL]; + int kick = 2; + vec3_t offset; + + 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; + + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/pistol.wav"), 1, ATTN_NORM, 0); + + // 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); + +// if (ent->client->machinegun_shots == 1) + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD / 2, DEFAULT_BULLET_VSPREAD / 2, MOD_PISTOL); + +// gi.WriteByte (svc_muzzleflash); +// gi.WriteShort (ent-g_edicts); +// gi.WriteByte (0); +// gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + ent->client->ps.gunframe++; + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 2; +} + +void Weapon_Pistol (edict_t *ent) +{ + static int pause_frames[] = {12, 0}; + static int fire_frames[] = {4, 0}; + + Weapon_Generic (ent, 3, 6, 15, 19, pause_frames, fire_frames, Weapon_Pistol_Fire); +} diff --git a/w_plague.c b/w_plague.c new file mode 100644 index 0000000..b44181d --- /dev/null +++ b/w_plague.c @@ -0,0 +1,197 @@ +#include "g_local.h" +/* +=============== +Plague Grenades +=============== +*/ + +void DiseaseGrenade_Explode (edict_t *ent) +{ + vec3_t origin; + edict_t *target; +// int rnd; + + if (ent->owner->client) + { + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); +// --ent->owner->client->pers.active_grenades[GRENADE_TYPE_PLAGUE]; + + } + //FIXME: if we are onground then raise our Z just a bit since we are a point? +// T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius,0); + + 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_PVS); + target= NULL; + + while ((target = findradius(target, ent->s.origin, 95)) != NULL) + { + //Don't go through walls + if (!visible(ent, target)) + continue; + + //Give them only a 1/3 chance of being infected + //rnd = rndnum(1,3); + //if (rnd == 1) + infect_person(target, ent->owner); + } + + //Just infect, don't damage +// T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius,0); + +// make some debris +// make_debris (ent); + // shake view + T_ShockWave(ent, 255, 1024); + // let blast move items +// T_ShockItems(ent); + // explode and destroy grenade + //Also spray out stuff for looks + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_TRACKER_EXPLOSION); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/rocklx1a.wav"), 1, ATTN_NORM, 0); + + // Blow up the grenade + //BecomeExplosion1(ent); + G_FreeEdict (ent); + return; + +} +void DiseaseGrenade_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = DiseaseGrenade_Explode; +} + + +void DiseaseGrenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { +// if (ent->owner->client) +// --ent->owner->client->pers.active_grenades[GRENADE_TYPE_PLAGUE]; + G_FreeEdict (ent); + return; + } + +// if (other->takedamage) +// { +// ent->enemy = other; + DiseaseGrenade_Explode(ent); +// } +} + + + +void fire_diseasegrenade (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; + +// ++self->client->pers.active_grenades[GRENADE_TYPE_PLAGUE]; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + grenade->wf_team = self->wf_team; + 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; + grenade->s.effects |= EF_DOUBLE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); +// grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2"); + grenade->s.modelindex = gi.modelindex (GRPLAGUE_MODEL); + grenade->s.skinnum = GRPLAGUE_SKIN; + grenade->owner = self; + grenade->touch = DiseaseGrenade_Touch; + grenade->nextthink = level.time + 4; + grenade->think = DiseaseGrenade_Explode; + grenade->dmg = (damage/3); + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = DiseaseGrenade_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + grenade->wf_team = self->wf_team; + + gi.linkentity (grenade); +} + +void weapon_diseaselauncher_fire (edict_t *ent) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 20; + float radius; + + radius = damage+5; + 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_diseasegrenade (ent, start, forward, damage, 300, 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] -= ent->client->pers.weapon->quantity; +} + +void Weapon_DiseaseGrenadeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_diseaselauncher_fire); +} \ No newline at end of file diff --git a/w_plaguetime.c b/w_plaguetime.c new file mode 100644 index 0000000..6e0462f --- /dev/null +++ b/w_plaguetime.c @@ -0,0 +1,210 @@ +#include "g_local.h" +/* +=============== +Plague Grenades +=============== +*/ + +void timeDiseaseGrenade_Explode (edict_t *ent) +{ + vec3_t origin; + edict_t *target; +// int rnd; + + if (ent->owner->client) + { + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); +// --ent->owner->client->pers.active_grenades[GRENADE_TYPE_PLAGUE]; + + } + //FIXME: if we are onground then raise our Z just a bit since we are a point? +// T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius,0); + + 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_PVS); + target= NULL; + + while ((target = findradius(target, ent->s.origin, 95)) != NULL) + { + //Don't go through walls + if (!visible(ent, target)) + continue; + + //Give them only a 1/3 chance of being infected + //rnd = rndnum(1,3); + //if (rnd == 1) + infect_person(target, ent->owner); + } + + //Just infect, don't damage +// T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius,0); + +// make some debris +// make_debris (ent); + // shake view + T_ShockWave(ent, 255, 1024); + // let blast move items +// T_ShockItems(ent); + // explode and destroy grenade + BecomeNewExplosion (ent); +} + +void timeDieseaseGrenade_think(edict_t *ent) +{ +// vec3_t origin; + edict_t *target; + int rnd; + vec3_t forward, right, up; + ent->s.angles[0]+=10*random()-5; + ent->s.angles[1]+=10*random()-5; + ent->s.angles[2]+=10*random()-5; + AngleVectors (ent->s.angles, forward, right, up); + ent->movetype = MOVETYPE_FLYRICOCHET; + VectorSet (ent->avelocity, 150, 150, 150); + //VectorMA (ent->velocity, 200 + crandom() * 10.0, up, ent->velocity); + //VectorMA (ent->velocity, crandom() * 10.0, right, ent->velocity); + + target=NULL; + while ((target = findradius(target, ent->s.origin, 575)) != NULL) + { + //Don't go through walls + if (!visible(ent, target)) + continue; + + //Give them only a 1/3 chance of being infected + rnd = ((int)(random()*1000)%3); + if (!rnd) + infect_person(target, ent->owner); + } + //Spray out some sparks at random + if(random()<0.30) + { + //Gregg add in some type of air sound to be played + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SHIELD_SPARKS); + gi.WritePosition (ent->s.origin); + gi.WriteDir (ent->s.angles); + gi.multicast (ent->s.origin, MULTICAST_PVS); + //The gas coming out pushes the grenade around + VectorScale (forward, 50+((int)(random()*1000)%150), ent->velocity); + } + //Some more sparks + if(random()>0.25) + { + //Gregg add in some type of air sound to be played + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SCREEN_SPARKS); + gi.WritePosition (ent->s.origin); + gi.WriteDir (ent->s.angles); + gi.multicast (ent->s.origin, MULTICAST_PVS); + //The gas coming out pushes the grenade around + VectorScale (forward, 40+((int)(random()*1000)%160), ent->velocity); + } + //Some really wierd sparks but sprays out a lot more of the plague + if(random()<0.15) + { + //Gregg add in some type of air sound to be played + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + while ((target = findradius(target, ent->s.origin, 450)) != NULL) + { + //Don't go through walls + if (!visible(ent, target)) + continue; + //Give them only a 1/4 chance of being infected + rnd = (int)(random()*1000)%4; + if (!rnd) + infect_person(target, ent->owner); + } + } + + if(ent->delaythink=timeDiseaseGrenade_Explode; + ent->nextthink=level.time + 0.5; +} +void timeDiseaseGrenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { +// if (ent->owner->client) +// --ent->owner->client->pers.active_grenades[GRENADE_TYPE_PLAGUE]; + G_FreeEdict (ent); + return; + } + + if (other->takedamage) + { + ent->enemy = other; + DiseaseGrenade_Explode(ent); + } +} + + + +void fire_timediseasegrenade (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; + +// ++self->client->pers.active_grenades[GRENADE_TYPE_PLAGUE]; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + grenade->wf_team = self->wf_team; + 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_FLYRICOCHET; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex (GRPLAGUETIME_MODEL); + grenade->s.skinnum = GRPLAGUETIME_SKIN; + grenade->owner = self; + grenade->touch = timeDiseaseGrenade_Touch; + grenade->nextthink = level.time + 1; + grenade->delay = level.time + 10; + grenade->think = timeDieseaseGrenade_think; + grenade->dmg = (damage/3); + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = GenericGrenade_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + grenade->wf_team = self->wf_team; + + gi.linkentity (grenade); +} diff --git a/w_plasmabomb.c b/w_plasmabomb.c new file mode 100644 index 0000000..752e85e --- /dev/null +++ b/w_plasmabomb.c @@ -0,0 +1,509 @@ +#include "g_local.h" +/* +=============== +Plasma Bomb +=============== +*/ +void DefusePlasmabomb(edict_t *ent, pmenu_t *p) +{ + float dist; + vec3_t distance; + distance[0]=ent->s.origin[0]-ent->selectedsentry->s.origin[0]; + distance[1]=ent->s.origin[1]-ent->selectedsentry->s.origin[1]; + distance[2]=ent->s.origin[2]-ent->selectedsentry->s.origin[2]; + dist=VectorLength(distance); + if(dist>100) + { + safe_cprintf(ent, PRINT_HIGH, "Plasmabomb too far away.\n"); + PMenu_Close(ent); + return; + } + //Set the time for defusing + ent->selectedsentry->sentrydelay= level.time + 0.4; + ent->selectedsentry->selectedsentry=ent; + ent->cantmove = 1; + VectorCopy(ent->s.origin,ent->LockedPosition); + PMenu_Close(ent); +} + +void Plasma_Near (edict_t *ent, edict_t *other) +{ + //Skip if they already have a menu up + if ((other->client) && (other->client->menu)) + return; + + if(other->sentrydelay>level.time) + return; + + //Only show menu a max of every 1 second + + //set up menu +// if (other->client && (other->wf_team != ent->wf_team) && (other->client->player_special & SPECIAL_DEFUSEPLASMABOMB)) + if(!other->bot_client) + if (other->client && (other->wf_team != ent->wf_team) && (other->client->player_special & SPECIAL_PLASMA_BOMB)) + { + PMenu_Close(other); + + sprintf(other->client->wfsentrystr[0], "*%s's Plasmabomb", ent->owner->client->pers.netname); + other->client->sentrymenu[0].text = other->client->wfsentrystr[0]; + other->client->sentrymenu[0].SelectFunc = NULL; + other->client->sentrymenu[0].align = PMENU_ALIGN_CENTER; + other->client->sentrymenu[0].arg = 0; + other->client->sentrymenu[1].text = "1. Defuse"; + other->client->sentrymenu[1].SelectFunc = DefusePlasmabomb; + other->client->sentrymenu[1].align = PMENU_ALIGN_LEFT; + other->client->sentrymenu[1].arg = 0; + + other->selectedsentry = ent;//Actually selected plasmabomb + + PMenu_Open(other, other->client->sentrymenu, -1, sizeof(other->client->sentrymenu) / sizeof(pmenu_t), true, false); + other->sentrydelay = level.time + 1.5; + + //Set timeout for menu (4 seconds) + if (other->client->menu) other->client->menu->MenuTimeout = level.time + 4; + } +} + +void PlasmaBombExplode(edict_t *self) +{ + edict_t *ent; + float points; + vec3_t v; + float dist; + int j; + vec3_t Vec1, Vec2, Vec3, Vec4; + vec3_t Vec5, Vec6; + + VectorSet(Vec1,28,28,0); + VectorSet(Vec2,28,-28,0); + VectorSet(Vec3,-28,-28,0); + VectorSet(Vec4,-28,28,0); + VectorSet(Vec5,0,0,28); + VectorSet(Vec6,0,0,-28); + VectorAdd(Vec1,self->s.origin,Vec1); + VectorAdd(Vec2,self->s.origin,Vec2); + VectorAdd(Vec3,self->s.origin,Vec3); + VectorAdd(Vec4,self->s.origin,Vec4); + VectorAdd(Vec5,self->s.origin,Vec5); + VectorAdd(Vec6,self->s.origin,Vec6); + if (self->s.frame == 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (self->s.origin); + gi.WritePosition (Vec1); + gi.multicast (self->s.origin, MULTICAST_PHS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (self->s.origin); + gi.WritePosition (Vec2); + gi.multicast (self->s.origin, MULTICAST_PHS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (self->s.origin); + gi.WritePosition (Vec3); + gi.multicast (self->s.origin, MULTICAST_PHS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (self->s.origin); + gi.WritePosition (Vec4); + gi.multicast (self->s.origin, MULTICAST_PHS); + + } + if (self->s.frame == 1) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (Vec1); + gi.WritePosition (Vec5); + gi.multicast (Vec1, MULTICAST_PHS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (Vec2); + gi.WritePosition (Vec5); + gi.multicast (Vec2, MULTICAST_PHS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (Vec3); + gi.WritePosition (Vec5); + gi.multicast (Vec3, MULTICAST_PHS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (Vec4); + gi.WritePosition (Vec5); + gi.multicast (Vec4, MULTICAST_PHS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (Vec1); + gi.WritePosition (Vec6); + gi.multicast (Vec1, MULTICAST_PHS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (Vec2); + gi.WritePosition (Vec6); + gi.multicast (Vec2, MULTICAST_PHS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (Vec3); + gi.WritePosition (Vec6); + gi.multicast (Vec3, MULTICAST_PHS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (Vec4); + gi.WritePosition (Vec6); + gi.multicast (Vec4, MULTICAST_PHS); + } + if (self->s.frame == 2) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (Vec5); + gi.WritePosition (self->s.origin); + gi.multicast (Vec5, MULTICAST_PHS); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (Vec6); + gi.WritePosition (self->s.origin); + gi.multicast (Vec6, MULTICAST_PHS); + + + } + + if (self->s.frame == 4) + { +/* + // the BFG effect + ent = NULL; + while ((ent = findradius(ent, self->s.origin, self->dmg_radius+500)) != NULL) + { + if (!ent->takedamage) + continue; + if (!CanDamage (ent, self)) + continue; + //The next 2 lines force person to be in the room when it explodes + //if (!CanDamage (ent, self->owner)) + // continue; + + //G.A.R. - only damage players (new) + if (!ent->client) + continue; + + //Calculate distance + 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; + if (!ent->client) + 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_PVS); + + T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_PLASMABOMB); + } +*/ +// ++TeT inline the findradius func so we can do some short cut checking +// and keep the resulting distance calculation + + ent = g_edicts; + for ( ; ent < &g_edicts[globals.num_edicts]; ent++) + { + if (!ent->inuse) + continue; + if (ent->solid == SOLID_NOT) + continue; + if (!ent->takedamage) + continue; + + // Do we want to damage other things like sentries? + //if (!ent->client) + // continue; + if (!CanDamage (ent, self)) + continue; + + // now we check how far it is from us + for (j = 0; j < 3; j++) + v[j] = self->s.origin[j] - (ent->s.origin[j] + (ent->mins[j] + ent->maxs[j])*0.5); + // inlined vectorLength func which returned sqrt of vecLength, + // instead we compare it to the square of dmg_radius + dist = 0; + for (j = 0; j < 3; j++) + dist += v[j] * v[j]; + if (dist > (self->dmg_radius * self->dmg_radius)) + continue; + // now that we have eliminated the junk, we do the sqrts here + // and make it a ratio + dist = sqrt(sqrt(dist)/self->dmg_radius); + points = self->radius_dmg * (1.0 - dist); + 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_PVS); + + T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_PLASMABOMB); + + } +// --TeT + } + + self->nextthink = level.time + FRAMETIME; + self->s.frame++; + if (self->s.frame == 5) + self->think = G_FreeEdict; +} + +void plasmabomb_think(edict_t *self) +{ +// vec3_t origin; + edict_t *target; + target = NULL; + while ((target = findradius(target, self->s.origin,75)) != NULL) + { + //Don't go through walls + if (!visible(self, target)) + continue; + //Check for plasma defusing + Plasma_Near(self,target); + } + if (self->delay < level.time) + { + 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 = PlasmaBombExplode; + self->nextthink = level.time + FRAMETIME; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_BIGEXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + if (self->PlasmaDelay < level.time) + { + self->owner->cantmove = 0; + } + if((self->sentrydelayselectedsentry)) + { + if(random()<0.08) + { + safe_cprintf(self->selectedsentry, PRINT_HIGH, "Mistake defusing Plasmabomb.\n"); + 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 = PlasmaBombExplode; + self->nextthink = level.time + FRAMETIME; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_BIGEXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + return; + } + self->selectedsentry->cantmove = 0; + + safe_cprintf(self->selectedsentry, PRINT_HIGH, "Plasmabomb defused.\n"); + + G_FreeEdict (self); + return; + } + self->nextthink = level.time + 0.1; +} + +void DropPlasmaBomb (edict_t *self, int time) +{ + edict_t *bfg; + + bfg = G_Spawn(); + bfg->wf_team = self->wf_team; + VectorCopy (self->s.origin, bfg->s.origin); + bfg->movetype = MOVETYPE_NONE; + bfg->clipmask = MASK_SHOT; + bfg->solid = SOLID_BBOX; + bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST; + VectorClear (bfg->mins); + VectorClear (bfg->maxs); + if(self->wf_team == CTF_TEAM1) //Shelton Red team sprite + bfg->s.modelindex = gi.modelindex ("sprites/r_bfg1.sp2"); + else if(self->wf_team == CTF_TEAM2) //Shelton Blue team sprite + bfg->s.modelindex = gi.modelindex ("sprites/b_bfg1.sp2"); + else + bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2"); + + bfg->owner = self; + bfg->radius_dmg = 300; //was 3000 // TeT was 2000 pts of damage - wow + bfg->dmg_radius = 2000; //was 4000 + bfg->classname = "plasma blast"; + bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav"); + bfg->think = plasmabomb_think; + bfg->nextthink = level.time + FRAMETIME; + bfg->delay = level.time + time; + bfg->PlasmaDelay =level.time + 2; + self->cantmove = 1; + VectorCopy(self->s.origin,self->LockedPosition); + self->PlasmaDelay = level.time + time + 10; + gi.linkentity (bfg); +} + +void DropPlasmaShort(edict_t *ent, pmenu_t *p) +{ + int time; + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] <50) + { + safe_cprintf(ent, PRINT_HIGH, "Not enough cells (50) for Plasma Bomb.\n"); + PMenu_Close(ent); + return; + } +// time= (((int)(random()*1000)%10))+10; + time= 10.0; + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] =ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] - 50; + DropPlasmaBomb(ent,time); + PMenu_Close(ent); +} +void DropPlasmaMedium(edict_t *ent, pmenu_t *p) +{ + int time; + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] <50) + { + safe_cprintf(ent, PRINT_HIGH, "Not enough cells (50) for Plasma Bomb.\n"); + PMenu_Close(ent); + return; + } +// time= (((int)(random()*1000)%14))+23; + time= 25.0; + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] =ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] - 50; + DropPlasmaBomb(ent,time); + PMenu_Close(ent); +} +void DropPlasmaLong(edict_t *ent, pmenu_t *p) +{ + int time; + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] <50) + { + safe_cprintf(ent, PRINT_HIGH, "Not enough cells (50) for Plasma Bomb.\n"); + PMenu_Close(ent); + return; + } +// time= (((int)(random()*1000)%20))+50; + time= 50.0; + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] =ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] - 50; + DropPlasmaBomb(ent,time); + PMenu_Close(ent); +} +void PlasmaBombMenu(edict_t *ent) +{ + PMenu_Close(ent); +if (!ent->bot_client) + sprintf(ent->client->wfsentrystr[0], "Plasmabomb Det Time");//acrid added s + ent->client->sentrymenu[0].text = ent->client->wfsentrystr[0]; + ent->client->sentrymenu[0].SelectFunc = NULL; + ent->client->sentrymenu[0].align = PMENU_ALIGN_CENTER; + ent->client->sentrymenu[0].arg = 0; + ent->client->sentrymenu[1].text = "1. Short"; + ent->client->sentrymenu[1].SelectFunc = DropPlasmaShort; + ent->client->sentrymenu[1].align = PMENU_ALIGN_LEFT; + ent->client->sentrymenu[1].arg = 0; + ent->client->sentrymenu[2].text = "2. Medium"; + ent->client->sentrymenu[2].SelectFunc = DropPlasmaMedium; + ent->client->sentrymenu[2].align = PMENU_ALIGN_LEFT; + ent->client->sentrymenu[2].arg = 0; + ent->client->sentrymenu[3].text = "3. Long"; + ent->client->sentrymenu[3].SelectFunc = DropPlasmaLong; + ent->client->sentrymenu[3].align = PMENU_ALIGN_LEFT; + ent->client->sentrymenu[3].arg = 0; + PMenu_Open(ent, ent->client->sentrymenu, -1, sizeof(ent->client->sentrymenu) / sizeof(pmenu_t), true, false); +} + +void cmd_PlasmaBombMenu(edict_t *ent) +{ + if (!ent->client) + return; + + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] <50) + { + safe_cprintf(ent, PRINT_HIGH, "Not enough cells (50) for Plasma Bomb.\n"); + return; + } + else + { + if (ent->PlasmaDelay < level.time) + { + PlasmaBombMenu(ent); + //ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] =ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] - 50; + //DropPlasmaBomb(ent,15); // Explode in 15 seconds + } + else + { + safe_cprintf(ent, PRINT_HIGH, "Not enough time has passed since last Plasma Bomb.\n"); + } + } +} +// TeT fixed command to take arguments +void cmd_PlasmaBomb(edict_t *ent) +{ + char *string; + int time = 0; + + string = gi.args(); + + if (!ent->client) return; + + if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] <50) + { + safe_cprintf(ent, PRINT_HIGH, "Not enough cells (50) for Plasma Bomb.\n"); + return; + } + if (ent->PlasmaDelay > level.time) + { + safe_cprintf(ent, PRINT_HIGH, "Not enough time has passed since last Plasma Bomb.\n"); + return; + } + + //argument = "Short", "Medium", "Long" + if (Q_stricmp ( string, "Short") == 0) + { + time = 10; + } + else if (Q_stricmp ( string, "Medium") == 0) + { + time = 25; + } + else if (Q_stricmp ( string, "Long") == 0) + { + time = 50; + } + else + { + //Otherwise pop up menu + cmd_PlasmaBombMenu(ent); + return; + } + if ( time > 0) + { + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= 50; + DropPlasmaBomb(ent,time); + } +} + diff --git a/w_poisondart.c b/w_poisondart.c new file mode 100644 index 0000000..98e3502 --- /dev/null +++ b/w_poisondart.c @@ -0,0 +1,165 @@ +#include "g_local.h" +void dart_prethink (edict_t *ent); +void stick(edict_t *projectile, edict_t *object); + +/* +====================== +Poison Dart Launcher +====================== +*/ +void poisondart_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ +// int index; + + if (other == self->owner) + return; + + if (!other->client) //only damage players + 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 (other->wf_team != self->wf_team) + { + if (other->s.origin[2]-self->s.origin[2] > self->viewheight-6) + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg*5, 2, DAMAGE_ENERGY, MOD_TRANQUILIZERDART); + other->Slower=level.time+20; + } + else + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 2, DAMAGE_ENERGY, MOD_TRANQUILIZERDART); + other->Slower=level.time+10; + } + } + } + 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); + } + + self->think = G_FreeEdict; + self->nextthink = level.time + 3 + 8 * crandom(); + + stick(self, other); +} + +void fire_poisondart (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->wf_team = self->wf_team; + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->velocity[2] += 100; + bolt->movetype = MOVETYPE_TOSS; //JR 1/4/98 changed so it bounces + bolt->clipmask = MASK_SHOT; + bolt->prethink = dart_prethink; // Keeps the arrow aligned, so it arcs through the air nicely. + bolt->gravity = 0.4; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorSet (bolt->mins,-1,-1,-3.5); + VectorSet (bolt->maxs,1,1,2.5); + bolt->s.modelindex = gi.modelindex ("models/dart/tris.md2"); + bolt->s.skinnum = 1; + bolt->owner = self; + bolt->touch = poisondart_touch; +// bolt->touch = infecteddart_touch; + bolt->nextthink = level.time + 1.7; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "poison dart"; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + +void weapon_poisondartlauncher_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage; + + if (deathmatch->value) + { damage = 5; + } + else + { + damage = 10; + } + + if (is_quad) + { + damage *= 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); + //start[0]=ent->s.origin[0] + forward[0]*1+right[0]*6; + //start[1]=ent->s.origin[1] + forward[1]*1+right[1]*6; + //start[2]=ent->s.origin[2] + forward[2]*1+right[2]*6+ent->viewheight-8; + + /* new stuff to fix left handedness */ + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + /* new stuff to fix left handedness */ + + fire_poisondart (ent, start, forward, damage, 850, EF_GREENGIB); + + // 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] -= ent->client->pers.weapon->quantity; +} + + +void Weapon_PoisonDartLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_poisondartlauncher_fire); +} \ No newline at end of file diff --git a/w_sentrykiller.c b/w_sentrykiller.c new file mode 100644 index 0000000..25a5443 --- /dev/null +++ b/w_sentrykiller.c @@ -0,0 +1,220 @@ +#include "g_local.h" +/* +========================= +Sentry Killer Rocket +========================= +*/ + +// WF & CCH: New think function for homing missiles +void sentryhoming_think (edict_t *ent) +{ + edict_t *target = NULL; + edict_t *blip = NULL; + vec3_t targetdir, blipdir; + vec_t speed; + +//gi.dprintf("Sentry Killer Think\n"); + + while ((blip = findradius(blip, ent->s.origin, 1500)) != NULL) + { + + //The only non-client to track is sentry guns + if ((strcmp(blip->classname, "SentryGun") != 0)) + continue; + if (blip == ent->owner) + continue; + //dont aim at same team unless friendly fire is on + if ((blip->wf_team == ent->wf_team) && (((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE)==0)) + //if (blip->wf_team == ent->wf_team) + continue; + if (!blip->takedamage) + continue; + if (blip->health <= 0) + continue; + if (!visible(ent, blip)) + continue; + VectorSubtract(blip->s.origin, ent->s.origin, blipdir); + blipdir[2] += 16; + if ((target == NULL) || (VectorLength(blipdir) < VectorLength(targetdir))) + { + target = blip; + VectorCopy(blipdir, targetdir); + } + } + + if (target != NULL) + { + // target acquired, nudge our direction toward it + VectorNormalize(targetdir); + VectorScale(targetdir, 0.2, targetdir); + VectorAdd(targetdir, ent->movedir, targetdir); + VectorNormalize(targetdir); + VectorCopy(targetdir, ent->movedir); + vectoangles(targetdir, ent->s.angles); + speed = VectorLength(ent->velocity); + VectorScale(targetdir, speed, ent->velocity); + + //is this the first time we locked in? sound warning for the target + if (ent->homing_lock == 0) + { + gi.sound (target, CHAN_AUTO, gi.soundindex ("homelock.wav"), 1, ATTN_NORM, 0); + ent->homing_lock = 1; + } + } + + ent->nextthink = level.time + .1; + ent->think = sentryhoming_think; +} + +void sentryrocket_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_sentryrocket (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/bomb/tris.md2"); + rocket->owner = self; + rocket->touch = sentryrocket_touch; + +//WF & CCH - Add homing lock status + rocket->homing_lock = 0; + + //Set the team of the rocket + rocket->wf_team = self->wf_team; + + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + + // See if this is a player and if they have homing on + rocket->die = Rocket_Die; + rocket->health = 10; + // CCH: a few more attributes to let the rocket 'die' + VectorSet(rocket->mins, -10, -3, 0); + VectorSet(rocket->maxs, 10, 3, 6); + rocket->mass = 10; + rocket->nextthink = level.time + .1; + rocket->think = sentryhoming_think; + + //reduce damage of homing rockets to 1/2 of normal rocket + rocket->dmg = damage; + 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); +} + +void Weapon_SentryRocketLauncher_Fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius; + int radius_damage; + + damage = wf_game.weapon_damage[WEAPON_SENTRYKILLER]; //was 150 G.R. + radius_damage = 100; //was 120 G.R. + damage_radius = 35; + 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); + + //WF - increase speed of rocket +// fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage); + fire_sentryrocket (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_SENTRYKILLER] , 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]-= 10; +} + +void Weapon_SentryRocketLauncher (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, pause_frames, fire_frames, Weapon_SentryRocketLauncher_Fire); +} + diff --git a/w_shc.c b/w_shc.c new file mode 100644 index 0000000..3a423a1 --- /dev/null +++ b/w_shc.c @@ -0,0 +1,194 @@ +#include "g_local.h" +/* +================= +SHC +================= +*/ +void fire_shc (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread) +{ + 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; + if(rand()<0.05) + burn_person(self, self, 10, MOD_SHC); + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, 4, kick, DAMAGE_BULLET,MOD_SHC); + burn_person(tr.ent, self, damage,MOD_SHC); + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } +} +void weapon_SHCRifle_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage = wf_game.weapon_damage[WEAPON_SHC]; + 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_shc (ent, start, forward, damage, kick,TE_GUNSHOT, 200, 200); + else + fire_shc (ent, start, forward, damage, kick,TE_GUNSHOT, 200, 200); + + // 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] -= ent->client->pers.weapon->quantity; +} + +void Weapon_SHCRifle (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, pause_frames, fire_frames, weapon_SHCRifle_fire); +} \ No newline at end of file diff --git a/w_shock.c b/w_shock.c new file mode 100644 index 0000000..d28af8b --- /dev/null +++ b/w_shock.c @@ -0,0 +1,218 @@ +#include "g_local.h" +/* +================= +Shock Grenade +================= +*/ +static void shockGrenade_Explode (edict_t *ent) + +{ + vec3_t origin; +// int i; + vec3_t offset; + + if (ent->owner->client) + { + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + } + + VectorSet(offset,0,0,0.5); + VectorAdd(offset,ent->s.origin,offset); + VectorCopy (offset, ent->s.origin); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + //GREGG - removed radius damage on explode + //T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, MOD_SHOCK); + + VectorMA (ent->s.origin, -.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_PVS); + +// make some debris +// make_debris (ent); + // shake view +// T_ShockWave(ent, 255, 1024); + // let blast move items +// T_ShockItems(ent); + // explode and destroy grenade + BecomeNewExplosion (ent); +} + +static void shockGrenade_Timer (edict_t *self) +{ + edict_t *ent; +// vec3_t velo; + ent = NULL; + while ((ent = findradius(ent, self->s.origin, 340)) != NULL) + { + if (ent == self) + continue; + + if (ent == self->owner) + continue; + + if (!ent->takedamage) + continue; + + if ((ent->wf_team == self->wf_team) && (((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE)==0)) + continue; + + //Don't go through walls + if (!visible(self, ent)) + continue; + + if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) + continue; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PARASITE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (ent->s.origin); + gi.WritePosition (self->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, wf_game.grenade_damage[GRENADE_TYPE_SHOCK], 0, DAMAGE_BULLET, MOD_SHOCK); + } + if(self->volumethink =shockGrenade_Explode; + self->nextthink = level.time + 0.1; +} + +static void shockGrenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + //BulletGrenade_Explode (ent); +} + +void shockGrenade_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = shockGrenade_Explode; +} + +void fire_shockgrenade (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; + + ++self->client->pers.active_grenades[GRENADE_TYPE_SHOCK]; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + grenade->grenade_index = GRENADE_TYPE_SHOCK; + grenade->wf_team = self->wf_team; + 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 (GRSHOCK_MODEL); + grenade->s.skinnum = GRSHOCK_SKIN; + grenade->owner = self; + grenade->touch = shockGrenade_Touch; //Stuff for cluster grenades when they explode + grenade->nextthink = level.time + timer; + grenade->volume = level.time + timer + 0.4; + grenade->think = shockGrenade_Timer; //stuff for cluster grenades exploding + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = shockGrenade_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + gi.linkentity (grenade); +} +void weapon_shockgrenadelauncher_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_shockgrenade (ent, start, forward, damage, 400, 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] -= ent->client->pers.weapon->quantity; +} + +void Weapon_shockGrenadeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_shockgrenadelauncher_fire); +} diff --git a/w_shrapnal.c b/w_shrapnal.c new file mode 100644 index 0000000..b9c64d9 --- /dev/null +++ b/w_shrapnal.c @@ -0,0 +1,314 @@ +#include "g_local.h" + +/* +================= +Shrapnal Grenade +================= +*/ + +/* +================= +fire_lead + +This is an internal support routine used for Shrapnal based weapons. +fire bullet creates a lot of effects, causing overflows +I copied the beef of the fire_bullet code, but with no effects +================= +*/ +static void fireShrapnal(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; + int content_mask = MASK_SHOT | MASK_WATER; + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + + //Don't add random spread if it's the sniper riffle + if (mod != MOD_SNIPERRIFLE) + { + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + if (gi.pointcontents (start) & MASK_WATER) + { + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + VectorCopy (tr.endpos, water_start); + if (!VectorCompare (start, tr.endpos)) + { + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self->owner, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod); + } + } + } +} + + +static void BulletGrenade_Explode (edict_t *ent) + +{ + vec3_t origin; + //Sean added these 4 vectors + vec3_t grenade1; + vec3_t grenade2; + vec3_t grenade3; + vec3_t grenade4; + vec3_t grenade5; + vec3_t grenade6; + vec3_t grenade7; + vec3_t grenade8; + int i; + vec3_t offset; + + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + VectorSet(offset,0,0,32); + VectorAdd(offset,ent->s.origin,offset); + VectorCopy (offset, ent->s.origin); + VectorSet(grenade1,20,20,0); + VectorSet(grenade2,20,-20,0); + VectorSet(grenade3,-20,20,0); + VectorSet(grenade4,-20,-20,0); + VectorSet(grenade5,0,20,0); + VectorSet(grenade6,0,-20,0); + VectorSet(grenade7,-20,0,0); + VectorSet(grenade8,20,0,0); + // Sean : explode the four grenades outwards +/* + fire_bullet (ent->owner, offset, grenade1, 4, 2, 15000, 15000,0); + fire_bullet (ent->owner, offset, grenade2, 4, 2, 15000, 15000,0); + fire_bullet (ent->owner, offset, grenade3, 4, 2, 15000, 15000,0); + fire_bullet (ent->owner, offset, grenade4, 4, 2, 15000, 15000,0); + fire_bullet (ent->owner, offset, grenade5, 4, 2, 15000, 15000,0); + fire_bullet (ent->owner, offset, grenade6, 4, 2, 15000, 15000,0); + fire_bullet (ent->owner, offset, grenade7, 4, 2, 15000, 15000,0); + fire_bullet (ent->owner, offset, grenade8, 4, 2, 15000, 15000,0); +*/ + + // ++TeT - fire bullet creates a lot of effects, causing overflows + // I copied the beef of the fire_bullet code, but with no effects + fire_bullet (ent, offset, grenade1, 4, 2, 15000, 15000,MOD_SHRAPNEL); + fire_bullet (ent, offset, grenade2, 4, 2, 15000, 15000,MOD_SHRAPNEL); + fire_bullet (ent, offset, grenade3, 4, 2, 15000, 15000,MOD_SHRAPNEL); + fire_bullet (ent, offset, grenade4, 4, 2, 15000, 15000,MOD_SHRAPNEL); + fire_bullet (ent, offset, grenade5, 4, 2, 15000, 15000,MOD_SHRAPNEL); + fire_bullet (ent, offset, grenade6, 4, 2, 15000, 15000,MOD_SHRAPNEL); + fire_bullet (ent, offset, grenade7, 4, 2, 15000, 15000,MOD_SHRAPNEL); + fire_bullet (ent, offset, grenade8, 4, 2, 15000, 15000,MOD_SHRAPNEL); + + // fire the remaining bullets without the effects + for (i = 0; i < 5; i++) + { + fireShrapnal (ent, offset, grenade1, wf_game.grenade_damage[GRENADE_TYPE_SHRAPNEL], 2, TE_GUNSHOT, 15000, 15000,MOD_SHRAPNEL); + fireShrapnal (ent, offset, grenade2, wf_game.grenade_damage[GRENADE_TYPE_SHRAPNEL], 2, TE_GUNSHOT, 15000, 15000,MOD_SHRAPNEL); + fireShrapnal (ent, offset, grenade3, wf_game.grenade_damage[GRENADE_TYPE_SHRAPNEL], 2, TE_GUNSHOT, 15000, 15000,MOD_SHRAPNEL); + fireShrapnal (ent, offset, grenade4, wf_game.grenade_damage[GRENADE_TYPE_SHRAPNEL], 2, TE_GUNSHOT, 15000, 15000,MOD_SHRAPNEL); + fireShrapnal (ent, offset, grenade5, wf_game.grenade_damage[GRENADE_TYPE_SHRAPNEL], 2, TE_GUNSHOT, 15000, 15000,MOD_SHRAPNEL); + fireShrapnal (ent, offset, grenade6, wf_game.grenade_damage[GRENADE_TYPE_SHRAPNEL], 2, TE_GUNSHOT, 15000, 15000,MOD_SHRAPNEL); + fireShrapnal (ent, offset, grenade7, wf_game.grenade_damage[GRENADE_TYPE_SHRAPNEL], 2, TE_GUNSHOT, 15000, 15000,MOD_SHRAPNEL); + fireShrapnal (ent, offset, grenade8, wf_game.grenade_damage[GRENADE_TYPE_SHRAPNEL], 2, TE_GUNSHOT, 15000, 15000,MOD_SHRAPNEL); + } + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + //TeT removed radius damage + //T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius,MOD_SHRAPNEL); + + VectorMA (ent->s.origin, -.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_PVS); + // SumFuka did this bit : give grenades up/outwards velocities + +// make some debris + //make_debris (ent); + // shake view + T_ShockWave(ent, 255, 1024); + // let blast move items + //T_ShockItems(ent); + // explode and destroy grenade + BecomeNewExplosion (ent); +} + +void Shrapnal_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = BulletGrenade_Explode; +} + + +static void BulletGrenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + ent->nextthink = level.time + .2; + ent->think = BulletGrenade_Explode; +// BulletGrenade_Explode (ent); +} + +void fire_bulletgrenade (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(); + grenade->wf_team = self->wf_team; + 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 (GRSHRAPNEL_MODEL); + grenade->s.skinnum = GRSHRAPNEL_SKIN; + + grenade->owner = self; + grenade->creator = self; + grenade->touch = BulletGrenade_Touch; //Stuff for cluster grenades when they explode + grenade->nextthink = level.time + timer; + grenade->think = BulletGrenade_Explode; //stuff for cluster grenades exploding + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "shrapnal"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = Shrapnal_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + gi.linkentity (grenade); +} +void weapon_shrapnalgrenadelauncher_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_bulletgrenade (ent, start, forward, damage, 400, 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] -= ent->client->pers.weapon->quantity; +} + +void Weapon_BulletGrenadeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_shrapnalgrenadelauncher_fire); +} diff --git a/w_slowgrenade.c b/w_slowgrenade.c new file mode 100644 index 0000000..cb63659 --- /dev/null +++ b/w_slowgrenade.c @@ -0,0 +1,201 @@ +#include "g_local.h" +/* +================= +Slow Grenade +================= +*/ +static void SlowGrenade_Explode (edict_t *ent) + +{ + vec3_t origin; +// int i; + vec3_t offset; + + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + VectorSet(offset,0,0,0.5); + VectorAdd(offset,ent->s.origin,offset); + VectorCopy (offset, ent->s.origin); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius,0); + + VectorMA (ent->s.origin, -.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_PVS); + // SumFuka did this bit : give grenades up/outwards velocities + +// make some debris + //make_debris (ent); + // shake view + T_ShockWave(ent, 255, 1024); + // let blast move items +// T_ShockItems(ent); + // explode and destroy grenade + BecomeNewExplosion (ent); +} + +static void SlowGrenade_Timer (edict_t *self) +{ + edict_t *ent; + vec3_t velo; + float Distance; + float Amount; + ent = NULL; + while ((ent = findradius(ent, self->s.origin, 1024)) != 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; + VectorSubtract(ent->s.origin,self->s.origin, velo); + Distance = VectorLength(velo); + Amount = 20 *(Distance/256); + if(Amount <1) + Amount = 1; + ent->Slower += Amount; + } + if(self->volumethink =SlowGrenade_Explode; + self->nextthink = level.time + 0.1; +} + + +static void SlowGrenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + +} + + +void fire_slowgrenade (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(); + grenade->wf_team = self->wf_team; + 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/grenade3/tris.md2"); + grenade->s.skinnum = 6; + + grenade->owner = self; + grenade->touch = SlowGrenade_Touch; //Stuff for cluster grenades when they explode + grenade->nextthink = level.time + timer; + grenade->volume = level.time + timer + 3.5; + grenade->think = SlowGrenade_Timer; //stuff for cluster grenades exploding + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = GenericGrenade_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + gi.linkentity (grenade); +} +void weapon_slowgrenade_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_slowgrenade (ent, start, forward, damage, 400, 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] -= ent->client->pers.weapon->quantity; +} + +void Weapon_SlowGrenadeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_slowgrenade_fire); +} \ No newline at end of file diff --git a/w_sniperrifle.c b/w_sniperrifle.c new file mode 100644 index 0000000..05849c7 --- /dev/null +++ b/w_sniperrifle.c @@ -0,0 +1,156 @@ +#include "g_local.h" + +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod); +int srkick = 8; +void lasersight_on (edict_t *ent); +void lasersight_off (edict_t *ent); +/* +======================== +Sniper Rifle +======================== + +G.A.R 10/22/98 -Removed laser sight code from here. Put in separate file + +*/ + +#define FIRE_FRAME_1 4 +#define FIRE_FRAME_2 7 + +void weapon_snipe_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; +// vec3_t end; + + if (!ent->client) return; + +//if (wfdebug) gi.dprintf("Snipe first start, frame = %d\n",ent->client->ps.gunframe); + + //Should laser sight be on? + if ((ent->client->pers.laseron) && (!ent->lasersight) && (ent->client->ps.gunframe == FIRE_FRAME_1)) + { + lasersight_on (ent); + } + + if (!ent->PlayerSnipingZoom) + ent->PlayerSnipingZoom =35; + if (ent->client->ps.gunframe == FIRE_FRAME_2) + { + if (ent->client->pers.autozoom) + ent->client->ps.fov = 90; + ent->client->ps.gunframe++; + return; + } + + if (ent->SniperDamage == 0) + { + if (ent->client->pers.autozoom) + ent->client->ps.fov = ent->PlayerSnipingZoom; + ent->SniperDamage = 20; + //if (ent->client->ps.gunframe == FIRE_FRAME_2) + ent->client->weapon_damage = ent->SniperDamage; + + if(!ent->client->pers.fastaim) + ent->superslow=1; + + return; + } + if (ent->client->buttons & BUTTON_ATTACK) + { + if (ent->client->pers.autozoom) + ent->client->ps.fov = ent->PlayerSnipingZoom; + if (ent->SniperDamage == 1) ent->SniperDamage = 0; +// ent->SniperDamage += 10; + ent->SniperDamage += 5; + if(!ent->client->pers.fastaim) + { + ent->SniperDamage += 8;//was 6 + //Change max damage now that there is location damage. + if (ent->SniperDamage > wf_game.weapon_damage[WEAPON_SNIPERRIFLE]) + ent->SniperDamage= wf_game.weapon_damage[WEAPON_SNIPERRIFLE]; + } + else + { + //Change max damage now that there is location damage. + ent->SniperDamage += 20; + if (ent->SniperDamage>110) + ent->SniperDamage= 110; + } + + + if (level.framenum & 3) + { + //gi.dprintf("N "); + } + else + { + //gi.dprintf("Y "); + ent->client->weapon_damage = ent->SniperDamage; + } + srkick +=2; + if (srkick>60) + srkick = 60; + return; + } + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorScale (forward, -3, ent->client->kick_origin); + + ent->client->kick_angles[0] = -7;//Gregg - I added some more kick + VectorSet(offset, 0, 7, ent->viewheight-8); + + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { + ent->SniperDamage *= 4; + if ((int)level.time & 3) + ent->client->weapon_damage = ent->SniperDamage; + srkick *= 4; + } + +// fire_bullet (ent, start, forward, ent->SniperDamage, srkick, 0, 0, MOD_SNIPERRIFLE); + fire_rail (ent, start, forward, ent->SniperDamage, srkick, MOD_SNIPERRIFLE); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SHOTGUN); + 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]--; + ent->SniperDamage =0; + ent->client->weapon_damage = ent->SniperDamage; + srkick = 8; + ent->superslow=0; + +//if (wfdebug) gi.dprintf("Snipe laser off\n"); + lasersight_off (ent); +} + +void Weapon_Snipe (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, pause_frames, fire_frames, weapon_snipe_fire); + +/* + static int pause_frames[] = {16, 22, 28, 0}; + static int fire_frames[] = {8, 9, 0}; + + Weapon_Generic (ent, 7, 13, 30, 33, pause_frames, fire_frames, weapon_snipe_fire); +*/ + + static int pause_frames[] = {16, 28, 35, 0}; + static int fire_frames[] = {FIRE_FRAME_1, FIRE_FRAME_2, 0}; + + Weapon_Generic (ent, 3, 12, 50, 55, pause_frames, fire_frames, weapon_snipe_fire); + + +} + diff --git a/w_stinger.c b/w_stinger.c new file mode 100644 index 0000000..c6f4924 --- /dev/null +++ b/w_stinger.c @@ -0,0 +1,204 @@ +#include "g_local.h" + +/* +====================================================================== + +Stinger Launcher +by Aleksey 'Fireball' Bragin, 08/12/1998 +Modified by Gregg Reno 12/24/98 + +====================================================================== +*/ + +void Rocket_Explode (edict_t *ent); + +//If stinger touches anything, just go dead +void stinger_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + + ent->s.sound = 0; + ent->movetype = MOVETYPE_BOUNCE; + ent->delay = level.time + 3; + + //Play an impact sound + //gi.sound (ent, CHAN_WEAPON, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0); + gi.sound (ent, CHAN_WEAPON, gi.soundindex ("darthit.wav"), 1, ATTN_NORM, 0); + +} + +void stinger_think (edict_t *ent) +{ + edict_t *target = NULL; + edict_t *blip = NULL; + vec3_t targetdir, blipdir; + vec_t speed; + float dist; + + //Blow up if we are past the delay time + if (level.time > ent->delay) + { + ent->nextthink = level.time + .1; + ent->think = Rocket_Explode; + return; + } + + while ((blip = findradius(blip, ent->s.origin, 1000)) != NULL) + { + if (!blip->client) + continue; + if (blip == ent->creator) + continue; + if (blip->disguised) + continue; + //dont aim at same team unless friendly fire is on + if ((blip->wf_team == ent->wf_team) && (((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE)==0)) + continue; +// if (!blip->takedamage) +// continue; + if (blip->health <= 0) + continue; + if (!visible(ent, blip)) + continue; + if (!infront(ent, blip)) + continue; + + if ((!blip->client->thrusting) && (!blip->client->ctf_grapple)) + continue; + + //We have a possible target. If we are close + //enough, just blow up. + VectorSubtract(blip->s.origin, ent->s.origin, blipdir); + dist = VectorLength(blipdir); + + if (dist < 100) + { + ent->nextthink = level.time + .1; + ent->think = Rocket_Explode; + return; + } + + blipdir[2] += 16; + if ((target == NULL) || (VectorLength(blipdir) < VectorLength(targetdir))) + { + target = blip; + VectorCopy(blipdir, targetdir); + } + } + + if (target != NULL) + { + // target acquired, nudge our direction toward it + VectorNormalize(targetdir); + VectorScale(targetdir, 0.28, targetdir); + VectorAdd(targetdir, ent->movedir, targetdir); + VectorNormalize(targetdir); + VectorCopy(targetdir, ent->movedir); + vectoangles(targetdir, ent->s.angles); + speed = VectorLength(ent->velocity); + VectorScale(targetdir, speed, ent->velocity); + + //is this the first time we locked in? sound warning for the target + if (ent->homing_lock == 0) + { +// gi.sound (target, CHAN_AUTO, gi.soundindex ("homelock.wav"), 1, ATTN_NORM, 0); +// gi.sound (target, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0); + ent->homing_lock = 1; + } + } + + ent->nextthink = level.time + .1; +} + +void fire_stinger (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->creator = self; + rocket->mod = MOD_STINGER; + rocket->touch = stinger_touch; + + //Set the team of the rocket + rocket->wf_team = self->wf_team; + + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + + rocket->nextthink = level.time + .1; + rocket->think = stinger_think; + + rocket->dmg = damage; + + rocket->delay = level.time + 10; //blow up after 10 seconds no mater what + + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->classname = "rocket"; + + // Don't forget to set owner + rocket->owner = self; + gi.linkentity (rocket); +} + +void Weapon_StingerLauncher_Fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius; + int radius_damage; + + damage = wf_game.weapon_damage[WEAPON_STINGER]; + 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_stinger (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_STINGER], 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] -=5; + + if (ent->client->pers.inventory[ent->client->ammo_index] < 0) + ent->client->pers.inventory[ent->client->ammo_index] = 0; +} + +void Weapon_StingerLauncher (edict_t *ent) +{ + static int pause_frames[] = {7, 0}; + static int fire_frames[] = {2, 0}; + + Weapon_Generic (ent, 1, 6, 8, 10, pause_frames, fire_frames, Weapon_StingerLauncher_Fire); +} diff --git a/w_telsacoil.c b/w_telsacoil.c new file mode 100644 index 0000000..322ce75 --- /dev/null +++ b/w_telsacoil.c @@ -0,0 +1,210 @@ +#include "g_local.h" +/* +=============== +Telsa Coil +=============== +*/ + +void fire_telsa(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end,offset; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + edict_t *ent; + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + ent = NULL; + VectorSet(offset,0,0,0); + while ((ent = findradius(ent, self->s.origin, 1024)) != NULL) + { + if (!ent->takedamage) + continue; + + if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) + continue; + + if (!ent->waterlevel) + continue; + + if(isvisible (self, ent)) + continue; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PARASITE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (tr.endpos); + gi.WritePosition (ent->s.origin); + gi.multicast (tr.endpos, MULTICAST_PVS); + T_Damage (ent, self, self, aimdir, ent->s.origin, offset, damage*2, kick, DAMAGE_BULLET,MOD_TELSA); + } + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET,MOD_TELSA); + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (5); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (0xd8dad9db); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + VectorSet(offset,0,24,0); + VectorAdd(offset,start,start); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PARASITE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (start, MULTICAST_PVS); + + gi.sound(self, CHAN_VOICE, gi.soundindex("electric.wav"), 1, ATTN_NORM, 0); + +} + + +void Weapon_Telsa_Fire (edict_t *ent) +{ + vec3_t forward, right,start; + vec3_t offset; + int damage,kick; + int xspread; + int yspread; + if (ent->client->pers.inventory[ent->client->ammo_index]<=0) + { + ent->client->ps.gunframe++; + return; + } if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe++; + return; + } + if (!(ent->client->pers.inventory[ent->client->ammo_index])) + { + ent->client->ps.gunframe++; + return; + } + damage = wf_game.weapon_damage[WEAPON_TELSA]; + xspread = 25; + yspread = 25; + kick = 2 + (((int)(random()*1000)) % 8); + + if (is_quad) + { + damage *= 4; + xspread *=2.5; + yspread *=2.5; + kick *=4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + VectorSet(offset, 0, 20, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + VectorAdd(start,offset,start); +// fire_needle(ent, start, forward, damage, kick, TE_BLASTER, xspread, yspread); + fire_telsa(ent, start, forward, damage, kick, TE_BLASTER, xspread, yspread); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity; +} +void Weapon_Telsa (edict_t *ent) +{ + static int pause_frames[] = {0}; + static int fire_frames[] = { 8, 0}; + + Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_Telsa_Fire); +} \ No newline at end of file diff --git a/w_tranquilizer.c b/w_tranquilizer.c new file mode 100644 index 0000000..03910fb --- /dev/null +++ b/w_tranquilizer.c @@ -0,0 +1,200 @@ +#include "g_local.h" +/* +================== +Tranquilizer Rifle +================== +*/ +void fire_tranquilizer (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread) +{ + 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; + if(rand()<0.05) + burn_person(self, self, 10, MOD_WF_FLAME); + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, 1, kick, DAMAGE_BULLET, MOD_TRANQUILIZER); + if (tr.ent->client) + tr.ent->Slower += 75; + else + { + tr.ent->monsterinfo.scale -= 0.1; + if (tr.ent->monsterinfo.scale<0.25) + tr.ent->monsterinfo.scale = 0.25; + } + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } +} +void weapon_TranquilizerRifle_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage = wf_game.weapon_damage[WEAPON_TRANQUILIZER]; + 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; + } + + + fire_tranquilizer (ent, start, forward, damage, kick,TE_GUNSHOT, 500, 500); + + + // 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] -= ent->client->pers.weapon->quantity; +} + +void Weapon_TranquilizerRifle (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, pause_frames, fire_frames, weapon_TranquilizerRifle_fire); +} \ No newline at end of file diff --git a/wf_classmgr.c b/wf_classmgr.c new file mode 100644 index 0000000..9021a08 --- /dev/null +++ b/wf_classmgr.c @@ -0,0 +1,327 @@ +// +// wf_classmgr.c -- Class Manager +// +// +// 7/10/98 - Gregg Reno +// + +// INCLUDES ///////////////////////////////////////////////// + +#include "g_local.h" +#include "wf_classmgr.h" + +//Global variable +int modID; + +void readline(FILE *file, char *str, int max); +void fixline(char *str); + +int wf_GetModelFromName(char *name); + +//Test noclass server var to see if a specific bit is set +/* +qboolean ClassAllowed(int classnum) +{ + int bitmask; + if (classnum <= 0 || classnum > 16) return true; + + bitmask = 1 << (classnum-1); //set the right bit + if ((int)noclass->value & bitmask) + return false; + else + return true; +} +*/ + +void ClearString(char *s, int len) +{ + int i; + for (i = 0; i < len; ++i) + s[i] = '\0'; +} + +void DecryptString(unsigned char *s, int len) +{ + int i; + for (i = 0; i < len; ++i) + { + if (s[i] == 255) s[i] = 0; + if (s[i] >= 128) s[i] = s[i] - 128; + } +} + +void EncryptString(unsigned char *s, int len) +{ + int i; + for (i = 0; i < len; ++i) + { + if (s[i] > 32 && s[i] < 128) s[i] = s[i] + 128; + } +} + +int IsEncrypted(unsigned char *s, int len) +{ + int i; + int retval; + retval = 0; + for (i = 0; i < len; ++i) + { + if (s[i] > 128) retval = 1; + } + + //Consider blank lines encrypted + if (s[0] == 0) retval = 1; + return (retval); +} + +int ReadEncryptedLine(FILE *fp, char *szLineIn, int maxlen, int lineno) +{ + readline(fp, szLineIn, maxlen); + if (IsEncrypted(szLineIn, strlen(szLineIn)) == 0) + { + DecryptString(szLineIn, strlen(szLineIn)); + gi.dprintf("Invalid file format - line %d: %s\n",lineno,szLineIn); + return (lineno + 1); + } + DecryptString(szLineIn, strlen(szLineIn)); + fixline(szLineIn); + +//gi.dprintf("ReadLine:%s\n",szLineIn); + + return (lineno + 1); +} + + +//Clear class info +void ClearClassInfo() +{ + int i; + int j; + + numclasses = 0; + for (i = 0; i < MAX_CLASSES; ++i) + { + ClearString(classinfo[i].name,32); + ClearString(classinfo[i].model_name,32); + ClearString(classinfo[i].skin1,32); + ClearString(classinfo[i].skin2,32); + + for (j = 0; j < 10; ++j) + classinfo[i].weapon[j] = 0; + for (j = 0; j < 3; ++j) + classinfo[i].grenade[j] = 0; + + classinfo[i].max_armor = 0; + classinfo[i].max_speed = 0; + classinfo[i].max_health = 0; + classinfo[i].special = 0; + classinfo[i].items = 0; + classinfo[i].limit = 99; + } +} + +//Load the class info from a file +int LoadClassInfo(char *filename) +{ + FILE *fp; + int i=0; + int fileclassnum; + //int j; + int len; + int lineno; + char versionstr[80]; + int version; + unsigned char szLineIn[80]; + char path[100]; + + modID = 0; + + ClearClassInfo(); + + //Make sure we open class def from the game directory + strcpy(path, gamedir->string); +#if defined(_WIN32) || defined(WIN32) + strcat(path,"\\"); +#else + strcat(path,"/"); +#endif + strcat(path,filename); + + //Open file + fp = WFOpenFile(NULL, path); + if (!fp) // opened successfully? + { + gi.dprintf("ClassMgr: Could not open file: %s\n",path); + return 0; + } + + //First line should be version # and mod id + lineno = ReadEncryptedLine(fp, szLineIn, 80, 0); + sscanf(szLineIn, "%s %d", versionstr, &modID); + + //Check for version 3 + if ((versionstr[0]=='V') && (versionstr[1] == '3')) + version = 3; + else + { + gi.dprintf("Invalid Version ID: [%s]. Should be V3.\n", versionstr); + WFCloseFile(NULL, fp); + return 0; + } + + //gi.dprintf("Game Dir = %s\n",gamedir->string); + + //Make sure the WF class files are run only from the wf directory +/* + if (modID != MOD_ID_WF) + { + if (strcmp(gamedir->string, "wf") == 0) + { + gi.dprintf("Sorry, MOCK class def files cannot be run from the wf directory\n"); + WFCloseFile(NULL, fp); + return 0; + } + } +*/ + if (modID == MOD_ID_WF) + { + gi.cvar ("mod", "The Weapons Factory", CVAR_SERVERINFO); + } + else + { + gi.cvar ("mod", "Mod Construction Kit", CVAR_SERVERINFO); + } + + + //Second line is the name of the class definition pack + lineno = ReadEncryptedLine(fp, szLineIn, 80, lineno); + if (strlen(szLineIn) >30) szLineIn[31] = '\0'; + + sscanf(szLineIn, "%s", classdefname); +//if (wfdebug) gi.dprintf("New class def loaded: %s\n",classdefname); + //Use file name instead + gi.dprintf("New class def loaded: %s\n",path); + + // Read lines from class info file + i = 1; + fileclassnum = 1; + while ((!feof(fp)) && (i<=MAX_CLASSES)) + { + //Line 1 - class name + lineno = ReadEncryptedLine(fp, szLineIn, 80, lineno); + len = strlen(szLineIn); + if (len <=0) continue; //Skip blank lines + + //Class definitions must start with "[" and end with "]" + + if ( (szLineIn[0] != '[') || (szLineIn[len-1] != ']')) + { + gi.dprintf("Invalid class definition: %s\n", szLineIn); + //for (j = 0; szLineIn[j] != 0; ++j) + // gi.dprintf("[%d]",szLineIn[j]); + WFCloseFile(NULL, fp); + return 0; + } + + // Save the class name + strncpy(classinfo[i].name, &szLineIn[1], len-2); + classinfo[i].name[len-1] = 0; + + +// if (ClassAllowed(fileclassnum)) +// gi.dprintf(" Class %d: <%s>\n", i, classinfo[i].name); +// else +// gi.dprintf(" Class <%s> disabled.\n", classinfo[i].name); + + //Line 2 - model and skins and decoy skin number + lineno = ReadEncryptedLine(fp, szLineIn, 80, lineno); + + sscanf(szLineIn, "%s %s %s %d", classinfo[i].model_name, classinfo[i].skin1, classinfo[i].skin2, &classinfo[i].decoyskin ); + classinfo[i].model = wf_GetModelFromName(classinfo[i].model_name); + +if (wfdebug) gi.dprintf(" Model=<%s>, Skin 1=<%s>, Skin 2=<%s>\n", classinfo[i].model_name, classinfo[i].skin1, classinfo[i].skin2 ); + + //Line 3 - weapons + lineno = ReadEncryptedLine(fp, szLineIn, 80, lineno); + sscanf(szLineIn, "%d %d %d %d %d %d %d %d %d %d ", + &classinfo[i].weapon[0], &classinfo[i].weapon[1], + &classinfo[i].weapon[2], &classinfo[i].weapon[3], + &classinfo[i].weapon[4], &classinfo[i].weapon[5], + &classinfo[i].weapon[6], &classinfo[i].weapon[7], + &classinfo[i].weapon[8], &classinfo[i].weapon[9]); +if (wfdebug) gi.dprintf(" Weapons: %d %d %d %d %d %d %d %d %d %d\n", + classinfo[i].weapon[0], classinfo[i].weapon[1], + classinfo[i].weapon[2], classinfo[i].weapon[3], + classinfo[i].weapon[4], classinfo[i].weapon[5], + classinfo[i].weapon[6], classinfo[i].weapon[7], + classinfo[i].weapon[8], classinfo[i].weapon[9]); + + //Line 4 - Grenades + lineno = ReadEncryptedLine(fp, szLineIn, 80, lineno); + sscanf(szLineIn, "%d %d %d", + &classinfo[i].grenade[0], + &classinfo[i].grenade[1] , + &classinfo[i].grenade[2]); + +if (wfdebug) gi.dprintf(" Grenades <%d> <%d> <%d>\n", + classinfo[i].grenade[0], + classinfo[i].grenade[1] , + classinfo[i].grenade[2]); + + //Line 5 - armor, speed, health, special, items + lineno = ReadEncryptedLine(fp, szLineIn, 80, lineno); + sscanf(szLineIn, "%d %d %d %d %d", + &classinfo[i].max_armor, &classinfo[i].max_speed, + &classinfo[i].max_health, &classinfo[i].special, &classinfo[i].items); +if (wfdebug) gi.dprintf(" armor=%d, speed=%d, health=%d, special=%d, items=%d\n", + classinfo[i].max_armor, classinfo[i].max_speed, + classinfo[i].max_health, classinfo[i].special,classinfo[i].items); + + + //Line 6 - Ammo limits + lineno = ReadEncryptedLine(fp, szLineIn, 80, lineno); + sscanf(szLineIn, "%d %d %d %d %d %d", + &classinfo[i].max_ammo[AMMO_BULLETS], + &classinfo[i].max_ammo[AMMO_SHELLS], + &classinfo[i].max_ammo[AMMO_ROCKETS], + &classinfo[i].max_ammo[AMMO_GRENADES], + &classinfo[i].max_ammo[AMMO_CELLS], + &classinfo[i].max_ammo[AMMO_SLUGS]); +// else +// { +// classinfo[i].max_ammo[AMMO_BULLETS] = 200; +// classinfo[i].max_ammo[AMMO_SHELLS] = 100; +// classinfo[i].max_ammo[AMMO_ROCKETS] = 50; +// classinfo[i].max_ammo[AMMO_GRENADES] = 50; +// classinfo[i].max_ammo[AMMO_CELLS]= 200; +// classinfo[i].max_ammo[AMMO_SLUGS] = 50; +// } + + //Get ready for next line + + //Should we skip this class? + +// if (ClassAllowed(fileclassnum)) +// { + numclasses = i; + i++; +// } + + ++fileclassnum; + + } + gi.dprintf("%d Classes Loaded\n", numclasses); + WFCloseFile(NULL, fp); + return 1; // normal exit +} + + + +void Cmd_Classdef_f () +{ + char *filename; + filename = gi.argv(2); // get filename from command line + LoadClassInfo(filename); +} + + diff --git a/wf_classmgr.h b/wf_classmgr.h new file mode 100644 index 0000000..b1842c3 --- /dev/null +++ b/wf_classmgr.h @@ -0,0 +1,32 @@ +/*============================================================================== +The Weapons Factory - +Class Manager Include File +Original code by Gregg Reno +==============================================================================*/ + +#define MAX_CLASSES 16 + +typedef struct +{ + char name[32]; + char model_name[32]; + int model; + char skin1[32]; + char skin2[32]; + int decoyskin; + int weapon[10]; + int grenade[3]; + int max_ammo[7]; + int max_armor; + int max_speed; + int max_health; + int special; + int items; + int limit; //Limit the number allowed for this class +} ClassInfo; + +ClassInfo classinfo[MAX_CLASSES + 1]; +char classdefname[32]; +int numclasses; + + diff --git a/wf_cluster.c b/wf_cluster.c new file mode 100644 index 0000000..13cfc2e --- /dev/null +++ b/wf_cluster.c @@ -0,0 +1,151 @@ +/*============================================================================== +The Weapons Factory - +Cluster Grenade Functions +Original code by ?? +Modified by Gregg Reno +==============================================================================*/ +#include "g_local.h" + +static void Cluster_Explode (edict_t *ent) + +{ + vec3_t origin; + + //Sean added these 4 vectors + + vec3_t grenade1; + vec3_t grenade2; + vec3_t grenade3; + vec3_t grenade4; + vec3_t grenade5; + + 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? + T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, MOD_WF_CLUSTER); + + 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_PVS); + + // SumFuka did this bit : give grenades up/outwards velocities + VectorSet(grenade1,20,20,40); + VectorSet(grenade2,20,-20,40); + VectorSet(grenade3,-20,20,40); + VectorSet(grenade4,-20,-20,40); + + VectorSet(grenade5,0,0,40); + + // Sean : explode the four grenades outwards + // Arguments: fire_grenade2 (self, start, aimdir, damage, speed, timer, damage_radius, held) + fire_grenade2(ent->owner, origin, grenade1, ent->dmg, 10, 1.0, 120, false); + fire_grenade2(ent->owner, origin, grenade2, ent->dmg, 10, 1.0, 120, false); + fire_grenade2(ent->owner, origin, grenade3, ent->dmg, 10, 1.0, 120, false); + fire_grenade2(ent->owner, origin, grenade4, ent->dmg, 10, 1.0, 120, false); +// fire_grenade2(ent->owner, origin, grenade4, ent->dmg, 30, 2.0, 120, false); + + G_FreeEdict (ent); +} + +void Cluster_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //gi.dprintf("Cluster: Touch\n"); + + if (other == ent->owner) + return; + + // Dont blow up if on same team + if (other->wf_team == ent->wf_team) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + Cluster_Explode (ent); +} + + +void fire_cluster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *grenade; + vec3_t dir; + int dmg; + vec3_t forward, right, up; + + dmg = wf_game.grenade_damage[GRENADE_TYPE_CLUSTER]; + + 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 (GRCLUSTER_MODEL); + grenade->s.skinnum = GRCLUSTER_SKIN; + grenade->owner = self; + grenade->touch = Cluster_Touch; + + grenade->nextthink = level.time + timer; + grenade->think = Cluster_Explode; + grenade->dmg = dmg; + grenade->dmg_radius = damage_radius; + grenade->classname = "cluster grenade"; + grenade->spawnflags = 1; + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + //set the team + grenade->wf_team = self->client->resp.ctf_team; + + if (timer <= 0.0) + Cluster_Explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} + diff --git a/wf_config.c b/wf_config.c new file mode 100644 index 0000000..45b1d70 --- /dev/null +++ b/wf_config.c @@ -0,0 +1,1035 @@ +// wf_config.c +// Functions to read the WF server configuration file (wfserver.ini) + +#include "g_local.h" +#include "wf_classmgr.h" + +void readline(FILE *file, char *str, int max); +void WFFillMapNames(); +void ClearMapVotes(); +void lcase(char *str); +int LoadClassInfo(char *filename); +void LoadWeaponInfo() ; + +FILE *fpconfig; +int config_line_no; +unsigned char config_line[201]; + +#define COMMENT_CHAR '/' + +void rtrim(char *str) +{ + int i; + + i = strlen(str) - 1; + + //now trim back the white space + while (i >= 0 && str[i] == ' ') + { + str[i] = 0; + --i; + } +} + +//Return true if the given string contains banned words +int BannedWords(edict_t *ent, char *str) +{ + int i; + + //Loop through the banned words + i = 0; + + while (i < MAX_BANWORDS && wf_game.banwords[i][0] != 0) + { + if( strstr( str, wf_game.banwords[i] ) ) + { + safe_cprintf(ent, PRINT_HIGH, "You can't say that on this server!\n"); + return 1; + } + + ++i; + } + + return 0; +} + +void RemoveComments(char *str) +{ + int i; + int len; + + i = 0; + len = strlen(str); + + while (i < len && str[i] != COMMENT_CHAR) + { + ++i; + } + + str[i] = 0; + + rtrim(str); +} + +//Copy a string until you hit a specific character or end of string +int CopyTill(char *fromstr, char *deststr, int start, char c) +{ + int i; + int pos = 0; + int len; + + i = start; + len = strlen(fromstr); + + while (i < len && fromstr[i] != c) + { + deststr[pos++] = fromstr[i]; + ++i; + } + + deststr[pos] = 0; + return i; +} + +//Skip all white space +int SkipWhiteSpace(char *str, int start) +{ + int i; + int len; + + i = start; + len = strlen(str); + + while (i < len && str[i] == ' ') + ++i; + return i; +} + +//Get a name / value pair in the form: +// "name1=value1" or +// "name1 = value1" +int getNameValue(char *sline, char *sname, char *svalue) +{ + int pos = 0; + + //Skip initial white space + pos = SkipWhiteSpace(sline, pos); + + //Get name first + pos = CopyTill(sline, sname, pos, '='); + rtrim(sname); + + //Get the equals sign + if (sline[pos] != '=') + { +//gi.dprintf("Equal sign not found at pos %d: [%s]\n", pos, sline); + sname[0] = 0; + svalue[0] = 0; + return 0; + } + else + ++pos; + + //Get value + pos = SkipWhiteSpace(sline, pos); + pos = CopyTill(sline, svalue, pos, '='); //this should get till end of string + rtrim(svalue); + +//gi.dprintf("N/V: [%s], n=[%s], v=[%s]\n", sline, sname, svalue); + return 1; +} + +//Get 4 chunks from an imput line +void get4Chunks(char *sline, char *s1, char *s2, char *s3, char *s4) +{ + int pos = 0; + + //string 1 + pos = SkipWhiteSpace(sline, pos); + pos = CopyTill(sline, s1, pos, ' '); + rtrim(s1); + + //string 2 + pos = SkipWhiteSpace(sline, pos); + pos = CopyTill(sline, s2, pos, ' '); + rtrim(s2); + + //string 3 + pos = SkipWhiteSpace(sline, pos); + pos = CopyTill(sline, s3, pos, ' '); + rtrim(s3); + + //string 4 + pos = SkipWhiteSpace(sline, pos); + pos = CopyTill(sline, s4, pos, ' '); + rtrim(s4); + + if (wfdebug) gi.dprintf("4chunks: [%s] [%s] [%s] [%s]\n", s1, s2, s3, s4); + +} + + +void cfgReadLine(FILE *fp, char *str, int maxlen) +{ + readline(fp, str, maxlen); + if (str[0] == -1) str[0] = 0; + RemoveComments(str); + //gi.dprintf("Read: %s, 1st=%d\n", str, str[0]); + ++config_line_no; +} + +void cfgReadLineKeepAll(FILE *fp, char *str, int maxlen) +{ + readline(fp, str, maxlen); + if (str[0] == -1) str[0] = 0; + //gi.dprintf("Read: %s, 1st=%d\n", str, str[0]); + rtrim(str); + ++config_line_no; +} + +//----------------- End of Utilities ------------------ + + +//------------------------------------------- +// config_map_list() - Read the map list lines +// Format = +// "mapname [V]" +// where V is an optional flag to indicate if the map is votable +int config_map_list() +{ + int numberOfMapsInFile = 0; + char sMapName[80]; + char sVoteOnly[80]; + + // read map names into array + while (!feof(fpconfig)) + { + cfgReadLine(fpconfig, config_line, 160); + //fixline(config_line); + sMapName[0] = 0; + sVoteOnly[0] = 0; + sscanf(config_line, "%s %s", sMapName, sVoteOnly ); + if (config_line[0] == 0) //Skip blank lines + continue; + if (Q_stricmp(sMapName, "###") == 0) // terminator is "###" + break; + if (sMapName[0] == '[') // new section name + break; + if (sMapName[0] == 0) //Skip blank lines + continue; + + //Found map +//gi.dprintf("Map In: %s [%s]\n", sMapName, sVoteOnly); + + + if (numberOfMapsInFile >= MAX_MAPS) + { + gi.dprintf("Map Skipped: %s. Limit is %d maps.\n", sMapName, MAX_MAPS); + } + else + { + + strncpy(maplist.mapnames[numberOfMapsInFile], sMapName, MAX_MAPNAME_LEN); + if (sVoteOnly[0] == 'v' || sVoteOnly[0] == 'V') + maplist.voteonly[numberOfMapsInFile] = true; + else + maplist.voteonly[numberOfMapsInFile] = false; + //gi.dprintf("...%s\n", maplist.mapnames[i]); + + numberOfMapsInFile++; + } + } + + if (numberOfMapsInFile == 0) + { + gi.dprintf ("WFConfig: No maps listed in [maplist] section\n"); + return 0; // abnormal exit -- no maps in file + } + + gi.dprintf ("WFConfig: %i map(s) loaded.\n", numberOfMapsInFile); + maplist.nummaps = numberOfMapsInFile; + if (maplist.nummaps) maplist.active = 1; + + //maplist.rotationflag = 0; + maplist.currentmap = -1; + WFFillMapNames(); //prepare menu + return 1; // normal exit +} + + +int config_scoring() +{ + int i=0; + int lval; + char sname[160]; + char svalue[160]; + + +// gi.dprintf ("WFConfig: Setting custom scores...\n"); + + // read map names into array + while (!feof(fpconfig)) + { + cfgReadLine(fpconfig, config_line, 160); + lcase(config_line); + + //Skip blank lines + if (config_line[0] == 0) + continue; + // Stop if we hit terminator + if (Q_stricmp(config_line, "###") == 0) + break; + // Stop if we hit new section name + if (config_line[0] == '[') + break; + + if (!getNameValue(config_line, sname, svalue)) + continue; //Skip bad name/value lines + + //Get scores + lval = (int) atol(svalue); + + if (Q_stricmp(sname, "frag_points") == 0) + CTF_FRAG_POINTS = lval; + else if (Q_stricmp(sname, "suicide_points") == 0) + CTF_SUICIDE_POINTS = lval; + else if (Q_stricmp(sname, "sentry_points") == 0) + CTF_SENTRY_POINTS = lval; + else if (Q_stricmp(sname, "capture_bonus") == 0) + CTF_CAPTURE_BONUS = lval; + else if (Q_stricmp(sname, "team_bonus") == 0) + CTF_TEAM_BONUS = lval; + else if (Q_stricmp(sname, "recovery_bonus") == 0) + CTF_RECOVERY_BONUS = lval; + else if (Q_stricmp(sname, "flag_bonus") == 0) + CTF_FLAG_BONUS = lval; + else if (Q_stricmp(sname, "frag_carrier_bonus") == 0) + CTF_FRAG_CARRIER_BONUS = lval; + else if (Q_stricmp(sname, "carrier_danger_protect_bonus") == 0) + CTF_CARRIER_DANGER_PROTECT_BONUS = lval; + else if (Q_stricmp(sname, "carrier_protect_bonus") == 0) + CTF_CARRIER_PROTECT_BONUS = lval; + else if (Q_stricmp(sname, "flag_defense_bonus") == 0) + CTF_FLAG_DEFENSE_BONUS = lval; + else if (Q_stricmp(sname, "return_flag_assist_bonus") == 0) + CTF_RETURN_FLAG_ASSIST_BONUS = lval; + else if (Q_stricmp(sname, "frag_carrier_assist_bonus") == 0) + CTF_FRAG_CARRIER_ASSIST_BONUS = lval; + else + gi.dprintf("WFConfig: Invalid Score Name: %s\n", sname); + } + + return 1; // normal exit +} + +int config_classlimits() +{ + int i=0; + char sname[160]; + char svalue[160]; + int iname; + int ivalue; + + +// gi.dprintf ("WFConfig: Setting class limits...\n"); + + // read map names into array + while (!feof(fpconfig)) + { + cfgReadLine(fpconfig, config_line, 160); + lcase(config_line); + + //Skip blank lines + if (config_line[0] == 0) + continue; + // Stop if we hit terminator + if (Q_stricmp(config_line, "###") == 0) + break; + // Stop if we hit new section name + if (config_line[0] == '[') + break; + + //Name = class number + //Value = limit + getNameValue(config_line, sname, svalue); + iname = (int) atol(sname); + ivalue = (int) atol(svalue); + + if (iname >= 1 && iname <= MAX_CLASSES) + { + classinfo[iname].limit = ivalue; +// gi.dprintf("Class limit of %s = %d\n", classinfo[iname].name, ivalue); + } + else + { + gi.dprintf("WFConfig: Bad class #: %d\n", iname); + } + + } + + return 1; // normal exit +} + + +int config_motd() +{ + int i=0; + wf_game.motd[0][0] = 0; + wf_game.motd[1][0] = 0; + wf_game.motd[2][0] = 0; + +// gi.dprintf ("WFConfig: Setting message of the day...\n"); + + // read map names into array + while (!feof(fpconfig)) + { + cfgReadLineKeepAll(fpconfig, config_line, 160); + + //Skip blank lines + if (config_line[0] == 0) + continue; + // Stop if we hit terminator + if (Q_stricmp(config_line, "###") == 0) + break; + // Stop if we hit new section name + if (config_line[0] == '[') + break; + + //Use MOTD line if # lines not exceeded + if (i < MAX_MOTD_LINES) + { + //Don't exceed length + if (strlen( config_line) > MAX_MOTD_LENGTH) + { + gi.dprintf("Max MOTD length exceeded. Truncated to %s characters.\n", MAX_MOTD_LENGTH); + config_line [ MAX_MOTD_LENGTH ] = 0; + } + strncpy(wf_game.motd[i], config_line, MAX_MOTD_LENGTH ); + ++i; + } + else + { + gi.dprintf("Max MOTD lines = %d. Others ignored\n", MAX_MOTD_LINES); + } + } + + RemoveComments(config_line); + rtrim(config_line); + return 1; // normal exit +} + + +int config_banwords() +{ + int i=0; + + +// gi.dprintf ("WFConfig: Setting banned words...\n"); + + // read map names into array + while (!feof(fpconfig)) + { + cfgReadLine(fpconfig, config_line, 160); + lcase(config_line); + + //Skip blank lines + if (config_line[0] == 0) + continue; + // Stop if we hit terminator + if (Q_stricmp(config_line, "###") == 0) + break; + // Stop if we hit new section name + if (config_line[0] == '[') + break; + + //Use MOTD line if # lines not exceeded + if (i < MAX_BANWORDS) + { + //Don't exceed length + if (strlen( config_line) > MAX_BANWORDS_LENGTH) + { + gi.dprintf("Max MOTD length exceeded. Truncated to %s characters.\n", MAX_BANWORDS_LENGTH); + config_line [ MAX_BANWORDS_LENGTH ] = 0; + } + strncpy(wf_game.banwords[i], config_line, MAX_BANWORDS_LENGTH ); + ++i; + } + else + { + gi.dprintf("Max banned word lines = %d. Others ignored\n", MAX_BANWORDS); + } + } + + wf_game.banwords[i][0] = 0; + + return 1; // normal exit +} + +void wfflags_set(int mask) +{ + wfflags->value = (int)((int)wfflags->value | mask); +} + +void wfflags_clear(int mask) +{ + wfflags->value = (int)((int)wfflags->value & ~mask); +} + +int config_general() +{ + int i=0; + int lval; + char sname[160]; + char svalue[160]; + int retval; + + +// gi.dprintf ("WFConfig: General settings...\n"); + + // read map names into array + while (!feof(fpconfig)) + { + cfgReadLine(fpconfig, config_line, 160); + lcase(config_line); + + //Skip blank lines + if (config_line[0] == 0) + continue; + // Stop if we hit terminator + if (Q_stricmp(config_line, "###") == 0) + break; + // Stop if we hit new section name + if (config_line[0] == '[') + break; + + if (!getNameValue(config_line, sname, svalue)) + continue; //Skip bad name/value lines + + //Get general settings (wfflags replacement) + lval = (int) atol(svalue); + + if (Q_stricmp(sname, "friendly_fire") == 0) + { + if (lval) + wfflags_set(WF_ALLOW_FRIENDLY_FIRE); + else + wfflags_clear(WF_ALLOW_FRIENDLY_FIRE); + } + else if (Q_stricmp(sname, "frag_logging") == 0) + { + if (lval) + wfflags_set(WF_FRAG_LOGGING); + else + wfflags_clear(WF_FRAG_LOGGING); + } + else if (Q_stricmp(sname, "no_fort_respawn") == 0) + { + if (lval) + wfflags_set(WF_NO_FORT_RESPAWN); + else + wfflags_clear(WF_NO_FORT_RESPAWN); + } + else if (Q_stricmp(sname, "no_homing") == 0) + { + if (lval) + wfflags_set(WF_NO_HOMING); + else + wfflags_clear(WF_NO_HOMING); + } + else if (Q_stricmp(sname, "no_decoys") == 0) + { + if (lval) + wfflags_set(WF_NO_DECOYS); + else + wfflags_clear(WF_NO_DECOYS); + } + else if (Q_stricmp(sname, "no_flying") == 0) + { + if (lval) + wfflags_set(WF_NO_FLYING); + else + wfflags_clear(WF_NO_FLYING); + } + else if (Q_stricmp(sname, "decoy_pursue") == 0) + { + if (lval) + wfflags_set(WF_DECOY_PURSUE); + else + wfflags_clear(WF_DECOY_PURSUE); + } + else if (Q_stricmp(sname, "no_turret") == 0) + { + if (lval) + wfflags_set( WF_NO_TURRET); + else + wfflags_clear( WF_NO_TURRET); + } + else if (Q_stricmp(sname, "no_earthquakes") == 0) + { + if (lval) + wfflags_set( WF_NO_EARTHQUAKES); + else + wfflags_clear( WF_NO_EARTHQUAKES); + } + else if (Q_stricmp(sname, "no_grapple") == 0) + { + if (lval) + wfflags_set( WF_NO_GRAPPLE); + else + wfflags_clear( WF_NO_GRAPPLE); + } + else if (Q_stricmp(sname, "map_voting") == 0) + { + if (lval) + wfflags_set( WF_MAP_VOTE); + else + wfflags_clear( WF_MAP_VOTE); + } + else if (Q_stricmp(sname, "anarchy") == 0) + { + if (lval) + wfflags_set( WF_ANARCHY); + else + wfflags_clear( WF_ANARCHY); + } + else if (Q_stricmp(sname, "zoid_flagcap") == 0) + { + if (lval) + wfflags_set( WF_ZOID_FLAGCAP); + else + wfflags_clear( WF_ZOID_FLAGCAP); + } + else if (Q_stricmp(sname, "no_player_classes") == 0) + { + if (lval) + wfflags_set( WF_NO_PLAYER_CLASSES); + else + wfflags_clear( WF_NO_PLAYER_CLASSES); + } + else if (Q_stricmp(sname, "zbot_detect") == 0) + { + if (lval) + wfflags_set( WF_ZBOT_DETECT); + else + wfflags_clear( WF_ZBOT_DETECT); + } + else if (Q_stricmp(sname, "auto_team_balance") == 0) + { + if (lval) + wfflags_set( WF_AUTO_TEAM_BALANCE); + else + wfflags_clear( WF_AUTO_TEAM_BALANCE); + } + + else if (Q_stricmp(sname, "ref_password") == 0) + { + strncpy(wf_game.ref_password, svalue, MAX_GAMEINFO_LENGTH); + } + + else if (Q_stricmp(sname, "classdef") == 0) + { + strncpy(wf_game.classdef_name, svalue, MAX_GAMEINFO_LENGTH); + + //Now load the class definitions + if (wf_game.classdef_name[0] != '\0') + { + retval = LoadClassInfo(wf_game.classdef_name); + } + else + { + // gi.dprintf("CLASSDEF - Default Class Def Selected\n"); + retval = LoadClassInfo("team10.class"); + } + + if (retval == 0) + { + gi.dprintf("ERROR: Could not load class file in quake2\\wf directory\n"); + } + + + } + + else if (Q_stricmp(sname, "logfilename") == 0) + { + strncpy(wf_game.stdlog_name, svalue, MAX_GAMEINFO_LENGTH); + } + else if (Q_stricmp(sname, "weaponfilename") == 0) + { + strncpy(wf_game.weaponfile_name, svalue, MAX_GAMEINFO_LENGTH); + //Now load the weapon definition file + if (wf_game.weaponfile_name[0] != '\0') + { + LoadWeaponInfo(); + if (retval == 0) + { + gi.dprintf("ERROR: Could not weapon config file in quake2\\wf directory\n"); + } + } + + } + else if (Q_stricmp(sname, "floodertime") == 0) + { + wf_game.floodertime = lval; + } + else if (Q_stricmp(sname, "unbalanced_limit") == 0) + { + wf_game.unbalanced_limit = lval; + } + + else if (Q_stricmp(sname, "special_lights") == 0) + { + if (lval) + wfflags_set( WF_SPECIAL_LIGHTS); + else + wfflags_clear(WF_SPECIAL_LIGHTS); + } + else if (Q_stricmp(sname, "map_rotation") == 0) + { + if (lval == 0) + maplist.rotationflag = ML_ROTATE_SEQ; + else if (lval == 1) + maplist.rotationflag = ML_ROTATE_RANDOM; + else + gi.dprintf("WFConfig: Bad maplist rotation: %d. Must be 0 or 1.\n", lval); + + //gi.dprintf("WFCONFIG - Rotation = %d, [%s] %s\n", lval, svalue, config_line); + } + else + gi.dprintf("WFConfig: Invalid General Setting: %s\n", config_line); + + //gi.dprintf("WFConfig: sname=%s, wfflags=%d\n", sname, (int)wfflags->value); + + } + + + return 1; // normal exit +} + +int OpenConfigFile() +{ + char path[100]; + + config_line_no = 0; + + //Make sure we open file from the game directory + strcpy(path, gamedir->string); + +#if defined(_WIN32) || defined(WIN32) + strcat(path,"\\"); +#else + strcat(path,"/"); +#endif + + if (wfconfig->string && wfconfig->string[0] != 0) + { + strcat(path, wfconfig->string); + } + else + { + strcat(path, "wfserver.ini"); + } + + //Open file + fpconfig = WFOpenFile(NULL, path); + if (!fpconfig) // opened successfully? + { + gi.dprintf("Config [%s] not found!\n",path); + return 0; + } + else + { + gi.dprintf("Loading config file: %s\n",path); + return 1; + } +} + +void ClearConfig() +{ + wf_game.ref_password[0] = 0; + wf_game.ref_ent = NULL; + wf_game.game_halted = 0; + ClearMapList(); + +//Weapon Damage +wf_game.weapon_damage[WEAPON_BLASTER] = DAMAGE_BLASTER; +wf_game.weapon_damage[WEAPON_SHOTGUN] = DAMAGE_SHOTGUN; +wf_game.weapon_damage[WEAPON_SUPERSHOTGUN] = DAMAGE_SUPERSHOTGUN; +wf_game.weapon_damage[WEAPON_MACHINEGUN] = DAMAGE_MACHINEGUN; +wf_game.weapon_damage[WEAPON_CHAINGUN] = DAMAGE_CHAINGUN; +wf_game.weapon_damage[WEAPON_HYPERBLASTER] = DAMAGE_HYPERBLASTER; +wf_game.weapon_damage[WEAPON_ROCKETLAUNCHER] = DAMAGE_ROCKETLAUNCHER; +wf_game.weapon_damage[WEAPON_GRENADELAUNCHER] =DAMAGE_GRENADELAUNCHER; +wf_game.weapon_damage[WEAPON_RAILGUN] = DAMAGE_RAILGUN; +wf_game.weapon_damage[WEAPON_BFG] = DAMAGE_BFG; +wf_game.weapon_damage[WEAPON_NEEDLER] = DAMAGE_NEEDLER; +wf_game.weapon_damage[WEAPON_NAG] = DAMAGE_NAG; +wf_game.weapon_damage[WEAPON_TELSA] = DAMAGE_TELSA; +wf_game.weapon_damage[WEAPON_LIGHTNING] = DAMAGE_LIGHTNING; +wf_game.weapon_damage[WEAPON_NAILGUN] = DAMAGE_NAILGUN; +wf_game.weapon_damage[WEAPON_CLUSTERROCKET] = DAMAGE_CLUSTERROCKET; +wf_game.weapon_damage[WEAPON_MAGBOLTED] = DAMAGE_MAGBOLTED; +wf_game.weapon_damage[WEAPON_PELLET] = DAMAGE_PELLET; +wf_game.weapon_damage[WEAPON_PULSE] = DAMAGE_PULSE; +wf_game.weapon_damage[WEAPON_SHC] = DAMAGE_SHC; +wf_game.weapon_damage[WEAPON_FLAREGUN] = DAMAGE_FLAREGUN; +wf_game.weapon_damage[WEAPON_NAPALMMISSLE] = DAMAGE_NAPALMMISSLE; +wf_game.weapon_damage[WEAPON_FLAMETHROWER] = DAMAGE_FLAMETHROWER; +wf_game.weapon_damage[WEAPON_TRANQUILIZER] = DAMAGE_TRANQUILIZER; +wf_game.weapon_damage[WEAPON_INFECTEDDART] = DAMAGE_INFECTEDDART; +wf_game.weapon_damage[WEAPON_LASERSNIPER] = DAMAGE_LASERSNIPER; +wf_game.weapon_damage[WEAPON_ARMORDART] = DAMAGE_ARMORDART; +wf_game.weapon_damage[WEAPON_SHOTGUNCHOKE] = DAMAGE_SHOTGUNCHOKE; +wf_game.weapon_damage[WEAPON_SNIPERRIFLE] = DAMAGE_SNIPERRIFLE; +wf_game.weapon_damage[WEAPON_LRPROJECTILE] = DAMAGE_LRPROJECTILE; +wf_game.weapon_damage[WEAPON_SENTRYKILLER] = DAMAGE_SENTRYKILLER; +wf_game.weapon_damage[WEAPON_MEGACHAINGUN] = DAMAGE_MEGACHAINGUN; +wf_game.weapon_damage[WEAPON_TRANQUILDART] = DAMAGE_TRANQUILDART; +wf_game.weapon_damage[WEAPON_KNIFE] = DAMAGE_KNIFE; +wf_game.weapon_damage[WEAPON_AK47] = DAMAGE_AK47; +wf_game.weapon_damage[WEAPON_PISTOL] = DAMAGE_PISTOL; +wf_game.weapon_damage[WEAPON_STINGER] = DAMAGE_STINGER; +wf_game.weapon_damage[WEAPON_DISRUPTOR] = DAMAGE_DISRUPTOR; +wf_game.weapon_damage[WEAPON_ETF_RIFLE] = DAMAGE_ETF_RIFLE; +wf_game.weapon_damage[WEAPON_PLASMA_BEAM] = DAMAGE_PLASMA_BEAM; +wf_game.weapon_damage[WEAPON_ION_RIPPER] = DAMAGE_ION_RIPPER; +wf_game.weapon_damage[WEAPON_PHALANX] = DAMAGE_PHALANX; +wf_game.weapon_damage[WEAPON_FREEZER] = DAMAGE_FREEZER; + +//Weapon Speed +wf_game.weapon_speed[WEAPON_BLASTER] = SPEED_BLASTER; +wf_game.weapon_speed[WEAPON_SHOTGUN] = SPEED_SHOTGUN; +wf_game.weapon_speed[WEAPON_SUPERSHOTGUN] = SPEED_SUPERSHOTGUN; +wf_game.weapon_speed[WEAPON_MACHINEGUN] = SPEED_MACHINEGUN; +wf_game.weapon_speed[WEAPON_CHAINGUN] = SPEED_CHAINGUN; +wf_game.weapon_speed[WEAPON_HYPERBLASTER] = SPEED_HYPERBLASTER; +wf_game.weapon_speed[WEAPON_ROCKETLAUNCHER] = SPEED_ROCKETLAUNCHER; +wf_game.weapon_speed[WEAPON_GRENADELAUNCHER] =SPEED_GRENADELAUNCHER; +wf_game.weapon_speed[WEAPON_RAILGUN] = SPEED_RAILGUN; +wf_game.weapon_speed[WEAPON_BFG] = SPEED_BFG; +wf_game.weapon_speed[WEAPON_NEEDLER] = SPEED_NEEDLER; +wf_game.weapon_speed[WEAPON_NAG] = SPEED_NAG; +wf_game.weapon_speed[WEAPON_TELSA] = SPEED_TELSA; +wf_game.weapon_speed[WEAPON_LIGHTNING] = SPEED_LIGHTNING; +wf_game.weapon_speed[WEAPON_NAILGUN] = SPEED_NAILGUN; +wf_game.weapon_speed[WEAPON_CLUSTERROCKET] = SPEED_CLUSTERROCKET; +wf_game.weapon_speed[WEAPON_MAGBOLTED] = SPEED_MAGBOLTED; +wf_game.weapon_speed[WEAPON_PELLET] = SPEED_PELLET; +wf_game.weapon_speed[WEAPON_PULSE] = SPEED_PULSE; +wf_game.weapon_speed[WEAPON_SHC] = SPEED_SHC; +wf_game.weapon_speed[WEAPON_FLAREGUN] = SPEED_FLAREGUN; +wf_game.weapon_speed[WEAPON_NAPALMMISSLE] = SPEED_NAPALMMISSLE; +wf_game.weapon_speed[WEAPON_FLAMETHROWER] = SPEED_FLAMETHROWER; +wf_game.weapon_speed[WEAPON_TRANQUILIZER] = SPEED_TRANQUILIZER; +wf_game.weapon_speed[WEAPON_INFECTEDDART] = SPEED_INFECTEDDART; +wf_game.weapon_speed[WEAPON_LASERSNIPER] = SPEED_LASERSNIPER; +wf_game.weapon_speed[WEAPON_ARMORDART] = SPEED_ARMORDART; +wf_game.weapon_speed[WEAPON_SHOTGUNCHOKE] = SPEED_SHOTGUNCHOKE; +wf_game.weapon_speed[WEAPON_SNIPERRIFLE] = SPEED_SNIPERRIFLE; +wf_game.weapon_speed[WEAPON_LRPROJECTILE] = SPEED_LRPROJECTILE; +wf_game.weapon_speed[WEAPON_SENTRYKILLER] = SPEED_SENTRYKILLER; +wf_game.weapon_speed[WEAPON_MEGACHAINGUN] = SPEED_MEGACHAINGUN; +wf_game.weapon_speed[WEAPON_TRANQUILDART] = SPEED_TRANQUILDART; +wf_game.weapon_speed[WEAPON_KNIFE] = SPEED_KNIFE; +wf_game.weapon_speed[WEAPON_AK47] = SPEED_AK47; +wf_game.weapon_speed[WEAPON_PISTOL] = SPEED_PISTOL; +wf_game.weapon_speed[WEAPON_STINGER] = SPEED_STINGER; +wf_game.weapon_speed[WEAPON_DISRUPTOR] = SPEED_DISRUPTOR; +wf_game.weapon_speed[WEAPON_ETF_RIFLE] = SPEED_ETF_RIFLE; +wf_game.weapon_speed[WEAPON_PLASMA_BEAM] = SPEED_PLASMA_BEAM; +wf_game.weapon_speed[WEAPON_ION_RIPPER] = SPEED_ION_RIPPER; +wf_game.weapon_speed[WEAPON_PHALANX] = SPEED_PHALANX; +wf_game.weapon_speed[WEAPON_FREEZER] = SPEED_FREEZER; + +//Grenade damage +wf_game.grenade_damage[GRENADE_TYPE_NORMAL] = DAMAGE_GRENADE; +wf_game.grenade_damage[GRENADE_TYPE_LASERBALL] = DAMAGE_LASERBALL; +wf_game.grenade_damage[GRENADE_TYPE_GOODYEAR] = DAMAGE_GOODYEAR; +wf_game.grenade_damage[GRENADE_TYPE_PROXIMITY] = DAMAGE_PROXIMITY; +wf_game.grenade_damage[GRENADE_TYPE_FLASH] = DAMAGE_FLASH; +wf_game.grenade_damage[GRENADE_TYPE_CLUSTER] = DAMAGE_CLUSTER; +wf_game.grenade_damage[GRENADE_TYPE_EARTHQUAKE] = DAMAGE_EARTHQUAKE; +wf_game.grenade_damage[GRENADE_TYPE_TURRET] = DAMAGE_TURRET; +wf_game.grenade_damage[GRENADE_TYPE_NAPALM] = DAMAGE_NAPALM; +wf_game.grenade_damage[GRENADE_TYPE_CONCUSSION] = DAMAGE_CONCUSSION; +wf_game.grenade_damage[GRENADE_TYPE_NARCOTIC] = DAMAGE_NARCOTIC; +wf_game.grenade_damage[GRENADE_TYPE_PLAGUE] = DAMAGE_PLAGUE; +wf_game.grenade_damage[GRENADE_TYPE_MAGNOTRON] = DAMAGE_MAGNOTRON; +wf_game.grenade_damage[GRENADE_TYPE_SHOCK] = DAMAGE_SHOCK; +wf_game.grenade_damage[GRENADE_TYPE_PIPEBOMB] = DAMAGE_PIPEBOMB; +wf_game.grenade_damage[GRENADE_TYPE_SHRAPNEL] = DAMAGE_SHRAPNEL; +wf_game.grenade_damage[GRENADE_TYPE_FLARE] = DAMAGE_FLARE; +wf_game.grenade_damage[GRENADE_TYPE_SLOW] = DAMAGE_SLOW; +wf_game.grenade_damage[GRENADE_TYPE_PLAGUETIME] = DAMAGE_PLAGUETIME; +wf_game.grenade_damage[GRENADE_TYPE_LASERCUTTER] = DAMAGE_LASERCUTTER; +wf_game.grenade_damage[GRENADE_TYPE_TESLA] = DAMAGE_TESLA; +wf_game.grenade_damage[GRENADE_TYPE_GAS] = DAMAGE_GAS; + +//Grenade speed +wf_game.grenade_speed[GRENADE_TYPE_NORMAL] = SPEED_GRENADE; +wf_game.grenade_speed[GRENADE_TYPE_LASERBALL] = SPEED_LASERBALL; +wf_game.grenade_speed[GRENADE_TYPE_GOODYEAR] = SPEED_GOODYEAR; +wf_game.grenade_speed[GRENADE_TYPE_PROXIMITY] = SPEED_PROXIMITY; +wf_game.grenade_speed[GRENADE_TYPE_FLASH] = SPEED_FLASH; +wf_game.grenade_speed[GRENADE_TYPE_CLUSTER] = SPEED_CLUSTER; +wf_game.grenade_speed[GRENADE_TYPE_EARTHQUAKE] = SPEED_EARTHQUAKE; +wf_game.grenade_speed[GRENADE_TYPE_TURRET] = SPEED_TURRET; +wf_game.grenade_speed[GRENADE_TYPE_NAPALM] = SPEED_NAPALM; +wf_game.grenade_speed[GRENADE_TYPE_CONCUSSION] = SPEED_CONCUSSION; +wf_game.grenade_speed[GRENADE_TYPE_NARCOTIC] = SPEED_NARCOTIC; +wf_game.grenade_speed[GRENADE_TYPE_PLAGUE] = SPEED_PLAGUE; +wf_game.grenade_speed[GRENADE_TYPE_MAGNOTRON] = SPEED_MAGNOTRON; +wf_game.grenade_speed[GRENADE_TYPE_SHOCK] = SPEED_SHOCK; +wf_game.grenade_speed[GRENADE_TYPE_PIPEBOMB] = SPEED_PIPEBOMB; +wf_game.grenade_speed[GRENADE_TYPE_SHRAPNEL] = SPEED_SHRAPNEL; +wf_game.grenade_speed[GRENADE_TYPE_FLARE] = SPEED_FLARE; +wf_game.grenade_speed[GRENADE_TYPE_SLOW] = SPEED_SLOW; +wf_game.grenade_speed[GRENADE_TYPE_PLAGUETIME] = SPEED_PLAGUETIME; +wf_game.grenade_speed[GRENADE_TYPE_LASERCUTTER] = SPEED_LASERCUTTER; +wf_game.grenade_speed[GRENADE_TYPE_TESLA] = SPEED_TESLA; +wf_game.grenade_speed[GRENADE_TYPE_GAS] = SPEED_GAS; +} + +//Load the weapon info from a file +void LoadWeaponInfo() +{ + FILE *fp; + int i=0; + unsigned char str[200]; + char path[100]; + char s1[60]; + char s2[60]; + char s3[60]; + char s4[60]; + long i2, i3, i4; + + //Make sure we open class def from the game directory + strcpy(path, gamedir->string); +#if defined(_WIN32) || defined(WIN32) + strcat(path,"\\"); +#else + strcat(path,"/"); +#endif + strcat(path,wf_game.weaponfile_name); + + //Open file + fp = WFOpenFile(NULL, path); + if (!fp) // opened successfully? + { + gi.dprintf("WeaponConfig: Could not open file: %s\n",path); + } + + readline(fp, str, 200); + if (str[0] == -1) str[0] = 0; + RemoveComments(str); + + while (!feof(fp)) + { + /*Parse components:, ie "W 1 100 800" where + + 1 = "W" for weapon or "G" for grenade + 2 = weapon or grenade id number + 3 = damage + 4 = speed + */ + get4Chunks( str, s1, s2, s3, s4); + + //What kind of line? + if (s1[0]) + { + sscanf(s2, "%d", &i2); //id number + sscanf(s3, "%d", &i3); //damage + sscanf(s4, "%d", &i4); //speed + + if (s1[0] == 'W' && i2 >= 0 && i2 <= WEAPON_COUNT) + { + wf_game.weapon_damage[i2] = i3; + wf_game.weapon_speed[i2] = i4; + + } + if (s1[0] == 'G' && i2 >= 0 && i2 <= GRENADE_TYPE_COUNT) + { + wf_game.grenade_damage[i2] = i3; + wf_game.grenade_speed[i2] = i4; + + } + } + + //Read next line + readline(fp, str, 200); + if (str[0] == -1) str[0] = 0; + RemoveComments(str); + } + + WFCloseFile(NULL, fp); + gi.dprintf("Loaded custom weapons config file: %s\n",path); +} + + + +int ProcessConfigFile() +{ + + if (!OpenConfigFile()) + return false; + + ClearConfig(); + + cfgReadLine(fpconfig, config_line, 200); + + while (!feof(fpconfig)) + { + //Did we find a section header? + if (config_line[0] == '[') + { + lcase(config_line); + + //What section? + if (Q_stricmp(config_line, "[maplist]") == 0) + { + config_map_list(); + } + else if (Q_stricmp(config_line, "[scoring]") == 0) + { + config_scoring(); + } + else if (Q_stricmp(config_line, "[classlimits]") == 0) + { + config_classlimits(); + } + else if (Q_stricmp(config_line, "[general]") == 0) + { + config_general(); + } + else if (Q_stricmp(config_line, "[motd]") == 0) + { + config_motd(); + } + else if (Q_stricmp(config_line, "[banwords]") == 0) + { + config_banwords(); + } + else + { + gi.dprintf("Server Config: Bad section at line %d: '%s'\n", config_line_no, config_line); + cfgReadLine(fpconfig, config_line, 200); + } + } + + //Skip blank lines + else if (config_line[0] == 0) + { + cfgReadLine(fpconfig, config_line, 200); + } + + //Skip everything after file terminator + else if (Q_stricmp(config_line, "###") == 0) + break; //skip rest of file + + else + { + gi.dprintf("Server Config: Line %d not in a section: %s\n", config_line_no, config_line); + cfgReadLine(fpconfig, config_line, 200); + } + } + WFCloseFile(NULL, fpconfig); +} diff --git a/wf_decoy.c b/wf_decoy.c new file mode 100644 index 0000000..6782c77 --- /dev/null +++ b/wf_decoy.c @@ -0,0 +1,652 @@ +/*============================================================================== +The Weapons Factory - +Decoy Mod +Original code by Gregg Reno +==============================================================================*/ + +/* NOTE - Now that eraser bots are in the code, decoys do not work as they used to. +They will no longer be active players, but rather just animated models. +They will not pursue or shoot. +*/ + +#include "g_local.h" +#include "m_player.h" +#include "wf_classmgr.h" + +qboolean IsCyborg (edict_t *ent); + +static int sound_idle; +static int sound_sight1; +static int sound_sight2; +static int sound_pain; +static int sound_death; +static int sound_on; + +edict_t *SV_TestEntityPosition (edict_t *ent); + +/* +============= +DecoyFit - see if the decoy will fit where the player is +creating it. + + -taken from m_move.c, M_CheckBottom function +============= +*/ +qboolean DecoyFit (edict_t *ent, edict_t *owner) +{ +// vec3_t mins, maxs, start, stop; + vec3_t mins, maxs; + trace_t trace; + + VectorAdd (ent->s.origin, ent->mins, mins); + VectorAdd (ent->s.origin, ent->maxs, maxs); + + //See if the mins,maxs or origin are in something solid + if (gi.pointcontents (mins) == CONTENTS_SOLID) return false; + if (gi.pointcontents (maxs) == CONTENTS_SOLID) return false; + if (gi.pointcontents (ent->s.origin) == CONTENTS_SOLID) return false; + + //Trace line from these two points to see if anything is in-between + trace = gi.trace(mins, ent->s.origin, ent->s.origin, maxs, ent, MASK_MONSTERSOLID ); + if (trace.fraction != 1.0) // 1.0 = nothing in between + return false; + + //Trace line from decoy origin to owner origin to see if anything is in-between + trace = gi.trace(ent->s.origin, ent->s.origin, ent->s.origin, owner->s.origin, ent, MASK_MONSTERSOLID ); + if (trace.fraction != 1.0) // 1.0 = nothing in between + return false; + + if (SV_TestEntityPosition (ent)) + return false; + + return true; +} + +#define DECOY_FIRST_FRAME 0 +#define DECOY_LAST_FRAME 39 + +void Decoy_Think(edict_t *self) +{ + ++self->s.frame; + + if (self->s.frame < DECOY_FIRST_FRAME) + self->s.frame = DECOY_FIRST_FRAME; + else if (self->s.frame > DECOY_LAST_FRAME) + self->s.frame = DECOY_FIRST_FRAME; + + self->nextthink = level.time + 0.1; +} + + + +// STAND frames +void decoy_idle (edict_t *self) +{ + if (random() > 0.8) + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void decoy_stand (edict_t *self); + +mframe_t decoy_frames_stand1 [] = +{ + ai_stand, 0, decoy_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t decoy_move_stand1 = {FRAME_stand01, FRAME_stand40, decoy_frames_stand1, decoy_stand}; + + +void decoy_stand (edict_t *self) + { + self->monsterinfo.currentmove = &decoy_move_stand1; + } + + +// TAUNT frames +void decoy_taunt (edict_t *self); +mframe_t decoy_frames_taunt1 [] = +{ + ai_stand, 0, decoy_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t decoy_move_taunt1 = {FRAME_taunt01, FRAME_taunt17, decoy_frames_taunt1, decoy_taunt}; +void decoy_taunt (edict_t *self) + { + self->monsterinfo.currentmove = &decoy_move_taunt1; + } + +// Flipoff frames +void decoy_flip (edict_t *self); +mframe_t decoy_frames_flip1 [] = +{ + ai_stand, 0, decoy_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t decoy_move_flip1 = {FRAME_flip01, FRAME_flip12, decoy_frames_flip1, decoy_flip}; +void decoy_flip (edict_t *self) + { + self->monsterinfo.currentmove = &decoy_move_flip1; + } + +// Salute frames +void decoy_salute (edict_t *self); +mframe_t decoy_frames_salute1 [] = +{ + ai_stand, 0, decoy_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL +}; +mmove_t decoy_move_salute1 = {FRAME_salute01, FRAME_salute11, decoy_frames_salute1, decoy_salute}; +void decoy_salute (edict_t *self) + { + self->monsterinfo.currentmove = &decoy_move_salute1; + } + + +// Wave frames +void decoy_wave (edict_t *self); +mframe_t decoy_frames_wave1 [] = +{ + ai_stand, 0, decoy_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL +}; +mmove_t decoy_move_wave1 = {FRAME_wave01, FRAME_wave11, decoy_frames_wave1, decoy_wave}; +void decoy_wave (edict_t *self) + { + self->monsterinfo.currentmove = &decoy_move_wave1; + } + +// Point frames +void decoy_point (edict_t *self); +mframe_t decoy_frames_point1 [] = +{ + ai_stand, 0, decoy_idle, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + ai_stand, 0, NULL, + + ai_stand, 0, NULL, + ai_stand, 0, NULL +}; +mmove_t decoy_move_point1 = {FRAME_point01, FRAME_point12, decoy_frames_point1, decoy_point}; +void decoy_point (edict_t *self) + { + self->monsterinfo.currentmove = &decoy_move_point1; + } +// +// RUN frames +// +void decoy_run (edict_t *self); +mframe_t decoy_frames_run [] = +{ + ai_run, 10, NULL, + ai_run, 11, NULL, + ai_run, 11, NULL, + ai_run, 16, NULL, + ai_run, 10, NULL, + ai_run, 15, NULL +}; +mmove_t decoy_move_run = {FRAME_run1, FRAME_run6, decoy_frames_run, decoy_run}; +void decoy_run (edict_t *self) + { + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &decoy_move_stand1; + return; + } + + self->monsterinfo.currentmove = &decoy_move_run; + } + + +// +// PAIN frames +// +mframe_t decoy_frames_pain1 [] = +{ + ai_move, -3, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL +}; +mmove_t decoy_move_pain1 = {FRAME_pain101, FRAME_pain104, decoy_frames_pain1, decoy_run}; + +void decoy_pain (edict_t *self, edict_t *other, float kick, int damage) + { + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3; + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &decoy_move_pain1; + } + + +// +// ATTACK frames +// +static int blaster_flash [] = {MZ2_SOLDIER_BLASTER_1, MZ2_SOLDIER_BLASTER_2, MZ2_SOLDIER_BLASTER_3, MZ2_SOLDIER_BLASTER_4, MZ2_SOLDIER_BLASTER_5, MZ2_SOLDIER_BLASTER_6, MZ2_SOLDIER_BLASTER_7, MZ2_SOLDIER_BLASTER_8}; +static int shotgun_flash [] = {MZ2_SOLDIER_SHOTGUN_1, MZ2_SOLDIER_SHOTGUN_2, MZ2_SOLDIER_SHOTGUN_3, MZ2_SOLDIER_SHOTGUN_4, MZ2_SOLDIER_SHOTGUN_5, MZ2_SOLDIER_SHOTGUN_6, MZ2_SOLDIER_SHOTGUN_7, MZ2_SOLDIER_SHOTGUN_8}; +static int machinegun_flash [] = {MZ2_SOLDIER_MACHINEGUN_1, MZ2_SOLDIER_MACHINEGUN_2, MZ2_SOLDIER_MACHINEGUN_3, MZ2_SOLDIER_MACHINEGUN_4, MZ2_SOLDIER_MACHINEGUN_5, MZ2_SOLDIER_MACHINEGUN_6, MZ2_SOLDIER_MACHINEGUN_7, MZ2_SOLDIER_MACHINEGUN_8}; + +void decoy_fire (edict_t *self, int flash_number) + { + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + vec3_t dir; + vec3_t end; + float r, u; + int flash_index; + + flash_index = shotgun_flash[flash_number]; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource (self->s.origin, monster_flash_offset[flash_index], forward, right, start); + + if (flash_number == 5 || flash_number == 6) + { + VectorCopy (forward, aim); + } + else + { + VectorCopy (self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract (end, start, aim); + vectoangles (aim, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*1000; + u = crandom()*500; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + VectorSubtract (end, start, aim); + VectorNormalize (aim); + } + + monster_fire_shotgun (self, start, aim, 2, 1, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SHOTGUN_COUNT, flash_index, MOD_SHOTGUN); + } + +// Fire weapon +void decoy_fire1 (edict_t *self) +{ + decoy_fire (self, 0); +} + +mframe_t decoy_frames_attack1 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, decoy_fire1, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t decoy_move_attack1 = {FRAME_attack1, FRAME_attack8, decoy_frames_attack1, decoy_run}; + +void decoy_attack(edict_t *self) + { + self->monsterinfo.currentmove = &decoy_move_attack1; + } + +// +// SIGHT frames +// + +void decoy_sight(edict_t *self, edict_t *other) + { + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); + } + +// +// DEATH sequence +// + +void decoy_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) + { + // int n; + + if (self->deadflag == DEAD_DEAD) + return; + + // regular death + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + + //do a BFG kind of explosion where the decoy was + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_EXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + //Clear pointer of owner + self->creator->decoy = NULL; + + //Remove entity instead of playing death sequence + G_FreeEdict (self); + + } + + +// +// SPAWN +// +qboolean spawn_decoy (edict_t *owner) +{ + edict_t *self; + vec3_t forward; + char modelname[64]; + char weaponname[64]; + int classnum; + + self = G_Spawn(); + + // Place decoy 100 units forward of our position + AngleVectors(owner->client->v_angle, forward, NULL, NULL); + VectorMA(owner->s.origin, 100, forward, self->s.origin); + + //set the team + self->wf_team = owner->client->resp.ctf_team; + + //Use same model and skin as the person creating decoy + self->model = owner->model; + + classnum = owner->client->pers.player_class; + sprintf(modelname, "wfactory/models/decoys/%s/tris.md2", classinfo[classnum].model_name); + sprintf(weaponname, "players/%s/weapon.md2",classinfo[classnum].model_name); + self->s.modelindex = gi.modelindex (modelname); + self->s.modelindex2 = gi.modelindex (weaponname); + self->s.skinnum = classinfo[classnum].decoyskin; + + self->s.frame = DECOY_FIRST_FRAME; + self->think = Decoy_Think; + self->nextthink = level.time + 0.1; + + //Set the skin color + + //red team skin number is 1 higher than blue + if (self->wf_team == CTF_TEAM1) //team 1 is red + { + ++self->s.skinnum; + } + + //test stuff for other decoy settings +/* + if ((int)wfflags->value & WF_TMP3) + { + self->s.skinnum = 0; //team 2 is blue + self->s.modelindex = gi.modelindex ("wfactory/models/decoys/crakhor/tris.md2"); + self->s.modelindex2 = gi.modelindex ("players/crakhor/weapon.md2"); + } +*/ + self->s.effects = 0; + self->s.frame = 0; + self->classname = "decoy"; + + self->monsterinfo.scale = MODEL_SCALE; + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->clipmask = MASK_PLAYERSOLID; + self->takedamage = DAMAGE_AIM; + + self->mass = 100; + self->pain = decoy_pain; + self->die = decoy_die; + self->monsterinfo.stand = decoy_stand; + self->monsterinfo.walk = NULL; + self->monsterinfo.run = decoy_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = decoy_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = decoy_sight; + + //Dont attack anything to start with + //self->monsterinfo.aiflags & AI_GOOD_GUY; + + //Stay in one spot or pursue depending on wfflag setting + if ((int)wfflags->value & WF_DECOY_PURSUE) + self->monsterinfo.aiflags = 0; + else + self->monsterinfo.aiflags &= AI_STAND_GROUND; + + self->monsterinfo.sight = decoy_sight; + //newdecoy for proxies etc if think ever gets fixed remove this + self->svflags |= SVF_MONSTER; + self->s.renderfx |= RF_FRAMELERP; + self->max_health = self->health; + + //Set up sounds + sound_idle = gi.soundindex ("soldier/solidle1.wav"); + sound_sight1 = gi.soundindex ("soldier/solsght1.wav"); + sound_sight2 = gi.soundindex ("soldier/solsrch1.wav"); + sound_pain = gi.soundindex ("soldier/solpain1.wav"); + + //Set up on/off sounds + if (IsFemale (owner)) + { + sound_death = gi.soundindex ("fdecoyof.wav"); + sound_on = gi.soundindex ("fdecoyon.wav"); + } + else if (IsCyborg (owner)) + { + sound_death = gi.soundindex ("cdecoyof.wav"); + sound_on = gi.soundindex ("cdecoyon.wav"); + } + else + { + sound_death = gi.soundindex ("mdecoyof.wav"); + sound_on = gi.soundindex ("mdecoyon.wav"); + } + + //Temporary - replease sounds with ones from Q2 + //sound_death = gi.soundindex ("misc/keyuse.wav"); + //sound_on = gi.soundindex ("soldier/solatck1.wav"); + + self->health = 30; + self->max_health = 30; + self->gib_health = -30; + + if (wfdebug) + { + //self->health = 200; + //self->max_health = 200; + } + + // Face the decoy the same direction as player + self->s.angles[PITCH] = owner->s.angles[PITCH]; + self->s.angles[YAW] = owner->s.angles[YAW]; + self->s.angles[ROLL] = owner->s.angles[ROLL]; + + // See if the decoy will fit + if (DecoyFit(self, owner) == false) + { + G_FreeEdict(self); + safe_cprintf (owner, PRINT_HIGH, "Decoy won't fit. Try aiming a little higher.\n"); + return false; + } + + //Link two entities together + owner->decoy = self; //for the owner, this is a pointer to the decoy + self->creator = owner; //for the decoy, this is a pointer to the owner + + gi.linkentity (self); + + // First animation sequence + self->monsterinfo.stand (self); + + //Let monster code control this decoy + //REMOVED FOR ERASER + //walkmonster_start (self); + + //Play the startup sound + gi.sound (self, CHAN_VOICE, sound_on, 1, ATTN_NORM, 0); + + //Reduce cell count + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + owner->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= DECOY_CELLS; + + return true; +} + + +// SP_Decoy - Handle DECOY command +void SP_Decoy(edict_t *self) { + + //See if we should decoy turn it on or off + char *string; + int turnon; + + if ((int)wfflags->value & WF_NO_DECOYS) + { + safe_cprintf (self, PRINT_HIGH, "Sorry, Decoys are DISABLED on this server.\n"); + return; + } + + string=gi.args(); + + if (Q_stricmp ( string, "on") == 0) + turnon = true; + else if (Q_stricmp ( string, "off") == 0) + turnon = false; + else { //toggle status + if (self->decoy) turnon = false; + else turnon = true; + } + +//gi.dprintf("Decoy on = %d, decoy ent = %d\n", turnon, self->decoy); + + //If they want to turn it on and it's already on, return + if ( (turnon == true) && (self->decoy) ) return; + + //If they want to turn it off and it's already off, return + if ( (turnon == false) && !(self->decoy) ) return; + + //Remove decoy if it exists + if ( self->decoy ) { + gi.sound (self->decoy, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + G_FreeEdict(self->decoy); + self->decoy = NULL; + safe_cprintf (self, PRINT_HIGH, "Decoy destroyed.\n"); + return; + } + + //Cant create decoy in observer/spectator mode + if (self->solid == SOLID_NOT) + { + safe_cprintf(self, PRINT_HIGH, "Can't create decoy in spectator mode (nice try!).\n"); + return; + } + + //Create decoy if you have enough cells + if (self->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < DECOY_CELLS) + { + safe_cprintf(self, PRINT_HIGH, "Not enough cells for decoy.\n"); + return; + } + if (spawn_decoy(self)) + safe_cprintf (self, PRINT_HIGH, "Decoy created.\n"); + } diff --git a/wf_earthquake.c b/wf_earthquake.c new file mode 100644 index 0000000..1780ce2 --- /dev/null +++ b/wf_earthquake.c @@ -0,0 +1,126 @@ +/*============================================================================== +The Weapons Factory - +Earthquake Grenade Functions +Original code by ?? +Modified by Gregg Reno +==============================================================================*/ +#include "g_local.h" + +void wf_earthquake (edict_t *owner); + +static void earthquake_explode (edict_t *ent) + +{ + vec3_t origin; + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + if ((int)wfflags->value & WF_NO_EARTHQUAKES) + safe_cprintf(ent->owner, PRINT_HIGH, "Earthquakes are disabled on this server.\n"); + else + wf_earthquake (ent);//start the earthquake + + T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, MOD_WF_EARTHQUAKE); + + 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_PVS); + + G_FreeEdict (ent); +} + +static void earthquake_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + //ent->enemy = other; + earthquake_explode (ent); +} + + +void fire_earthquake_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/grenade2/tris.md2"); + grenade->owner = self; + + grenade->nextthink = level.time + timer; + grenade->think = earthquake_explode; + grenade->touch = earthquake_touch; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "earthquake grenade"; + grenade->spawnflags = 1; + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + //set the team + grenade->wf_team = self->client->resp.ctf_team; + + if (timer <= 0.0) + earthquake_explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } + + +} + diff --git a/wf_feign.c b/wf_feign.c new file mode 100644 index 0000000..b806714 --- /dev/null +++ b/wf_feign.c @@ -0,0 +1,109 @@ +/***************************************************************** + + Feign source code - by Acrid-, acridcola@hotmail.com + + .............................................................. + + This file is Copyright(c) 1999, Acrid-, All Rights Reserved. + + .............................................................. + + + Should you decide to release a modified version of the Feign, you + MUST include the following text (minus the BEGIN and END lines) in + the documentation for your modification, and also on all web pages + related to your modification, should they exist. + + --- BEGIN --- + + The Feign and related Feign code is a product of Acrid- designed + for Weapons Factory, and is available as part of the Weapons Factory + Source Code or a seperate tutorial. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Acrid- at: + acridcola@hotmail.com + + --- END --- + + have fun, + + Acrid- + + *****************************************************************/ +#include "g_local.h" +//Feign code by Acrid +// +// last things, stop rapid fire weapons from continued fire after feign off, +// maybe turn off pain sounds when taking damage + +void feign_on (edict_t *ent) +{ + static int i; + i = (i+1)%3; + + //Take some damage + T_Damage (ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 10, 10, DAMAGE_NO_ARMOR, MOD_FEIGN); + + VectorClear (ent->avelocity); + ent->takedamage = DAMAGE_YES; + ent->movetype = MOVETYPE_TOSS;//needed _none so pain doesnt start animation//solved elsewhere + ent->s.modelindex2 = 0; // remove linked weapon model + ent->s.angles[0] = 0; + ent->s.angles[2] = 0; + ent->s.sound = 0; + ent->client->weapon_sound = 0; + ent->maxs[2] = -8; + ent->viewheight = 0; + + /* set knockback flag so grenades and other weapons + with knockback dont move players view + used in T_damage code in g_combat.c */ + ent->flags |= FL_NO_KNOCKBACK; + + // stop running/footsteps + VectorClear (ent->velocity); + + //newgrap 5/99 + if (Is_Grappling(ent->client)) + { + CTFPlayerResetGrapple2(ent); + } + + //dont keep firing/no model to fire/model removed from player view + ent->client->pers.lastweapon = ent->client->pers.weapon; + ent->client->pers.weapon = NULL;// needed? + ent->client->ps.gunindex = 0; + + // start a random animation + switch (i) + { + case 0: + ent->s.frame = FRAME_death101-1; + ent->client->anim_end = FRAME_death106; + break; + case 1: + ent->s.frame = FRAME_death201-1; + ent->client->anim_end = FRAME_death206; + break; + case 2: + ent->s.frame = FRAME_death301-1; + ent->client->anim_end = FRAME_death308; + break; + } + gi.sound (ent, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0); +} +void feign_off(edict_t *ent) +{ + + //relink weapon model + ent->s.modelindex2 = 255; + //clear knockback flag + ent->flags &= ~FL_NO_KNOCKBACK; + //bring back player view weapon + ent->client->pers.weapon = ent->client->pers.lastweapon; + + if (ent->client->pers.weapon) + ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); + +} \ No newline at end of file diff --git a/wf_fileio.c b/wf_fileio.c new file mode 100644 index 0000000..f11f48d --- /dev/null +++ b/wf_fileio.c @@ -0,0 +1,144 @@ +// +// fileio.c -- file access routines +// +// 1/98 - L. Allan Campbell (Geist) +// + +// INCLUDES ///////////////////////////////////////////////// + +#include "g_local.h" + + + +// FUNCTIONS //////////////////////////////////////////////// + +// +// WFOpenFile +// +// Opens a file for reading. This function will probably need +// a major overhaul in future versions so that it will handle +// writing, appending, etc. +// +// Args: +// ent - entity (client) to print diagnostic messages to. +// filename - name of file to open. +// +// Return: file handle of open file stream. +// Returns NULL if file could not be opened. +// +FILE *WFOpenFile(edict_t *ent, char *filename) +{ + FILE *fp = NULL; + + if ((fp = fopen(filename, "r")) == NULL) // test to see if file opened + { + // file did not load + gi.dprintf ("Could not open file \"%s\".\n", filename); + return NULL; + } + + return fp; +} + + +// +// WFCloseFile +// +// Closes a file that was previously opened with WFOpenFile(). +// +// Args: +// ent - entity (client) to print diagnostic messages to. +// fp - file handle of file stream to close. +// +// Return: (none) +// +void WFCloseFile(edict_t *ent, FILE *fp) +{ + if (fp) // if the file is open + { + fclose(fp); + } + else // no file is opened + gi.dprintf("ERROR -- WFCloseFile() exception.\n"); +} + + +// FUNCTIONS //////////////////////////////////////////////// + +void fixline(char *str) +{ + int i; + i = 0; + while (str[i] != 0) + { + if (str[i] >=128) str[i] = 0; + if (str[i] < 32 && str[i] != '[' && str[i] != ']') str[i] = 0; +//gi.dprintf("<%d>", str[i]); + ++i; + } +} + +//Convert string to lower case +void lcase(char *str) +{ + int i; + i = 0; + while (str[i] != 0) + { + + str[i] = tolower(str[i]); + ++i; + } +} + +//Read a line from a file +void readline(FILE *file, char *str, int max) +{ + int i; + int enough; + unsigned char c; + + i = 0; + enough = 0; + while (i < max && !feof(file) && enough == 0) + { + c = fgetc(file); + if (c >= ' ') str[i++] = c; + if (c == 13 || c == 10) enough = 1; +//gi.dprintf("{%d}",c); + + } + str[i] = 0; + + --i; + //now trim back the white space + while (i >= 0 && str[i] == ' ') + { + str[i] = 0; + --i; + } +//gi.dprintf("ReadLine: [%s]\n", str); +} + +void OldReadLine( FILE *fp, char *buffer, int maxlen) +{ + int curlen; + char c; + + curlen = 0; + + //Read two lines from file + while (!feof(fp)) + { + c = fgetc(fp); +//gi.dprintf("{%d}",c); + + if ((c == 13) || (c == 10)) break; + if (curlen > maxlen) break; + + *buffer = c; + ++buffer; + ++curlen; + } + *buffer = '\0'; +} diff --git a/wf_fileio.h b/wf_fileio.h new file mode 100644 index 0000000..a0f3ee7 --- /dev/null +++ b/wf_fileio.h @@ -0,0 +1,9 @@ +// +// fileio.h +// + +// PROTOTYPES /////////////////////////////////////////////// + +FILE *WFOpenFile(edict_t *ent, char *filename); +void WFCloseFile(edict_t *ent, FILE *fp); +void ReadLine( FILE *fp, char *buffer, int maxlen); diff --git a/wf_flagcap.c b/wf_flagcap.c new file mode 100644 index 0000000..08bb680 --- /dev/null +++ b/wf_flagcap.c @@ -0,0 +1,453 @@ +/*============================================================================== +The Weapons Factory - +Team Fortress Style Flag Capture Functions +Original code by Gregg Reno +==============================================================================*/ +#include "g_local.h" +#include "stdlog.h" // StdLog - Mark Davies + +extern gitem_t *flag1_item; +extern gitem_t *flag2_item; +extern ctfgame_t ctfgame; + +void CTFResetFlags(void); +void CTFResetFlag(int ctf_team); +void WFRemoveDisguise(edict_t *ent); +int PlayerChangeScore(edict_t *self, int points); + +qboolean WFUnbalanced_Teams(edict_t *flag, edict_t *player) +{ + int team_limit; + int flagteamcount = 0, playerteamcount = 0; + int i; + int diff; + edict_t *target; + int flagteam; + + team_limit = wf_game.unbalanced_limit; + + // figure out what team this flag is + if (strcmp(flag->classname, "item_flag_team1") == 0) + flagteam = CTF_TEAM1; + else if (strcmp(flag->classname, "item_flag_team2") == 0) + flagteam = CTF_TEAM2; + else + flagteam = 0; + + + //Is any limit set? + if (team_limit <= 0) + { +//gi.dprintf("No Limit Set\n"); + return false; + } + + //Teams must be set + if ((flagteam == 0) || (player->wf_team == 0)) + { +//gi.dprintf("Someone doesn't have their wf_team flag set: %d, %d\n", +// flagteam, player->wf_team ); + return false; + } + + //Must be on different teams + if (flagteam == player->wf_team == 0) + + //Count players on each team + for (i = 1; i <= maxclients->value; i++) + { + target = &g_edicts[i]; + + if (!target->inuse) + continue; + + if (target->wf_team == flagteam ) + flagteamcount++; + + if (target->wf_team == player->wf_team) + playerteamcount++; + } + + diff = playerteamcount - flagteamcount; +//gi.dprintf("Diff=%d, playercount = %d, flagcount = %d, team limit = %d\n", +// diff, playerteamcount,flagteamcount,team_limit); + + if (diff >= team_limit) + { + safe_cprintf(player, PRINT_HIGH, "Sorry, you can't pick up flag. Teams are not balanced.\n"); + return true; + } + else + return false; +} + +qboolean WFPickup_Flag(edict_t *ent, edict_t *other) +{ + int ctf_team; + gitem_t *flag_item, *enemy_flag_item; + + // figure out what team this flag is + if (strcmp(ent->classname, "item_flag_team1") == 0) + { + ctf_team = CTF_TEAM1; + } + else if (strcmp(ent->classname, "item_flag_team2") == 0) + { + ctf_team = CTF_TEAM2; + } + else + { + safe_cprintf(other, PRINT_HIGH, "Don't know what team the flag is on.\n"); + return false; + } + + // same team, if the flag at base, check to he has the enemy flag + if (ctf_team == CTF_TEAM1) + { + flag_item = flag1_item; + enemy_flag_item = flag2_item; + } + else + { + flag_item = flag2_item; + enemy_flag_item = flag1_item; + } + + //Cant create decoy in observer/spectator mode + if (other->solid == SOLID_NOT) + { + safe_cprintf(other, PRINT_HIGH, "Nice try, but observers can't pick up the flag!.\n"); + return false; + } + + + if (other->client->resp.ctf_team != CTF_TEAM1 && + other->client->resp.ctf_team != CTF_TEAM2) + { + safe_cprintf(other, PRINT_HIGH, "I don't know how you did it, but you don't have a team!.\n"); + return false; + } + + //Note - you can't pick up your own flag - just the enemy's + if (ctf_team != other->client->resp.ctf_team) + { + + // hey, its not our flag, pick it up + if (PlayerChangeScore(other,CTF_FLAG_BONUS)) + { + gi.bprintf(PRINT_HIGH, "%s got the %s flag!\n", + other->client->pers.netname, CTFTeamName(ctf_team)); + + // Log Flag Pickup - MarkDavies + sl_LogScore( &gi, + other->client->pers.netname, + NULL, + "F Pickup", + -1, + CTF_FLAG_BONUS); + } + + if (ctf_team == CTF_TEAM1) + flag1dropped = 0; //clear the dropped flag flag + else if (ctf_team == CTF_TEAM2) + flag2dropped = 0; + + other->client->pers.inventory[ITEM_INDEX(flag_item)] = 1; + other->client->resp.ctf_flagsince = level.time; + + //Remove player disguise + if (other->disguised) WFRemoveDisguise(other); + + // pick up the flag + // if it's not a dropped flag, we just make is disappear + // if it's dropped, it will be removed by the pickup caller + if (!(ent->spawnflags & DROPPED_ITEM)) + { + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + return true; + } + return false; +} + +//Something touched home base +void Home_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int i; + edict_t *player; + gitem_t *flag_item, *enemy_flag_item; + + //Did someone try to put something on top of the base? If so, blow it up! + if (!strcmp(other->classname, "SentryGun") ) + { + turret_remove(other->creator); + } + if (!strcmp(other->classname, "SentryStand") ) + { + turret_remove(other->standowner); + } + if (!strcmp(other->classname, "depot") ) + { + if (other->owner) other->owner->supply = NULL; + //G_FreeEdict(other); + BecomeExplosion1 (other); + } + if (!strcmp(other->classname, "healingdepot") ) + { + if (other->owner) other->owner->supply = NULL; + //G_FreeEdict(other); + BecomeExplosion1 (other); + } + + //Did a player touch? + if (!other->client) + return; + + //Home base and player must be on same team + if (ent->wf_team != other->wf_team) + return; + + // same team, if the flag at base, check to see if he has the enemy flag + if (other->wf_team == CTF_TEAM1) + { + flag_item = flag1_item; + enemy_flag_item = flag2_item; + } + else + { + flag_item = flag2_item; + enemy_flag_item = flag1_item; + } + + //Does the player have the enemy flag? + if (other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) + { + //Yes - TOUCHDOWN! + other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)] = 0; + + ctfgame.last_flag_capture = level.time; + ctfgame.last_capture_team = other->wf_team; + if (other->wf_team == CTF_TEAM1) + ctfgame.team1++; + else + ctfgame.team2++; + + gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0); + + // other gets another 10 frag bonus + if (PlayerChangeScore(other,CTF_CAPTURE_BONUS)) + { + gi.bprintf(PRINT_HIGH, "%s captured the %s flag!\n", + other->client->pers.netname, CTFOtherTeamName(other->wf_team)); + + // Log Flag Capture - MarkDavies + sl_LogScore( &gi, + other->client->pers.netname, + NULL, + "F Capture", + -1, + CTF_CAPTURE_BONUS); + } + //execute the trigger if it exists + if (ent->orig_map_entity) + { + G_UseTargets (ent->orig_map_entity, other); +//if (wfdebug) gi.dprintf("Using Target %s\n", ent->orig_map_entity->target); + } + else + { +//if (wfdebug) gi.dprintf("No Target\n"); + } + + // Ok, let's do the player loop, hand out the bonuses + for (i = 1; i <= maxclients->value; i++) + { + player = &g_edicts[i]; + if (!player->inuse) + continue; + if (!player->client) + continue; + if (!other->client) + continue; + if (player->client->resp.ctf_team != other->client->resp.ctf_team) + { + player->client->resp.ctf_lasthurtcarrier = -5; + } + else if (player->client->resp.ctf_team == other->client->resp.ctf_team) + { + if (player != other) + { + if (PlayerChangeScore(player,CTF_TEAM_BONUS)) + { + // Log Flag Capture Team Score - MarkDavies + sl_LogScore( &gi, + player->client->pers.netname, + NULL, + "Team Score", + -1, + CTF_TEAM_BONUS); + } + } + // award extra points for capture assists + if (player->client->resp.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time) + { + if (PlayerChangeScore(player,CTF_RETURN_FLAG_ASSIST_BONUS)) + { + gi.bprintf(PRINT_HIGH, "%s gets an assist for returning the flag!\n", player->client->pers.netname); + + // Log Flag Capture Team Score - MarkDavies + sl_LogScore( &gi, + player->client->pers.netname, + NULL, + "F Return Assist", + -1, + CTF_RETURN_FLAG_ASSIST_BONUS); + } + + } + if (player->client->resp.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time) + { + if (PlayerChangeScore(player,CTF_FRAG_CARRIER_ASSIST_BONUS)) + { + + gi.bprintf(PRINT_HIGH, "%s gets an assist for fragging the flag carrier!\n", player->client->pers.netname); + // Log Flag Capture Frag Assist Score - MarkDavies + sl_LogScore( &gi, + player->client->pers.netname, + NULL, + "FC Frag Assist", + -1, + CTF_FRAG_CARRIER_ASSIST_BONUS); + } + + } + } + } + if (ent->wf_team == CTF_TEAM1) + CTFResetFlag(CTF_TEAM2); + else + CTFResetFlag(CTF_TEAM1); + } +} + +void Home_Think (edict_t *ent) +{ + //For some reason, entity needs a think function + ent->nextthink = level.time + 5.0; +} + + +void Create_Home_Base(edict_t *flag, int team) +{ + edict_t *home; + + home = G_Spawn (); + + home->wf_team = team; + +//gi.dprintf("--Create Home: %s. Target = %s, ent = %d\n", flag->classname, flag->target, flag); + + //Save a pointer to the flag/home base entity so the + //triggers will work + if ((flag->target) && (flag->target[0] != 0)) + home->orig_map_entity = flag; + + VectorCopy(flag->s.origin, home->s.origin); + home->s.origin[2] += 40; + //home->classname="depot"; + home->takedamage=DAMAGE_NO; + home->movetype= MOVETYPE_TOSS; + home->mass = 200; + home->solid = SOLID_BBOX; + home->clipmask=MASK_ALL; + home->deadflag =DEAD_NO; + home->clipmask = MASK_SHOT; + home->model = flag->model; + home->s.modelindex = gi.modelindex ("models/objects/flagbase/tris.md2"); + + if (team == CTF_TEAM1) + { + home->classname="home_team1"; + home->s.skinnum = 0; + } + else + { + home->classname="home_team2"; + home->s.skinnum = 1; + } + + home->solid = SOLID_BBOX; + + VectorSet (home->mins, -20, -20, -15); + VectorSet (home->maxs, 20, 20, -13); + home->s.frame =0; + home->waterlevel = 0; + home->watertype=0; + home->health= 100; + home->max_health =100; + home->gib_health = -80; +// home->die = supplydepot_die; + home->owner = flag; + home->dmg = 100; + home->dmg_radius = 100; +// home->touch = SupplyTouch; +// home->think =SupplyThink; + home->touch = Home_Touch; + home->think = Home_Think; + home->nextthink = level.time + 1.0; + VectorClear (home->velocity); + gi.linkentity (home); + +// gi.dprintf("FLAG CREATED.\n"); +} + + +void Create_All_Home_Bases (void) +{ + edict_t *spot; + + spot = NULL; + + //Team 1 home base + + //First try home base (flag return) position + spot = G_Find (spot, FOFS(classname), "item_flagreturn_team1"); + + //Next try flag spot + if (!spot) spot = G_Find (spot, FOFS(classname), "item_flag_team1"); + + if (!spot) + { + gi.dprintf("WARNING - Could not place flag return entity for team 1!\n"); + } + else + { + Create_Home_Base(spot, CTF_TEAM1); + } + + + //Team 2 home base + + //First try home base (flag return) position + spot = NULL; + spot = G_Find (spot, FOFS(classname), "item_flagreturn_team2"); + + //Next try flag spot + if (!spot) spot = G_Find (spot, FOFS(classname), "item_flag_team2"); + + if (!spot) + { + gi.dprintf("WARNING - Could not place flag return entity for team 2!\n"); + } + else + { + if (strcmp(spot->classname, "item_flagreturn_team2") == 0)//4/99 + spot->s.skinnum = 1;//3/99 small fix for g_items.c/bot + + Create_Home_Base(spot, CTF_TEAM2); + } + + +} diff --git a/wf_flame.c b/wf_flame.c new file mode 100644 index 0000000..3cbadf4 --- /dev/null +++ b/wf_flame.c @@ -0,0 +1,165 @@ +/* +================= +Flame Grenade +================= +*/ +#include "g_local.h" + +void Flame_Explode (edict_t *ent) +{ + vec3_t origin; + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + //No damage for now + //T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, MOD_WF_FLAME); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + TOTALWORLDFLAMES--; + G_FreeEdict (ent); +// gi.dprintf("Flame Explode\n"); +} + +void Flame_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ +// if (other == ent->owner) +// return; + + if (surf && (surf->flags & SURF_SKY)) + { + TOTALWORLDFLAMES--; + G_FreeEdict (ent); + return; + } + + //Attach to enemy if they touch the flame + if (!ent->enemy) //First time touch of an enemy? + { + //Only do this for certain kinds of entities + if (other->client) //is is a client? + { + ent->enemy = other; + } + else if (!strcmp(other->classname, "decoy")) + { + ent->enemy = other; + } + else if (!strcmp(other->classname, "player")) + { + ent->enemy = other; + } + + //Stop the flames forward motion + if (ent->enemy) + { + ent->movetype = MOVETYPE_NONE; + VectorClear (ent->velocity); + //burn em baby, dead people don't scream (TeT) + if (ent->enemy->health > 0) + { + gi.sound (ent->enemy, CHAN_WEAPON, gi.soundindex ("scream.wav"), 1, ATTN_NORM, 0); + } + + } + } +} + +void flame_think (edict_t *ent) + { + + ent->nextthink = level.time + .2; + ent->think = flame_think; + + //Exlode the flame if time has run out + if (level.time > ent->delay) + { + Flame_Explode(ent); + return; + } + + //Exlode the flame if it's in the water + if (ent->waterlevel) + { + gi.sound (ent, CHAN_WEAPON, gi.soundindex ("world/airhiss1.wav"), 1, ATTN_NORM, 0); + Flame_Explode(ent); + return; + } + + //If an enemy is defined, move the flame to it + if (ent->enemy) + { + //If enemy is dead, destroy flame + if (ent->enemy->deadflag == DEAD_DEAD) + { + Flame_Explode(ent); + } + else + { + + //Move flame to where the entity is + VectorCopy (ent->enemy->s.origin, ent->s.origin); + + //Cause some damage (increased from 3 to 4) + T_Damage (ent->enemy, ent, ent->owner, ent->velocity, ent->s.origin, NULL, 4, 0, 0, MOD_WF_FLAME); + +// gi.dprintf("Flame Move\n"); + } + } +} + +void fire_flame (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held) +{ + edict_t *flame; + vec3_t dir; + vec3_t forward, right, up; + + if(TOTALWORLDFLAMES>=MAX_FLAMES) + { + if (!self->bot_client && self->client)//ERASER ACRID + gi.bprintf (PRINT_HIGH, "WF WARNING!!! MAX FLAMES HAVE BEEN REACHED!\n"); + return; + } + TOTALWORLDFLAMES++; + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + flame = G_Spawn(); + VectorCopy (start, flame->s.origin); + VectorScale (aimdir, speed, flame->velocity); + VectorMA (flame->velocity, 200 + crandom() * 10.0, up, flame->velocity); + VectorMA (flame->velocity, crandom() * 10.0, right, flame->velocity); + VectorSet (flame->avelocity, 300, 300, 300); + + flame->movetype = MOVETYPE_BOUNCE; + flame->clipmask = MASK_SHOT; + flame->solid = SOLID_BBOX; +// flame->s.effects |= (int)(wftest->value) | EF_ANIM_ALLFAST; + flame->s.effects |= EF_BFG | EF_HYPERBLASTER | EF_ANIM_ALLFAST; + VectorSet (self->mins, -16, -16, -24); + VectorSet (self->maxs, 16, 16, 32); + + flame->s.modelindex = gi.modelindex ("sprites/fire.sp2"); + + flame->owner = self; + flame->touch = Flame_Touch; + + flame->nextthink = level.time + .2; + flame->think = flame_think; + flame->delay = level.time + 6.0; //stick around for this number of seconds + + flame->dmg = damage; + flame->dmg_radius = damage_radius; + flame->classname = "flame"; + flame->spawnflags = 1; + //flame->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + flame->enemy = NULL; + + if (timer <= 0.0) + Flame_Explode (flame); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (flame); + } +} \ No newline at end of file diff --git a/wf_flash.c b/wf_flash.c new file mode 100644 index 0000000..d398055 --- /dev/null +++ b/wf_flash.c @@ -0,0 +1,176 @@ +/*============================================================================== +The Weapons Factory - +Flash Grenade Functions +Original code by ?? +Modified by Gregg Reno +==============================================================================*/ +#include "g_local.h" +#define FLASH_RADIUS 300 +#define BLIND_FLASH 50 // Time of blindness in FRAMES + + +void Flash_Explode (edict_t *ent) +{ + vec3_t offset, v; + edict_t *target; + float Distance, BlindTimeAdd; +// vec3_t origin; + + //safe_cprintf(ent->owner, PRINT_HIGH, "Flash goes BOOM!\n"); + + // Move it off the ground so people are sure to see it + VectorSet(offset, 0, 0, 10); + VectorAdd(ent->s.origin, offset, ent->s.origin); + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + target = NULL; + while ((target = findradius(target, ent->s.origin, FLASH_RADIUS)) != NULL) + { +// if (target == ent->owner) +// continue; // You know when to close your eyes, don't you? + if (!target->client) + continue; // It's not a player + if (!visible(ent, target)) + continue; // The grenade can't see it +// if (!infront(target, ent)) +// continue; // It's not facing it + + //dont blind teammates + if (target->wf_team == ent->wf_team) + continue; + + // Find distance + VectorSubtract(ent->s.origin, target->s.origin, v); + Distance = VectorLength(v); + + // Calculate blindness factor + if ( Distance < FLASH_RADIUS/10 ) + BlindTimeAdd = BLIND_FLASH; // Blind completely + else + BlindTimeAdd = 1.5 * BLIND_FLASH * ( 1 / ( ( Distance - FLASH_RADIUS*2 ) / (FLASH_RADIUS*2) - 2 ) + 1 ); // Blind partially + if ( BlindTimeAdd < 0 ) + BlindTimeAdd = 0; // Do not blind at all. + + // Not facing it, but still blinded a little + if (!infront(target, ent)) + BlindTimeAdd *= .5; + // You know when to close your eyes, don't you? Doesn't quite do the job. :) + if (target == ent->owner) + { + target->client->blindTime += BlindTimeAdd * .3; + target->client->blindBase = BLIND_FLASH; + continue; + } + + // Increment the blindness counter + target->client->blindTime += BlindTimeAdd * 1.5; +// +//target->client->blindTime = 300; +// + target->client->blindBase = BLIND_FLASH; + target->s.angles[YAW] = (rand() % 360); // Whee! + + // Let the player know what just happened + // (It's just as well, he won't see the message immediately!) + safe_cprintf(target, PRINT_HIGH, "You are blinded by a flash grenade!!!\n"); + + // Let the owner of the grenade know it worked + safe_cprintf(ent->owner, PRINT_HIGH, "%s is blinded by your flash grenade!\n", + target->client->pers.netname); + } + + // Blow up the grenade + BecomeExplosion1(ent); +} + + +void Flash_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + // Dont blow up if on same team + if (other->wf_team == ent->wf_team) + return; + + // If it goes in to orbit, it's gone... + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + // All this does is make the bouncing noises when it hits something... + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb1a.wav"), + 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb2a.wav"), + 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex("weapons/grenlb1b.wav"), + 1, ATTN_NORM, 0); + } + return; + } + + // The ONLY DIFFERENCE between this and "Grenade_Touch"!! + Flash_Explode (ent); +} + + + +void fire_flash (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(); + grenade->wf_team = self->wf_team; + 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->s.modelindex = gi.modelindex (GRFLASH_MODEL); + grenade->s.skinnum = GRFLASH_SKIN; + grenade->owner = self; + grenade->touch = Flash_Touch; + grenade->think = Flash_Explode; + grenade->classname = "flash_grenade"; + grenade->nextthink = level.time + timer; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + grenade->spawnflags = 1; + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + //safe_cprintf(self, PRINT_HIGH, "FLASH!\n"); + + if (timer <= 0.0) + GenericGrenade_Explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} diff --git a/wf_freezer.c b/wf_freezer.c new file mode 100644 index 0000000..29edbb6 --- /dev/null +++ b/wf_freezer.c @@ -0,0 +1,256 @@ +/***************************************************************** + + Freezer gun source code - by Acrid-, acridcola@hotmail.com + + .............................................................. + + This file is Copyright(c) 1999, Acrid-, All Rights Reserved. + + .............................................................. + + + Should you decide to release a modified version of the Freezer gun, you + MUST include the following text (minus the BEGIN and END lines) in + the documentation for your modification, and also on all web pages + related to your modification, should they exist. + + --- BEGIN --- + + The Freezer gun and related code is a product of Acrid- designed + for Weapons Factory, and is available as part of the Weapons Factory + Source Code or a seperate tutorial. + + This program MUST NOT be sold in ANY form. If you have paid for + this product, you should contact Acrid- at: + acridcola@hotmail.com + + --- END --- + + have fun, + + Acrid- + + *****************************************************************/ + +/* +============================== +FREEZER CODE by Acrid + +1/14/99 updated 3/99-5/99 +Notes: if you change ammo consumption ,there are 4 spots, +1 in g_items,and 3 here +Current rate is 3 per shot +============================== +*/ +#include "g_local.h" + +//New functions by Gregg Reno- used for testing freeze on self +void freeze_player(edict_t *ent) +{ + if (ent->health <= 0)//5/99 + return; + + // make em frozen + ent->frozen = 1; + + // set the time till thaw + ent->frozentime = level.time + 4; //4 seconds of freeze (was 6) + gi.sound (ent, CHAN_WEAPON, gi.soundindex("weapons/freezer/freeze.wav"), 1, ATTN_NORM, 0); + +} + +void unfreeze_player(edict_t *ent) +{ + ent->frozen = 0; + ent->frozentime = level.time - 1; + gi.sound (ent, CHAN_WEAPON, gi.soundindex("weapons/freezer/shatter1.wav"), 1, ATTN_NORM, 0); +} + +/* +================================================ + Same as blaster_touch with some freezing info +================================================= +*/ +void freezer_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + extern void ClientUserinfoChanged (edict_t *ent, char *userinfo); + + int mod; + + + mod = MOD_FREEZER; +//note; returns in this part of the code will produce a richocet type effect,try shooting bots + if (other == self->owner) + return; +// if (other->bot_client) +// return; + + if (other->wf_team == self->wf_team) + { + other->frozen = 0; + } + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } +/*note: Probably better to ignore feigns because of returns in + ClientThink, but you still take damage*/ + if (other->client && !(other->frozen) && !(other->client->pers.feign) + && other->wf_team != self->wf_team) + { + freeze_player(other); + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + + 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_freezer (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *freezer; + trace_t tr; + + VectorNormalize (dir); + + freezer = G_Spawn(); + freezer->wf_team = self->wf_team; + VectorCopy (start, freezer->s.origin); + VectorCopy (start, freezer->s.old_origin); + vectoangles (dir, freezer->s.angles); + VectorScale (dir, speed, freezer->velocity); + freezer->movetype = MOVETYPE_FLYMISSILE; + freezer->clipmask = MASK_SHOT; + freezer->solid = SOLID_BBOX; + freezer->s.effects |= effect |EF_COLOR_SHELL; + freezer->s.renderfx |= RF_SHELL_BLUE|RF_SHELL_GREEN|RF_SHELL_RED; + VectorClear (freezer->mins); + VectorClear (freezer->maxs); + freezer->s.modelindex = gi.modelindex ("models/spike/tris.md2"); + freezer->s.sound = gi.soundindex ("weapons/freezer/freezefly.wav"); + freezer->classname = "iceball"; + freezer->owner = self; + freezer->touch = freezer_touch; + freezer->nextthink = level.time + 2; + freezer->think = G_FreeEdict; + freezer->dmg = damage; + gi.linkentity (freezer); + + if (self->client) + check_dodge (self, freezer->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, freezer->s.origin, freezer, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (freezer->s.origin, -10, dir, freezer->s.origin); + freezer->touch (freezer, tr.ent, NULL, NULL); + } +} +/* +================================================ + Same as orig blaster_fire but freeze code added +================================================ +*/ +void Freezer_Fire (edict_t *ent, vec3_t g_offset, int damage, qboolean hyper, int effect) +{ + vec3_t forward, right; + vec3_t start; + vec3_t offset; + float volume; + + + // check and abort firing if we don't have enough acrid 3/99 + if (ent->client->pers.inventory[ent->client->ammo_index] < 3) + { + ent->client->ps.gunframe++; + return; + } + + 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_freezer (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_FREEZER], effect); + + if (is_silenced) + volume = .5; + else + volume = 1.0; + + gi.sound (ent, CHAN_WEAPON, gi.soundindex("weapons/freezer/freezefire.wav"), volume, ATTN_NORM, 0); + + PlayerNoise(ent, start, PNOISE_WEAPON); + //remove ammo acrid 3/99 - modified by Gregg + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 3; +} +/* +================================================== + Same as Weapon_Blaster_Fire but freeze code added +================================================== +*/ +void Weapon_Freezer_Fire (edict_t *ent) +{ + int damage; + + damage = wf_game.weapon_damage[WEAPON_FREEZER];//3/99 + + //Change weapon if ammo falls below 3 //4/99 + if (ent->client->pers.inventory[ent->client->ammo_index] < 3) + { + + ent->client->ps.gunframe = 19;//start of idle frames + 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; + } + //white particles trail + Freezer_Fire (ent, vec3_origin, damage, false, EF_GRENADE); + ent->client->ps.gunframe++; +} +/* +================================== + Same as Weapon_Blaster with mod +================================== +*/ +void Weapon_Freezer (edict_t *ent) +{ + //Rail Gun frames acrid 3/99 + static int pause_frames[] = {56, 0}; + static int fire_frames[] = {4, 0}; + + Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, Weapon_Freezer_Fire); + +} diff --git a/wf_friends.c b/wf_friends.c new file mode 100644 index 0000000..a68ddd6 --- /dev/null +++ b/wf_friends.c @@ -0,0 +1,164 @@ +/*============================================================================== +The Weapons Factory - +Locate Friends +Original code by Gregg Reno +==============================================================================*/ +#include "g_local.h" + +void G_Spawn_DBTrail(int type, vec3_t start, vec3_t endpos ); +void G_Spawn_Trails(int type, vec3_t start, vec3_t endpos, vec3_t origin ); +extern int temp_ent_type; + +/* +================= +Cmd_Friend_f +Set who is a friend +syntax is "friend name +where + number = player number (showplayers) + name = name of player to look up +================= +*/ +void Cmd_Friend_f (edict_t *ent) +{ + char *string; + edict_t *e; + edict_t *target; + int i; + + if (!ent->client) return; + + string=gi.args(); + + if (string[0] == 0) + { + ent->client->pers.friend_ent[0] = NULL; + safe_cprintf (ent, PRINT_HIGH, "Friend setting cleared\n"); + return; + } + + target = NULL; + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + //match on netname? + if (Q_stricmp(e->client->pers.netname, string) == 0) + { + if (!e->client->pers.hasfriends) + { + safe_cprintf (ent, PRINT_HIGH, "Sorry, %s has no friends\n", + e->client->pers.netname); + return; + } + else + { + target = e; + break; + } + } + } + + if (!target) + { + safe_cprintf (ent, PRINT_HIGH, "Could not find player [%s]\n",string); + } + else + { + ent->client->pers.friend_ent[0] = target; + safe_cprintf (ent, PRINT_HIGH, "Friend set to %s\n", string); + } +} + +void Cmd_ShowFriends_f (edict_t *ent) +{ + edict_t *friend_ent; + + if (!ent->client) return; + + friend_ent = ent->client->pers.friend_ent[0]; + if (!friend_ent) + { + safe_cprintf (ent, PRINT_HIGH, "No friend set. Use 'friend ' command.\n"); + return; + } + + if (!friend_ent->client->pers.hasfriends) + { + safe_cprintf (ent, PRINT_HIGH, "%s has no friends!.\n", + friend_ent->client->pers.netname); + return; + } + + if (!friend_ent->client) + { + safe_cprintf (ent, PRINT_HIGH, "Friend is not an active player.\n"); + return; + } + + //Now do the blue light effect + if (temp_ent_type == 1) + G_Spawn_Trails(TE_BFG_LASER, ent->s.origin, friend_ent->s.origin, ent->s.origin ); + else if (temp_ent_type == 2) + G_Spawn_Trails(TE_BUBBLETRAIL, ent->s.origin, friend_ent->s.origin, ent->s.origin ); + else if (temp_ent_type == 3) + G_Spawn_Trails(TE_RAILTRAIL, ent->s.origin, friend_ent->s.origin, ent->s.origin ); + else if (temp_ent_type == 4) + G_Spawn_Trails(TE_RAILTRAIL2, ent->s.origin, friend_ent->s.origin, ent->s.origin ); + else if (temp_ent_type == 5) + G_Spawn_Trails(TE_BUBBLETRAIL2, ent->s.origin, friend_ent->s.origin, ent->s.origin ); + else + G_Spawn_DBTrail(TE_DEBUGTRAIL, ent->s.origin, friend_ent->s.origin ); + +} + + +//If the friend is close, trace a line to them +void ShowCloseFriend_f (edict_t *ent) +{ + edict_t *friend_ent; + vec3_t distance; + int dist; + + if (!ent->client) return; + + //Skip if no friend defined + friend_ent = ent->client->pers.friend_ent[0]; + if (!friend_ent) + return; + + //Skip if the friend is not allowing it + if (!friend_ent->client->pers.hasfriends) + return; + + //only do this every second or so + //(15 = low 4 bits. Which means all 3 bits will only be set + //every 16 times through. + if ((level.framenum & 31) != 31) + return; + + //Calc distance to friend + distance[0] = ent->s.origin[0] - friend_ent->s.origin[0]; + distance[1] = ent->s.origin[1] - friend_ent->s.origin[1]; + distance[2] = ent->s.origin[2] - friend_ent->s.origin[2]; + dist=VectorLength(distance); + if(dist <= 800) + { + if (temp_ent_type == 1) + G_Spawn_Trails(TE_BFG_LASER, ent->s.origin, friend_ent->s.origin, ent->s.origin ); + else if (temp_ent_type == 2) + G_Spawn_Trails(TE_BUBBLETRAIL, ent->s.origin, friend_ent->s.origin, ent->s.origin ); + else if (temp_ent_type == 3) + G_Spawn_Trails(TE_RAILTRAIL, ent->s.origin, friend_ent->s.origin, ent->s.origin ); + else if (temp_ent_type == 4) + G_Spawn_Trails(TE_RAILTRAIL2, ent->s.origin, friend_ent->s.origin, ent->s.origin ); + else if (temp_ent_type == 5) + G_Spawn_Trails(TE_BUBBLETRAIL2, ent->s.origin, friend_ent->s.origin, ent->s.origin ); + else + G_Spawn_DBTrail(TE_DEBUGTRAIL, ent->s.origin, friend_ent->s.origin ); + } +} + + diff --git a/wf_goodyear.c b/wf_goodyear.c new file mode 100644 index 0000000..f001cad --- /dev/null +++ b/wf_goodyear.c @@ -0,0 +1,205 @@ +/*============================================================================== +The Weapons Factory - +Goodyear Grenade Functions +Original code by Gregg Reno +==============================================================================*/ +/* + This grendade is launched like a ballon and explodes like + a proximity bomb. +*/ + +#include "g_local.h" + +/* +================= +fire_goodyear +================= +*/ + +void Goodyear_Explode (edict_t *ent) +{ + vec3_t origin; + + + 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? + T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, MOD_WF_GOODYEAR); + + 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_PVS); + + G_FreeEdict (ent); +} + +// When a grenade 'dies', it blows up next frame +void Goodyear_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = Goodyear_Explode; +} +void Goodyear_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //if (other == ent->owner) + // return; + + // Dont blow up if on same team + if (other->wf_team == ent->wf_team) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + //ent->enemy = other; + Goodyear_Explode (ent); +} + + +// New think function for goodyear grenades +void goodyear_think (edict_t *ent) +{ + edict_t *blip = NULL; + + if (level.time > ent->delay) + { + Goodyear_Explode(ent); + return; + } + + //is it armed yet? + if (level.time < ent->delay2) + { + ent->nextthink = level.time + .1; + return; + } + + ent->think = goodyear_think; + while ((blip = findradius(blip, ent->s.origin, 100)) != NULL) + { + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + continue; + //if (blip == ent->owner) + // continue; + //dont attack same team + if (blip->wf_team == ent->wf_team) + continue; + if (blip->disguised) + continue; + if (!blip->takedamage) + continue; + if (blip->health <= 0) + continue; + if (!visible(ent, blip)) + continue; + ent->think = Goodyear_Explode; + break; + } + + ent->nextthink = level.time + .1; +} + + +void fire_goodyear (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *goodyear; + vec3_t dir; + vec3_t forward, right, up; + int dmg; + + ++self->client->pers.active_grenades[GRENADE_TYPE_GOODYEAR]; + + //reduce damage + dmg = wf_game.grenade_damage[GRENADE_TYPE_GOODYEAR]; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + goodyear = G_Spawn(); + VectorCopy (start, goodyear->s.origin); + vectoangles (aimdir, goodyear->s.angles); + VectorScale (aimdir, speed, goodyear->velocity); + + VectorSet (goodyear->avelocity, 300, 300, 300); + + goodyear->grenade_index = GRENADE_TYPE_GOODYEAR; + goodyear->movetype = MOVETYPE_FLOAT; + goodyear->clipmask = MASK_SHOT; + goodyear->solid = SOLID_BBOX; + goodyear->s.effects |= EF_GRENADE; + VectorClear (goodyear->mins); + VectorClear (goodyear->maxs); +// VectorSet(goodyear->mins, -10, -10, 0); +// VectorSet(goodyear->maxs, 10, 10, 10); + goodyear->s.modelindex = gi.modelindex (GRGOODYEAR_MODEL); + goodyear->s.skinnum = GRGOODYEAR_SKIN; + + goodyear->owner = self; + goodyear->touch = Goodyear_Touch; + + goodyear->nextthink = level.time + .1; + goodyear->think = goodyear_think; + goodyear->delay = level.time + 120; + goodyear->delay2 = level.time + 1; //don't exlode for at least 1 second + + goodyear->dmg = dmg; + goodyear->dmg_radius = damage_radius; + goodyear->classname = "goodyear"; + + goodyear->die = Goodyear_Die; + goodyear->takedamage = DAMAGE_YES; + //goodyear->takedamage = DAMAGE_NO; + goodyear->spawnflags = 1; + goodyear->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + goodyear->mass = 2; + goodyear->health = 20; + goodyear->max_health = 20; + + //set the team + if ((int)wfflags->value & WF_ANARCHY) + goodyear->wf_team = 0; //fire at anybody + else + goodyear->wf_team = self->client->resp.ctf_team; + + gi.linkentity (goodyear); +} diff --git a/wf_include.h b/wf_include.h new file mode 100644 index 0000000..979f22f --- /dev/null +++ b/wf_include.h @@ -0,0 +1,755 @@ +/*============================================================================== +The Weapons Factory - +Global Include File +Original code by Gregg Reno +==============================================================================*/ + +#define WF_VERSION "WF Version 4.2" +#define WF_URL "www.captured.com/weaponsfactory" + +/* ------------------------------- + CONSTANTS AND MACROS +------------------------------- */ + +//id number for WF approved class file +#define MOD_ID_WF 3784 +#define MOD_ID_OMEGA 4791 +#define MOD_ID_Q2TF 1949 //www.planetice.net/q2tf + +//Damage Values +#define DAMAGE_BLASTER 15 +#define DAMAGE_SHOTGUN 4 +#define DAMAGE_SUPERSHOTGUN 6 +#define DAMAGE_MACHINEGUN 8 +#define DAMAGE_CHAINGUN 6 +#define DAMAGE_HYPERBLASTER 12 +#define DAMAGE_ROCKETLAUNCHER 105 +#define DAMAGE_GRENADELAUNCHER 120 +#define DAMAGE_RAILGUN 80 +#define DAMAGE_BFG 200 +#define DAMAGE_NEEDLER 3 +#define DAMAGE_NAG 5 +#define DAMAGE_TELSA 5 +#define DAMAGE_LIGHTNING 45 +#define DAMAGE_NAILGUN 4 +#define DAMAGE_CLUSTERROCKET 45 +#define DAMAGE_MAGBOLTED 25 +#define DAMAGE_PELLET 110 +#define DAMAGE_PULSE 3 +#define DAMAGE_SHC 3 +#define DAMAGE_FLAREGUN 0 +#define DAMAGE_NAPALMMISSLE 6 +#define DAMAGE_FLAMETHROWER 15 +#define DAMAGE_TRANQUILIZER 5 +#define DAMAGE_INFECTEDDART 20 +#define DAMAGE_LASERSNIPER 0 +#define DAMAGE_ARMORDART 20 +#define DAMAGE_SHOTGUNCHOKE 4 +#define DAMAGE_SNIPERRIFLE 160 +#define DAMAGE_LRPROJECTILE 0 +#define DAMAGE_SENTRYKILLER 120 +#define DAMAGE_MEGACHAINGUN 5 +#define DAMAGE_TRANQUILDART 5 +#define DAMAGE_KNIFE 80 +#define DAMAGE_AK47 13 +#define DAMAGE_PISTOL 35 +#define DAMAGE_STINGER 125 +#define DAMAGE_DISRUPTOR 30 +#define DAMAGE_ETF_RIFLE 10 +#define DAMAGE_PLASMA_BEAM 0 +#define DAMAGE_ION_RIPPER 30 +#define DAMAGE_PHALANX 70 +#define DAMAGE_FREEZER 15 + +//Speed values +#define SPEED_BLASTER 1000 +#define SPEED_SHOTGUN 10 +#define SPEED_SUPERSHOTGUN 10 +#define SPEED_MACHINEGUN 10 +#define SPEED_CHAINGUN 10 +#define SPEED_HYPERBLASTER 10 +#define SPEED_ROCKETLAUNCHER 725 +#define SPEED_GRENADELAUNCHER 600 +#define SPEED_RAILGUN 10 +#define SPEED_BFG 400 + +#define SPEED_NEEDLER 10 +#define SPEED_NAG 1000 +#define SPEED_TELSA 10 +#define SPEED_LIGHTNING 950 +#define SPEED_NAILGUN 10 +#define SPEED_CLUSTERROCKET 370 +#define SPEED_MAGBOLTED 1000 +#define SPEED_PELLET 550 +#define SPEED_PULSE 10 +#define SPEED_SHC 10 +#define SPEED_FLAREGUN 850 +#define SPEED_NAPALMMISSLE 390 +#define SPEED_FLAMETHROWER 400 +#define SPEED_TRANQUILIZER 10 +#define SPEED_INFECTEDDART 390 +#define SPEED_LASERSNIPER 10 +#define SPEED_ARMORDART 850 +#define SPEED_SHOTGUNCHOKE 10 +#define SPEED_SNIPERRIFLE 10 +#define SPEED_LRPROJECTILE 10 +#define SPEED_SENTRYKILLER 325 +#define SPEED_MEGACHAINGUN 10 +#define SPEED_TRANQUILDART 10 +#define SPEED_KNIFE 10 +#define SPEED_AK47 10 +#define SPEED_PISTOL 10 +#define SPEED_STINGER 800 +#define SPEED_DISRUPTOR 1000 +#define SPEED_ETF_RIFLE 750 +#define SPEED_PLASMA_BEAM 10 +#define SPEED_ION_RIPPER 500 +#define SPEED_PHALANX 725 +#define SPEED_FREEZER 1000 + +//Grenade damage +#define DAMAGE_GRENADE 125 +#define DAMAGE_LASERBALL 2 +#define DAMAGE_GOODYEAR 80 +#define DAMAGE_PROXIMITY 80 +#define DAMAGE_FLASH 0 +#define DAMAGE_CLUSTER 65 +#define DAMAGE_EARTHQUAKE 0 +#define DAMAGE_TURRET 37 +#define DAMAGE_NAPALM 18 +#define DAMAGE_CONCUSSION 0 +#define DAMAGE_NARCOTIC 0 +#define DAMAGE_PLAGUE 11 +#define DAMAGE_MAGNOTRON 80 +#define DAMAGE_SHOCK 9 +#define DAMAGE_PIPEBOMB 110 +#define DAMAGE_SHRAPNEL 15 +#define DAMAGE_FLARE 0 +#define DAMAGE_SLOW 0 +#define DAMAGE_PLAGUETIME 0 +#define DAMAGE_LASERCUTTER 5 +#define DAMAGE_TESLA 3 +#define DAMAGE_GAS 0 + +#define SPEED_GRENADE 800 +#define SPEED_LASERBALL 800 +#define SPEED_GOODYEAR 800 +#define SPEED_PROXIMITY 800 +#define SPEED_FLASH 800 +#define SPEED_CLUSTER 800 +#define SPEED_EARTHQUAKE 800 +#define SPEED_TURRET 800 +#define SPEED_NAPALM 800 +#define SPEED_CONCUSSION 800 +#define SPEED_NARCOTIC 800 +#define SPEED_PLAGUE 800 +#define SPEED_MAGNOTRON 800 +#define SPEED_SHOCK 800 +#define SPEED_PIPEBOMB 800 +#define SPEED_SHRAPNEL 800 +#define SPEED_FLARE 800 +#define SPEED_SLOW 800 +#define SPEED_PLAGUETIME 800 +#define SPEED_LASERCUTTER 800 +#define SPEED_TESLA 800 +#define SPEED_GAS 800 + + +//Some Max Values +#define MAX_ARMOR 400 +#define MAX_HEALTH 200 + +// Grenade types +#define GRENADE_TYPE_NONE 0 +#define GRENADE_TYPE_NORMAL 1 +#define GRENADE_TYPE_LASERBALL 2 +#define GRENADE_TYPE_GOODYEAR 3 +#define GRENADE_TYPE_PROXIMITY 4 +#define GRENADE_TYPE_FLASH 5 +#define GRENADE_TYPE_CLUSTER 6 +#define GRENADE_TYPE_EARTHQUAKE 7 +#define GRENADE_TYPE_TURRET 8 +#define GRENADE_TYPE_NAPALM 9 +#define GRENADE_TYPE_CONCUSSION 10 +#define GRENADE_TYPE_NARCOTIC 11 +#define GRENADE_TYPE_PLAGUE 12 +#define GRENADE_TYPE_MAGNOTRON 13 +#define GRENADE_TYPE_SHOCK 14 +#define GRENADE_TYPE_PIPEBOMB 15 +#define GRENADE_TYPE_SHRAPNEL 16 +#define GRENADE_TYPE_FLARE 17 +#define GRENADE_TYPE_SLOW 18 +#define GRENADE_TYPE_PLAGUETIME 19 +#define GRENADE_TYPE_LASERCUTTER 20 +#define GRENADE_TYPE_TESLA 21 +#define GRENADE_TYPE_GAS 22 +#define GRENADE_TYPE_COUNT 22 //How many grenade types? + +//Grenade skins & models +#define GRMODEL_GRENADE1 "models/objects/grenade/tris.md2" +#define GRMODEL_GRENADE2 "models/objects/grenade2/tris.md2" +#define GRMODEL_GRENADE3 "models/objects/grenade3/tris.md2" +#define GRMODEL_V_GRENADE1 "models/weapons/v_handgr/tris.md2" +#define GRMODEL_V_GRENADE2 "models/weapons/v_handgr2/tris.md2" +#define GRMODEL_V_GRENADE3 "models/weapons/v_handgr3/tris.md2" + +#define GRNORMAL_MODEL GRMODEL_GRENADE1 +#define GRNORMAL_SKIN 0 + +#define GRLAUNCHER_MODEL GRMODEL_GRENADE2 +#define GRLAUNCHER_SKIN 0 + +#define GRLASERBALL_MODEL GRMODEL_GRENADE3 +#define GRLASERBALL_SKIN 1 + +#define GRGOODYEAR_MODEL GRMODEL_GRENADE3 +#define GRGOODYEAR_SKIN 1 +//#define GRGOODYEAR_SKIN 3 + +#define GRPROXIMITY_MODEL GRMODEL_GRENADE2 +#define GRPROXIMITY_SKIN 2 + +#define GRFLASH_MODEL GRMODEL_GRENADE3 +//define GRFLASH_MODEL "models/objects/flashgr/tris.md2" +#define GRFLASH_SKIN 4 + +#define GRCLUSTER_MODEL GRMODEL_GRENADE2 +#define GRCLUSTER_SKIN 4 + +#define GRTURRET_MODEL GRMODEL_GRENADE3 +#define GRTURRET_SKIN 2 + +#define GRNAPALM_MODEL GRMODEL_GRENADE2 +#define GRNAPALM_SKIN 6 + +#define GRCONCUSSION_MODEL GRMODEL_GRENADE3 +#define GRCONCUSSION_SKIN 0 + +#define GRPLAGUE_MODEL GRMODEL_GRENADE2 +#define GRPLAGUE_SKIN 1 + +#define GRMAGNOTRON_MODEL GRMODEL_GRENADE2 +#define GRMAGNOTRON_SKIN 0 + +#define GRSHOCK_MODEL GRMODEL_GRENADE2 +#define GRSHOCK_SKIN 3 + +#define GRPIPEBOMB_MODEL GRMODEL_GRENADE2 +#define GRPIPEBOMB_SKIN 7 + +#define GRSHRAPNEL_MODEL GRMODEL_GRENADE3 +#define GRSHRAPNEL_SKIN 5 + +#define GRFLARE_MODEL GRMODEL_GRENADE2 +#define GRFLARE_SKIN 5 + +#define GRPLAGUETIME_MODEL GRMODEL_GRENADE1 +#define GRPLAGUETIME_SKIN 0 + +#define GRLASERCUTTER_MODEL GRMODEL_GRENADE1 +#define GRLASERCUTTER_SKIN 0 + +//need new skins +#define GRTESLA_MODEL GRMODEL_GRENADE3 +#define GRTESLA_SKIN 0 + +//need new skins +#define GRGAS_MODEL GRMODEL_GRENADE2 +#define GRGAS_SKIN 0 + +//#define GREARTHQUAKE_MODEL GRMODEL_GRENADE1 +//#define GREARTHQUAKE_SKIN 999 + +//#define GRNARCOTIC_MODEL GRMODEL_GRENADE +//#define GRNARCOTIC_SKIN 9999 + +//#define GRSLOW_MODEL GRMODEL_GRENADE +//#define GRSLOW_SKIN + +//Maximum number of active grenades for each type +#define MAX_TYPE_NORMAL 999 +#define MAX_TYPE_LASERBALL 1 +#define MAX_TYPE_GOODYEAR 4 +#define MAX_TYPE_PROXIMITY 4 +#define MAX_TYPE_FLASH 999 +#define MAX_TYPE_CLUSTER 999 +#define MAX_TYPE_EARTHQUAKE 999 +#define MAX_TYPE_TURRET 2 +#define MAX_TYPE_NAPALM 2 +#define MAX_TYPE_CONCUSSION 1 +#define MAX_TYPE_NARCOTIC 999 +#define MAX_TYPE_PLAGUE 2 +#define MAX_TYPE_MAGNOTRON 1 +#define MAX_TYPE_SHOCK 2 +#define MAX_TYPE_PIPEBOMB 6 +#define MAX_TYPE_SHRAPNAL 999 +#define MAX_TYPE_FLARE 999 +#define MAX_TYPE_SLOW 999 +#define MAX_TYPE_PLAGUETIME 999 +#define MAX_TYPE_LASERCUTTER 1 +#define MAX_TYPE_TESLA 1 +#define MAX_TYPE_GAS 2 + +// proximity grenade +#define PROXIMITY_TYPE_GRENADE 1 +#define PROXIMITY_TYPE_PIPE 2 + +// grenade requirements +#define LASERBALL_CELLS 18 +#define DECOY_CELLS 0 +#define EARTHQUAKE_GRENADES 6 +#define TURRET_GRENADES 2 +#define TURRET_SLUGS 4 + +//Other +#define RESPAWN_PROTECT_TIME 8.0 + +//Model Types +#define CLASS_MODEL_MALE 1 +#define CLASS_MODEL_FEMALE 2 +#define CLASS_MODEL_CYBORG 3 +#define CLASS_MODEL_ANY 4 //use the player's choice + +// Weapon types +#define WEAPON_NONE 0 +#define WEAPON_BLASTER 1 +#define WEAPON_SHOTGUN 2 +#define WEAPON_SUPERSHOTGUN 3 +#define WEAPON_MACHINEGUN 4 +#define WEAPON_CHAINGUN 5 +#define WEAPON_HYPERBLASTER 6 +#define WEAPON_ROCKETLAUNCHER 7 +#define WEAPON_GRENADELAUNCHER 8 +#define WEAPON_RAILGUN 9 +#define WEAPON_BFG 10 + +#define WEAPON_NEEDLER 11 +#define WEAPON_NAG 12 +#define WEAPON_TELSA 13 +#define WEAPON_LIGHTNING 14 +#define WEAPON_NAILGUN 15 +#define WEAPON_CLUSTERROCKET 16 +#define WEAPON_MAGBOLTED 17 +#define WEAPON_PELLET 18 +#define WEAPON_PULSE 19 +#define WEAPON_SHC 20 + +#define WEAPON_FLAREGUN 21 +#define WEAPON_NAPALMMISSLE 22 +#define WEAPON_FLAMETHROWER 23 +#define WEAPON_TRANQUILIZER 24 +#define WEAPON_INFECTEDDART 25 +#define WEAPON_LASERSNIPER 26 //unused now +#define WEAPON_ARMORDART 27 +#define WEAPON_SHOTGUNCHOKE 28 //unused now +#define WEAPON_SNIPERRIFLE 29 +#define WEAPON_LRPROJECTILE 30 + +#define WEAPON_SENTRYKILLER 31 +#define WEAPON_MEGACHAINGUN 32 +#define WEAPON_TRANQUILDART 33 +#define WEAPON_KNIFE 34 +#define WEAPON_AK47 35 +#define WEAPON_PISTOL 36 +#define WEAPON_STINGER 37 +#define WEAPON_DISRUPTOR 38 +#define WEAPON_ETF_RIFLE 39 +#define WEAPON_PLASMA_BEAM 40 + +#define WEAPON_ION_RIPPER 41 +#define WEAPON_PHALANX 42 +#define WEAPON_FREEZER 43 //acrid3 + +#define WEAPON_COUNT 43 //How many grenade types? + +// Player items +#define ITEM_NONE 0 +#define ITEM_REBREATHER 1 +#define ITEM_BODYARMOR 2 +#define ITEM_COMBATARMOR 4 +#define ITEM_JACKETARMOR 8 +#define ITEM_POWERSCREEN 16 +#define ITEM_POWERSHIELD 32 +#define ITEM_SILENCER 64 + +//Temporary - use these for new items +#define ITEM_t1 128 +#define ITEM_t2 256 +#define ITEM_t3 512 +#define ITEM_t4 1024 +#define ITEM_t5 2048 +#define ITEM_t6 4096 + +// Type of ammo player can pick up +#define WF_AMMO_CELLS 1 +#define WF_AMMO_SHELLS 2 +#define WF_AMMO_BULLETS 4 +#define WF_AMMO_ROCKETS 8 +#define WF_AMMO_SLUGS 16 +#define WF_AMMO_GRENADES 32 + +// Player special abilities +#define SPECIAL_NONE 0 +#define SPECIAL_JETPACK 1 //1 +#define SPECIAL_BIOSENTRY 2 //2 - was binoculars +#define SPECIAL_REMOTE_CAMERA 4 //3 +#define SPECIAL_ANTIGRAV_BOOTS 8 //4 +#define SPECIAL_DISGUISE 16 //5 +#define SPECIAL_FEIGN 32 //6 +#define SPECIAL_HEALING 64 //7 +#define SPECIAL_SENTRY_GUN 128 //8 +#define SPECIAL_MISSILE_LAUNCHER 256 //9 +#define SPECIAL_MERCENARY 512 //10 - was repair armor +#define SPECIAL_ALARMS 1024 //11 +#define SPECIAL_KAMIKAZE 2048 //12 +#define SPECIAL_LASER_DEFENSE 4096 //13 +#define SPECIAL_FLAME_RESISTANCE 8192 //14 +#define SPECIAL_TRIPBOMB 16384 //15 +#define SPECIAL_CLOAK 32768 //16 +#define SPECIAL_SUPPLY_DEPOT 65536 //17 +#define SPECIAL_PLASMA_BOMB 131072 //18 +#define SPECIAL_GRAPPLE 262144 //19 +#define SPECIAL_SNIPING 524288 //20 +#define SPECIAL_LASER 1048576 //21 +#define SPECIAL_AUTOZOOM 2097152 //22 +#define SPECIAL_QUICKAIM 4194304 //23 The new sniper ability +//Temporary - use these for new special abilities +//#define SPECIAL_t6 4194304 +//THESE TWO ARE RESERVED +#define SPECIAL_OPTION1 8388608 +#define SPECIAL_OPTION2 16777216 +#define SPECIAL_COUNT 24 + +//MAX COUNTS FOR EACH SPECIAL THING +#define MAX_SPECIAL_REMOTE_CAMERA 1 +#define MAX_SPECIAL_SENTRY_GUN 1 +#define MAX_SPECIAL_MISSILE_LAUNCHER 1 +#define MAX_SPECIAL_ALARMS 2 +#define MAX_SPECIAL_LASER_DEFENSE 1 +#define MAX_SPECIAL_TRIPBOMB 2 +#define MAX_SPECIAL_QUAKE 2 +#define MAX_SPECIAL_SUPPLY_DEPOT 1 +#define MAX_SPECIAL_PLASMA_BOMB 1 + +//Sequential list of special items, rather than bitmask +#define ITEM_SPECIAL_REMOTE_CAMERA 1 +#define ITEM_SPECIAL_SENTRY_GUN 2 +#define ITEM_SPECIAL_MISSILE_LAUNCHER 3 +#define ITEM_SPECIAL_ALARMS 4 +#define ITEM_SPECIAL_LASER_DEFENSE 5 +#define ITEM_SPECIAL_TRIPBOMB 6 +#define ITEM_SPECIAL_QUAKE 7 +#define ITEM_SPECIAL_SUPPLY_DEPOT 8 +#define ITEM_SPECIAL_PLASMA_BOMB 9 + + +#define CLASS_DEF_NAME "WF Standard" + + +//Scanner stuff +#define SCANNER_UNIT 32 +#define SCANNER_RANGE 100 +#define SCANNER_UPDATE_FREQ 1 +#define PIC_LEFT "pics/arrowleft.pcx" +#define PIC_LEFT_TAG "scanner/arrowleft" +#define PIC_RIGHT "pics/arrowright.pcx" +#define PIC_RIGHT_TAG "scanner/arrowright" +#define PIC_UP "pics/arrowup.pcx" +#define PIC_UP_TAG "scanner/arrowup" +#define PIC_DOWN "pics/arrowdown.pcx" +#define PIC_DOWN_TAG "scanner/arrowdown" +#define PIC_SCANNER "pics/scanner/scanner.pcx" +#define PIC_SCANNER_TAG "scanner/scanner" +#define SAFE_STRCAT(org,add,maxlen) if ((strlen(org) + strlen(add)) < maxlen) strcat(org,add); +#define LAYOUT_MAX_LENGTH 1400 + +//Fast weapons switching +#define fastswitch 1 + +//Cloaking stuff +#define CLOAK_ACTIVATE_TIME 1.5 // cloak after 1.5 seconds +#define CLOAK_DRAIN 2 // every CLOAK_DRAIN frames, +#define CLOAK_AMMO 1 // drain CLOAK_AMMO amount of cells + +/* ------------------------------- + FUNCTION PROTOTYPES +------------------------------- */ +// wf_jetpack.c +void ApplyThrust (edict_t *ent); + +// wf_proximity.c +void fire_proximity (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, int proximity_type); + +// wf_laserball.c +void fire_laserball (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); + +// wf_decoy.c +void SP_Decoy(edict_t *self); + +// wf_goodyear.c +void fire_goodyear (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); + +// wf_flash.c +void fire_flash (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); + +// wf_cluster.c +void fire_cluster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); + +// wf_pipebomb.c +void fire_pipebomb (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void Cmd_DetPipes_f (edict_t *ent); + +// wf_misc.c +void Cmd_Grenade_f (edict_t *ent); +void Cmd_Grenade1 (edict_t *ent); +void Cmd_Grenade2 (edict_t *ent); +void Cmd_Grenade3 (edict_t *ent); +void Cmd_Thrust_f (edict_t *ent); +void Cmd_Homing_f (edict_t *ent); +void Cmd_Feign_f (edict_t *ent); +void Cmd_Reno_f (edict_t *ent); +void Cmd_WFFlags_f (edict_t *ent); +qboolean K2_IsProtected(edict_t *ent); +void wf_show_grenade_type(edict_t *ent); +qboolean wf_IsWeapon(char *classname); +void wf_InitPlayerClass(gclient_t *client); +void Cmd_DSkin_f (edict_t *ent); +void Cmd_ShowClass (edict_t *ent); +int wf_GetClassModel(int p_class); +void WFPlayer_Die (edict_t *ent); +void WFPlayer_ChangeClassTeam (edict_t *ent); + +// b_turret.c +void turret_remove(edict_t *ent); + + +//wf_lasersight.c +void LaserSightThink (edict_t *self); +void SP_LaserSight(edict_t *self); + +// wf_scanner.c +void Toggle_Scanner (edict_t *ent); +void ShowScanner(edict_t *ent,char *layout); +void ClearScanner(gclient_t *client); +qboolean Pickup_Scanner (edict_t *ent, edict_t *other); + +// wf_jet.c +qboolean Jet_AvoidGround( edict_t *ent ); +qboolean Jet_Active( edict_t *ent ); +void Jet_BecomeExplosion( edict_t *ent, int damage ); +void Jet_ApplyJet( edict_t *ent, usercmd_t *ucmd ); + +// wf_earthquake.c +void fire_earthquake_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); + +//wf_turret.c +void grenturret_think2(edict_t *ent); //Prototypes for grenade turret +void grenturret_think4(edict_t *ent); //think functions +void Cmd_Turret_f(edict_t *ent); +void grenturret_think4 (edict_t *ent); +void grenturret_think3 (edict_t *ent); +void grenturret_think2 (edict_t *ent); +void grenturret_think1 (edict_t *ent); +void fire_turret_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); + +//wf_napalm.c +void fire_napalm (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); + +//wf_flame.c +void fire_flame (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); + + +// Map Rotation Support +// DEFINES + +#define MAX_MAPS 32 + +#define MAX_MAPNAME_LEN 16 + +#define ML_ROTATE_SEQ 0 +#define ML_ROTATE_RANDOM 1 +#define ML_ROTATE_NUM_CHOICES 2 + + +// STRUCTURES + typedef struct +{ + int nummaps; // number of maps in list + char mapnames[MAX_MAPS][MAX_MAPNAME_LEN]; + char rotationflag; // set to ML_ROTATE_* + int currentmap; // index to current map + int votes[MAX_MAPS]; + qboolean voteonly[MAX_MAPS]; //True if this map should only be + //voted on, but not in rotation + qboolean warning_given; // Have we given people a warning to vote? + int active; //is map list active? +} maplist_t; + + //Different flame info + typedef struct + { + vec3_t pos1; + vec3_t vel1; + vec3_t pos2; + vec3_t vel2; + vec3_t pos3; + vec3_t vel3; + vec3_t pos4; + vec3_t vel4; + } Flame_Info; + + // IP ban list +#define MAX_IP_LENGTH 25 +//#define MIN_IP_LENGTH 3 +#define MIN_IP_LENGTH 1 +typedef struct ban_s { + char ip[MAX_IP_LENGTH + 1]; // store a banned IP + int subnet; // index of last '.' if last IP field + // is 0 (i.e. subnet is banned) + struct ban_s *next; // pointer to next banned IP +} ban_t; + +#define MAX_MOTD_LINES 3 +#define MAX_BANWORDS 25 +#define MAX_MOTD_LENGTH 60 +#define MAX_BANWORDS_LENGTH 40 +#define MAX_GAMEINFO_LENGTH 40 + + +typedef struct +{ + char ref_password[MAX_GAMEINFO_LENGTH]; //password of the ref + edict_t *ref_ent; //entity of the ref + int game_halted; //set if scoring and damage is halted + char motd[MAX_MOTD_LINES][MAX_MOTD_LENGTH+1]; + char banwords[MAX_BANWORDS+1][MAX_BANWORDS_LENGTH+1]; + char classdef_name[MAX_GAMEINFO_LENGTH+1]; + char stdlog_name[MAX_GAMEINFO_LENGTH+1]; + int unbalanced_limit; + int floodertime; + int show_ref_skin; //Set if ref wants to show as different skin + char weaponfile_name[MAX_GAMEINFO_LENGTH+1]; + int weapon_damage[WEAPON_COUNT + 1]; + int weapon_speed[WEAPON_COUNT + 1]; + int grenade_damage[GRENADE_TYPE_COUNT + 1]; + int grenade_speed[GRENADE_TYPE_COUNT + 1]; + int ref_picked_map; + +} game_info_t; + + +// GLOBALS +maplist_t maplist; +ban_t *banlist; +game_info_t wf_game; + +// PROTOTYPES + +//wf_maplist.c +int LoadMapList(edict_t *ent, char *filename); +void ClearMapList(); +void Cmd_Maplist_f (edict_t *ent); +void Display_Maplist_Usage(edict_t *ent); +void ClearMapVotes() ; +int MapMaxVotes(); +void VoteForMap(int i); +void DumpMapVotes(); +void Cmd_Maplist_f (edict_t *ent); +void MaplistNextMap(); + +// g_ctf.c +void WFSpecialMenu(edict_t *ent); +void WFMapVote(edict_t *ent); + +// g_main.c +void EndDMLevel (void); + +// remotecam.c +void cmd_CameraPlace(edict_t *ent); +void cmd_CameraToggle(edict_t *ent); + + +// fileio.c +#include "wf_fileio.h" + +//w_laser.c +void cmd_TripBomb(edict_t *self); + +//wf_ipban.c +int IsBanned(char *ip); +void ReadBans(); +void WriteBans(); +void Adm_Ban(char *cmd); +void Adm_Unban(char *cmd); +void Adm_Bans(char *cmd); +void Adm_KickBan(char *cmd); + +//misc function prototypes +void SP_SupplyDepot(edict_t *self); +void place_alarm (int number,edict_t *ent); +void cmd_PlasmaBomb(edict_t *ent); +void cmd_LaserDefense(edict_t *ent); +void fire_napalm (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void burn_person(edict_t *target, edict_t *owner, int damage, int mod); +void infect_person(edict_t *target, edict_t *owner); +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)); +void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed); +void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); +void Grenade_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +void Grenade_Explode (edict_t *ent); +void GenericGrenade_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +void GenericGrenade_Explode (edict_t *ent); +void Rocket_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +void NoAmmoWeaponChange (edict_t *ent); +void ThrowUpNow(edict_t *self); +void TimedNuke_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); +void DiseaseGrenade_Explode (edict_t *ent); +void fire_concussiongrenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_diseasegrenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_magnogrenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_bulletgrenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_flare (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_shockgrenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_freezer (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect);//acrid 3/99 + +qboolean wf_CanUse(gclient_t *cl, edict_t *ent); +void WFRemoveDisguise(edict_t *ent); +void fire_laser_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); +void lasersight_off (edict_t *ent); +void lasersight_on (edict_t *ent); +void feign_off (edict_t *ent); +void feign_on (edict_t *ent); +void Cmd_WFPlayTeam (edict_t *self, char *wavename, int all); +void WFOpenClassMenu(edict_t *ent); +void stuffcmd(edict_t *e, char *s); +void Cmd_ShowPlayers(edict_t *ent); +void place_missile (edict_t *ent); +void UpgradeMissileTurret(edict_t *self); +void Create_All_Bases(); +qboolean WFPickup_Flag(edict_t *ent, edict_t *other); +char getClassCode (gclient_t *cl); +void WFChooseDisguise(edict_t *ent); +void WFRemoveDisguise(edict_t *ent); +qboolean WFUnbalanced_Teams(edict_t *flag, edict_t *player); +void alarm_remove(edict_t *self); +void SP_HealingDepot(edict_t *self); +void botDebugPrint (char *fmt, ...); + + +#include "m_player.h" +#include "dwm.h" +#include "q_devels.h" +#include "kamikaze.h" + +extern qboolean is_quad; +extern byte is_silenced; + +//JR Stuff for flames and a max amount of Fire +#define MAX_FLAMES 60 +int TOTALWORLDFLAMES; + +//Don't allow players to drop an unlimited number of ammo ents +#define MAX_DROPPEDAMMO 30 +int TOTALDROPPEDAMMO; + + diff --git a/wf_ipban.c b/wf_ipban.c new file mode 100644 index 0000000..1311d49 --- /dev/null +++ b/wf_ipban.c @@ -0,0 +1,535 @@ +/*============================================================================== +The Weapons Factory - +IP Banning Code +Original code by Red Barchetta +Modified by Gregg Reno +==============================================================================*/ +#include "g_local.h" +#define BAN_FILE "listip.cfg" +#define IPSEP '.' + +/* Comments +I didn't add anything from newer CTF versions. My ban commands are "ban" +and "unban" (with "bans" showing a list). It would be easy enough to +rename them, though, if you rewrite them to behave like Zoid's. + +Here comes... I'm just going to paste the pieces of code into the message, +since I need to cut up files to extract it. There are definitely +better/shorter ways to do some of this stuff... it just isn't worth the +time to make it efficient, since it is only called when you add an IP to +the list. + +Also, I don't know if WF has an admin system (I would magine it does). If +not, just rip out the admin checks in the commands (Adm_*). Also, you +obviosly need to put calls to Adm_* in g_cmds.c (and g_svcmds.c if you want). + +Have fun... don't distribute this, and please send me whatever +changes/improvements you make. + + + // I use this to allow the file name to be changed. + // put it in with the other cvar inits at game start if you want it + // (you need to declare ban_file and gamedir to be extern cvar_t *'s + ban_file = gi.cvar("ban_file", "", CVAR_USERINFO); + + +*/ + +int isspace(char c) +{ + if (c == ' ' || c == 10 || c == 13) return 1; + else return 0; +} + +//The filter ban determines how the ip addresses are used. If +//the filterban cvar = 1 (normal), then any player matching an +//item in the ban list will be disallowed. If it is set to 0, then +//all player that do NOT have an entry in the banlist will be +//disallowed. This function simply changes the return code +//based on the filter ban +int CheckFilterBan(int retval) +{ + if ((int)filterban->value == 0) + { + if (retval == 0) //swap return values + return 1; + else + return 0; + } + else + return retval; +} + +// check to see if ip is on the banlist +int IsBanned(char *ip) +{ + ban_t *tmp = banlist; +//gi.dprintf("Testing ban of %s\n",ip); + + while (tmp) + { + if (tmp->subnet) + { + if (!strncmp(tmp->ip, ip, tmp->subnet)) + return CheckFilterBan(1); + } + else if (!strcmp(tmp->ip, ip)) + { + return CheckFilterBan(1); + } + + // "else" + tmp = tmp->next; + } + + // if we're here, ip is not on the banlist + return CheckFilterBan(0); +} + +// check for properly-formatted IP address +// NOTE you may need to hack this up to do Zoid-like subnet banning... +int IsValidIP(char *ip) { + char *term; // don't need to allocate, for whatever reason... + // in fact, crashes if allocated and then later free()'d + + char *dot1, *dot2, *dot3, *end; + + if (!((dot1 = strchr(ip, IPSEP)) // check for first dot + && (dot2 = strchr(dot1 + 1, IPSEP)) // check for second dot + && (dot3 = strchr(dot2 + 1, IPSEP)) // check for third dot + && !strchr(dot3 + 1, IPSEP))) { // shouldn't be more than 3 dots + return 0; + } + + // find the end of the address (this is the address of the NULL char) + end = ip + strlen(ip); + + // check for more than 3 chars in any field + // - 1's are to account for the 2 dots + if ((dot1 - ip > 3) || (dot2 - dot1 - 1 > 3) + || (dot3 - dot2 - 1 > 3) || (end - dot3 - 1 > 3)) { + return 0; + } + + // now check for fewer than 1 char in any field + if ((dot1 - ip < 1) || (dot2 - dot1 - 1 < 1) + || (dot3 - dot2 - 1 < 1) || (end - dot3 - 1 < 1)) { + return 0; + } + + // make sure fields contain only digits + if (strtol(ip, &term, 10) > 255 || *term != '.') { + return 0; + } + if (strtol(dot1 + 1, &term, 10) > 255 || *term != '.') { + return 0; + } + if (strtol(dot2 + 1, &term, 10) > 255 || *term != '.') { + return 0; + } + if (strtol(dot3 + 1, &term, 10) > 255 || *term != (char) NULL) { + return 0; + } + + // IP address is good + return 1; +} + +// read in the banlist +// called +void ReadBans() { + FILE *banfile; + + ban_t *tmp; + + int len; + int num3; + char *dot3; + char *bufptr; + char *filename; + + char buf[81]; + + // need to tack the "game" onto the filename... +// if (banfile = fopen(filename = va("%s/%s", *(gamedir->string) +// ? gamedir->string : "baseq2", +// ban_file->string[0] ? ban_file->string : BAN_FILE), "r")) { + if (banfile = fopen(filename = va("%s/%s", *(gamedir->string) + ? gamedir->string : "baseq2",BAN_FILE), "r")) { + // read a line + while (fgets(buf, 80, banfile)) { + // don't consider the '\n' that may be in buf[] + bufptr = buf; + while (!isspace(*bufptr)) + bufptr++; + *bufptr = (char) NULL; + + if ((len = strlen(buf)) <= MAX_IP_LENGTH + && IsValidIP(buf) && !IsBanned(buf)) { + // add to head of list + tmp = banlist; + banlist = (ban_t *) malloc(sizeof(ban_t)); + strcpy(banlist->ip, buf); + banlist->next = tmp; + + // check for subnet ban + dot3 = strrchr(buf, IPSEP); + if (!(num3 = (int) strtol(dot3 + 1, (char **) NULL, 10))) + // this is 1 + INDEX of the last '.' in the IP + banlist->subnet = dot3 - buf + 1; + else + banlist->subnet = 0; // subnet is *not* banned + + gi.dprintf("Added %s to the banlist.\n", + banlist->ip); + } + else { + gi.dprintf("Invalid or already-banned IP \"%s\" read from file \"%s\".\n", + buf, filename); + } + } + + fclose(banfile); + } + // else nothing to do! +} + +// save the banlist to disk +void WriteBans() +{ + int i; + FILE *banfile; + + ban_t *tmp; + + // need to tack the "game" onto the filename... +// if (banfile = fopen(va("%s/%s", *(gamedir->string) ? gamedir->string +// : "baseq2", +// ban_file->string[0] ? ban_file->string : BAN_FILE), "w")) { + if (banfile = fopen(va("%s/%s", *(gamedir->string) ? gamedir->string + : "baseq2", BAN_FILE), "w")) { + tmp = banlist; + + // write bans + i = 0; + while (tmp) + { + fprintf(banfile, "%s\n", tmp->ip); + tmp = tmp->next; + ++i; + } + + fclose(banfile); + } + // else nothing to do! + gi.dprintf("%d IP addresses saved.\n",i); +} + +// test an IP against ban list +void Adm_Test(char *cmd) { + + ban_t *tmp = banlist; + + if (!cmd) + { + gi.dprintf("usage: sv testip \n"); + return; + } + +//gi.dprintf("Testing ban of %s\n",ip); + + while (tmp) + { + if (tmp->subnet) + { + if (!strncmp(tmp->ip, cmd, tmp->subnet)) + { + gi.dprintf("%s banned. Matched subnet %s\n",cmd,tmp->ip); + return; + } + } + else if (!strcmp(tmp->ip, cmd)) + { + gi.dprintf("%s banned. Matched %s\n",cmd,tmp->ip); + return; + } + + // "else" + tmp = tmp->next; + } + + gi.dprintf("IP %s is NOT banned. \n",cmd); + + +} + +// add an IP to the ban list +void Adm_Ban(char *cmd) { + int len; + int no; + int num3; + int x = 0; + + char *junk; + char *dot3; + char *tmp_ip; +// char *x; + char to_ban[MAX_IP_LENGTH + 1]; + + ban_t *tmp; + + edict_t *cl_ent; + + + if (!cmd) + { + gi.dprintf("usage: sv addip \n"); + return; + } + + len = strlen(cmd); +//gi.dprintf("Cmd=[%s], Len = %d, minlen = %d\n",cmd, len,MIN_IP_LENGTH); + + if (len > MAX_IP_LENGTH) + { + gi.dprintf("Invalid IP address.\n"); + return; + } + else if (len < MIN_IP_LENGTH) + { + // check if this is a valid player number + no = (int) strtol(cmd, &junk, 10); + if ((no < 0) + || (no >= game.maxclients) + || (*junk != (char) NULL)) + { + gi.dprintf("Invalid player number.\n"); + return; + } + + cl_ent = g_edicts + 1 + no; // get player + + if (!cl_ent->inuse) { + gi.dprintf("Player number %d is not currently in use.\n", no); + return; + } + + // get player's IP + tmp_ip = Info_ValueForKey(cl_ent->client->pers.userinfo, "ip"); + + // chop port number off of the IP address + while (++x <= MAX_IP_LENGTH) { + if (*(tmp_ip + x) == ':' + || *(tmp_ip + x) == (char) NULL) + break; + } +#if 0 + // chop port number off of the IP address + x = tmp_ip; + while (x) { + if (*(++x) == ':') { + *x = (char) NULL; + break; + } + } +#endif + // copy the IP to the ban string... +// strncpy(to_ban, tmp_ip, MAX_IP_LENGTH); + strncpy(to_ban, tmp_ip, x); + + // guarantee NULL terminate... +// to_ban[MAX_IP_LENGTH] = (char) NULL; + to_ban[x + 1] = (char) NULL; + } + // this is a normal IP (we hope) + else if (IsValidIP(cmd)) { + // copy the IP to the ban string... + strncpy(to_ban, cmd, MAX_IP_LENGTH); + + // guarantee NULL terminate... + to_ban[MAX_IP_LENGTH] = (char) NULL; + } + else { // invalid IP *and* player id. + gi.dprintf("Invalid IP address.\n"); + return; + } + + // now do the actual ban + if (IsBanned(to_ban)) { + gi.dprintf("%s is already banned.\n", to_ban); + } + else { // insert at head of list + tmp = banlist; + banlist = (ban_t *) malloc(sizeof(ban_t)); +// banlist->ip = (char *) malloc(len + 1); + strcpy(banlist->ip, to_ban); + banlist->next = tmp; + + // check for subnet ban +// dot3 = strrchr(gi.argv(1 + sv), IPSEP); + dot3 = strrchr(to_ban, IPSEP); + if (!(num3 = (int) strtol(dot3 + 1, (char **) NULL, 10))) + // this is 1 + INDEX of the last '.' in the IP + banlist->subnet = dot3 - to_ban + 1; + else + banlist->subnet = 0; // subnet is *not* banned + + gi.dprintf("Added %s to the banlist.\n", banlist->ip); + + // make it permanent if possible + WriteBans(); + } +} + +// remove an IP from the ban list +void Adm_Unban(char *cmd) { + int len; + int sv = 0; // changes to 1 if first arg. is "sv" (server command) + + ban_t *tmp; + ban_t *tmp_prev = (ban_t *) NULL; + + + if (!cmd) + { + gi.dprintf("usage: sv removeip \n"); + return; + } + + if ((len = strlen(cmd)) > MAX_IP_LENGTH + || len < MIN_IP_LENGTH) { + gi.dprintf("Invalid IP address.\n"); + } + else { + if (IsBanned(cmd)) { + tmp = banlist; + + // check first individually so we can get "previous" + if (strcmp(tmp->ip, cmd)) { + tmp_prev = tmp; + tmp = tmp->next; + + while (tmp && strcmp(tmp->ip, + cmd)) { + tmp_prev = tmp; + tmp = tmp->next; + } + } + + if (tmp) { // just to be sure + if (tmp_prev) + // take tmp out of the list + tmp_prev->next = tmp->next; + else //this one is the first in the list + banlist = tmp->next; + +// free(tmp->ip); + free(tmp); + + gi.dprintf("Removed %s from the banlist.\n", + cmd); + + // make it permanent if possible + WriteBans(); + } + else // subnet is banned + gi.dprintf("The entire subnet containing %s is banned. You need to remove the subnet ban to unban this IP.\n", + cmd); + } + else { + gi.dprintf("%s is not on the ban list.\n", cmd); + } + } +} + +// show all bans +void Adm_Bans(char *cmd) { + int i = 0; + int sv = 0; // changes to 1 if first arg. is "sv" (server command) + + ban_t *tmp; + + tmp = banlist; + + if (tmp) + { + gi.dprintf("-------------------\n"); + gi.dprintf("BANNED IP ADDRESSES\n"); + gi.dprintf("-------------------\n"); + } + + while (tmp) { + gi.dprintf("%s\n", tmp->ip); + + tmp = tmp->next; + i++; + } + + if (!i) + gi.dprintf("No banned IPs.\n"); + else + gi.dprintf("%d total banned IPs.\n", i); +} + +// kick *and* ban a player all in one shot. Prevents rejoins (obviously)... +void Adm_KickBan(char *cmd) { + int no; + int sv = 0; // changes to 1 if first arg. is "sv" (server command) + int x = 0; + + char *tmp_ip; + char kbstr[32]; + char *term; + + edict_t *cl_ent; + + if (!cmd) + { + gi.dprintf("usage: sv kickban \n"); + return; + } + + // check if this is a valid player number + if ((no = (int) strtol(cmd, &term, 10)) < 0 + || no >= game.maxclients || *term) { + gi.dprintf("Invalid player number %s.\n", + cmd); + return; + } + + cl_ent = g_edicts + 1 + no; // get player + + if (!cl_ent->inuse) { + gi.dprintf("Player number %d is not currently in use.\n", no); + return; + } + + // get player's IP + tmp_ip = Info_ValueForKey(cl_ent->client->pers.userinfo, "ip"); + + // chop port number off of the IP address + while (++x <= MAX_IP_LENGTH) { + if (*(tmp_ip + x) == ':' || *(tmp_ip + x) == (char) NULL) + break; + } + + // create ban command line-- need to use the server version + strcpy(kbstr, "sv ban "); + strncat(kbstr, tmp_ip, x); + strcat(kbstr, "\n"); + + // ban! + gi.AddCommandString(kbstr); + + // create the kick command line + strcpy(kbstr, "kick "); + strncat(kbstr, cmd, 3); + strcat(kbstr, "\n"); + +//COMMENTED-- is this causing kb to crash the server? +// // let the player know (sort of) +// gi.dprintf("Don't come back!\n"); + + // kick! + gi.AddCommandString(kbstr); +} diff --git a/wf_jet.c b/wf_jet.c new file mode 100644 index 0000000..0a53f0e --- /dev/null +++ b/wf_jet.c @@ -0,0 +1,262 @@ +/*============================================================================== +The Weapons Factory - +Jetpack Functions +Original code by ?? +Modified by Gregg Reno +==============================================================================*/ + +#include "g_local.h" + +/*we get silly velocity-effects when we are on ground and try to + accelerate, so lift us a little bit if possible*/ +qboolean Jet_AvoidGround( edict_t *ent ) + { + vec3_t new_origin; + trace_t trace; + qboolean success; + + /*Check if there is enough room above us before we change origin[2]*/ + new_origin[0] = ent->s.origin[0]; + new_origin[1] = ent->s.origin[1]; + new_origin[2] = ent->s.origin[2] + 0.5; + trace = gi.trace( ent->s.origin, ent->mins, ent->maxs, new_origin, ent, MASK_MONSTERSOLID ); + + if ((success = (trace.plane.normal[2]==0))) /*no ceiling?*/ + ent->s.origin[2] += 0.5; /*then make sure off ground*/ + + return success; + } + + +/*This function returns true if the jet is activated + (surprise, surprise)*/ +qboolean Jet_Active( edict_t *ent ) + { + if (!ent->client) return false; + + return ( ent->client->Jet_framenum >= level.framenum ); + } + + +/*If a player dies with activated jetpack this function will be called + and produces a little explosion*/ +void Jet_BecomeExplosion( edict_t *ent, int damage ) + { + int n; + + gi.WriteByte( svc_temp_entity ); + gi.WriteByte( TE_EXPLOSION1 ); /*TE_EXPLOSION2 is possible too*/ + gi.WritePosition( ent->s.origin ); + gi.multicast( ent->s.origin, MULTICAST_PVS ); + gi.sound( ent, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0 ); + + /*throw some gib*/ + for ( n=0; n<4; n++ ) + ThrowGib( ent, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC ); + ThrowClientHead( ent, damage ); + ent->takedamage = DAMAGE_NO; + + } + + +/*The lifting effect is done through changing the origin, it + gives the best results. Of course its a little dangerous because + if we dont take care, we can move into solid*/ +void Jet_ApplyLifting( edict_t *ent ) + { + float delta; + vec3_t new_origin; + trace_t trace; + int time = 24; /*must be >0, time/10 = time in sec for a + complete cycle (up/down)*/ + float amplitude = 2.0; + + /*calculate the z-distance to lift in this step*/ + delta = sin( (float)((level.framenum%time)*(360/time))/180*M_PI ) * amplitude; + delta = (float)((int)(delta*8))/8; /*round to multiples of 0.125*/ + + VectorCopy( ent->s.origin, new_origin ); + new_origin[2] += delta; + + if( VectorLength(ent->velocity) == 0 ) + { + /*i dont know the reason yet, but there is some floating so we + have to compensate that here (only if there is no velocity left)*/ + new_origin[0] -= 0.125; + new_origin[1] -= 0.125; + new_origin[2] -= 0.125; + } + + /*before we change origin, its important to check that we dont go + into solid*/ + trace = gi.trace( ent->s.origin, ent->mins, ent->maxs, new_origin, ent, MASK_MONSTERSOLID ); + if ( trace.plane.normal[2] == 0 ) + VectorCopy( new_origin, ent->s.origin ); + } + + +/*This function applys some sparks to your jetpack, this part is + exactly copied from Muce's and SumFuka's JetPack-tutorial and does a + very nice effect.*/ +void Jet_ApplySparks ( edict_t *ent ) + { + vec3_t forward, right; + vec3_t pack_pos, jet_vector; + + AngleVectors(ent->client->v_angle, forward, right, NULL); + VectorScale (forward, -7, pack_pos); + VectorAdd (pack_pos, ent->s.origin, pack_pos); + pack_pos[2] += (ent->viewheight); + VectorScale (forward, -50, jet_vector); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPARKS); + gi.WritePosition (pack_pos); + gi.WriteDir (jet_vector); + gi.multicast (pack_pos, MULTICAST_PVS); + } + + +/*if the angle of the velocity vector is different to the viewing + angle (flying curves or stepping left/right) we get a dotproduct + which is here used for rolling*/ +void Jet_ApplyRolling( edict_t *ent, vec3_t right ) + { + float roll, + value = 0.05, + sign = -1; /*set this to +1 if you want to roll contrariwise*/ + + roll = DotProduct( ent->velocity, right ) * value * sign; + ent->client->kick_angles[ROLL] = roll; + } + + +/*Now for the main movement code. The steering is a lot like in water, that + means your viewing direction is your moving direction. You have three + direction Boosters: the big Main Booster and the smaller up-down and + left-right Boosters. + There are only 2 adds to the code of the first tutorial: the Jet_next_think + and the rolling. + The other modifications results in the use of the built-in quake functions, + there is no change in moving behavior (reinventing the wheel is a lot of + "fun" and a BIG waste of time ;-))*/ +void Jet_ApplyJet( edict_t *ent, usercmd_t *ucmd ) + { + float direction; + vec3_t acc; + vec3_t forward, right; + int i; + int acceleration; + + if ( ent->health <= 0) + { + return; + } + + //Determine flying speed based on class speed + if (ent->client->player_speed < 360) // Slow Flyer + acceleration = 30; + else + acceleration = 54; // Fast Flyer + + /*clear gravity so we dont have to compensate it with the Boosters*/ + ent->client->ps.pmove.gravity = 0; + + /*calculate the direction vectors dependent on viewing direction + (length of the vectors forward/right is always 1, the coordinates of + the vectors are values of how much youre looking in a specific direction + [if youre looking up to the top, the x/y values are nearly 0 the + z value is nearly 1])*/ + AngleVectors( ent->client->v_angle, forward, right, NULL ); + + /*Run jet only 10 times a second so movement dont depends on fps + because ClientThink is called as often as possible + (fps<10 still is a problem ?)*/ + if ( ent->client->Jet_next_think <= level.framenum ) + { + ent->client->Jet_next_think = level.framenum + 1; + + /*clear acceleration-vector*/ + VectorClear( acc ); + + /*if we are moving forward or backward add MainBooster acceleration + (60)*/ + if ( ucmd->forwardmove ) + { + /*are we accelerating backward or forward?*/ + direction = (ucmd->forwardmove<0) ? -1.0 : 1.0; + + /*add the acceleration for each direction*/ + acc[0] += direction * forward[0] * acceleration; + acc[1] += direction * forward[1] * acceleration; + acc[2] += direction * forward[2] * acceleration; + } + + /*if we sidestep add Left-Right-Booster acceleration (40)*/ + if ( ucmd->sidemove ) + { + /*are we accelerating left or right*/ + direction = (ucmd->sidemove<0) ? -1.0 : 1.0; + + /*add only to x and y acceleration*/ + acc[0] += right[0] * direction * 40; + acc[1] += right[1] * direction * 40; + } + + /*if we crouch or jump add Up-Down-Booster acceleration (30)*/ + if ( ucmd->upmove ) + acc[2] += ucmd->upmove > 0 ? 30 : -30; + + /*now apply some friction dependent on velocity (higher velocity results + in higher friction), without acceleration this will reduce the velocity + to 0 in a few steps*/ + ent->velocity[0] += -(ent->velocity[0]/6.0); + ent->velocity[1] += -(ent->velocity[1]/6.0); + ent->velocity[2] += -(ent->velocity[2]/7.0); + + /*then accelerate with the calculated values. If the new acceleration for + a direction is smaller than an earlier, the friction will reduce the speed + in that direction to the new value in a few steps, so if youre flying + curves or around corners youre floating a little bit in the old direction*/ + VectorAdd( ent->velocity, acc, ent->velocity ); + + /*round velocitys (is this necessary?)*/ + 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; + + /*Bound velocitys so that friction and acceleration dont need to be + synced on maxvelocitys*/ + for ( i=0 ; i<2 ; i++) /*allow z-velocity to be greater*/ + { + if (ent->velocity[i] > 300) + ent->velocity[i] = 300; + else if (ent->velocity[i] < -300) + ent->velocity[i] = -300; + } + + /*add some gentle up and down when idle (not accelerating)*/ + if( VectorLength(acc) == 0 ) + Jet_ApplyLifting( ent ); + + }//if ( ent->client->Jet_next_think... + + /*add rolling when we fly curves or boost left/right*/ + Jet_ApplyRolling( ent, right ); + + + //Only play sound and show sparks after sound is done playing + if (ent->client->next_thrust_sound < level.time) + { + gi.sound (ent, CHAN_BODY, gi.soundindex("jetfly.wav"), 1, ATTN_NORM, 0); +// gi.sound (ent, CHAN_BODY, gi.soundindex("weapons/rockfly.wav"), 1, ATTN_NORM, 0); + + /*last but not least add some smoke*/ + Jet_ApplySparks( ent ); + + ent->client->next_thrust_sound=level.time + 2.0; + } + + } + + /*end of jet.c*/ diff --git a/wf_knife.c b/wf_knife.c new file mode 100644 index 0000000..f39e649 --- /dev/null +++ b/wf_knife.c @@ -0,0 +1,247 @@ +#include "g_local.h" +/* +========================== +Knife +========================== +*/ +void drop_temp_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); +void drop_make_touchable (edict_t *ent); +void WFRemoveDisguise(edict_t *ent); + +void ChuckAmmoFromSentry(edict_t *ent) +{ + edict_t *dropped; + vec3_t forward, right; + gitem_t *item; + int count; + + if (ent->light_level <= 0) return;//out of ammo + + item = FindItem("Bullets"); + + if (item == NULL) + { + //gi.dprintf("Cant find bullets item\n"); + return; + } + + 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); + gi.setmodel (dropped, dropped->item->world_model); + dropped->solid = SOLID_TRIGGER; + dropped->movetype = MOVETYPE_TOSS; + dropped->touch = drop_temp_touch; + dropped->owner = ent; + + 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; + + //Do 100 bullets at a time + if (ent->light_level > 100) + count = 100; + else + count = ent->light_level; + + dropped->count = count; + ent->light_level -= count; + + gi.linkentity (dropped); + +} +/* +================= +fire_stab +================= +*/ +void fire_stab (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int mod) +{ + trace_t tr; + edict_t *ignore; +// int mask; +// qboolean water; + vec3_t forward, wallp; + int content_mask = MASK_PLAYERSOLID |MASK_MONSTERSOLID |MASK_SHOT; + + int bodypos; //Where on the body was the hit? + +// VectorMA (start, 8192, aimdir, end); +// VectorCopy (start, from); + ignore = self; + // Setup "little look" to close wall + VectorCopy(self->s.origin,wallp); + + // Cast along view angle + AngleVectors (self->client->v_angle, forward, NULL, NULL); + + // Setup end point + wallp[0]=self->s.origin[0]+forward[0]*50; + wallp[1]=self->s.origin[1]+forward[1]*50; + wallp[2]=self->s.origin[2]+forward[2]*50; + + // trace + tr = gi.trace (self->s.origin, NULL, NULL, wallp, self, content_mask); + + // Line complete ? (ie. no collision) + if (tr.fraction == 1.0) + { + //gi.dprintf ("Too far from entity.\n"); + //A complete miss will not remove your disguise + //if ((self->disguiseshots <= 0) && (self->disguised)) self->disguiseshots = 1; + return; + } + +//gi.dprintf("Knife Hit %s\n", tr.ent->classname); + + //Location damage! Was it a head or leg shot? + if (tr.ent->client) + { + if (self->disguised) WFRemoveDisguise(self); + + if (tr.endpos[2] < (tr.ent->s.origin[2] - 10)) + { + bodypos = 1; // leg shot + } + else if (tr.endpos[2] > ((tr.ent->s.origin[2] + 20))) + { + bodypos = 2; + } + else + { + bodypos = 0; //Body shot + //See if it was in the back! + if (!infront(tr.ent, self)) + { + bodypos = 3; //in the back! + damage = damage * 5; + } + } + + if (tr.ent->wf_team != self->wf_team) + { + //Play a hit sound + if (bodypos == 3) + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/knifescream.wav"), 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/knifehit.wav"), 1, ATTN_NORM, 0); + } + else + damage = 0; + + } + else + { + //For non-clients +//gi.dprintf("Hit item: %s\n",tr.ent->classname); + + //If it is a sentry gun, remove some ammo + if (strcmp(tr.ent->classname, "SentryGun") == 0) + { + if ((tr.ent->wf_team != self->wf_team) && (self->client->player_special & SPECIAL_DISGUISE)) + { + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/knifehit.wav"), 1, ATTN_NORM, 0); + ChuckAmmoFromSentry(tr.ent); + damage = damage * .1; + + //Hitting the sentry gun will not remove your disguise + //if (self->disguiseshots <= 0) self->disguiseshots = 1; + } + else + damage = 0; + } + + //If it is a decoy, don't do much + else if (strcmp(tr.ent->classname, "decoy") == 0) + { + //gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/disembowel.wav"), 1, ATTN_NORM, 0); + } + + + //Otherwise, make it look like a bullet shot. + else + { + //damage = 0; + if (self->disguised) WFRemoveDisguise(self); + fire_bullet (self, start, aimdir, 0, kick, 0, 0, MOD_SNIPERRIFLE); + } + } + + if ((damage) && (CanDamage(tr.ent, self)) ) + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, mod); + + +// if (self->client) +// PlayerNoise(self, tr.endpos, PNOISE_IMPACT); +} + + + +void weapon_knife_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage = wf_game.weapon_damage[WEAPON_KNIFE]; + int kick = 4; + + + if (ent->client->ps.gunframe == 11) + { + //Play a slicing cound + gi.sound (ent, CHAN_WEAPON, gi.soundindex("weapons/knifeswish.wav"), 1, ATTN_NORM, 0); + + ent->client->ps.gunframe++; + return; + } + + //Must be frame 13 here then + 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; + } + + fire_stab (ent, start, forward, damage, kick, MOD_KNIFE); + + // 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_IMPACT); + + //does not take any ammo +// if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) +// ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_Knife (edict_t *ent) +{ + static int pause_frames[] = {22, 28, 34, 0}; + static int fire_frames[] = {11, 13, 0}; //Call fire frame on 8 and 13 + + Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_knife_fire); +} + diff --git a/wf_laserball.c b/wf_laserball.c new file mode 100644 index 0000000..b4408dc --- /dev/null +++ b/wf_laserball.c @@ -0,0 +1,267 @@ +/*============================================================================== +The Weapons Factory - +Laserball Functions +Original code by Gregg Reno +==============================================================================*/ +#include +#include "g_local.h" +#include "q_devels.h" +extern void target_laser_start (edict_t *self); + +/* +================= +laserball_explode +================= +*/ + +void laserball_cleanup (edict_t *ent) +{ + edict_t *lent; + edict_t *next; + + + // clean up laser entities + for (lent = ent->teamchain; lent; lent = next) + { + next = lent->teamchain; + G_FreeEdict (lent); + } +} + +void laserball_explode (edict_t *ent) +{ + vec3_t origin; + + laserball_cleanup(ent); + + 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); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, MOD_WF_LASERBALL); + + //Do the explosion + 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_PVS); + + G_FreeEdict (ent); +} + +/* +================= +laserball_think +================= +*/ +void laserball_think (edict_t *ent) + { + edict_t *target = NULL; + edict_t *blip = NULL; + edict_t *lent = NULL; + edict_t *next = NULL; + edict_t *beam = NULL; + edict_t *check; + vec3_t targetdir, blipdir; + int found; + + float points; + vec3_t v; + float dist; + + //Exlode the laserball if time has run out + if (level.time > ent->delay) //don't exploce on contact + { + laserball_explode(ent); + return; + } + + lent = ent->teamchain; + //ent->teamchain = NULL; + + while ((blip = findradius(blip, ent->s.origin, 350)) != NULL) + { + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + continue; + if (blip == ent->owner) + continue; + //dont attack same team unless friendly fire is allowed + if ((blip->wf_team == ent->wf_team) && (((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE)==0)) + continue; + if (blip->disguised) + continue; + if (!blip->takedamage) + continue; + if (blip->health <= 0) + continue; + if (!visible(ent, blip)) + continue; +// if (!infront(ent, blip)) +// continue; + VectorSubtract(blip->s.origin, ent->s.origin, blipdir); + blipdir[2] += 16; + if ((target == NULL) || (VectorLength(blipdir) < VectorLength(targetdir))) + { + target = blip; + VectorCopy(blipdir, targetdir); + } + } + + if (target != NULL) + { + // see if this is already on the list + found = false; + for (check = ent->teamchain; check; check = check->teamchain) + if (check->enemy == target) found = true; + if (found == false) { + //Make the laser entity + +//safe_cprintf (owner, PRINT_HIGH, "LB enemy. MyTeam=%d, Enemyteam=%d\n", self->s.modelindex, self->wf_team, target->wf_team ); + + beam = G_Spawn(); + beam->flags |= FL_TEAMSLAVE; + beam->teamchain = ent->teamchain; + beam->teammaster = ent; + ent->teamchain = beam; + beam->owner = ent->owner; + + //Set the laser color based on team + if (ent->wf_team == CTF_TEAM1) //team 1 is red + { + beam->spawnflags = 1 | 2; + } + else + { + beam->spawnflags = 1 | 8; //team 2 is blue + } + + beam->enemy = target; + beam->activator = ent->owner; + beam->dmg = wf_game.grenade_damage[GRENADE_TYPE_LASERBALL]; + beam->classname = "laserball laser"; + target_laser_start (beam); + beam->movetype = MOVETYPE_FLYMISSILE; + gi.linkentity (beam); + + //Green flash the enemy + VectorAdd (target->mins, target->maxs, v); + VectorMA (target->s.origin, 0.5, v, v); + VectorSubtract (ent->s.origin, v, v); + dist = VectorLength(v); + points = ent->radius_dmg * (1.0 - sqrt(dist/ent->dmg_radius)); + if (target == ent->owner) + points = points * 0.5; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_EXPLOSION); + gi.WritePosition (target->s.origin); + gi.multicast (target->s.origin, MULTICAST_PVS); + //T_Damage (target, ent, ent->owner, ent->velocity, target->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY); + } + } + + ent->nextthink = level.time + .1; + ent->think = laserball_think; + } + +/* +================= +laserball_touch +================= +*/ +void laserball_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + + if (other == ent->owner) + return; + + //gi.dprintf("TOUCH\n"); + + if (surf && (surf->flags & SURF_SKY)) + { + laserball_explode (ent); + return; + } + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + //laserball_explode (ent); + +} + +/* +================= +fire_laserball +================= +*/ +//void fire_laserball (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +void fire_laserball (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *laserball; + + vec3_t dir; + vec3_t forward, right, up; + + ++self->client->pers.active_grenades[GRENADE_TYPE_LASERBALL]; + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + self->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= LASERBALL_CELLS; + + //Increase the speed on this ! + speed = speed * 1.5; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + laserball = G_Spawn(); + laserball->grenade_index = GRENADE_TYPE_LASERBALL; + + VectorCopy (start, laserball->s.origin); + VectorScale (aimdir, speed, laserball->velocity); + VectorMA (laserball->velocity, 200 + crandom() * 10.0, up, laserball->velocity); + VectorMA (laserball->velocity, crandom() * 10.0, right, laserball->velocity); + VectorSet (laserball->avelocity, 300, 300, 300); + laserball->movetype = MOVETYPE_BOUNCE; +// laserball->movetype = MOVETYPE_FLOAT; + laserball->clipmask = MASK_SHOT; + laserball->solid = SOLID_BBOX; + laserball->s.effects |= EF_GRENADE; + VectorClear (laserball->mins); + VectorClear (laserball->maxs); + + //Try to raise up the laserball a bit + VectorSet(laserball->mins, -7,-7,-7); + VectorSet(laserball->maxs, 7, 7, 7); + + laserball->s.modelindex = gi.modelindex (GRLASERBALL_MODEL); + laserball->s.skinnum = GRLASERBALL_SKIN; +// laserball->s.modelindex = gi.modelindex ("models/objects/laserball/tris.md2"); + laserball->owner = self; + laserball->touch = laserball_touch; + laserball->teammaster = laserball; + laserball->teamchain = NULL; + + laserball->nextthink = level.time + .1; + laserball->think = laserball_think; + laserball->delay = level.time + 3.0; + + //set the team + laserball->wf_team = self->client->resp.ctf_team; + + laserball->dmg = damage; + laserball->radius_dmg = damage_radius; + laserball->dmg_radius = damage_radius; + laserball->classname = "laserball"; + + gi.linkentity (laserball); +} + + diff --git a/wf_local_client.h b/wf_local_client.h new file mode 100644 index 0000000..96fd7e3 --- /dev/null +++ b/wf_local_client.h @@ -0,0 +1,87 @@ +// WF - Added variables for client structure + + + // Jetpack stuff (NOTE: take out unused stuff) + float Jet_framenum; /*burn out time when jet is activated*/ + float Jet_remaining; /*remaining fuel time*/ + float Jet_next_think; + + // GREGG/MUCE: added for jetpack thrusting. + qboolean thrusting; // 1 on 0 off + float next_thrust_sound; + float blindTime, blindBase; //for flash grenades + + //Respawn protection + float protecttime; //How long to protect (in frames) + + //Flood protection & spamming + int floodtime1; + int floodtime2; + + //Player class variables + int grenade_type1; + int grenade_type2; + int grenade_type3; + int player_armor; + int player_model; + int player_ammo; + int player_speed; + int player_maxhealth; + int player_special; + int player_items; + + //Camera variables + //SBOF: chasecam variables + int chasetoggle; + edict_t *chasecam; + edict_t *oldplayer; + // JDB: new variable for lowlight vision 4/4/98 + qboolean lowlight; + int remotetoggle; + + //kamikaze support + int kamikaze_mode; + float kamikaze_framenum; + float kamikaze_timeleft; + + //Give a menu structure to each player so it + //isn't changed between players + #define MAX_SPECIAL_MENU_ITEMS 12 + + pmenu_t wfspecial[MAX_SPECIAL_MENU_ITEMS]; + char wfmenustr[MAX_SPECIAL_MENU_ITEMS][64]; + + pmenu_t sentrymenu[MAX_SPECIAL_MENU_ITEMS]; + char wfsentrystr[MAX_SPECIAL_MENU_ITEMS][64]; + + //Id function arrays. Hardcoded for no particular reason. + pmenu_t iddisplay[30]; + char wfidstr[30][64]; + + //Tracker target + edict_t *tracker_target; + + //Gas grenade settings + float Gas_Time; + float Gas_Delay; + float GasR; + float GasG; + float GasB; + vec3_t GasViewOffset; + int GasFOV; + + //Weapon damage + int weapon_damage; + + int silenced; //doesn't allow a player to talk or play sounds + + //NEWGrapple Variables 4/99 + edict_t *hook; + edict_t *hook_touch; + qboolean on_hook; + int hook_frame; + + // cloaking + qboolean cloaking; + float cloaktime; + int cloakdrain; diff --git a/wf_local_edict.h b/wf_local_edict.h new file mode 100644 index 0000000..9684df4 --- /dev/null +++ b/wf_local_edict.h @@ -0,0 +1,118 @@ +//WF - New variables for edict structure + + int homing_lock; //0 = not locked, 1 = locked + edict_t *lasersight; //pointer to laser sight + edict_t *decoy; //Pointer to decoy + edict_t *creator; //Who created this (used by decoy) + edict_t *missile; //pointer to laser sight + int wf_team; //team of player,decoy, whatever + qboolean noteamdamage; //True if same team can't damage entity + + //Used for item limits + int grenade_index; //if set, this is an index into the active_grenades array + int special_index; //if set, this is an index into the active_special array + + //Used by map entities + int bonustype; + int bonusvalue; + edict_t *orig_map_entity; //points to the spawned map flag return map entity, rather + //than the one created in wf_flagcap.c + //Turret data - Imp was here + float turrettime; + float turretdie; + int turretammo; + //int turretsoundcountdown; //Countdown to when next sound is played + vec3_t targetdir; + vec3_t orig_angles; + + //Disguise + int disguised; + int FrameShot; + int disguising; + float disguisetime; + int disguisingteam; + int disguisedteam; + int disguiseshots; + + //sentry stuff + edict_t *selectedsentry; + edict_t *standowner; + + //Means of Death for this entity + int mod; + + int superslow; + int lame; //reduce speed if they were shot in the leg + int nospeed; + float sentrydelay; + + int CanFloat; + float FloatTime; + int HitTopOfWater; + int GoneDown; + + //a different flame thrower + Flame_Info flameinfo; + + int SniperDamage; + int disease; + edict_t *diseased_by; + edict_t *alarm1; + edict_t *alarm2; + edict_t *alarm3; + float PlasmaDelay; + int Flames; + float PlayerSnipingZoom; + edict_t *sentry; + int ShotNumber; + edict_t *remotecam;//the remote camera + edict_t *remote; + edict_t *supply; + float DrunkTime; + int cantmove; //Set if entity should be frozen + int Slower; //for slow grenade and tranquilizer + float DizzyYaw; + float DizzyPitch; + float DizzyRoll; + vec3_t LockedPosition; + vec3_t camposition; //Camera viewpoint + int menutime; + edict_t *orb; + int kick; + + // freezer code + char oldskin[60]; + qboolean frozen; + qboolean frozenbody; + int frozentime; + //WF ACRID 3/99 + + + //Unused items + /* + int snipe; + int boots; + int ViewPoint; //1 is back 0 is normal + int ClassSpeed; + int TeamNumber; + int HaveFlag; + int OnTurret; + int ShootingTurret; + int LaserOrbs; + int FireDamage; + int LaserBomb; + int ClassNumber; + int max_armor;//JR 2/20/98 + edict_t *searchlight;//JR 2/23/98 + edict_t *turret1;//JR 2/25/98 + edict_t *turret2; + edict_t *cable; + edict_t *satellite; + usercmd_t *cmd; + //SBOF: Chasecam variables + int chasedist1; + int chasedist2; + vec3_t sndvelocity; +*/ + + float delay2; // used for second timer \ No newline at end of file diff --git a/wf_local_pers.h b/wf_local_pers.h new file mode 100644 index 0000000..96beaeb --- /dev/null +++ b/wf_local_pers.h @@ -0,0 +1,34 @@ +//WF - Additional persistant data + int player_class; + int next_player_class; + int scanner_active; + int grenade_type; + int grenade_num; + qboolean HasVoted; //True if the person voted for a map + int active_grenades[GRENADE_TYPE_COUNT + 1]; + int active_special[SPECIAL_COUNT + 1]; + + //Homing state + qboolean homing_state; // are homing missiles activated + + //For sniper + int laseron; + int autozoom; + int fastaim; + + int feign; + +#define SPAM_NOTEAMSOUND 1 //Turns off wfplay_team sounds +#define SPAM_NOTEAMSOUNDTEXT 2 //Turns off wfplay_team sound text +#define SPAM_NOTEAMMSG 4 //Turns off player messages + int nospam_level; + + //Auto execut class specific configs? + qboolean autoconfig; + + //Friend settings +#define MAX_FRIENDS 4 + edict_t *friend_ent[MAX_FRIENDS]; + int hasfriends; + int i_am_a_bot; //set if this is a bot client + diff --git a/wf_maplist.c b/wf_maplist.c new file mode 100644 index 0000000..f2fd9fd --- /dev/null +++ b/wf_maplist.c @@ -0,0 +1,347 @@ +// +// maplist.c -- server maplist rotation/manipulation routines +// +// +// 1/98 - L. Allan Campbell (Geist) +// + +// INCLUDES ///////////////////////////////////////////////// + +#include "g_local.h" + +//Clear the map votes +void ClearMapVotes() +{ + int i; + for (i=0; i < MAX_MAPS; ++i) + maplist.votes[i] = 0; + +} + +//Find highest voted map +/* + Returns + -1 No votes + 0-31 Index to selected map +*/ +int MapMaxVotes() +{ + int i; + int numvotes; + int index; + + numvotes = 0; + index = -1; + i = 0; + + while (i < maplist.nummaps) + { + if (maplist.votes[i] > numvotes) + { + numvotes = maplist.votes[i]; + index = i; + } + ++i; + } + return(index); +} + +void VoteForMap(int i) +{ + if (i >= 0 && i < maplist.nummaps) + ++maplist.votes[i]; +} + +void DumpMapVotes() +{ + int i; + for (i = 0; i < maplist.nummaps; ++i) + gi.dprintf("%d. %s (%d votes)\n", + i, maplist.mapnames[i], maplist.votes[i]); +} + + + // +// ClearMapList +// +// Clears/invalidates maplist. Might add more features in the future, +// but resetting .nummaps to 0 will suffice for now. +// +// Args: +// ent - entity (client) to print diagnostic messages to (future development). +// +// Return: (none) +// +void ClearMapList() +{ + maplist.nummaps = 0; + ClearMapVotes(); + maplist.active = 0; +} + + + // +// DisplayMaplistUsage +// +// Displays current command options for maplists. +// +// Args: +// ent - entity (client) to display help screen (usage) to. +// +// Return: (none) +// +void DisplayMaplistUsage(edict_t *ent) +{ + gi.dprintf ("-------------------------------------\n"); + gi.dprintf ("usage:\n"); + gi.dprintf ("SV MAPLIST START to go to 1st map\n"); + gi.dprintf ("SV MAPLIST NEXT to go to next map\n"); + gi.dprintf ("SV MAPLIST to display current list\n"); + gi.dprintf ("SV MAPLIST OFF to clear/disable\n"); + gi.dprintf ("SV MAPLIST RANDOM for random map rotation\n"); + gi.dprintf ("SV MAPLIST SEQUENTIAL for sequential map rotation\n"); + gi.dprintf ("SV MAPLIST HELP for this screen\n"); + gi.dprintf ("-------------------------------------\n"); +} + +// MaplistNextMap +// Choose the next map in the list, or use voting system +void MaplistNextMap(edict_t *ent) +{ + int votemap; + int i; + int j; + + DumpMapVotes(); + +// Jason - j is so we alter the struct member currentmaps in the loops. + + j = maplist.currentmap; + + switch (maplist.rotationflag) // choose next map in list + { + case ML_ROTATE_SEQ: // sequential rotation + +// Jason - If there is only one map don't infinate loop + + if (maplist.nummaps > 1) + { + +// Jason - do while loops chooses next map and obeys the voteonly array + + do + { + i = (j + 1) % maplist.nummaps; + j++; + } + while (maplist.voteonly[i] == true); + } + else + i = 0; + break; + + case ML_ROTATE_RANDOM: // random rotation + if (maplist.nummaps > 1) + { + do + { + i = (int) (random() * maplist.nummaps); + } + while ((maplist.voteonly[i] == true) || (i == j)); + } + else + i = 0; + break; + + default: // should never happen, but set to first map if it does + i=0; + } // end switch + + //See if map voting is on + if ((int)wfflags->value & WF_MAP_VOTE) + { + votemap = MapMaxVotes(); + if (votemap >= 0) //Yes there was one picked + i = votemap; + ClearMapVotes(); + } + + maplist.currentmap = i; + ent->map = maplist.mapnames[i]; +} + + + +// +// Cmd_Maplist_f +// +// Main command line parsing function. Enables/parses/diables maplists. +// +// Args: +// ent - entity (client) to display messages to, if necessary. +// +// Return: (none) +// +// TODO: change "client 0" privs to be for server only, if dedicated. +// only allow other clients to view list and see HELP screen. +// (waiting for point release for this feature) +// +#define MLIST_ARG_1 2 +#define MLIST_ARG_2 3 +#define MLIST_ARG_3 4 + + +void Cmd_Maplist_f (edict_t *ent) +{ + int i; // looping and temp variable + int argcount; +// char *filename; + + argcount = gi.argc() - 1; +//gi.dprintf("argcount = %d, arg1 = %s, arg2 = %s\n", argcount, gi.argv(MLIST_ARG_1), +// gi.argv(MLIST_ARG_2)); + + switch (argcount) + { + case 2: // various commands, or enable and assume rotationflag default +//gi.dprintf("arg count 2\n"); + if (Q_stricmp(gi.argv(MLIST_ARG_1), "HELP") == 0) + { + DisplayMaplistUsage(ent); + break; + } + if (Q_stricmp(gi.argv(MLIST_ARG_1), "START") == 0) + { + if (maplist.nummaps > 0) // does a maplist exist? + EndDMLevel(); + else + { + gi.dprintf("Can't start - No maps in current list\n"); + DisplayMaplistUsage(ent); + } + break; + } + else if (Q_stricmp(gi.argv(MLIST_ARG_1), "NEXT") == 0) + { + if (maplist.nummaps > 0) // does a maplist exist? + EndDMLevel(); + else + { + gi.dprintf("Can't do next - No maps in current list\n"); + DisplayMaplistUsage(ent); + } + break; + } + else if (Q_stricmp(gi.argv(MLIST_ARG_1), "RANDOM") == 0) + { + maplist.rotationflag = ML_ROTATE_RANDOM; + gi.dprintf("Map rotation set to random.\n"); + break; + } + else if (Q_stricmp(gi.argv(MLIST_ARG_1), "SEQUENTIAL") == 0) + { + maplist.rotationflag = ML_ROTATE_SEQ; + gi.dprintf("Map rotation set to sequential.\n"); + break; + } + else if (Q_stricmp(gi.argv(MLIST_ARG_1), "OFF") == 0) + { + if (maplist.nummaps > 0) // does a maplist exist? + { + gi.dprintf ("Maplist disabled.\n"); + maplist.active = 0; + } + else + { + // maplist doesn't exist, so display usage + gi.dprintf("No maps in current list\n"); + DisplayMaplistUsage(ent); + } + break; + } + else + { + maplist.rotationflag = 0; + } + + // no break here is intentional; supposed to fall though to case 3 + case 3: // enable maplist - all args explicitly stated on command line +//gi.dprintf("arg count 3\n"); + if (gi.argc() == 3) // this is required, because it can still = 2 + { + i = atoi(gi.argv(MLIST_ARG_2)); + if (Q_stricmp(gi.argv(MLIST_ARG_1), "GOTO") == 0) + { + // user trying to goto specified map # in list + if ((i<1) || (i>maplist.nummaps)) + DisplayMaplistUsage(ent); + else + { + ent = G_Spawn (); + ent->classname = "target_changelevel"; + ent->map = maplist.mapnames[i-1]; + maplist.currentmap = i-1; + BeginIntermission(ent); + } + break; + } + else + { + // user trying to specify new maplist + if ((i<0) || (i>=ML_ROTATE_NUM_CHOICES)) // check for valid rotationflag + { + // outside acceptable values for rotationflag + DisplayMaplistUsage(ent); + break; + } + else + { + maplist.rotationflag = atoi(gi.argv(MLIST_ARG_2)); + } + } + } + gi.dprintf("The map list is now loaded automatically through the wfserver.ini file.\n"); + break; + case 1: // display current maplist +//gi.dprintf("arg count 1\n"); + if (maplist.nummaps > 0) // does a maplist exist? + { + gi.dprintf ("-------------------------------------\n"); + for (i=0; iclient->pers.weapon != g) + { + ent->client->newweapon = g; + ent->client->ps.gunframe = 1; + ent->client->weaponstate = WEAPON_READY; + ent->client->grenade_time = 0; + Weapon_Grenade (ent); + } + lasersight_off (ent); + + wf_show_grenade_type(ent); + //ent->cantmove = 0; //free player movement + //this allows cyborg free movement after + //dropping plasma bomb. had to disable it + +} + +//What model is used by this class? +int wf_GetClassModel(int p_class) +{ + int p_model; + + if (p_class <= 0 || p_class > numclasses) + p_model = CLASS_MODEL_MALE; + else + p_model = classinfo[p_class].model; + + return (p_model); + +} + +//What model is used by this class? +int wf_GetModelFromName(char *name) +{ + + if (name[0] == 'F' || name[0] == 'f') + return CLASS_MODEL_FEMALE; + else if (name[0] == 'C' || name[0] == 'c') + return CLASS_MODEL_CYBORG; + else + return CLASS_MODEL_MALE; + +} + + +/* +================= +Cmd_Grenade_f +Set the type of grenade +================= +*/ +void Cmd_Grenade_f (edict_t *ent) +{ + char *string; + int max_grenade_types; + int old_type; + + //If weapon is being fired don't switch + if(!(ent->client->weaponstate == WEAPON_READY)) + return; + + string=gi.args(); + + old_type = ent->client->pers.grenade_type; + + //If player classes are on, just cycle through the three + //grenade types + if (((int)wfflags->value & WF_NO_PLAYER_CLASSES) == 0) + max_grenade_types = 3; + else + max_grenade_types = GRENADE_TYPE_COUNT; + + //If no argument, go to next grenade type + if (Q_stricmp ( string, "") == 0) + { + //Pick next grenade number + ent->client->pers.grenade_num += 1; + if (ent->client->pers.grenade_num > max_grenade_types) + ent->client->pers.grenade_num = 1; + + //Set grenade type based on grenade number + if (((int)wfflags->value & WF_NO_PLAYER_CLASSES) == 0) + { + if (ent->client->pers.grenade_num == 1) ent->client->pers.grenade_type = ent->client->grenade_type1; + if (ent->client->pers.grenade_num == 2) ent->client->pers.grenade_type = ent->client->grenade_type2; + if (ent->client->pers.grenade_num == 3) ent->client->pers.grenade_type = ent->client->grenade_type3; + //Test for empty grenade type. If so, use slot 1 grenade + if (ent->client->pers.grenade_type == GRENADE_TYPE_NONE) + { + ent->client->pers.grenade_num = 1; + ent->client->pers.grenade_type = ent->client->grenade_type1; + } + } + else + ent->client->pers.grenade_type = ent->client->pers.grenade_num; + } + else if (Q_stricmp ( string, "normal") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_NORMAL; + } + else if (Q_stricmp ( string, "laserball") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_LASERBALL; + } + else if (Q_stricmp ( string, "proximity") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_PROXIMITY; + } + else if (Q_stricmp ( string, "cluster") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_CLUSTER; + } +// else if (Q_stricmp ( string, "pipebomb") == 0) +// { +// ent->client->pers.grenade_type = GRENADE_TYPE_PIPEBOMB; +// } + else if (Q_stricmp ( string, "goodyear") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_GOODYEAR; + } + else if (Q_stricmp ( string, "flash") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_FLASH; + } + else if (Q_stricmp ( string, "quake") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_EARTHQUAKE; + } + else if (Q_stricmp ( string, "turret") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_TURRET; + } + else if (Q_stricmp ( string, "napalm") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_NAPALM; + } + else if (Q_stricmp ( string, "concussion") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_CONCUSSION; + } + else if (Q_stricmp ( string, "narcotic") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_NARCOTIC; + } + else if (Q_stricmp ( string, "plague") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_PLAGUE; + } + else if (Q_stricmp ( string, "magnotron") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_MAGNOTRON; + } + else if (Q_stricmp ( string, "shock") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_SHOCK; + } + else if (Q_stricmp ( string, "pipebomb") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_PIPEBOMB; + } + else if (Q_stricmp ( string, "shrapnel") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_SHRAPNEL; + } + else if (Q_stricmp ( string, "flare") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_FLARE; + } + else if (Q_stricmp ( string, "slow") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_SLOW; + } + else if (Q_stricmp ( string, "lasercutter") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_LASERCUTTER; + } + else if (Q_stricmp ( string, "tesla") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_TESLA; + } + else if (Q_stricmp ( string, "gas") == 0) + { + ent->client->pers.grenade_type = GRENADE_TYPE_GAS; + } + else //default to normal grenade + { + ent->client->pers.grenade_type = GRENADE_TYPE_NORMAL; + } + + //Check for valid grenade + if (((int)wfflags->value & WF_NO_PLAYER_CLASSES) == 0) + { + if ((ent->client->pers.grenade_type != ent->client->grenade_type1) && + (ent->client->pers.grenade_type != ent->client->grenade_type2) && + (ent->client->pers.grenade_type != ent->client->grenade_type3)) + { + safe_cprintf (ent, PRINT_HIGH, "You can't use that type of grenade.\n"); + ent->client->pers.grenade_type = old_type; + } + + } + + wf_set_grenade(ent); +} + + + + +void Cmd_Grenade1 (edict_t *ent) +{ + + //If weapon is being fired don't switch + if(!(ent->client->weaponstate == WEAPON_READY)) + return; + + ent->client->pers.grenade_num = 1; + ent->client->pers.grenade_type = ent->client->grenade_type1; +// wf_show_grenade_type(ent); + wf_set_grenade(ent); +} + +void Cmd_Grenade2 (edict_t *ent) +{ + + //If weapon is being fired don't switch + if(!(ent->client->weaponstate == WEAPON_READY)) + return; + + ent->client->pers.grenade_num = 2; + ent->client->pers.grenade_type = ent->client->grenade_type2; +// wf_show_grenade_type(ent); + wf_set_grenade(ent); +} + +void Cmd_Grenade3 (edict_t *ent) +{ + + //If weapon is being fired don't switch + if(!(ent->client->weaponstate == WEAPON_READY)) + return; + //This grenade type could be empty + if (ent->client->grenade_type3 == GRENADE_TYPE_NONE) + { + //Use grenade 1 instead + ent->client->pers.grenade_num = 1; + ent->client->pers.grenade_type = ent->client->grenade_type1; + } + else + { + ent->client->pers.grenade_num = 3; + ent->client->pers.grenade_type = ent->client->grenade_type3; + } +// wf_show_grenade_type(ent); + wf_set_grenade(ent); +} + +/* wf_show_grenade_type */ +void wf_show_grenade_type(edict_t *ent) +{ + ent->client->weapon_damage = 0; + if (!ent->client) return; + + if (ent->client->pers.grenade_type > 0 && + ent->client->pers.grenade_type <= GRENADE_TYPE_COUNT) + ent->client->weapon_damage = wf_game.grenade_damage[ent->client->pers.grenade_type]; + + switch (ent->client->pers.grenade_type) + { + case (GRENADE_TYPE_NORMAL): + safe_cprintf (ent, PRINT_HIGH, "Normal Grenade\n"); + break; + case (GRENADE_TYPE_PROXIMITY): + safe_cprintf (ent, PRINT_HIGH, "Proximity Grenade\n"); + break; + case (GRENADE_TYPE_CLUSTER): + safe_cprintf (ent, PRINT_HIGH, "Cluster Grenade\n"); + break; + case (GRENADE_TYPE_LASERBALL): + safe_cprintf (ent, PRINT_HIGH, "Laserball Grenade\n"); + break; + case (GRENADE_TYPE_FLASH): + safe_cprintf (ent, PRINT_HIGH, "Flash Grenade\n"); + break; + case (GRENADE_TYPE_GOODYEAR): + safe_cprintf (ent, PRINT_HIGH, "Goodyear Grenade\n"); + break; + case (GRENADE_TYPE_EARTHQUAKE): + safe_cprintf (ent, PRINT_HIGH, "Earthquake Grenade\n"); + break; + case (GRENADE_TYPE_TURRET): + safe_cprintf (ent, PRINT_HIGH, "Turret Grenade\n"); + break; + case (GRENADE_TYPE_CONCUSSION): + safe_cprintf (ent, PRINT_HIGH, "Concussion Grenade\n"); + break; + case (GRENADE_TYPE_NARCOTIC): + safe_cprintf (ent, PRINT_HIGH, "Narcotic Grenade\n"); + break; + case (GRENADE_TYPE_PLAGUE): + safe_cprintf (ent, PRINT_HIGH, "Plague Grenade\n"); + break; + case (GRENADE_TYPE_MAGNOTRON): + safe_cprintf (ent, PRINT_HIGH, "Magnotron Grenade\n"); + break; + case (GRENADE_TYPE_SHOCK): + safe_cprintf (ent, PRINT_HIGH, "Shock Grenade\n"); + break; + case (GRENADE_TYPE_PIPEBOMB): + safe_cprintf (ent, PRINT_HIGH, "Pipebomb Grenade\n"); + break; + case (GRENADE_TYPE_SHRAPNEL): + safe_cprintf (ent, PRINT_HIGH, "Shrapnel Grenade\n"); + break; + case (GRENADE_TYPE_FLARE): + safe_cprintf (ent, PRINT_HIGH, "Flare\n"); + break; + case (GRENADE_TYPE_SLOW): + safe_cprintf (ent, PRINT_HIGH, "Slow Grenade\n"); + break; + case (GRENADE_TYPE_NAPALM): + safe_cprintf (ent, PRINT_HIGH, "Napalm Grenade\n"); + break; + case (GRENADE_TYPE_LASERCUTTER): + safe_cprintf (ent, PRINT_HIGH, "Lasercutter Grenade\n"); + break; + case (GRENADE_TYPE_TESLA): + safe_cprintf (ent, PRINT_HIGH, "Tesla Grenade\n"); + break; + case (GRENADE_TYPE_GAS): + safe_cprintf (ent, PRINT_HIGH, "Gas Grenade\n"); + break; + default: + safe_cprintf (ent, PRINT_HIGH, "UNKNOWN Grenade\n"); + ent->client->weapon_damage = 0; + break; + } +} + + +/* +================= +Cmd_Thrust_f + +MUCE: +To set jetpack on or off +================= +*/ +void Cmd_Thrust_f (edict_t *ent) +{ + char *string; + + string=gi.args(); + + if (Q_stricmp ( string, "on") == 0) + { + ent->client->thrusting=1; + ent->client->next_thrust_sound=0; + } + else + { + ent->client->thrusting=0; + } +} + +/* +================= +Cmd_Homing_f +CCH: whole new function for adjusting homing missile state +================= +*/ +void Cmd_Homing_f (edict_t *ent) +{ + char *string; + gitem_t *weap; + + string=gi.args(); + + if (Q_stricmp ( string, "on") == 0) + { + safe_cprintf (ent, PRINT_HIGH, "HOMING MISSILES ON\n"); + ent->client->pers.homing_state = 1; + } + else if (Q_stricmp ( string, "off") == 0) + { + safe_cprintf (ent, PRINT_HIGH, "Homing missiles off\n"); + ent->client->pers.homing_state = 0; + } + else //If no "on" or "off", toggle state + { + if (ent->client->pers.homing_state == 0) + { + safe_cprintf (ent, PRINT_HIGH, "HOMING MISSILES ON\n"); + ent->client->pers.homing_state = 1; + } + else + { + safe_cprintf (ent, PRINT_HIGH, "Homing missiles off\n"); + ent->client->pers.homing_state = 0; + } + } + + //Special case for napam missile. If it's active and + //homing is on, turn on lasersight + + weap = FindItem("Rocket Napalm Launcher"); + if (ent->client->pers.weapon == weap) + { + if (ent->client->pers.homing_state == 1) + lasersight_on (ent); + else + lasersight_off (ent); + } +} + +/* +================= +Cmd_AutoConfig_f +If set, class specific config files will be run each time +a player chooses a new class +================= +*/ +void Cmd_AutoConfig_f (edict_t *ent) +{ + char *string; + + string=gi.args(); + + if (Q_stricmp ( string, "on") == 0) + { + safe_cprintf (ent, PRINT_HIGH, "Auto Config On\n"); + ent->client->pers.autoconfig = 1; + } + else if (Q_stricmp ( string, "off") == 0) + { + safe_cprintf (ent, PRINT_HIGH, "Auto Config Off\n"); + ent->client->pers.autoconfig = 0; + } + else //If no "on" or "off", toggle state + { + if (ent->client->pers.autoconfig == 0) + { + safe_cprintf (ent, PRINT_HIGH, "Auto Config On\n"); + ent->client->pers.autoconfig = 1; + } + else + { + safe_cprintf(ent, PRINT_HIGH, "Auto Config Off\n"); + ent->client->pers.autoconfig = 0; + } + } + AutoClassConfig(ent); +} + +/* +================= +Cmd_Reno_f + +Special Reno Commands +================= +*/ +void Cmd_Reno_f (edict_t *ent) +{ + char *string; + + string=gi.args(); + + if (Q_stricmp ( string, "enter") == 0) + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("renoenter.wav"), 1, ATTN_NORM, 0); + } + if (Q_stricmp ( string, "exit") == 0) + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("renoexit.wav"), 1, ATTN_NORM, 0); + } + if (Q_stricmp ( string, "talk") == 0) + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("renotalk.wav"), 1, ATTN_NORM, 0); + } +} + +//Respawn protection function from Keys2 Mod +qboolean K2_IsProtected(edict_t *ent) +{ + if (!ent->client) + return false; + + if (ent->client->protecttime > level.time) + return true; + else + return false; +} + + +/* +================================== +Cmd_WFFlags + +Show the Weapons Factory Flags +================================== +*/ +void Cmd_WFFlags_f (edict_t *ent) +{ + if (ent->bot_client) return; + if (!ent->client) return; + + safe_cprintf (ent, PRINT_HIGH, "WF Flags = %d\n", (int)wfflags->value); + + if ((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE) + safe_cprintf (ent, PRINT_HIGH, "WF_ALLOW_FRIENDLY_FIRE is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_ALLOW_FRIENDLY_FIRE is OFF\n"); + + if ((int)wfflags->value & WF_FRAG_LOGGING) + safe_cprintf (ent, PRINT_HIGH, "WF_FRAG_LOGGING is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_FRAG_LOGGING is OFF\n"); + + if ((int)wfflags->value & WF_NO_FORT_RESPAWN) + safe_cprintf (ent, PRINT_HIGH, "WF_NO_FORT_RESPAWN is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_NO_FORT_RESPAWN is OFF\n"); + + if ((int)wfflags->value & WF_NO_HOMING) + safe_cprintf (ent, PRINT_HIGH, "WF_NO_HOMING is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_NO_HOMING is OFF\n"); + + + if ((int)wfflags->value & WF_NO_DECOYS) + safe_cprintf (ent, PRINT_HIGH, "WF_NO_DECOYS is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_NO_DECOYS is OFF\n"); + + if ((int)wfflags->value & WF_NO_FLYING) + safe_cprintf (ent, PRINT_HIGH, "WF_NO_FLYING is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_NO_FLYING is OFF\n"); + + if ((int)wfflags->value & WF_DECOY_PURSUE) + safe_cprintf (ent, PRINT_HIGH, "WF_DECOY_PURSUE is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_DECOY_PURSUE is OFF\n"); + + + if ((int)wfflags->value & WF_NO_TURRET) + safe_cprintf (ent, PRINT_HIGH, "WF_NO_TURRET is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_NO_TURRET is OFF\n"); + + if ((int)wfflags->value & WF_NO_EARTHQUAKES) + safe_cprintf (ent, PRINT_HIGH, "WF_NO_EARTHQUAKES is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_NO_EARTHQUAKES is OFF\n"); + + if ((int)wfflags->value & WF_NO_GRAPPLE) + safe_cprintf (ent, PRINT_HIGH, "WF_NO_GRAPPLE is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_NO_GRAPPLE is OFF\n"); + + if ((int)wfflags->value & WF_MAP_VOTE) + safe_cprintf (ent, PRINT_HIGH, "WF_MAP_VOTE is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_MAP_VOTE is OFF\n"); + + if ((int)wfflags->value & WF_ZOID_FLAGCAP) + safe_cprintf (ent, PRINT_HIGH, "WF_ZOID_FLAGCAP is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_ZOID_FLAGCAP is OFF\n"); + + if ((int)wfflags->value & WF_NO_PLAYER_CLASSES) + safe_cprintf (ent, PRINT_HIGH, "WF_NO_PLAYER_CLASSES are ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_NO_PLAYER_CLASSES are OFF\n"); + + if ((int)wfflags->value & WF_ZBOT_DETECT) + safe_cprintf (ent, PRINT_HIGH, "WF_ZBOT_DETECT is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_ZBOT_DETECT is OFF\n"); + + if ((int)wfflags->value & WF_AUTO_TEAM_BALANCE) + safe_cprintf (ent, PRINT_HIGH, "WF_AUTO_TEAM_BALANCE is ON\n"); + else + safe_cprintf (ent, PRINT_HIGH, "WF_AUTO_TEAM_BALANCE is OFF\n"); +} + +void SV_WFFlags_f () +{ + + gi.dprintf ( "WF Flags = %d\n", (int)wfflags->value); + + if ((int)wfflags->value & WF_ALLOW_FRIENDLY_FIRE) + gi.dprintf ("WF_ALLOW_FRIENDLY_FIRE is ON\n"); + else + gi.dprintf ("WF_ALLOW_FRIENDLY_FIRE is OFF\n"); + + if ((int)wfflags->value & WF_FRAG_LOGGING) + gi.dprintf ("WF_FRAG_LOGGING is ON\n"); + else + gi.dprintf ("WF_FRAG_LOGGING is OFF\n"); + + if ((int)wfflags->value & WF_NO_FORT_RESPAWN) + gi.dprintf ("WF_NO_FORT_RESPAWN is ON\n"); + else + gi.dprintf ("WF_NO_FORT_RESPAWN is OFF\n"); + + if ((int)wfflags->value & WF_NO_HOMING) + gi.dprintf ("WF_NO_HOMING is ON\n"); + else + gi.dprintf ("WF_NO_HOMING is OFF\n"); + + if ((int)wfflags->value & WF_NO_DECOYS) + gi.dprintf ("WF_NO_DECOYS is ON\n"); + else + gi.dprintf ("WF_NO_DECOYS is OFF\n"); + + + if ((int)wfflags->value & WF_NO_FLYING) + gi.dprintf ("WF_NO_FLYING is ON\n"); + else + gi.dprintf ("WF_NO_FLYING is OFF\n"); + + if ((int)wfflags->value & WF_DECOY_PURSUE) + gi.dprintf ("WF_DECOY_PURSUE is ON\n"); + else + gi.dprintf ("WF_DECOY_PURSUE is OFF\n"); + + + if ((int)wfflags->value & WF_NO_TURRET) + gi.dprintf ("WF_NO_TURRET is ON\n"); + else + gi.dprintf ("WF_NO_TURRET is OFF\n"); + + if ((int)wfflags->value & WF_NO_EARTHQUAKES) + gi.dprintf ("WF_NO_EARTHQUAKES is ON\n"); + else + gi.dprintf ("WF_NO_EARTHQUAKES is OFF\n"); + + if ((int)wfflags->value & WF_NO_GRAPPLE) + gi.dprintf ("WF_NO_GRAPPLE is ON\n"); + else + gi.dprintf ("WF_NO_GRAPPLE is OFF\n"); + + if ((int)wfflags->value & WF_MAP_VOTE) + gi.dprintf ("WF_MAP_VOTE is ON\n"); + else + gi.dprintf ("WF_MAP_VOTE is OFF\n"); + + if ((int)wfflags->value & WF_ZOID_FLAGCAP) + gi.dprintf ("WF_ZOID_FLAGCAP is ON\n"); + else + gi.dprintf ("WF_ZOID_FLAGCAP is OFF\n"); + + if ((int)wfflags->value & WF_NO_PLAYER_CLASSES) + gi.dprintf ("WF_NO_PLAYER_CLASSES are ON\n"); + else + gi.dprintf ("WF_NO_PLAYER_CLASSES are OFF\n"); + + if ((int)wfflags->value & WF_ZBOT_DETECT) + gi.dprintf ("WF_ZBOT_DETECT is ON\n"); + else + gi.dprintf ("WF_ZBOT_DETECT is OFF\n"); + + if ((int)wfflags->value & WF_AUTO_TEAM_BALANCE) + gi.dprintf ("WF_AUTO_TEAM_BALANCE is ON\n"); + else + gi.dprintf ("WF_AUTO_TEAM_BALANCE is OFF\n"); + + +} + + +/* +================================== +wf_IsWeapon + +Determine if an item is a weapon that should be removed +from map (if player classes are used + +Also checks for other items to remove +================================== +*/ +qboolean wf_IsWeapon(char *classname) +{ + if (strcmp(classname, "weapon_shotgun") == 0) return true; + if (strcmp(classname, "weapon_supershotgun") == 0) return true; + if (strcmp(classname, "weapon_machinegun") == 0) return true; + if (strcmp(classname, "weapon_chaingun") == 0) return true; + if (strcmp(classname, "weapon_grenadelauncher") == 0) return true; + if (strcmp(classname, "weapon_rocketlauncher") == 0) return true; + if (strcmp(classname, "weapon_hyperblaster") == 0) return true; + if (strcmp(classname, "weapon_railgun") == 0) return true; + if (strcmp(classname, "weapon_bfg") == 0) return true; + + //Removes power shield from map too + if (strcmp(classname, "item_power_shield") == 0) return true; + + return false; +} + +/*================================== +wf_CanUse + +Determine if an item can be used by the player +================================== +*/ +qboolean wf_CanUse(gclient_t *cl, edict_t *ent) +{ + + if (!ent->item) return true; + if (!ent->item->flags) return true; + if (!cl->player_items) return true; + + //Armor + if (ent->item->flags & IT_ARMOR) + { + if ((cl->player_items & ITEM_BODYARMOR) && (ent->item->tag == ARMOR_BODY)) + return(true); + if ((cl->player_items & ITEM_COMBATARMOR) && (ent->item->tag == ARMOR_COMBAT)) + return(true); + if ((cl->player_items & ITEM_JACKETARMOR) && (ent->item->tag == ARMOR_JACKET)) + return(true); + return (false); + } + + //Ammo + if (ent->item->flags & IT_AMMO) + { + if ((cl->player_ammo & WF_AMMO_BULLETS) && (ent->item->tag == AMMO_BULLETS)) + return (true); + if ((cl->player_ammo & WF_AMMO_SHELLS) && (ent->item->tag == AMMO_SHELLS)) + return (true); + if ((cl->player_ammo & WF_AMMO_ROCKETS) && (ent->item->tag == AMMO_ROCKETS)) + return (true); + if ((cl->player_ammo & WF_AMMO_GRENADES) && (ent->item->tag == AMMO_GRENADES)) + return (true); + if ((cl->player_ammo & WF_AMMO_CELLS) && (ent->item->tag == AMMO_CELLS)) + return (true); + if ((cl->player_ammo & WF_AMMO_SLUGS) && (ent->item->tag == AMMO_SLUGS)) + return (true); + return (false); + } + + //All other items can be picked up + return (true); +} + +void wf_GiveItem(gclient_t *client, char *item_name, int item_count) +{ + gitem_t *item; + + item = FindItem(item_name); + if (item) + { + client->pers.inventory[ITEM_INDEX(item)] = item_count; +// gi.dprintf ("WF: Gave item: %s\n",item_name); + } + else + gi.dprintf ("WF: Can't give item: %s\n",item_name); +} + + +void wf_InitPlayerClass(gclient_t *client) +{ + int i; + int j; + int curr_weapon; + gitem_t *item; + + //Clear grenade count +// for (i=1; i <= GRENADE_TYPE_COUNT; ++i) +// client->pers.active_grenades[i] = 0; + + client->player_ammo = WF_AMMO_SHELLS | WF_AMMO_GRENADES; + + i = client->pers.player_class; + client->player_armor = classinfo[i].max_armor; + client->player_speed = classinfo[i].max_speed; + client->player_maxhealth = classinfo[i].max_health; + client->grenade_type1 = classinfo[i].grenade[0]; + client->grenade_type2 = classinfo[i].grenade[1]; + client->grenade_type3 = classinfo[i].grenade[2]; + client->player_special = classinfo[i].special; + client->player_items = classinfo[i].items; + client->player_model = classinfo[i].model; + + //Ammo limits + client->pers.max_bullets = classinfo[i].max_ammo[AMMO_BULLETS]; + client->pers.max_shells = classinfo[i].max_ammo[AMMO_SHELLS]; + client->pers.max_rockets = classinfo[i].max_ammo[AMMO_ROCKETS]; + client->pers.max_grenades = classinfo[i].max_ammo[AMMO_GRENADES]; + client->pers.max_cells = classinfo[i].max_ammo[AMMO_CELLS]; + client->pers.max_slugs = classinfo[i].max_ammo[AMMO_SLUGS]; + + // Set the ammo flags + if (client->pers.max_bullets) client->player_ammo |= WF_AMMO_BULLETS; + if (client->pers.max_shells) client->player_ammo |= WF_AMMO_SHELLS; + if (client->pers.max_rockets) client->player_ammo |= WF_AMMO_ROCKETS; + if (client->pers.max_grenades) client->player_ammo |= WF_AMMO_GRENADES; + if (client->pers.max_cells) client->player_ammo |= WF_AMMO_CELLS; + if (client->pers.max_slugs) client->player_ammo |= WF_AMMO_SLUGS; + +// client->pers.max_health = client->pers.health; + + //Give everyone a few grenades and set default grenade + wf_GiveItem(client,"Grenades", 5); + client->pers.grenade_type = client->grenade_type1; + + + //Give weapons and ammo to the player + for (j = 0; j < 10; ++j) + { + curr_weapon = classinfo[i].weapon[j]; + + //2 + if (curr_weapon == WEAPON_SHOTGUN) + { + wf_GiveItem(client,"Shotgun", 1); + wf_GiveItem(client,"Shells", 10); + client->player_ammo |= WF_AMMO_SHELLS; + } + + //3 + if (curr_weapon == WEAPON_SUPERSHOTGUN) + { + wf_GiveItem(client,"Super Shotgun", 1); + wf_GiveItem(client,"Shells", 20); + client->player_ammo |= WF_AMMO_SHELLS; + } + + //4 + if (curr_weapon == WEAPON_MACHINEGUN) + { + wf_GiveItem(client,"Machinegun", 1); + wf_GiveItem(client,"Bullets", 50); + client->player_ammo |= WF_AMMO_BULLETS; + } + + //5 + if (curr_weapon == WEAPON_CHAINGUN) + { + wf_GiveItem(client,"Chaingun", 1); + wf_GiveItem(client,"Bullets", 150); + client->player_ammo |= WF_AMMO_BULLETS; + } + + //6 + if (curr_weapon == WEAPON_HYPERBLASTER) + { + wf_GiveItem(client,"HyperBlaster", 1); + wf_GiveItem(client,"Cells", 50); + client->player_ammo |= WF_AMMO_CELLS; + } + + //7 + if (curr_weapon == WEAPON_ROCKETLAUNCHER) + { + wf_GiveItem(client,"Rocket Launcher", 1); + wf_GiveItem(client,"Rockets", 5); + wf_GiveItem(client,"Cells", 20); + client->player_ammo |= WF_AMMO_CELLS; + client->player_ammo |= WF_AMMO_ROCKETS; + } + + //8 + if (curr_weapon == WEAPON_GRENADELAUNCHER) + { + wf_GiveItem(client,"Grenade Launcher", 1); + wf_GiveItem(client,"Grenades", 15); + client->player_ammo |= WF_AMMO_GRENADES; + } + + //9 + if (curr_weapon == WEAPON_RAILGUN) + { + wf_GiveItem(client,"Railgun", 1); + wf_GiveItem(client,"Slugs", 10); + client->player_ammo |= WF_AMMO_SLUGS; + } + + //10 + if (curr_weapon == WEAPON_BFG) + { + wf_GiveItem(client,"BFG10K", 1); + wf_GiveItem(client,"Cells", 150); + client->player_ammo |= WF_AMMO_CELLS; + } + + //New Weapons + //11 + if (curr_weapon == WEAPON_NEEDLER) + { + wf_GiveItem(client,"Needler", 1); + wf_GiveItem(client,"Bullets", 50); + client->player_ammo |= WF_AMMO_BULLETS; + } + + //12 + if (curr_weapon == WEAPON_NAG) + { + wf_GiveItem(client,"Nervous Accelerator Gun", 1); + wf_GiveItem(client,"Cells", 20); + client->player_ammo |= WF_AMMO_CELLS; + } + + //13 + if (curr_weapon == WEAPON_TELSA) + { + wf_GiveItem(client,"Telsa Coil", 1); + wf_GiveItem(client,"Cells", 50); + client->player_ammo |= WF_AMMO_CELLS; + } + + //14 + if (curr_weapon == WEAPON_LIGHTNING) + { + wf_GiveItem(client,"Lightning Gun", 1); + wf_GiveItem(client,"Slugs", 20); + client->player_ammo |= WF_AMMO_SLUGS; + } + + //15 + if (curr_weapon == WEAPON_NAILGUN) + { + wf_GiveItem(client,"NailGun", 1); + wf_GiveItem(client,"Bullets", 50); + client->player_ammo |= WF_AMMO_BULLETS; + } + + //16 + if (curr_weapon == WEAPON_CLUSTERROCKET) + { + wf_GiveItem(client,"Cluster Rocket Launcher", 1); + wf_GiveItem(client,"Rockets", 5); + client->player_ammo |= WF_AMMO_ROCKETS; + } + + //17 + if (curr_weapon == WEAPON_MAGBOLTED) + { + wf_GiveItem(client,"Mag Bolted", 1); + wf_GiveItem(client,"Bullets", 50); + client->player_ammo |= WF_AMMO_BULLETS; + } + + //18 + if (curr_weapon == WEAPON_PELLET) + { + wf_GiveItem(client,"Pellet Rocket Launcher", 1); + wf_GiveItem(client,"Rockets", 10); + client->player_ammo |= WF_AMMO_ROCKETS; + } + + //19 + if (curr_weapon == WEAPON_PULSE) + { + wf_GiveItem(client,"Pulse Cannon", 1); + wf_GiveItem(client,"Bullets", 50); + client->player_ammo |= WF_AMMO_BULLETS; + } + + //20 + if (curr_weapon == WEAPON_SHC) + { + wf_GiveItem(client,"SHC Rifle", 1); + wf_GiveItem(client,"Shells", 10); + client->player_ammo |= WF_AMMO_SHELLS; + } + + //21 + if (curr_weapon == WEAPON_FLAREGUN) + { + wf_GiveItem(client,"Flare Gun", 1); + wf_GiveItem(client,"Bullets", 50); + client->player_ammo |= WF_AMMO_BULLETS; + } + + //22 + if (curr_weapon == WEAPON_NAPALMMISSLE) + { + wf_GiveItem(client,"Rocket Napalm Launcher", 1); + wf_GiveItem(client,"Rockets", 10); + client->player_ammo |= WF_AMMO_ROCKETS; + } + + //23 + if (curr_weapon == WEAPON_FLAMETHROWER) + { + wf_GiveItem(client,"FlameThrower", 1); + wf_GiveItem(client,"Cells", 50); + client->player_ammo |= WF_AMMO_CELLS; + } + + //24 + if (curr_weapon == WEAPON_TRANQUILIZER) + { + wf_GiveItem(client,"Tranquilizer", 1); + wf_GiveItem(client,"Shells", 10); + client->player_ammo |= WF_AMMO_SHELLS; + } + + //25 + if (curr_weapon == WEAPON_INFECTEDDART) + { + wf_GiveItem(client,"Infected Dart Launcher", 1); + wf_GiveItem(client,"Shells", 10); + client->player_ammo |= WF_AMMO_SHELLS; + } + + //26 + if (curr_weapon == WEAPON_LASERSNIPER) + { + wf_GiveItem(client,"Laser Sniper Rifle", 1); + wf_GiveItem(client,"Slugs", 10); + client->player_ammo |= WF_AMMO_SLUGS; + } + + //27 + if (curr_weapon == WEAPON_ARMORDART) + { + wf_GiveItem(client,"Poison Dart Launcher", 1); + wf_GiveItem(client,"Shells", 10); + client->player_ammo |= WF_AMMO_SHELLS; + } + + //28 + if (curr_weapon == WEAPON_SHOTGUNCHOKE) + { + wf_GiveItem(client,"Shotgun Choke", 1); + wf_GiveItem(client,"Shells", 10); + client->player_ammo |= WF_AMMO_SHELLS; + } + + //29 + if (curr_weapon == WEAPON_SNIPERRIFLE) + { + wf_GiveItem(client,"Sniper Rifle", 1); + wf_GiveItem(client,"Slugs", 10); + client->player_ammo |= WF_AMMO_SLUGS; + + //Turn on sniping flag for this weapon + client->player_special = client->player_special | SPECIAL_SNIPING; + } + + //30 + if (curr_weapon == WEAPON_LRPROJECTILE) + { + wf_GiveItem(client,"Projectile Launcher", 1); + wf_GiveItem(client,"Rockets", 10); + client->player_ammo |= WF_AMMO_ROCKETS; + } + + //31 + if (curr_weapon == WEAPON_SENTRYKILLER) + { + wf_GiveItem(client,"sentry killer", 1); + wf_GiveItem(client,"rockets", 25); + client->player_ammo |= WF_AMMO_ROCKETS; + } + + //32 + if (curr_weapon == WEAPON_MEGACHAINGUN) + { + wf_GiveItem(client,"Mega Chaingun", 1); + wf_GiveItem(client,"Bullets", 250); + client->player_ammo |= WF_AMMO_BULLETS; + } + + //33 + if (curr_weapon == WEAPON_TRANQUILDART) + { + wf_GiveItem(client,"Tranquilizer Dart Launcher", 1); + wf_GiveItem(client,"Shells", 10); + client->player_ammo |= WF_AMMO_SHELLS; + } + + //34 + if (curr_weapon == WEAPON_KNIFE) + { + wf_GiveItem(client,"Knife", 1); + wf_GiveItem(client,"Shells", 40); + client->player_ammo |= WF_AMMO_SHELLS; + } + + //35 + if (curr_weapon == WEAPON_AK47) + { + wf_GiveItem(client,"AK47", 1); + wf_GiveItem(client,"Bullets", 50); + client->player_ammo |= WF_AMMO_BULLETS; + } + + //36 + if (curr_weapon == WEAPON_PISTOL) + { + wf_GiveItem(client,"Pistol", 1); + wf_GiveItem(client,"Bullets", 50); + client->player_ammo |= WF_AMMO_BULLETS; + } + + //37 + if (curr_weapon == WEAPON_STINGER) + { + wf_GiveItem(client,"Stinger Launcher", 1); + wf_GiveItem(client,"Rockets", 20); + client->player_ammo |= WF_AMMO_ROCKETS; + } + + //38 + if (curr_weapon == WEAPON_DISRUPTOR) + { + wf_GiveItem(client,"Disruptor", 1); + wf_GiveItem(client,"Cells", 10); + client->player_ammo |= WF_AMMO_CELLS; + } + + //39 + if (curr_weapon == WEAPON_ETF_RIFLE) + { + wf_GiveItem(client,"ETF Rifle", 1); + wf_GiveItem(client,"Slugs", 10); + client->player_ammo |= WF_AMMO_SLUGS; + } + + + //40 + if (curr_weapon == WEAPON_PLASMA_BEAM) + { + wf_GiveItem(client,"Plasma Beam", 1); + wf_GiveItem(client,"Cells", 15); + client->player_ammo |= WF_AMMO_CELLS; + } + + //41 + if (curr_weapon == WEAPON_ION_RIPPER) + { + wf_GiveItem(client,"Ionripper", 1); + wf_GiveItem(client,"Cells", 20); + client->player_ammo |= WF_AMMO_CELLS; + } + + //42 + if (curr_weapon == WEAPON_PHALANX) + { + wf_GiveItem(client,"Phalanx", 1); + wf_GiveItem(client,"Slugs", 10); + client->player_ammo |= WF_AMMO_SLUGS; + } + //43 acrid 3/99 + if (curr_weapon == WEAPON_FREEZER) + { + wf_GiveItem(client,"Freezer", 1); + wf_GiveItem(client,"Slugs", 10); + client->player_ammo |= WF_AMMO_SLUGS; + } + } + + //Special items require ammo + if (client->player_special & SPECIAL_SUPPLY_DEPOT) + { + wf_GiveItem(client,"Rockets", 10); + client->player_ammo |= WF_AMMO_ROCKETS; + wf_GiveItem(client,"Slugs", 10); + client->player_ammo |= WF_AMMO_SLUGS; + } + if (client->player_special & SPECIAL_SENTRY_GUN) + { + wf_GiveItem(client,"Bullets", 100); + client->player_ammo |= WF_AMMO_BULLETS; + } + + if (client->player_special & SPECIAL_BIOSENTRY) + { + wf_GiveItem(client,"Bullets", 100); + client->player_ammo |= WF_AMMO_BULLETS; + } + + if (client->player_special & SPECIAL_REMOTE_CAMERA) + { + wf_GiveItem(client,"Cells", 25); + client->player_ammo |= WF_AMMO_CELLS; + } + + + //Don't give grapple to everyone + if (((int)wfflags->value & WF_NO_GRAPPLE) == 0) + { + + item = FindItem("Grapple"); + + //Is this class capable of using the grapple? + if (client->player_special & SPECIAL_GRAPPLE) + { + client->pers.inventory[ITEM_INDEX(item)] = 1; + } + else + { + client->pers.inventory[ITEM_INDEX(item)] = 0; + } + } + + + //Some grenades need other ammo + if (client->grenade_type1 == GRENADE_TYPE_TURRET || client->grenade_type2 == GRENADE_TYPE_TURRET || client->grenade_type3 == GRENADE_TYPE_TURRET) + { + wf_GiveItem(client,"Slugs", 10); + client->player_ammo |= WF_AMMO_SLUGS; + } + + if (client->grenade_type1 == GRENADE_TYPE_LASERBALL || client->grenade_type2 == GRENADE_TYPE_LASERBALL || client->grenade_type3 == GRENADE_TYPE_LASERBALL) + { + wf_GiveItem(client,"Cells", 10); + client->player_ammo |= WF_AMMO_CELLS; + } + + + //Give special items + if (client->player_items & ITEM_REBREATHER) + { + wf_GiveItem(client,"Rebreather", 1); + //client->breather_framenum = level.framenum + 999; + } + +/* Don't give out armor. Let them pick it up + if (client->player_items & ITEM_BODYARMOR) + wf_GiveItem(client,"Body Armor", 1); + + if (client->player_items & ITEM_COMBATARMOR) + wf_GiveItem(client,"Combat Armor", 1); + + if (client->player_items & ITEM_JACKETARMOR) + wf_GiveItem(client,"Jacket Armor", 1); + +*/ + if (client->player_items & ITEM_POWERSCREEN) + wf_GiveItem(client,"Power Screen", 1); + + if (client->player_items & ITEM_POWERSHIELD) + wf_GiveItem(client,"Power Shield", 1); + + if (client->player_items & ITEM_SILENCER) + { + client->silencer_shots = 999; + wf_GiveItem(client,"Silencer", 1); + } + + if (client->player_special & SPECIAL_HEALING) + wf_GiveItem(client,"AutoDoc", 1); + + + +} + + + + +/* +================= +Cmd_DSkin_f + +Decoy Skin Commands +================= +*/ +void Cmd_DSkin_f (edict_t *ent) +{ + char *string; + + string=gi.args(); + + if (Q_stricmp ( string, "0") == 0) + { + if (ent->decoy) + ent->decoy->s.skinnum = 0; + } + if (Q_stricmp ( string, "1") == 0) + { + if (ent->decoy) + ent->decoy->s.skinnum = 1; + } + if (Q_stricmp ( string, "2") == 0) + { + if (ent->decoy) + ent->decoy->s.skinnum = 2; + } + if (Q_stricmp ( string, "3") == 0) + { + if (ent->decoy) + ent->decoy->s.skinnum = 3; + } + if (Q_stricmp ( string, "0") == 0) + { + if (ent->decoy) + ent->decoy->s.skinnum = 0; + } + +} +char getClassCode (gclient_t *c) +{ + if (!c) return '?'; + + //Return first letter of the class name + return (classinfo[c->pers.player_class].name[0]); +} + +char *getClassName (gclient_t *c) +{ + + if (!c) + return "(unknown)"; + + return (classinfo[c->pers.player_class].name); +} + +char *getNextClassName (gclient_t *c) +{ + + if (!c) + return "(unknown)"; + + return (classinfo[c->pers.next_player_class].name); +} + + + +/* +================= +Cmd_ShowClass +================= +*/ +void Cmd_ShowClass (edict_t *ent) +{ + + if (ent->client->pers.player_class) + safe_cprintf (ent, PRINT_HIGH, "You are a %s\n", classinfo[ent->client->pers.player_class].name); + else + safe_cprintf (ent, PRINT_HIGH, "No class selected\n"); +} + +//Show all the players +void Cmd_ShowPlayers(edict_t *ent) +{ +// edict_t *cl_ent; +// gclient_t *cl; + edict_t *e; + + int i; + char classname[32]; + + strcpy(classname,"123"); + + //Show red players first + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + //getClassName(e->client, classname); + if (e->client->resp.ctf_team == CTF_TEAM1) + { + safe_cprintf (ent, PRINT_HIGH, "Red %3d %8s %s\n", + e->client->resp.score, + getClassName(e->client), + e->client->pers.netname); + } + } + + //Next blue + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + //getClassName(e->client, classname); + if (e->client->resp.ctf_team == CTF_TEAM2) + { + safe_cprintf (ent, PRINT_HIGH, "Blue %3d %8s %s\n", + e->client->resp.score, + getClassName(e->client), + e->client->pers.netname); + } + } + + //Then observers + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + //getClassName(e->client, classname); + if (e->client->resp.ctf_team <= 0) + { + safe_cprintf (ent, PRINT_HIGH, "Obsv %3d %8s %s\n", + e->client->resp.score, + getClassName(e->client), + e->client->pers.netname); + } + } +} + +//Play a sound for a team. If the "all" argument is set, play it for all teams +void Cmd_WFPlayTeam (edict_t *self, char *wavename, int all) +{ + int i; + edict_t *e; + char cmd[250]; + + if (wfdebug) gi.dprintf("Before: %s\n",wavename); + + for (i = 0; wavename[i] != 0 && i < 200; ++i) + { + if ((wavename[i] == ';')||(wavename[i] == '%')) wavename[i] = 0; + } + + if (strlen(wavename) > 200) wavename[200] = '\0'; + sprintf(cmd, "play %s\n",wavename); + + if (wfdebug) gi.dprintf("After: %s\n",wavename); + + //Loop through all the entities to find players on the same team + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + if (e->bot_client) + continue; + if (e->client->pers.nospam_level & SPAM_NOTEAMSOUND) + continue; //they don't want team sounds + + if ((e->wf_team == self->wf_team) || (all)) + { + stuffcmd(e,cmd); + if ((e->client->pers.nospam_level & SPAM_NOTEAMSOUNDTEXT) == 0) + safe_cprintf (e, PRINT_HIGH, "From %s: %s\n", self->client->pers.netname, wavename); + } + } +} + +//When player dies, clean up entities +void WFPlayer_Die (edict_t *ent) +{ + edict_t *blip = NULL; + int i; + + if (!ent) return; + + HealPlayer(ent); + + ent->disguised=0; + ent->client->pers.feign = 0;//acrid + + if (ent->client->menu)//acrid 3/99 + PMenu_Close(ent); + + //WF - Scanner +// ClearScanner(ent->client); + +//WF JR concussion grenades + + lasersight_off (ent); + + //See if mercenary team should change + if (ent->client->player_special & SPECIAL_MERCENARY) + ent->client->resp.ctf_team = get_mercenary_team(ent); + + for (i=1, blip=g_edicts+i ; i < globals.num_edicts ; i++,blip++) + { + if (blip->owner == ent) + { + + //Flames + if (!strcmp(blip->classname, "fire") ) + { + //Remove_Flame(blip); + blip->dmg = 0; + } + + //Pipebombs + else if (!strcmp(blip->classname, "pipebomb") ) + { + blip->think = Pipebomb_Explode; + blip->nextthink = level.time + .1; + } + + //Turret grenades + else if (!strcmp(blip->classname, "turret") ) + { + blip->think=Turret_Explode; //Drops to the ground and explodes + blip->nextthink=level.time+2; //Looks better than just disappearing + blip->movetype=MOVETYPE_BOUNCE; + blip->s.effects = EF_GRENADE; //Lights out!!! + blip->s.renderfx = 0; //Goodbye shell :( + } + + } + } +} + +void AutoClassConfig(edict_t *ent) +{ + char exec_str[100]; + + //See if we should auto exec a class file + if (ent->client->pers.autoconfig) + { + sprintf(exec_str, "exec %s.cfg\n", getNextClassName(ent->client)); + stuffcmd(ent, exec_str); + } +} + +//If player changes class or team +void WFPlayer_ChangeClassTeam (edict_t *ent) +{ + edict_t *blip = NULL; + int i; + +//if (wfdebug) +//{ +// gi.dprintf( "Curr Class = %d, Next Class = %d\n", +// ent->client->player_class, ent->client->resp.next_player_class); +// +//} + +//gi.dprintf("ChangeClass\n"); + lasersight_off (ent); + ent->disguised=0; + + if (ent->remotecam) remote_remove(ent->remotecam); + + //Turn off anti-grav boots + if (ent->flags & FL_BOOTS) ent->flags -= FL_BOOTS; + + for (i=1, blip=g_edicts+i ; i < globals.num_edicts ; i++,blip++) + { + if ((blip->owner) && (blip->owner == ent)) + { + + //Supply depot + if (!strcmp(blip->classname, "depot") ) + { + if (blip->owner) blip->owner->supply = NULL; + G_FreeEdict(blip); + } + + //Healing depot + else if (!strcmp(blip->classname, "healingdepot") ) + { + if (blip->owner) blip->owner->supply = NULL; + G_FreeEdict(blip); + } + + //Goodyear grenades + else if (!strcmp(blip->classname, "goodyear") ) + { + blip->think = Goodyear_Explode; + blip->nextthink = level.time + .1; + } + + //Proximity grenades + else if (!strcmp(blip->classname, "proximity") ) + { + blip->think = Proximity_Explode; + blip->nextthink = level.time + .1; + } + + + //Alarms + else if (!strcmp(blip->classname, "Alarm") ) + { + alarm_remove(blip); + } + + //Laser defense + else if (!strcmp(blip->classname, "laser_defense_gr") ) + { + laser_defense_die(blip, NULL, NULL, 0, NULL); + } + } + + //These items use creator instead of owner + //so that you can't walk through them + if ((blip->creator) && (blip->creator == ent)) + { + //Sentry guns + if (!strcmp(blip->classname, "SentryGun") ) + { + turret_remove(blip->creator); + } + + //BioSentry + if (!strcmp(blip->classname, "biosentry") ) + { + Biosentry_remove(blip->creator); + } + + //Missile turrets + if (!strcmp(blip->classname, "MissileTurret") ) + { + mturret_remove(blip->creator); + } + } + } + + AutoClassConfig(ent); +} + +#define BONUSTYPE_CAPTURE 1 +#define BONUSTYPE_PLAYERFRAG 2 +#define BONUSTYPE_TEAMFRAG 3 + +//Some map entities can award a bonus +void WFAddBonus(edict_t *self, edict_t *other) +{ + int bonusval; + + //Like they captured the flag + if (self->bonustype == BONUSTYPE_CAPTURE) + { + gi.bprintf(PRINT_HIGH, "%s got the capture bonus!\n",other->client->pers.netname); + + // Log Flag Capture - MarkDavies + sl_LogScore( &gi, other->client->pers.netname, NULL, "F Capture", -1, 1); //One point awarded + + // Log frag bonus + if (self->bonusvalue) + bonusval = self->bonusvalue; + else + bonusval = 1; + + if (other->wf_team == CTF_TEAM1) + ctfgame.team1 += bonusval; + else + ctfgame.team2 += bonusval; + + gi.sound (self, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0); + } + + //Give the person a frag + else if (self->bonustype == BONUSTYPE_PLAYERFRAG) + { + gi.bprintf(PRINT_HIGH, "%s got the frag bonus!\n", other->client->pers.netname); + + // Log frag bonus + if (self->bonusvalue) + other->client->resp.score += self->bonusvalue; + else + other->client->resp.score++; + + sl_LogScore( &gi, other->client->pers.netname, NULL, "Kill",0,1 ); // StdLog - Mark Davies + + gi.sound (self, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0); + } + + //Give the team a frag + else if (self->bonustype == BONUSTYPE_TEAMFRAG) + { + //yet to do + } +} diff --git a/wf_napalm.c b/wf_napalm.c new file mode 100644 index 0000000..2010de0 --- /dev/null +++ b/wf_napalm.c @@ -0,0 +1,173 @@ +/*============================================================================== +The Weapons Factory - +Napalm Grenade Functions +Original code by ?? +Modified by Gregg Reno +==============================================================================*/ +#include "g_local.h" + +static void Napalm_Explode (edict_t *ent) + +{ + vec3_t origin; + edict_t *blip; + + //Sean added these 4 vectors + + vec3_t grenade1; + vec3_t grenade2; + vec3_t grenade3; + vec3_t grenade4; + vec3_t grenade5; + + if (ent->owner->client) + { +// PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + gi.sound (ent, CHAN_VOICE, gi.soundindex ("jumppack.wav"), 1, ATTN_NORM, 0); + } + + //No damage for now + //T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, MOD_WF_CLUSTER); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + + // SumFuka did this bit : give grenades up/outwards velocities + VectorSet(grenade1,20,20,40); + VectorSet(grenade2,20,-20,40); + VectorSet(grenade3,-20,20,40); + VectorSet(grenade4,-20,-20,40); + + VectorSet(grenade5,0,0,40); + + // Sean : explode the four grenades outwards + // Arguments: fire_grenade2 (self, start, aimdir, damage, speed, timer, damage_radius, held) + //fire_flame(ent->owner, origin, grenade1, ent->dmg, 8, 1.0, 120, false); + //fire_flame(ent->owner, origin, grenade2, ent->dmg, 8, 1.0, 120, false); + //fire_flame(ent->owner, origin, grenade3, ent->dmg, 8, 1.0, 120, false); + //fire_flame(ent->owner, origin, grenade4, ent->dmg, 8, 1.0, 120, false); + + //See if there is anything to catch on fire + blip = NULL; + while ((blip = findradius(blip, ent->s.origin, 150)) != NULL) + { + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + continue; + if (blip == ent->owner) + continue; + //dont attack same team + if (blip->wf_team == ent->wf_team) + continue; + if (!blip->takedamage) + continue; + if (blip->health <= 0) + continue; + if (!visible(ent, blip)) + continue; + + burn_person(blip, ent->owner, wf_game.grenade_damage[GRENADE_TYPE_NAPALM], MOD_NAPALMGRENADE); + break; + } + + + 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_PVS); + + G_FreeEdict (ent); +} + +void Napalm_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //gi.dprintf("Napalm: Touch\n"); + + if (other == ent->owner) + return; + + // Dont blow up if on same team + if (other->wf_team == ent->wf_team) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + Napalm_Explode (ent); +} + + +void fire_napalm (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *grenade; + vec3_t dir; + int dmg; + vec3_t forward, right, up; + + ++self->client->pers.active_grenades[GRENADE_TYPE_NAPALM]; + + //Reduce damage of Napalm + dmg = damage / 2; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + grenade->grenade_index = GRENADE_TYPE_NAPALM; + 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; + grenade->s.effects |= EF_BFG | EF_HYPERBLASTER; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex (GRNAPALM_MODEL); + grenade->s.skinnum = GRNAPALM_SKIN; + grenade->owner = self; + grenade->touch = Napalm_Touch; + + grenade->nextthink = level.time + timer; + grenade->think = Napalm_Explode; + grenade->dmg = dmg; + grenade->dmg_radius = damage_radius; + grenade->classname = "napalm grenade"; + grenade->spawnflags = 1; + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + //set the team + grenade->wf_team = self->client->resp.ctf_team; + + if (timer <= 0.0) + Napalm_Explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} + diff --git a/wf_pipebomb.c b/wf_pipebomb.c new file mode 100644 index 0000000..7fbba54 --- /dev/null +++ b/wf_pipebomb.c @@ -0,0 +1,175 @@ +/*============================================================================== +The Weapons Factory - +Pipe Bomb Functions +Original code by Chris Hilton +Modified by Gregg Reno +==============================================================================*/ +#include "g_local.h" + +//From Chris Hilton + +void Pipebomb_Explode (edict_t *ent) +{ + vec3_t origin; + +// gi.dprintf("Pipebomb Explode\n"); + + 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? + T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, MOD_WF_PIPEBOMB); + + 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_PVS); + + G_FreeEdict (ent); +} + +void Pipebomb_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //gi.dprintf("Pipebomb TOUCH\n"); + + if (other == ent->owner) + return; + + // Dont blow up if on same team + if (other->wf_team == ent->wf_team) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + //ent->enemy = other; + Pipebomb_Explode (ent); +} + +void Pipebomb_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = Pipebomb_Explode; +} + + +void fire_pipebomb (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); + + ++self->client->pers.active_grenades[GRENADE_TYPE_PIPEBOMB]; + + grenade = G_Spawn(); + grenade->grenade_index = GRENADE_TYPE_PIPEBOMB; + grenade->wf_team = self->client->resp.ctf_team; + + 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 = Pipebomb_Touch; + + grenade->nextthink = level.time + 180; //3 minutes + grenade->think = Pipebomb_Explode; + + //grenade->delay = level.time + 60; + grenade->dmg = wf_game.grenade_damage[GRENADE_TYPE_PIPEBOMB]; + grenade->dmg_radius = damage_radius; + grenade->classname = "pipebomb"; + grenade->spawnflags = 1; + + //Make these silent! +// grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = Pipebomb_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + if (timer <= 0.0) + { + //Pipebomb_Explode (grenade); +// gi.dprintf("Pipebomb: Zero timer!\n"); + } + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} + +/* +================= +Cmd_DetPipes_f +CCH: new function to detonate all pipebombs within 1000 units +================= +*/ +void Cmd_DetPipes_f (edict_t *ent) +{ + edict_t *blip = NULL; + + while ((blip = findradius(blip, ent->s.origin, 1000)) != NULL) + { + if (!strcmp(blip->classname, "pipebomb") && blip->owner == ent) + { + blip->think = Pipebomb_Explode; + blip->nextthink = level.time + .1; + } + } +} + + + diff --git a/wf_proximity.c b/wf_proximity.c new file mode 100644 index 0000000..5ac7970 --- /dev/null +++ b/wf_proximity.c @@ -0,0 +1,202 @@ +/*============================================================================== +The Weapons Factory - +Proximity Bomb Functions +Original code by (??) +Modified by Gregg Reno +==============================================================================*/ +#include "g_local.h" + + +void Proximity_Explode (edict_t *ent) +{ + vec3_t origin; + + //gi.dprintf("Proxim: Explode\n"); + if (ent->owner->client) + { + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + } + + + T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, MOD_WF_PROXIMITY); + + 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_PVS); + + G_FreeEdict (ent); +} + +// CCH: When a grenade 'dies', it blows up next frame +void Proximity_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + //gi.dprintf("Proxim: Die\n"); + + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = Proximity_Explode; +} + +void Proximity_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //gi.dprintf("Proxim: Touch\n"); + + //if (other == ent->owner) + // return; + + // Dont blow up if on same team + if (other->wf_team == ent->wf_team) + return; + + if (other->disguised) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + //ent->enemy = other; + Proximity_Explode (ent); +} + +// CCH: New think function for proximity grenades +void proxim_think (edict_t *ent) +{ + edict_t *blip = NULL; + + if (level.time > ent->delay) + { + Proximity_Explode(ent); + return; + } + + //is it armed yet? + if (level.time < ent->delay2) + { + ent->nextthink = level.time + .1; + return; + } + + ent->think = proxim_think; + + while ((blip = findEnemyWithinRadius(ent, blip, ent->s.origin, 100)) != NULL) + { + ent->think = Proximity_Explode; + break; + } + + ent->nextthink = level.time + .1; +} + +void fire_proximity (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, int proximity_type) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + int dmg; + + ++self->client->pers.active_grenades[GRENADE_TYPE_PROXIMITY]; + + //reduce damage + dmg = wf_game.grenade_damage[GRENADE_TYPE_PROXIMITY]; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + grenade->grenade_index = GRENADE_TYPE_PROXIMITY; + 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); + + //What type of proximity? 1= grenade 2=pipe + if (proximity_type == PROXIMITY_TYPE_GRENADE) + { + grenade->s.modelindex = gi.modelindex (GRPROXIMITY_MODEL); + grenade->s.skinnum = GRPROXIMITY_SKIN; + } + else + { + grenade->s.modelindex = gi.modelindex (GRPIPEBOMB_MODEL); + grenade->s.skinnum = GRPIPEBOMB_SKIN; + } + grenade->owner = self; + grenade->touch = Proximity_Touch; + + grenade->nextthink = level.time + .1; + grenade->think = proxim_think; + grenade->delay = level.time + 120; + grenade->delay2 = level.time + 1; //don't exlode for at least 1 second + + grenade->dmg = dmg; + grenade->dmg_radius = damage_radius; + grenade->classname = "proximity"; + grenade->spawnflags = 1; + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + // CCH: a few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -3, -3, 0); + VectorSet(grenade->maxs, 3, 3, 6); + grenade->mass = 2; + grenade->health = 10; + grenade->die = Proximity_Die; + grenade->takedamage = DAMAGE_YES; + grenade->monsterinfo.aiflags = AI_NOSTEP; + //set the team + if ((int)wfflags->value & WF_ANARCHY) + grenade->wf_team = 0; //fire at anybody + else + grenade->wf_team = self->client->resp.ctf_team; + + + + if (timer <= 0.0) + Proximity_Explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} diff --git a/wf_quake.c b/wf_quake.c new file mode 100644 index 0000000..ee172e8 --- /dev/null +++ b/wf_quake.c @@ -0,0 +1,68 @@ +/*============================================================================== +The Weapons Factory - +Earthquake Function +Original code by Gregg Reno +==============================================================================*/ +/* + This command causes an earthquake +*/ + +#include "g_local.h" + +void wf_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; + else + G_FreeEdict (self); + +} + + +//Set up an earthquake entity +void wf_earthquake (edict_t *owner) +{ + edict_t *self; + + self = G_Spawn(); + + VectorCopy(owner->s.origin, self->s.origin); + + //set the team +// self->wf_team = owner->client->resp.ctf_team; + self->count = 3; //how long it will last (3 seconds?) + self->speed = 200; + self->svflags |= SVF_NOCLIENT; + self->think = wf_earthquake_think; + self->timestamp = level.time + self->count; + self->nextthink = level.time + FRAMETIME; + self->last_move_time = 0; + self->noise_index = gi.soundindex ("world/quake.wav"); + gi.linkentity (self); + +} diff --git a/wf_referee.c b/wf_referee.c new file mode 100644 index 0000000..9fdf42e --- /dev/null +++ b/wf_referee.c @@ -0,0 +1,386 @@ +/*============================================================================== +The Weapons Factory - +Referee Functions +Original code by Gregg Reno +==============================================================================*/ +#include "g_local.h" + +void WFEndDMLevel (char *mapname); +void WFMapVote(edict_t *ent); + +//Util to find a player +edict_t *refFindPlayer(char *name) +{ + edict_t *e; + int i; + + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + //match on netname? + if (Q_stricmp(e->client->pers.netname, name) == 0) + { + return e; + } + } + return NULL; +} + + +void Cmd_Ref_Password (edict_t *ent) +{ + char *string; + + if (ent->bot_client) return; + if (!ent->client) return; + + if (wf_game.ref_ent && !wf_game.ref_ent->inuse) + { + wf_game.ref_ent = NULL; + } + + if (wf_game.ref_ent) + { + safe_cprintf (ent, PRINT_HIGH, "Sorry, there already is a REF.\n"); + return; + } + + string=gi.args(); + + if (wf_game.ref_password == NULL || wf_game.ref_password[0] == 0) + { + safe_cprintf (ent, PRINT_HIGH, "The REF has been disabled on this server\n"); + return; + } + + + if (Q_stricmp ( string, wf_game.ref_password) == 0) + { + wf_game.ref_ent = ent; + my_bprintf (PRINT_MEDIUM, "%s is now the REF.\n", wf_game.ref_ent->client->pers.netname); +// safe_cprintf (ent, PRINT_HIGH, "You are now the REF.\n"); + } + else //If no "on" or "off", toggle state + { + safe_cprintf (ent, PRINT_HIGH, "Sorry, password is not valid\n"); + wf_game.ref_ent = NULL; + } +} + +void Cmd_Ref_Show (edict_t *ent) +{ + if (ent->bot_client) return; + if (!ent->client) return; + + + if (wf_game.ref_ent && !wf_game.ref_ent->inuse) + { + wf_game.ref_ent = NULL; + } + + if (wf_game.ref_ent && wf_game.ref_ent->client) + { + safe_cprintf (ent, PRINT_HIGH, "The REF is %s.\n", + wf_game.ref_ent->client->pers.netname); + } + else + { + safe_cprintf (ent, PRINT_HIGH, "Right now, there is no REF.\n"); + } +} + +void Cmd_Ref_Skin_On (edict_t *ent) +{ + char *s; + + if (ent->bot_client) return; + if (!ent->client) return; + + if (wf_game.ref_ent && !wf_game.ref_ent->inuse) + { + wf_game.ref_ent = NULL; + } + + if (wf_game.ref_ent == ent) + { + wf_game.show_ref_skin = 1; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + safe_cprintf(ent, PRINT_HIGH, "Ref Skin ON.\n"); + } + else + { + safe_cprintf (ent, PRINT_HIGH, "You are not the REF.\n"); + } +} + +void Cmd_Ref_Skin_Off (edict_t *ent) +{ + char *s; + + if (ent->bot_client) return; + if (!ent->client) return; + + if (wf_game.ref_ent && !wf_game.ref_ent->inuse) + { + wf_game.ref_ent = NULL; + } + + if (wf_game.ref_ent == ent) + { + wf_game.show_ref_skin = 0; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + safe_cprintf(ent, PRINT_HIGH, "Ref Skin OFF.\n"); + } + else + { + safe_cprintf (ent, PRINT_HIGH, "You are not the REF.\n"); + } +} + + +void Cmd_Ref_NextMap (edict_t *ent) +{ + if (ent->bot_client) return; + if (!ent->client) return; + + if (wf_game.ref_ent && !wf_game.ref_ent->inuse) + { + wf_game.ref_ent = NULL; + } + + if (wf_game.ref_ent == ent) + { + if (maplist.nummaps > 0) // does a maplist exist? + EndDMLevel(); + else + { + safe_cprintf (ent, PRINT_HIGH, "Can't do next - No maps in current list\n"); + } + } + else + { + safe_cprintf (ent, PRINT_HIGH, "You are not the REF.\n"); + } +} + +void Cmd_Ref_PickMap (edict_t *ent) +{ +// char *string; + + if (ent->bot_client) return; + if (!ent->client) return; + + if (wf_game.ref_ent && !wf_game.ref_ent->inuse) + { + wf_game.ref_ent = NULL; + } + + /* + string=gi.args(); + + if (string[0] == 0) + { + safe_cprintf (ent, PRINT_HIGH, "You have to specify a map name.\n"); + return; + } + */ + + if (wf_game.ref_ent == ent) + { + //safe_cprintf (ent, PRINT_HIGH, "Changing to map '%s'.\n", string); + //WFEndDMLevel(string); + + //Bring up vote menu instead of using a typed map name + wf_game.ref_picked_map = 1; + WFMapVote(ent); + } + else + { + safe_cprintf (ent, PRINT_HIGH, "You are not the REF.\n"); + } + + + +} + +void Cmd_Ref_Kick (edict_t *ent) +{ + char *string; + edict_t *e; + + if (ent->bot_client) return; + if (!ent->client) return; + + if (wf_game.ref_ent && !wf_game.ref_ent->inuse) + { + wf_game.ref_ent = NULL; + } + + string=gi.args(); + + if (wf_game.ref_ent == ent) + { + //Find the player with the given name + e = refFindPlayer(string); + if (e) + { + safe_cprintf(e, PRINT_HIGH, "You have been kicked by the REF.\n"); + stuffcmd(e, "disconnect\n"); + my_bprintf (PRINT_MEDIUM, "Player '%s' was kicked by the REF.\n", string); + } + else + { + safe_cprintf (ent, PRINT_HIGH, "Player '%s' not found.\n", string); + } + } + else + { + safe_cprintf (ent, PRINT_HIGH, "You are not the REF.\n"); + } +} + +void Cmd_Ref_Start (edict_t *ent) +{ + if (ent->bot_client) return; + if (!ent->client) return; + + if (wf_game.ref_ent && !wf_game.ref_ent->inuse) + { + wf_game.ref_ent = NULL; + } + + if (wf_game.ref_ent == ent) + { + my_bprintf (PRINT_MEDIUM, "REF HAS STARTED THE GAME\n"); + wf_game.game_halted = 0; + } + else + { + safe_cprintf (ent, PRINT_HIGH, "You are not the REF.\n"); + } +} + +void Cmd_Ref_Stop (edict_t *ent) +{ + if (ent->bot_client) return; + if (!ent->client) return; + + if (wf_game.ref_ent && !wf_game.ref_ent->inuse) + { + wf_game.ref_ent = NULL; + } + + if (wf_game.ref_ent == ent) + { + my_bprintf (PRINT_MEDIUM, "REF HAS STOPPED THE GAME\n"); + wf_game.game_halted = 1; + } + else + { + safe_cprintf (ent, PRINT_HIGH, "You are not the REF.\n"); + } +} + +//Don't allow a player to talk +void Cmd_Ref_NoTalk (edict_t *ent) +{ + char *string; + edict_t *e; + + if (ent->bot_client) return; + if (!ent->client) return; + + if (wf_game.ref_ent && !wf_game.ref_ent->inuse) + { + wf_game.ref_ent = NULL; + } + + string=gi.args(); + + if (wf_game.ref_ent == ent) + { + //Find the player with the given name + e = refFindPlayer(string); + if (e) + { + safe_cprintf(e, PRINT_HIGH, "The REF has silenced you!\n"); + my_bprintf(PRINT_HIGH, "The REF has silenced %s!\n",e->client->pers.netname); + e->client->silenced = 1; + } + else + { + safe_cprintf (ent, PRINT_HIGH, "Player '%s' not found.\n", string); + } + } + else + { + safe_cprintf (ent, PRINT_HIGH, "You are not the REF.\n"); + } +} + +//Allow a player to talk again +void Cmd_Ref_Talk (edict_t *ent) +{ + char *string; + edict_t *e; + + if (ent->bot_client) return; + if (!ent->client) return; + + if (wf_game.ref_ent && !wf_game.ref_ent->inuse) + { + wf_game.ref_ent = NULL; + } + + string=gi.args(); + + if (wf_game.ref_ent == ent) + { + //Find the player with the given name + e = refFindPlayer(string); + if (e) + { + safe_cprintf(e, PRINT_HIGH, "The REF says you to talk again.\n"); + e->client->silenced = 0; + } + else + { + safe_cprintf (ent, PRINT_HIGH, "Player '%s' not found.\n", string); + } + } + else + { + safe_cprintf (ent, PRINT_HIGH, "You are not the REF.\n"); + } +} + +//Lets ref stop being the ref +void Cmd_Ref_Leave (edict_t *ent) +{ + + if (ent->bot_client) return; + if (!ent->client) return; + + if (wf_game.ref_ent && !wf_game.ref_ent->inuse) + { + wf_game.ref_ent = NULL; + } + + if (wf_game.ref_ent == ent) + { + if (wf_game.show_ref_skin) + Cmd_Ref_Skin_Off(ent); + wf_game.ref_ent = NULL; + + safe_cprintf (ent, PRINT_HIGH, "You are no longer the ref.\n"); + } + else + { + safe_cprintf (ent, PRINT_HIGH, "You are not the REF.\n"); + } +} diff --git a/wf_scanner.c b/wf_scanner.c new file mode 100644 index 0000000..cd3bf34 --- /dev/null +++ b/wf_scanner.c @@ -0,0 +1,128 @@ +/*============================================================================== +The Weapons Factory - +Scanner Functions +Original code by ?? +Modified by Gregg Reno +==============================================================================*/ +#include "g_local.h" + +void ClearScanner(gclient_t *client) +{ + client->pers.scanner_active=0; +} + + +void ShowScanner(edict_t *ent,char *layout) +{ +// int i; + + edict_t *player = g_edicts; + edict_t *target; + + char stats[64]; +// float len; + int hd; +// int sx, sy; + vec3_t v; + vec3_t dp; + vec3_t normal = {0,0,-1}; + + // Main scanner graphic draw + Com_sprintf (stats, sizeof(stats),"xv 80 yv 40 picn %s ", PIC_SCANNER_TAG); + SAFE_STRCAT(layout,stats,LAYOUT_MAX_LENGTH); + + + target = ent->client->tracker_target; + if (!target) return; + + //See if target is in front + if (!infront(ent, target)) return; + + // calc player to decoy vector + VectorSubtract (ent->s.origin, target->s.origin, v); + +//gi.dprintf("v 0=%f, 1=%f, 2=%f\n",v[0], v[1], v[2]); + + // save height differential + hd = v[2]; + + // remove height component +// v[2] = 0; + + // normal vector to enemy + VectorNormalize(v); + + // rotate round player view angle (yaw) + RotatePointAroundVector( dp, normal, v, ent->s.angles[1]); + + // setup arrows + if (dp[1] < -.1) + { + Com_sprintf (stats, sizeof(stats),"xv %i yv %i picn %s ", + 118, 108, PIC_LEFT_TAG); + } + else if (dp[1] > .1) + { + Com_sprintf (stats, sizeof(stats),"xv %i yv %i picn %s ", + 178, 108, PIC_RIGHT_TAG); + } + + SAFE_STRCAT(layout,stats,LAYOUT_MAX_LENGTH); + + // clear stats + *stats = 0; + + // set up/down arrow + if (dp[2] < -.03) + { + Com_sprintf (stats, sizeof(stats),"xv %i yv %i picn %s ", + 148, 88,PIC_UP_TAG); + } + else if (dp[2] > .03) + { + Com_sprintf (stats, sizeof(stats),"xv %i yv %i picn %s ", + 148, 128,PIC_DOWN_TAG); + } + +//gi.dprintf("dp 0=%f, 1=%f, 2=%f, hd=%f\n",dp[0], dp[1], dp[2], hd); + + // any up/down ? + if (*stats) + SAFE_STRCAT(layout,stats,LAYOUT_MAX_LENGTH); + +} +void Toggle_Scanner(edict_t *ent) +{ + char string[1040]; + + if ((!ent->client) || (ent->health <=0)) + return; + + ent->client->showinventory = 0; + ent->client->showscores = 0; + + //toggle low on/off bit (and clear scores/inventory display if required) + if (ent->client->pers.scanner_active) + { + ent->client->pers.scanner_active = 0; + } + else + { + + //What do we want to track? + //ent->client->tracker_target = ent->decoy; + + if (ent->wf_team == 1) + ent->client->tracker_target = G_Find(NULL, FOFS(classname), "item_flag_team2"); + else if (ent->wf_team == 2) + ent->client->tracker_target = G_Find(NULL, FOFS(classname), "item_flag_team1"); + else + ent->client->tracker_target = NULL; + + string[0] = 0; + ent->client->pers.scanner_active = 1; + ShowScanner(ent,string); + gi.WriteByte (svc_layout); + gi.WriteString (string); + } +} diff --git a/wf_tempents.c b/wf_tempents.c new file mode 100644 index 0000000..7fe67ca --- /dev/null +++ b/wf_tempents.c @@ -0,0 +1,305 @@ +#include "g_local.h" + + +//Temp Entity handling from Maj.Bitch + +//====================================================== +//========== Spawn Temp Entity Functions =============== +//====================================================== + + + +/* +Spawns (type) Splash with {count} particles of {color} at {start} moving +in {direction} and Broadcasts to all in Potentially Visible Set from +vector (origin) + +TE_LASER_SPARKS - Splash particles obey gravity +TE_WELDING_SPARKS - Splash particles with flash of light at {origin} +TE_SPLASH - Randomly shaded shower of particles + +colors: +1 - red/gold - blaster type sparks +2 - blue/white - blue +3 - brown - brown +4 - green/white - slime green +5 - red/orange - lava red +6 - red - blood red +All others are grey +*/ +//====================================================== +void G_Spawn_Splash(int type, int count, int color, vec3_t start, vec3_t movdir, vec3_t origin ) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(type); + gi.WriteByte(count); + gi.WritePosition(start); + gi.WriteDir(movdir); + gi.WriteByte(color); + gi.multicast(origin, MULTICAST_PVS); +} + +//====================================================== +/* +Spawns a string of successive (type) models of from record (rec_no) +from (start) to (endpos) which are offset by vector (offset) and +Broadcasts to all in Potentially Visible Set from vector (origin) + +Type: +TE_GRAPPLE_CABLE - The grappling hook cable +TE_MEDIC_CABLE_ATTACK - NOT IMPLEMENTED IN ENGINE +TE_PARASITE_ATTACK - NOT IMPLEMENTED IN ENGINE +*/ +//====================================================== +void G_Spawn_Models(int type, short rec_no, vec3_t start, vec3_t endpos, vec3_t offset, vec3_t origin ) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(type); + gi.WriteShort(rec_no); + gi.WritePosition(start); + gi.WritePosition(endpos); + gi.WritePosition(offset); + gi.multicast(origin, MULTICAST_PVS); +} + +//====================================================== +/* +Spawns a trail of (type) from {start} to {end} and Broadcasts to all +in Potentially Visible Set from vector (origin) + +TE_BFG_LASER - Spawns a green laser +TE_BUBBLETRAIL - Spawns a trail of bubbles +TE_RAILTRAIL - Spawns a blue spiral trail filled with white smoke +*/ +//====================================================== +void G_Spawn_Trails(int type, vec3_t start, vec3_t endpos, vec3_t origin ) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(type); + gi.WritePosition(start); + gi.WritePosition(endpos); + gi.multicast(origin, MULTICAST_PVS); +} + +//====================================================== +/* +Spawns sparks of (type) from {start} in direction of {movdir} and +Broadcasts to all in Potentially Visible Set from vector (origin) + +TE_BLASTER - Spawns blaster sparks +TE_BLOOD - Spawns spurt of red blood +TE_BULLET_SPARKS - Same as TE_SPARKS, with a bullet puff and richochet sound +TE_GREENBLOOD - NOT IMPLEMENTED - Spawns a spurt of green blood +TE_GUNSHOT - Spawns a grey splash of particles, with a bullet puff +TE_SCREEN_SPARKS - Spawns a large green/white splash of sparks +TE_SHIELD_SPARKS - Spawns a large blue/violet splash of sparks +TE_SHOTGUN - Spawns a small grey splash of spark particles, with a bullet puff +TE_SPARKS - Spawns a red/gold splash of spark particles +*/ +//====================================================== +void G_Spawn_Sparks(int type, vec3_t start, vec3_t movdir, vec3_t origin ) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(type); + gi.WritePosition(start); + gi.WriteDir(movdir); + gi.multicast(origin, MULTICAST_PVS); +} + +//====================================================== +/* +Spawns a (type) explosion at (start} and Broadcasts to all Potentially +Visible Sets from {origin} + +TE_BFG_BIGEXPLOSION - Spawns a BFG particle explosion +TE_BFG_EXPLOSION - Spawns a BFG explosion sprite +TE_BOSSTPORT - Spawns a mushroom-cloud particle effect +TE_EXPLOSION1 - Spawns a mid-air-style explosion +TE_EXPLOSION2 - Spawns a nuclear-style explosion +TE_GRENADE_EXPLOSION - Spawns a grenade explosion +TE_GRENADE_EXPLOSION_WATER - Spawns an underwater grenade explosion +TE_ROCKET_EXPLOSION - Spawns a rocket explosion +TE_ROCKET_EXPLOSION_WATER - Spawns an underwater rocket explosion + +Note: The last four EXPLOSION entries overlap to some degree. +TE_GRENADE_EXPLOSION is the same as TE_EXPLOSION2, +TE_ROCKET_EXPLOSION is the same as TE_EXPLOSION1, +and both of the EXPLOSION_WATER entries are the same, visually. +*/ +//====================================================== +void G_Spawn_Explosion(int type, vec3_t start, vec3_t origin ) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(type); + gi.WritePosition(start); + gi.multicast(origin, MULTICAST_PVS); +} + +//====================================================== +/* +Spawns flames of (type) from {start} in direction of {movdir} and +Broadcasts to all in Potentially Visible Set from vector (origin) + +TE_FLAME - Spawns a flame +*/ +//====================================================== + +void G_Spawn_Flame(int type, short rec_no, int count, vec3_t start, vec3_t origin, vec3_t pos1,vec3_t pos2,vec3_t pos3, vec3_t pos4 ) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (type); + gi.WriteShort(rec_no); + gi.WriteShort(count); + gi.WritePosition (start); + gi.WritePosition (origin); + gi.WritePosition (pos1); + gi.WritePosition (pos2); + gi.WritePosition (pos3); + gi.WritePosition (pos4); + gi.multicast (origin, MULTICAST_PVS); + +} + +//====================================================== +/* +Spawns lightning of (type) from {start} in direction of {movdir} and +Broadcasts to all in Potentially Visible Set from vector (origin) + +TE_LIGHTNING - Spawns lightning +*/ +//====================================================== +void G_Spawn_Lightning(int type, short rec_no, short rec_no2, vec3_t start, vec3_t origin) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (type); + gi.WriteShort (rec_no); // destination entity + gi.WriteShort (rec_no2); // source entity + gi.WritePosition (origin); + gi.WritePosition (start); + gi.multicast (start, MULTICAST_PVS); +} + +/* +Spawns a debug trail from [start] to [endpos] +Type: +TE_DEBUG - The debug trail +*/ +//====================================================== +void G_Spawn_DBTrail(int type, vec3_t start, vec3_t endpos ) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (type); + gi.WritePosition (start); + gi.WritePosition (endpos); + gi.multicast (start, MULTICAST_ALL); +} + +//====================================================== +/* +Spawns a trail of (type) from {start} to {end} and Broadcasts to all +in Potentially Visible Set from vector (origin) + +TE_FLASHLIGHT - Spawns a green laser +*/ +//====================================================== +void G_Spawn_Light(int type, short rec_no, vec3_t origin ) +{ + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (type); + gi.WritePosition (origin); + gi.WriteShort (rec_no); + gi.multicast (origin, MULTICAST_PVS); +} + + +void show_Temp_Ent(int type, short rec_no, short rec_no2, int count, int color, vec3_t start, vec3_t endpos, vec3_t offset, vec3_t movdir, vec3_t origin, vec3_t pos1,vec3_t pos2,vec3_t pos3, vec3_t pos4 ) +{ + switch (type) + { + case TE_LASER_SPARKS: + case TE_WELDING_SPARKS: + case TE_SPLASH: + case TE_TUNNEL_SPARKS: + G_Spawn_Splash(type, count, color, start, movdir, origin); + return; + + case TE_GRAPPLE_CABLE: + case TE_MEDIC_CABLE_ATTACK: + case TE_PARASITE_ATTACK: + G_Spawn_Models(type, rec_no, start, endpos, offset, origin ); + return; + + case TE_BFG_LASER: + case TE_BUBBLETRAIL: + case TE_RAILTRAIL: + case TE_RAILTRAIL2: + case TE_BUBBLETRAIL2: + G_Spawn_Trails(type, start, endpos, origin ); + return; + + case TE_BLASTER: + case TE_BLOOD: + case TE_BULLET_SPARKS: + case TE_GREENBLOOD: + case TE_GUNSHOT: + case TE_SCREEN_SPARKS: + case TE_SHIELD_SPARKS: + case TE_SHOTGUN: + case TE_SPARKS: + case TE_BLUEHYPERBLASTER: + case TE_BLASTER2: + G_Spawn_Sparks(type, start, movdir, origin ); + return; + + case TE_BFG_BIGEXPLOSION: + case TE_BFG_EXPLOSION: + case TE_BOSSTPORT: + case TE_EXPLOSION1: + case TE_EXPLOSION2: + case TE_GRENADE_EXPLOSION: + case TE_GRENADE_EXPLOSION_WATER: + case TE_ROCKET_EXPLOSION: + case TE_ROCKET_EXPLOSION_WATER: + case TE_PLASMA_EXPLOSION: + case TE_PLAIN_EXPLOSION: + case TE_EXPLOSION1_BIG: + case TE_EXPLOSION1_NP: + G_Spawn_Explosion( type, start, origin ); + return; + + case TE_FLAME: + G_Spawn_Flame(type, rec_no, count, start, origin, pos1, pos2, pos3, pos4 ); + return; + + case TE_LIGHTNING: + G_Spawn_Lightning(type, rec_no, rec_no2, start, origin); + return; + + case TE_DEBUGTRAIL: + G_Spawn_DBTrail(type, start, endpos ); + return; + + case TE_FLASHLIGHT: + G_Spawn_Light(type, rec_no, origin ); + return; + + case TE_FORCEWALL: + case TE_HEATBEAM: + case TE_MONSTER_HEATBEAM: + case TE_STEAM: + case TE_MOREBLOOD: + case TE_HEATBEAM_SPARKS: + case TE_HEATBEAM_STEAM: + case TE_CHAINFIST_SMOKE: + case TE_ELECTRIC_SPARKS: + case TE_TRACKER_EXPLOSION: + case TE_TELEPORT_EFFECT: + case TE_DBALL_GOAL: + case TE_WIDOWBEAMOUT: + case TE_NUKEBLAST: + case TE_WIDOWSPLASH: + case TE_FLECHETTE: + return; + } +} \ No newline at end of file diff --git a/wf_turret.c b/wf_turret.c new file mode 100644 index 0000000..fb4df83 --- /dev/null +++ b/wf_turret.c @@ -0,0 +1,376 @@ +/* +Grenade Turrets +by xxxx xxx +Modified by Gregg Reno +*/ + +#include "g_local.h" + +void Turret_Explode (edict_t *ent); + +//The grenade turrets hit the ceiling and stick out halfway. +//this makes it so they drop down a bit, eliminating this. +void grenturret_think4 (edict_t *ent) +{ + vec3_t down; + int speed; + + down[0]=0; //We're going DOWN!!! + down[1]=0; + down[2]=-100; + + VectorNormalize(down); + VectorCopy(down, ent->movedir); + speed=75; + VectorScale(down, speed, ent->velocity); + + ent->nextthink=level.time + .3; + ent->think=grenturret_think2; +} + +void grenturret_think3 (edict_t *ent) +{ + if (((int) (ent->turrettime * 10)) % 10==0) + ent->movedir[2]*=-1; + + //Finally, KILL KILL KILL!!! + if (ent->show_hostile==false) + { + ent->show_hostile=true; + ent->nextthink=level.time + .1; + ent->think=grenturret_think3; + } + else + { + fire_rail (ent, ent->s.origin, ent->targetdir, wf_game.grenade_damage[GRENADE_TYPE_TURRET], 250, MOD_WF_TURRET); + ent->nextthink=level.time + .1; + ent->think=grenturret_think2; + ent->turrettime=0; + ent->turretammo-=1; + } + ent->turrettime+=.1; +} + +// Second think function for da grenade turrets +//Initially this was a homing think function from qdevels www.planetquake.com/qdevels +//Imp was here (duh) +//MUST go before grenturret_think1 so that think1 can set ent->think to grenturret_think2 + +//Note 3/22: Putting think2 before 1 isn't necessary anymore, since I +//prototyped it in g_local.h, but what the hell... + +void grenturret_think2 (edict_t *ent) + +{ + edict_t *target = NULL; + edict_t *blip = NULL; + vec3_t targetdir, blipdir; +// vec3_t *start; +// vec3_t point; +// vec3_t dir; + + VectorScale(ent->movedir, 12, ent->velocity); //Keep speed at 25 + + if (((int) (ent->turrettime * 10)) % 10==0) + ent->movedir[2]*=-1; + + if (!(ent->turrettime < 2.0)) + { + //Find targets + while ((blip = findradius(blip, ent->s.origin, 1024)) != NULL) + { + if (!(blip->svflags & SVF_MONSTER) && !blip->client) + continue; + if (blip->solid == SOLID_NOT) + continue; //don't see observers + //dont attack same team + if (blip->wf_team == ent->wf_team) + continue; + + if (!blip->takedamage) + continue; + if (blip->health <= 0) + continue; + if (blip->disguised) + continue; + + if (!visible(ent, blip)) + continue; + VectorSubtract(blip->s.origin, ent->s.origin, blipdir); + blipdir[2] += 20; //don't shoot at the feet + if ((target == NULL) || (VectorLength(blipdir) < VectorLength(targetdir))) + { + target = blip; + VectorCopy(blipdir, targetdir); + VectorCopy(targetdir, ent->targetdir); + } + } + } + + ent->nextthink = level.time + .1; + + if (target!=NULL) + { + // target acquired, shoot it DOWN!!!! + // Well, not quite. Call think3 to shoot .2 seconds later... + ent->enemy = target; + ent->think=grenturret_think3; + ent->show_hostile=false; // + } + +//If our turret is out of ammo or has been too long, kill it +if (level.time>=ent->turretdie || ent->turretammo<=0) + { + ent->think=Turret_Explode; //Drops to the ground and explodes + ent->nextthink=level.time+2; //Looks better than just disappearing + ent->movetype=MOVETYPE_BOUNCE; + + //FIXME: This is ugly... is it possible to subtract effects + //instead of totally redefining the effects? + ent->s.effects = EF_GRENADE; //Lights out!!! + ent->s.renderfx = 0; //Goodbye shell :( + } +ent->turrettime+=.1; +} + +//First new think function for grenade turrets +//Imp was here +void grenturret_think1 (edict_t *ent) +{ + vec3_t up; + vec3_t right; + int speed; + + //Take out gravity + ent->movetype=MOVETYPE_FLYMISSILE; + + up[0]=0; //We're going UP!!! + up[1]=0; + up[2]=100; + + right[0]=100; //we're pointing right... ugly hack + right[1]=0; + right[2]=0; + + ent->s.effects |= EF_HYPERBLASTER; //Lots of fun with green lights +/* For now, turn off special effects. Use skins instead + ent->s.effects |= EF_COLOR_SHELL; //Green shell... fun! + + // ent->s.renderfx |= RF_SHELL_GREEN; //It's a GREEN shell!!! + + if (ent->wf_team == CTF_TEAM1) //team 1 is red + ent->s.renderfx |= RF_SHELL_RED; + else + ent->s.renderfx |= RF_SHELL_BLUE; +*/ + + VectorNormalize(up); + VectorCopy(up, ent->movedir); + speed=300; + VectorScale(up, speed, ent->velocity); + + ent->avelocity[0]=0; +// ent->avelocity[1]=360*5; + ent->avelocity[1]=360; + ent->avelocity[2]=0; + + vectoangles(right, ent->s.angles); + + ent->nextthink=level.time+.5; + ent->think=grenturret_think4; + ent->turrettime=1; +} + +void Turret_Explode (edict_t *ent) +{ + vec3_t origin; + + if (ent->owner->client) + { + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + } + + T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, MOD_WF_TURRET); + + 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 Turret_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + Turret_Explode (ent); + return; + } + + if (!other->takedamage) + { +//WF + if (ent->think==grenturret_think4) //Move the grenade away from + ent->nextthink=level.time; //the ceiling when it hits it +//WF + + + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + ent->enemy = other; + Turret_Explode (ent); +} + +// When a grenade 'dies', it blows up next frame +void Turret_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + .1; + self->think = Turret_Explode; +} + + +void fire_turret_grenade (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; + + +if (wfdebug) +{ +gi.dprintf("fire_turret_grenade start.\n"); +} + ++self->client->pers.active_grenades[GRENADE_TYPE_TURRET]; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + grenade->grenade_index = GRENADE_TYPE_TURRET; + 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->clipmask = MASK_PLAYERSOLID; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + + //Try to raise up the object a bit + VectorSet(grenade->mins, -20,-20,-20); + VectorSet(grenade->maxs, 20, 20, 20); + + grenade->s.modelindex = gi.modelindex ("models/objects/turretg/tris.md2"); + if (self->wf_team == CTF_TEAM1) //team 1 is red + grenade->s.skinnum = 0; + else + grenade->s.skinnum = 1; + +// grenade->s.modelindex = gi.modelindex (GRTURRET_MODEL); +// grenade->s.skinnum = GRTURRET_SKIN; + + grenade->owner = self; + grenade->touch = Turret_Touch; + + if ((int)wfflags->value & WF_ANARCHY) + grenade->wf_team = 0; //fire at anybody + else + grenade->wf_team = self->client->resp.ctf_team; + + // A few more attributes to let the grenade 'die' + VectorSet(grenade->mins, -10, -10, 0); + VectorSet(grenade->maxs, 10, 10, 10); + grenade->mass = 40; + grenade->health = 10; + grenade->gib_health = -10; + grenade->max_health = 10; + grenade->die = Turret_Die; + grenade->takedamage = DAMAGE_AIM; + grenade->monsterinfo.aiflags = AI_NOSTEP; + + if ((int)wfflags->value & WF_NO_TURRET) + { + safe_cprintf(self, PRINT_HIGH, "Turret Grenades Are Disabled\n"); + grenade->nextthink = level.time + 2; + grenade->think = Turret_Explode; + } + else if ( self->client->pers.inventory[ITEM_INDEX(FindItem("Grenades"))] >= TURRET_GRENADES + && self->client->pers.inventory[ITEM_INDEX(FindItem("Slugs"))] >= TURRET_SLUGS) + { + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + { + self->client->pers.inventory[ITEM_INDEX(FindItem("Grenades"))] -= TURRET_GRENADES; + self->client->pers.inventory[ITEM_INDEX(FindItem("Slugs"))] -= TURRET_SLUGS; + } + grenade->nextthink = level.time + timer; + grenade->think = grenturret_think1; + grenade->turrettime=0; +// grenade->turretdie=level.time+30.55 + timer; + grenade->turretdie=level.time+61.10 + timer; + grenade->turretammo=6; + + } + else + { + safe_cprintf(self, PRINT_HIGH, "You need %d Grenades and %d Slugs for Grenade Turret\n",TURRET_GRENADES,TURRET_SLUGS); + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + self->client->pers.inventory[ITEM_INDEX(FindItem("Grenades"))] -= 1; + grenade->nextthink = level.time + 2; + grenade->think = Turret_Explode; + } + + grenade->dmg = wf_game.grenade_damage[GRENADE_TYPE_TURRET]; + grenade->dmg_radius = damage_radius; + grenade->classname = "turret"; + if (held) + grenade->spawnflags = 3; + else + grenade->spawnflags = 1; + + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + if (timer <= 0.0) + Turret_Explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} + + diff --git a/x_weap.c b/x_weap.c new file mode 100644 index 0000000..0c58ee6 --- /dev/null +++ b/x_weap.c @@ -0,0 +1,485 @@ +#include "g_local.h" + +void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); +void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + + +// RAFAEL +void fire_blueblaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn (); + 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/blaser/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"; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } + +} + +// RAFAEL + +/* + fire_ionripper +*/ + +void ionripper_sparks (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WELDING_SPARKS); + gi.WriteByte (0); + gi.WritePosition (self->s.origin); + gi.WriteDir (vec3_origin); + gi.WriteByte (0xe4 + (rand()&3)); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + +// RAFAEL +void ionripper_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); + + if (other->takedamage) + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, MOD_RIPPER); + + } + else + { + return; + } + + G_FreeEdict (self); +} + + +// RAFAEL +void fire_ionripper (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *ion; + trace_t tr; + + VectorNormalize (dir); + + ion = G_Spawn (); + VectorCopy (start, ion->s.origin); + VectorCopy (start, ion->s.old_origin); + vectoangles (dir, ion->s.angles); + VectorScale (dir, speed, ion->velocity); + +// ion->movetype = MOVETYPE_WALLBOUNCE; + ion->movetype = MOVETYPE_FLYRICOCHET; + ion->clipmask = MASK_SHOT; + ion->solid = SOLID_BBOX; + ion->s.effects |= effect; + + ion->s.renderfx |= RF_FULLBRIGHT; + + VectorClear (ion->mins); + VectorClear (ion->maxs); + ion->s.modelindex = gi.modelindex ("models/objects/boomrang/tris.md2"); + ion->s.sound = gi.soundindex ("misc/lasfly.wav"); + ion->owner = self; + ion->touch = ionripper_touch; + ion->nextthink = level.time + 3; + ion->think = ionripper_sparks; + ion->dmg = damage; + ion->dmg_radius = 100; + gi.linkentity (ion); + + if (self->client) + check_dodge (self, ion->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, ion->s.origin, ion, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (ion->s.origin, -10, dir, ion->s.origin); + ion->touch (ion, tr.ent, NULL, NULL); + } + +} + + +// RAFAEL +/* +fire_heat +*/ + + +void heat_think (edict_t *self) +{ + edict_t *target = NULL; + edict_t *aquire = NULL; + vec3_t vec; + vec3_t oldang; + int len; + int oldlen = 0; + + VectorClear (vec); + + // aquire new target + while (( target = findradius (target, self->s.origin, 1024)) != NULL) + { + + if (self->owner == target) + continue; + if (!target->svflags & SVF_MONSTER) + continue; + if (!target->client) + continue; + if (target->health <= 0) + continue; + if (!visible (self, target)) + continue; + + // if we need to reduce the tracking cone + /* + { + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorSubtract (target->s.origin, self->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + + if (dot > 0.6) + continue; + } + */ + + if (!infront (self, target)) + continue; + + VectorSubtract (self->s.origin, target->s.origin, vec); + len = VectorLength (vec); + + if (aquire == NULL || len < oldlen) + { + aquire = target; + self->target_ent = aquire; + oldlen = len; + } + } + + if (aquire != NULL) + { + VectorCopy (self->s.angles, oldang); + VectorSubtract (aquire->s.origin, self->s.origin, vec); + + vectoangles (vec, self->s.angles); + + VectorNormalize (vec); + VectorCopy (vec, self->movedir); + VectorScale (vec, 500, self->velocity); + } + + self->nextthink = level.time + 0.1; +} + +// RAFAEL +void fire_heat2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *heat; + + heat = G_Spawn(); + VectorCopy (start, heat->s.origin); + VectorCopy (dir, heat->movedir); + vectoangles (dir, heat->s.angles); + VectorScale (dir, speed, heat->velocity); + heat->movetype = MOVETYPE_FLYMISSILE; + heat->clipmask = MASK_SHOT; + heat->solid = SOLID_BBOX; + heat->s.effects |= EF_ROCKET; + VectorClear (heat->mins); + VectorClear (heat->maxs); + heat->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2"); + heat->owner = self; + heat->touch = rocket_touch; + + heat->nextthink = level.time + 0.1; + heat->think = heat_think; + + heat->dmg = damage; + heat->radius_dmg = radius_damage; + heat->dmg_radius = damage_radius; + heat->s.sound = gi.soundindex ("weapons/rockfly.wav"); + + if (self->client) + check_dodge (self, heat->s.origin, dir, speed); + + gi.linkentity (heat); +} + + + +// RAFAEL + +/* + fire_plasma +*/ + +void plasma_touch2 (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + + 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_PHALANX); + } + + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_PHALANX); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PLASMA_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + G_FreeEdict (ent); +} + + +// RAFAEL +void fire_plasma (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *plasma; + + plasma = G_Spawn(); + VectorCopy (start, plasma->s.origin); + VectorCopy (dir, plasma->movedir); + vectoangles (dir, plasma->s.angles); + VectorScale (dir, speed, plasma->velocity); + plasma->movetype = MOVETYPE_FLYMISSILE; + plasma->clipmask = MASK_SHOT; + plasma->solid = SOLID_BBOX; + + VectorClear (plasma->mins); + VectorClear (plasma->maxs); + + plasma->owner = self; + plasma->touch = plasma_touch2; + plasma->nextthink = level.time + 8000/speed; + plasma->think = G_FreeEdict; + plasma->dmg = damage; + plasma->radius_dmg = radius_damage; + plasma->dmg_radius = damage_radius; + plasma->s.sound = gi.soundindex ("weapons/rockfly.wav"); + + plasma->s.modelindex = gi.modelindex ("sprites/s_photon.sp2"); + plasma->s.effects |= EF_PLASMA | EF_ANIM_ALLFAST; + + if (self->client) + check_dodge (self, plasma->s.origin, dir, speed); + + gi.linkentity (plasma); + + +} + + +// RAFAEL +/* + RipperGun +*/ + +void weapon_ionripper_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + vec3_t tempang; + int damage; + int kick; + + damage = wf_game.weapon_damage[WEAPON_ION_RIPPER]; + kick = 40; + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + VectorCopy (ent->client->v_angle, tempang); + tempang[YAW] += crandom(); + + AngleVectors (tempang, forward, right, NULL); + + VectorScale (forward, -3, ent->client->kick_origin); + ent->client->kick_angles[0] = -3; + + // VectorSet (offset, 0, 7, ent->viewheight - 8); + VectorSet (offset, 16, 7, ent->viewheight - 8); + + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_ionripper (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_ION_RIPPER], EF_IONRIPPER); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent - g_edicts); + gi.WriteByte (MZ_IONRIPPER | 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] -= ent->client->pers.weapon->quantity; + + if (ent->client->pers.inventory[ent->client->ammo_index] < 0) + ent->client->pers.inventory[ent->client->ammo_index] = 0; +} + + +void Weapon_Ionripper (edict_t *ent) +{ +// static int pause_frames[] = {36, 0}; +// static int fire_frames[] = {5, 0}; + +// Weapon_Generic (ent, 4, 6, 36, 39, pause_frames, fire_frames, weapon_ionripper_fire); + + //Use blaster model for now + static int pause_frames[] = {19, 32, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, weapon_ionripper_fire); +} + + +// +// Phalanx +// + +void weapon_phalanx_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t offset; + vec3_t v; + int kick = 12; + int damage; + float damage_radius; + int radius_damage; + + damage = wf_game.weapon_damage[WEAPON_PHALANX] + (int)(random() * 10.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] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (ent->client->ps.gunframe == 8) + { + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] - 1.5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, right, up); + + radius_damage = 30; + damage_radius = 120; + + fire_plasma (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_PHALANX], damage_radius, radius_damage); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + } + else + { + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] + 1.5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, right, up); + fire_plasma (ent, start, forward, damage, 725, damage_radius, radius_damage); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_PHALANX | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + } + + ent->client->ps.gunframe++; + +} + +void Weapon_Phalanx (edict_t *ent) +{ + static int pause_frames[] = {29, 42, 55, 0}; + static int fire_frames[] = {7, 8, 0}; + + Weapon_Generic (ent, 5, 20, 58, 63, pause_frames, fire_frames, weapon_phalanx_fire); + +} + diff --git a/z_api.h b/z_api.h new file mode 100644 index 0000000..dee3eb7 --- /dev/null +++ b/z_api.h @@ -0,0 +1,127 @@ +/* Only the Windows DLL is currently supported */ +#ifndef _ZIPAPI_H +#define _ZIPAPI_H + +#include "z_zip.h" + +#ifndef PATH_MAX +# define PATH_MAX 128 +#endif + +#if defined(WINDLL) || defined(API) +#include +/* Porting definations between Win 3.1x and Win32 */ +#ifdef WIN32 +# define far +# define _far +# define __far +# define near +# define _near +# define __near +#endif + +/*--------------------------------------------------------------------------- + Prototypes for public Zip API (DLL) functions. + ---------------------------------------------------------------------------*/ + +#define ZPVER_LEN sizeof(ZpVer) +/* These defines are set to zero for now, until OS/2 comes out + with a dll. + */ +#define D2_MAJORVER 0 +#define D2_MINORVER 0 +#define D2_PATCHLEVEL 0 + +/* intended to be a private struct: */ +typedef struct _zip_ver { + uch major; /* e.g., integer 5 */ + uch minor; /* e.g., 2 */ + uch patchlevel; /* e.g., 0 */ + uch not_used; +} _zip_version_type; + +typedef struct _ZpVer { + ulg structlen; /* length of the struct being passed */ + ulg flag; /* bit 0: is_beta bit 1: uses_zlib */ + char betalevel[10]; /* e.g., "g BETA" or "" */ + char date[20]; /* e.g., "4 Sep 95" (beta) or "4 September 1995" */ + char zlib_version[10]; /* e.g., "0.95" or NULL */ + _zip_version_type zip; + _zip_version_type os2dll; + _zip_version_type windll; +} ZpVer; + +# ifndef EXPENTRY +# define EXPENTRY WINAPI +# endif + +#ifndef DEFINED_ONCE +#define DEFINED_ONCE +typedef int (far *DLLPRNT) (FILE *, unsigned int, char *); +typedef int (WINAPI DLLPASSWORD) (char *, char *, int, char *); +#endif +typedef LPSTR (WINAPI DLLCOMMENT)(LPSTR); + +/* Structures */ + +typedef struct { /* zip options */ +BOOL fSuffix; /* include suffixes (not implemented) */ +BOOL fEncrypt; /* encrypt files */ +BOOL fSystem; /* include system and hidden files */ +BOOL fVolume; /* Include volume label */ +BOOL fExtra; /* Include extra attributes */ +BOOL fNoDirEntries; /* Do not add directory entries */ +BOOL fExcludeDate; /* Exclude files earlier than specified date */ +BOOL fIncludeDate; /* Include only files earlier than specified date */ +BOOL fVerbose; /* Mention oddities in zip file structure */ +BOOL fQuiet; /* Quiet operation */ +BOOL fCRLF_LF; /* Translate CR/LF to LF */ +BOOL fLF_CRLF; /* Translate LF to CR/LF */ +BOOL fJunkDir; /* Junk directory names */ +BOOL fRecurse; /* Recurse into subdirectories */ +BOOL fGrow; /* Allow appending to a zip file */ +BOOL fForce; /* Make entries using DOS names (k for Katz) */ +BOOL fMove; /* Delete files added or updated in zip file */ +BOOL fDeleteEntries; /* Delete files from zip file */ +BOOL fUpdate; /* Update zip file--overwrite only if newer */ +BOOL fFreshen; /* Freshen zip file--overwrite only */ +BOOL fJunkSFX; /* Junk SFX prefix */ +BOOL fLatestTime; /* Set zip file time to time of latest file in it */ +BOOL fComment; /* Put comment in zip file */ +BOOL fOffsets; /* Update archive offsets for SFX files */ +BOOL fPrivilege; /* Use privileges (WIN32 only) */ +BOOL fEncryption; /* TRUE if encryption supported, else FALSE. + this is a read only flag */ +int fRepair; /* Repair archive. 1 => -F, 2 => -FF */ +char fLevel; /* Compression level (0 - 9) */ +char Date[9]; /* Date to include after */ +char szRootDir[PATH_MAX]; /* Directory to use as base for zipping */ +} ZPOPT, _far * LPZPOPT; + +typedef struct { +int argc; /* Count of files to zip */ +LPSTR lpszZipFN; /* name of archive to create/update */ +char **FNV; /* array of file names to zip up */ +} ZCL, _far *LPZCL; + +typedef struct { +DLLPRNT print; +DLLCOMMENT *comment; +DLLPASSWORD *password; +} ZIPUSERFUNCTIONS, far * LPZIPUSERFUNCTIONS; +/* +void EXPENTRY ZpVersion(ZpVer far *); +int EXPENTRY ZpInit(ZIPUSERFUNCTIONS far * lpZipUserFunc); +BOOL EXPENTRY ZpSetOptions(ZPOPT); +ZPOPT EXPENTRY ZpGetOptions(void); +int EXPENTRY ZpArchive(ZCL C); +*/ +/* Functions not yet supported */ +#if 0 +int EXPENTRY ZpMain (int argc, char **argv); +int EXPENTRY ZpAltMain (int argc, char **argv, ZpInit *init); +#endif +#endif /* WINDLL? || API? */ + +#endif _ZIPAPI_H + diff --git a/z_unzip.h b/z_unzip.h new file mode 100644 index 0000000..65e063b --- /dev/null +++ b/z_unzip.h @@ -0,0 +1,568 @@ +/*--------------------------------------------------------------------------- + + unzip.h (new) + + This header file contains the public macros and typedefs required by + both the UnZip sources and by any application using the UnZip API. If + UNZIP_INTERNAL is defined, it includes unzpriv.h (containing includes, + prototypes and extern variables used by the actual UnZip sources). + + ---------------------------------------------------------------------------*/ + + +#ifndef __unzip_h /* prevent multiple inclusions */ +#define __unzip_h + + +/*--------------------------------------------------------------------------- + Predefined, machine-specific macros. + ---------------------------------------------------------------------------*/ + +#ifdef POCKET_UNZIP /* WinCE port */ +# include "wince/punzip.h" /* must appear before windows.h */ +#endif + +#ifdef WINDLL +# include +#endif + +#ifdef __GO32__ /* MS-DOS extender: NOT Unix */ +# ifdef unix +# undef unix +# endif +# ifdef __unix +# undef __unix +# endif +# ifdef __unix__ +# undef __unix__ +# endif +#endif + +#if ((defined(__convex__) || defined(__convexc__)) && !defined(CONVEX)) +# define CONVEX +#endif + +#if (defined(unix) || defined(__unix) || defined(__unix__)) +# ifndef UNIX +# define UNIX +# endif +#endif /* unix || __unix || __unix__ */ +#if (defined(M_XENIX) || defined(COHERENT) || defined(__hpux)) +# ifndef UNIX +# define UNIX +# endif +#endif /* M_XENIX || COHERENT || __hpux */ +#if (defined(CONVEX) || defined(MINIX) || defined(_AIX) || defined(__QNX__)) +# ifndef UNIX +# define UNIX +# endif +#endif /* CONVEX || MINIX || _AIX || __QNX__ */ + +#if (defined(VM_CMS) || defined(MVS)) +# define CMS_MVS +#endif + +#if (defined(__OS2__) && !defined(OS2)) +# define OS2 +#endif + +#if (defined(__TANDEM) && !defined(TANDEM)) +# define TANDEM +#endif + +#if (defined(__VMS) && !defined(VMS)) +# define VMS +#endif + +#if (defined(__WIN32__) && !defined(WIN32)) +# define WIN32 +#endif + +#ifdef __COMPILER_KCC__ +# include +# ifdef SYS_T20 +# define TOPS20 +# endif +#endif /* __COMPILER_KCC__ */ + +/* Borland C does not define __TURBOC__ if compiling for a 32-bit platform */ +#ifdef __BORLANDC__ +# ifndef __TURBOC__ +# define __TURBOC__ +# endif +# if (!defined(__MSDOS__) && !defined(OS2) && !defined(WIN32)) +# define __MSDOS__ +# endif +#endif + +/* define MSDOS for Turbo C (unless OS/2) and Power C as well as Microsoft C */ +#ifdef __POWERC +# define __TURBOC__ +# define MSDOS +#endif /* __POWERC */ + +#if (defined(__MSDOS__) && !defined(MSDOS)) /* just to make sure */ +# define MSDOS +#endif + +#if (defined(linux) && !defined(LINUX)) +# define LINUX +#endif + +#ifdef __riscos +# define RISCOS +#endif + +#if (defined(THINK_C) || defined(MPW)) +# define MACOS +#endif +#if (defined(__MWERKS__) && defined(macintosh)) +# define MACOS +#endif + +/* use prototypes and ANSI libraries if __STDC__, or Microsoft or Borland C, or + * Silicon Graphics, or Convex?, or IBM C Set/2, or GNU gcc/emx, or Watcom C, + * or Macintosh, or Windows NT, or Sequent, or Atari or IBM RS/6000. + */ +#if (defined(__STDC__) || defined(MSDOS) || defined(WIN32) || defined(__EMX__)) +# ifndef PROTO +# define PROTO +# endif +# ifndef MODERN +# define MODERN +# endif +#endif +#if (defined(__IBMC__) || defined(__BORLANDC__) || defined(__WATCOMC__)) +# ifndef PROTO +# define PROTO +# endif +# ifndef MODERN +# define MODERN +# endif +#endif +#if (defined(MACOS) || defined(ATARI_ST) || defined(RISCOS)) +# ifndef PROTO +# define PROTO +# endif +# ifndef MODERN +# define MODERN +# endif +#endif +/* Sequent running Dynix/ptx: non-modern compiler */ +#if (defined(_AIX) || defined(sgi) || (defined(_SEQUENT_) && !defined(PTX))) +# ifndef PROTO +# define PROTO +# endif +# ifndef MODERN +# define MODERN +# endif +#endif +#if (defined(CMS_MVS) || defined(__BEOS__)) /* || defined(CONVEX) */ +# ifndef PROTO +# define PROTO +# endif +# ifndef MODERN +# define MODERN +# endif +#endif +#if defined(__BEOS__) && defined(__GNUC__) /* brain damage for our GCC port */ +# define __USE_FIXED_PROTOTYPES__ +#endif + +/* turn off prototypes if requested */ +#if (defined(NOPROTO) && defined(PROTO)) +# undef PROTO +#endif + +/* used to remove arguments in function prototypes for non-ANSI C */ +#ifdef PROTO +# define OF(a) a +#else +# define OF(a) () +#endif + +/* enable the "const" keyword only if MODERN and if not otherwise instructed */ +#ifdef MODERN +# if (!defined(ZCONST) && (defined(USE_CONST) || !defined(NO_CONST))) +# define ZCONST const +# endif +#endif + +#ifndef ZCONST +# define ZCONST +#endif + +/* bad or (occasionally?) missing stddef.h: */ +#if (defined(M_XENIX) || defined(DNIX)) +# define NO_STDDEF_H +#endif + +#if (defined(M_XENIX) && !defined(M_UNIX)) /* SCO Xenix only, not SCO Unix */ +# define SCO_XENIX +# define NO_LIMITS_H /* no limits.h, but MODERN defined */ +# define NO_UID_GID /* no uid_t/gid_t */ +# define size_t int +#endif + +#ifdef realix /* Modcomp Real/IX, real-time SysV.3 variant */ +# define SYSV +# define NO_UID_GID /* no uid_t/gid_t */ +#endif + +#if (defined(_AIX) && !defined(_ALL_SOURCE)) +# define _ALL_SOURCE +#endif + +#if defined(apollo) /* defines __STDC__ */ +# define NO_STDLIB_H +#endif + +#ifdef DNIX +# define SYSV +# define SHORT_NAMES /* 14-char limitation on path components */ +/* # define FILENAME_MAX 14 */ +# define FILENAME_MAX NAME_MAX /* GRR: experiment */ +#endif + +#if (defined(SYSTEM_FIVE) || defined(__SYSTEM_FIVE)) +# ifndef SYSV +# define SYSV +# endif +#endif /* SYSTEM_FIVE || __SYSTEM_FIVE */ +#if (defined(M_SYSV) || defined(M_SYS5)) +# ifndef SYSV +# define SYSV +# endif +#endif /* M_SYSV || M_SYS5 */ +/* __SVR4 and __svr4__ catch Solaris on at least some combos of compiler+OS */ +#if (defined(__SVR4) || defined(__svr4__) || defined(sgi) || defined(__hpux)) +# ifndef SYSV +# define SYSV +# endif +#endif /* __SVR4 || __svr4__ || sgi || __hpux */ +#if (defined(LINUX) || defined(__QNX__)) +# ifndef SYSV +# define SYSV +# endif +#endif /* LINUX || __QNX__ */ + +#if (defined(ultrix) || defined(__ultrix) || defined(bsd4_2)) +# if (!defined(BSD) && !defined(SYSV)) +# define BSD +# endif +#endif /* ultrix || __ultrix || bsd4_2 */ +#if (defined(sun) || defined(pyr) || defined(CONVEX)) +# if (!defined(BSD) && !defined(SYSV)) +# define BSD +# endif +#endif /* sun || pyr || CONVEX */ + +#ifdef pyr /* Pyramid: has BSD and AT&T "universes" */ +# ifdef BSD +# define pyr_bsd +# define USE_STRINGS_H /* instead of more common string.h */ +# define ZMEM /* ZMEM now uses bcopy/bzero: not in AT&T universe */ +# endif /* (AT&T memcpy claimed to be very slow, though) */ +# define DECLARE_ERRNO +#endif /* pyr */ + +/* stat() bug for Borland, VAX C (also GNU?), and Atari ST MiNT on TOS + * filesystems: returns 0 for wildcards! (returns 0xffffffff on Minix + * filesystem or `U:' drive under Atari MiNT.) Watcom C was previously + * included on this list; it would be good to know what version the problem + * was fixed at, if it did exist. Watcom 10.6 has a separate stat() problem: + * it fails on "." when the current directory is a root. This is covered by + * giving it a separate definition of SSTAT in OS-specific header files. */ +#if (defined(__TURBOC__) || defined(VMS) || defined(__MINT__)) +# define WILD_STAT_BUG +#endif + +#ifdef WILD_STAT_BUG +# define SSTAT(path,pbuf) (iswild(path) || stat(path,pbuf)) +#else +# define SSTAT stat +#endif + +#ifdef REGULUS /* returns the inode number on success(!)...argh argh argh */ +# define stat(p,s) zstat(p,s) +#endif + +#define STRNICMP zstrnicmp + +#define UzpMatch match + + +/*--------------------------------------------------------------------------- + OS-dependent includes + ---------------------------------------------------------------------------*/ + +#ifdef EFT +# define LONGINT off_t /* Amdahl UTS nonsense ("extended file types") */ +#else +# define LONGINT long +#endif + +#ifdef MODERN +# ifndef NO_STDDEF_H +# include +# endif +# ifndef NO_STDLIB_H +# include /* standard library prototypes, malloc(), etc. */ +# endif + typedef size_t extent; + typedef void zvoid; +#else /* !MODERN */ +# ifndef AOS_VS /* mostly modern? */ +# ifndef CMS_MVS + LONGINT lseek(); +# endif +# ifdef VAXC /* not fully modern, but does have stdlib.h and void */ +# include +# else + char *malloc(); +# define void int +# endif /* ?VAXC */ +# endif /* !AOS_VS */ + typedef unsigned int extent; + typedef char zvoid; +#endif /* ?MODERN */ + + +/*--------------------------------------------------------------------------- + Grab system-dependent definition of EXPENTRY for prototypes below. + ---------------------------------------------------------------------------*/ + +#if 0 +#if (defined(OS2) && !defined(FUNZIP)) +# ifdef UNZIP_INTERNAL +# define INCL_NOPM +# define INCL_DOSNLS +# define INCL_DOSPROCESS +# define INCL_DOSDEVICES +# define INCL_DOSDEVIOCTL +# define INCL_DOSERRORS +# define INCL_DOSMISC +# ifdef OS2DLL +# define INCL_REXXSAA +# include +# endif +# endif /* UNZIP_INTERNAL */ +# include +# define UZ_EXP EXPENTRY +#endif /* OS2 && !FUNZIP */ +#endif /* 0 */ + +#if (defined(OS2) && !defined(FUNZIP)) +# if defined(__IBMC__) || defined(__WATCOMC__) +# define UZ_EXP _System /* compiler keyword */ +# else +# define UZ_EXP +# endif +#endif /* OS2 && !FUNZIP */ + +#if (defined(WINDLL) || defined(USE_UNZIP_LIB)) +# ifndef EXPENTRY +# define UZ_EXP WINAPI +# else +# define UZ_EXP EXPENTRY +# endif +#endif + +#ifndef UZ_EXP +# define UZ_EXP +#endif + + +/*--------------------------------------------------------------------------- + Public typedefs. + ---------------------------------------------------------------------------*/ + +typedef unsigned char uch; /* code assumes unsigned bytes; these type- */ +typedef unsigned short ush; /* defs replace byte/UWORD/ULONG (which are */ +typedef unsigned long ulg; /* predefined on some systems) & match zip */ + +/* InputFn is not yet used and is likely to change: */ +#ifdef PROTO + typedef int (UZ_EXP MsgFn) (zvoid *pG, uch *buf, ulg size, int flag); + typedef int (UZ_EXP InputFn) (zvoid *pG, uch *buf, int *size, int flag); + typedef void (UZ_EXP PauseFn) (zvoid *pG, ZCONST char *prompt, int flag); + typedef int (UZ_EXP PasswdFn) (zvoid *pG, int *rcnt, char *pwbuf, + int size, ZCONST char *zfn, + ZCONST char *efn); +#ifdef WINDLL + typedef int (WINAPI ReplaceFn) (char *); + typedef void (WINAPI SoundFn) (void); +#endif +#else /* !PROTO */ + typedef int (UZ_EXP MsgFn) (); + typedef int (UZ_EXP InputFn) (); + typedef void (UZ_EXP PauseFn) (); + typedef int (UZ_EXP PasswdFn) (); +#ifdef WINDLL + typedef int (WINAPI ReplaceFn) (); + typedef void (WINAPI SoundFn) (); +#endif +#endif /* ?PROTO */ + +typedef struct _UzpBuffer { /* rxstr */ + ulg strlength; /* length of string */ + char *strptr; /* pointer to string */ +} UzpBuffer; + +typedef struct _UzpInit { + ulg structlen; /* length of the struct being passed */ + + /* GRR: can we assume that each of these is a 32-bit pointer? if not, + * does it matter? add "far" keyword to make sure? */ + MsgFn *msgfn; + InputFn *inputfn; + PauseFn *pausefn; + + void (* UZ_EXP userfn)(); /* user init function to be called after */ + /* globals constructed and initialized */ + + /* pointer to program's environment area or something? */ + /* hooks for performance testing? */ + /* hooks for extra unzip -v output? (detect CPU or other hardware?) */ + /* anything else? let me (Greg) know... */ +} UzpInit; + +/* intended to be a private struct: */ +typedef struct _ver { + uch major; /* e.g., integer 5 */ + uch minor; /* e.g., 2 */ + uch patchlevel; /* e.g., 0 */ + uch not_used; +} _version_type; + +typedef struct _UzpVer { + ulg structlen; /* length of the struct being passed */ + ulg flag; /* bit 0: is_beta bit 1: uses_zlib */ + char *betalevel; /* e.g., "g BETA" or "" */ + char *date; /* e.g., "4 Sep 95" (beta) or "4 September 1995" */ + char *zlib_version; /* e.g., "0.95" or NULL */ + _version_type unzip; + _version_type zipinfo; + _version_type os2dll; + _version_type windll; +} UzpVer; + +/* for Visual BASIC access to Windows DLLs: */ +typedef struct _UzpVer2 { + ulg structlen; /* length of the struct being passed */ + ulg flag; /* bit 0: is_beta bit 1: uses_zlib */ + char betalevel[10]; /* e.g., "g BETA" or "" */ + char date[20]; /* e.g., "4 Sep 95" (beta) or "4 September 1995" */ + char zlib_version[10]; /* e.g., "0.95" or NULL */ + _version_type unzip; + _version_type zipinfo; + _version_type os2dll; + _version_type windll; +} UzpVer2; + +typedef struct central_directory_file_header { /* CENTRAL */ + uch version_made_by[2]; + uch version_needed_to_extract[2]; + ush general_purpose_bit_flag; + ush compression_method; + ush last_mod_file_time; + ush last_mod_file_date; + ulg crc32; + ulg csize; + ulg ucsize; + ush filename_length; + ush extra_field_length; + ush file_comment_length; + ush disk_number_start; + ush internal_file_attributes; + ulg external_file_attributes; + ulg relative_offset_local_header; +} cdir_file_hdr; + + +#define UZPINIT_LEN sizeof(UzpInit) +#define UZPVER_LEN sizeof(UzpVer) +#define cbList(func) int (* UZ_EXP func)(char *filename, cdir_file_hdr *crec) + + +/*--------------------------------------------------------------------------- + Return (and exit) values of the public UnZip API functions. + ---------------------------------------------------------------------------*/ + +/* external return codes */ +#define PK_OK 0 /* no error */ +#define PK_COOL 0 /* no error */ +#define PK_GNARLY 0 /* no error */ +#define PK_WARN 1 /* warning error */ +#define PK_ERR 2 /* error in zipfile */ +#define PK_BADERR 3 /* severe error in zipfile */ +#define PK_MEM 4 /* insufficient memory (during initialization) */ +#define PK_MEM2 5 /* insufficient memory (password failure) */ +#define PK_MEM3 6 /* insufficient memory (file decompression) */ +#define PK_MEM4 7 /* insufficient memory (memory decompression) */ +#define PK_MEM5 8 /* insufficient memory (not yet used) */ +#define PK_NOZIP 9 /* zipfile not found */ +#define PK_PARAM 10 /* bad or illegal parameters specified */ +#define PK_FIND 11 /* no files found */ +#define PK_DISK 50 /* disk full */ +#define PK_EOF 51 /* unexpected EOF */ + +#define IZ_CTRLC 80 /* user hit ^C to terminate */ +#define IZ_UNSUP 81 /* no files found: all unsup. compr/encrypt. */ +#define IZ_BADPWD 82 /* no files found: all had bad password */ + +/* internal and DLL-only return codes */ +#define IZ_DIR 76 /* potential zipfile is a directory */ +#define IZ_CREATED_DIR 77 /* directory created: set time and permissions */ +#define IZ_VOL_LABEL 78 /* volume label, but can't set on hard disk */ +#define IZ_EF_TRUNC 79 /* local extra field truncated (PKZIP'd) */ + +/* return codes of password fetches (negative = user abort; positive = error) */ +#define IZ_PW_ENTERED 0 /* got some password string; use/try it */ +#define IZ_PW_CANCEL -1 /* no password available (for this entry) */ +#define IZ_PW_CANCELALL -2 /* no password, skip any further pwd. request */ +#define IZ_PW_ERROR 5 /* = PK_MEM2 : failure (no mem, no tty, ...) */ + + +/*--------------------------------------------------------------------------- + Prototypes for public UnZip API (DLL) functions. + ---------------------------------------------------------------------------*/ + +int UZ_EXP UzpMain OF((int argc, char **argv)); +int UZ_EXP UzpAltMain OF((int argc, char **argv, UzpInit *init)); +UzpVer * UZ_EXP UzpVersion OF((void)); +int UZ_EXP UzpUnzipToMemory OF((char *zip, char *file, + UzpBuffer *retstr)); +int UZ_EXP UzpFileTree OF((char *name, cbList(callBack), + char *cpInclude[], char *cpExclude[])); + +#ifdef WINDLL + void UZ_EXP UzpNoPrinting OF((int)); /* turns all messages on/off */ +#endif +void UZ_EXP UzpVersion2 OF((UzpVer2 *version)); +int UZ_EXP UzpGrep OF((char *archive, char *file, + char *pattern, int cmd, int SkipBin)); +int UZ_EXP UzpValidate OF((char *archive, int AllCodes)); + + +/* default I/O functions (can be swapped out via UzpAltMain() entry point): */ + +int UZ_EXP UzpMessagePrnt OF((zvoid *pG, uch *buf, ulg size, int flag)); +int UZ_EXP UzpMessageNull OF((zvoid *pG, uch *buf, ulg size, int flag)); +int UZ_EXP UzpInput OF((zvoid *pG, uch *buf, int *size, int flag)); +void UZ_EXP UzpMorePause OF((zvoid *pG, ZCONST char *prompt, int flag)); +int UZ_EXP UzpPassword OF((zvoid *pG, int *rcnt, char *pwbuf, + int size, ZCONST char *zfn, + ZCONST char *efn)); + + +/*--------------------------------------------------------------------------- + Remaining private stuff for UnZip compilation. + ---------------------------------------------------------------------------*/ +/* +#ifdef UNZIP_INTERNAL +# include "unzpriv.h" +#endif +*/ + +#endif /* !__unzip_h */ diff --git a/z_zip.h b/z_zip.h new file mode 100644 index 0000000..36a11a6 --- /dev/null +++ b/z_zip.h @@ -0,0 +1,15 @@ +/* This is a dummy zip.h to allow the source files shared with Zip (crypt.c, + crc32.c, crctab.c, ttyio.c) to compile for UnZip. */ + +#ifndef __zip_h /* don't include more than once */ +#define __zip_h + +#define UNZIP_INTERNAL +#include "z_unzip.h" + +#define local static + +#define ZE_MEM PK_MEM +#define ziperr(c, h) return + +#endif /* !__zip_h */ diff --git a/zbotcheck.c b/zbotcheck.c new file mode 100644 index 0000000..5a0fb3a --- /dev/null +++ b/zbotcheck.c @@ -0,0 +1,102 @@ +/*============================================================================ +ZbotCheck v1.01 for Quake 2 by Matt "WhiteFang" Ayres (matt@lithium.com) + +This is provided for mod authors to implement Zbot detection, nothing more. +The code has so far proven to be reliable at detecting Zbot auto-aim clients +(cheaters). However, no guarantees of any kind are made. This is provided +as-is. You must be familiar with Quake 2 mod coding to make use of this. + +In g_local.h, add to struct client_respawn_t: + + short angles[2][2]; + int tog; + int jitter; + float jitter_time; + float jitter_last; + +Next, in p_client.c, add a simple forward declaration: + + qboolean ZbotCheck(edict_t *ent, usercmd_t *ucmd); + +Then in p_client.c, anywhere in the ClientThink function, call the +ZbotCheck function. Pass it the same parameters you get from ClientThink. +It will return true if the client is using a Zbot. Simple example: + + if(ZbotCheck(ent, ucmd)) + gi.bprintf(PRINT_HIGH, ">>> Zbot detected: %s\n", + ent->client->pers.netname); + +From here you can do as you please with the cheater. ZbotCheck will only +return true once, following returns will be false. +============================================================================*/ + +#include "g_local.h" + +#define ZBOT_JITTERMAX 4 +#define ZBOT_JITTERTIME 10 +#define ZBOT_JITTERMOVE 500 + +//Set this player as a zbot, and mess with them +//This is to help solve problem where you can't actually kick them off server +void I_AM_A_ZBOT(edict_t *ent) +{ + //stuffcmd(ent, "disconnect\n"); // kick out zbots. + + //Really mess them up + ent->movetype = MOVETYPE_NOCLIP; + ent->client->pers.feign = 1; + ent->client->pers.i_am_a_bot = 1; + + VectorClear (ent->avelocity); + ent->takedamage = DAMAGE_YES; + ent->s.modelindex2 = 0; // remove linked weapon model + ent->s.angles[0] = 0; + ent->s.angles[2] = 0; + ent->s.sound = 0; + ent->client->weapon_sound = 0; + ent->maxs[2] = -8; + ent->viewheight = 0; + ent->flags |= FL_NO_KNOCKBACK; + // stop running/footsteps + VectorClear (ent->velocity); + + //dont keep firing/no model to fire/model removed from player view + ent->client->pers.lastweapon = ent->client->pers.weapon; + ent->client->pers.weapon = NULL;// needed? + ent->client->ps.gunindex = 0; + +} + +qboolean ZbotCheck(edict_t *ent, usercmd_t *ucmd) { + int tog0, tog1; + client_respawn_t *resp = &ent->client->resp; + + tog0 = resp->tog; + resp->tog ^= 1; + tog1 = resp->tog; + + if(ucmd->angles[0] == resp->angles[tog1][0] && + ucmd->angles[1] == resp->angles[tog1][1] && + ucmd->angles[0] != resp->angles[tog0][0] && + ucmd->angles[1] != resp->angles[tog0][1] && + abs(ucmd->angles[0] - resp->angles[tog0][0]) + + abs(ucmd->angles[1] - resp->angles[tog0][1]) >= ZBOT_JITTERMOVE) { + + if(level.time <= resp->jitter_last + 0.1) { + if(!resp->jitter) + resp->jitter_time = level.time; + if(++resp->jitter == ZBOT_JITTERMAX) + return true; + } + + resp->jitter_last = level.time; + } + + resp->angles[tog1][0] = ucmd->angles[0]; + resp->angles[tog1][1] = ucmd->angles[1]; + + if(level.time > resp->jitter_time + ZBOT_JITTERTIME) + resp->jitter = 0; + + return false; +} diff --git a/zip32.dll b/zip32.dll new file mode 100644 index 0000000..c3ef431 Binary files /dev/null and b/zip32.dll differ