as released 1999-02-12

This commit is contained in:
archive 2023-08-12 08:39:01 +00:00
commit 4f4f26e7a0
42 changed files with 38118 additions and 0 deletions

251
CHANGES Normal file
View file

@ -0,0 +1,251 @@
CHANGES FROM ACTION 1.1b2 TO ACTION 1.5
General Weapon Changes
o Ducking alone (without lasersight) now improves aim. Previously, it only
helped if you had a lasersight too (we believe this was a bug). See "Spread
Modifiers" heading for more specific information.
o Guns now center their fire around your crosshair, rather than an invisible
spot down/right of your crosshair.
o Players have smaller head regions. Any shot that would have hit the head in
the old system that doesn't now will instead hit the chest.
o Added support for Akimbo and Sniper Rifle bullet shell ejection, and support
for left handed usage with shell ejection turned on.
Specific Weapon Changes
o M4 Assault Rifle is now less accurate, the original accuracy was due to a bug
in the selection of which random function to call that determined spread. The
same bug caused it to fire way to the right, and is also gone.
o M3 Super 90 Assault Shotgun now does slightly more damage. Reloads slightly
faster.
o Sniper Rifle doesn't do the re-chambering sequence when leaving 6X
magnification mode. Reloads slightly faster. Lens-changing and firing
behavior improved slightly in some ways.
o Combat Knife remembers what mode it was last in (throwing/stabbing). Also has
improved animation sequence for putting away a knife prepared for throwing.
Doesn't have the old bug where knife becomes unusable for several seconds while
playing random animation frames. Your last knife now isn't stopped by vests
incorrectly. Knife slashing damage has been changed.
o M26 Fragmentation Grenade now drops to the ground at your feet if the pin is
pulled and user switches to another weapon or begins bandaging. Slight
improvement in animation sequence for it dropping from view.
o In teamplay, you now start with a full pistol plus one extra clip
(previously you got no extra clips).
Spread Modifiers:
This section gives the specific details on all the spread modifiers in the
game. These only apply to the weapons that fire bullets.
(without lasersight)
Standing: base
Walking: 2x as much spread
Running: 6x as much spread
Crouching: 35% reduction in spread over standing.
(laser, compared to standing from above)
Standing: 30% reduction in spread over standing w/o laser.
Walking: base (standing from above)
Running: base
Crouching: same as crouching w/o the laser
Burst mode (or semi-auto mode with the pistol) gives an additional 30% spread
reduction to any of the above.
The Sniper Rifle when zoomed (any magnification) is always 100% accurate,
regardless of any of the above.
Item Changes
o Lasersight improves accuracy while standing still and not ducking (makes you
almost as accurate as crouching). Lasersight provides no benefit to a user who
is also ducking. (See "Spread Modifiers".) Lasersight's red dot is
translucent and follows your crosshair more closely.
o Silencer no longer suppresses flashes from Akimbo pistols.
o Bandolier correctly cleans up after itself when dropped. If server
operator sets "ir" to 1 then the new command "irvision" is available which
toggles on/off infra-red vision while wearing the bandolier. The new
"tgren" variable can be set by the server operator to a number, and that
many grenades will be given to each player who selects the bandolier in
teamplay (default is 0). The bandolier now allows you to carry one extra
weapon in teamplay (the normal max carry is now 1 in teamplay, the
previous behavior was a bug).
Existing Command Changes
o "use special" now loops through all available special weapons, ie: if you
have two it will toggle between them.
o "drop special" will discard only one special weapon at a time. It defaults
to the one you are currently using and then goes through your inventory in this
order: Sniper Rifle, Handcannon, M3 Super 90 Assault Shotgun, MP5/10
Submachinegun, and finally M4 Assault Rifle. Switches to single pistol if
currently using Akimbo pistols, this is also the way it worked previously.
o "weapon": Knives now remember their last mode correctly. Live players no
longer hear a "click" when a nearby observer changes their weapon mode.
o "team": Players can now use the "team none" to leave the teams and become a
spectator again in teamplay.
Other Changes/Improvements/New Features
o After dying in teamplay, you remain at the spot you died as an observer,
instead of being moved to a spawn point.
o When a player enters a server, the default team to join is the one that
currently has less players.
o "sv_roundtimelimit" server variable is now "roundtimelimit", for consistency
with other server variable names. The "roundtimelimit" feature now uses this
method for determining who won: which team has the most players, if the same,
then which team has the most total health.
o All items (such as ammo) can be dropped, see the README file for exact item
names.
o The new code appears to use a somewhat less CPU when running a server than
1.1b2 did.
o Jumpkick code improved, jumpkicks are more predictable and reliable,
less randomness.
o Location of grenade and item in right-side HUD reversed for a more logical
arrangement.
o New "id" command allows a player to turn off player identification if he
doesn't want it.
o Players can become observers in DM using the standard Q2 "spectator" variable
(spectator 0 or spectator 1).
o Messages from dead players are now visible inbetween end-of-round and the
start of the next round, for end of round chatting.
o MOTD: Server operator can now control how long the MOTD stays up by using the
"motd_time" variable (see README for more info). Much more info about the
server is now in the MOTD. If a "motd.txt" file is present in your action
directory, it will be appended to the standard MOTD. The new "motd" command
allows a player to bring up the MOTD at any time.
o Teams-by-skin and teams-by-model now work in DM (other teamplay features also
work in conjunction with DM teams: radios, partners, etc...)
o New "unpartner" command allows you to break a partnership.
o New unstick system. The new system is unobtrusive, there is no "unstick"
command and you do not get moved around in any way. Players remain in the
"transparent" state where they are able to walk through fellow "transparent"
players. After the "lights, camera" sequence finishes, players are removed
from the "transparent" state as soon as they are free of other team members.
Players can be shot and affected normally by all weapons/items regardless of
"transparent" state.
o When in center-handed mode, you now get an icon at the bottom middle of the
screen of which weapon you are currently using.
o Server operator can use the new "radiolog" variable to turn on logging
of all radio messages to the console.
o Dead bodies are now removed at the beginning of each teamplay round, this
should allow for more strategy (you can tell who's down) and also remove some
lag (less entities lying around).
o New server variables: "weapons" and "items". These control the maximum
number of special weapons and items that each player can carry. "items" always
defaults to 1. "weapons" defaults to 2 in teamplay, 1 in DM. Special-weapon
carrying is handled more cleanly in teamplay now, a player will always be able
to carry up to 2 special weapons in teamplay, not just his main weapon plus the
first other one he picks up.
o New server variable "knifelimit" allows the server operator to control how
many thrown knives can be lying around on the map at any given time. Default is
40. This should be lowered if you are having overflow or lag problems when
players are using lots of knives.
o The "allweapon" and "allitem" server variables can be set to 1 in DM or
teamplay to give all weapons and all items to each player at spawn. If used in
teamplay, normal weapon/item menus etc will not be presented. Defaults on both
are of course 0.
o The new "choose" command allows you to select your special weapon or item in
teamplay, without going through the menus.
o IP banning and flood protection are now supported (see README).
o The "give" (cheats) command has been improved to work better in the Action
environment.
o New "noscore" server variable allows a server operator to disable
individual scores in teamplay.
o New "nohud" server variable allows a server operator to disable the
standard HUD on his server to record better-looking special demos.
o New variable substitutions allow you to tell your teammates your current
weapon, item, ammo, and health, as well as which teammates are near you
(see README for a list).
Other Bugs Gone
o Various death message typos and grammar errors corrected.
o Long model/skin names work in action.ini.
o "###" lines are not required at the end-of-sections in action.ini.
o Scoreboards: The main scoreboard now looks decent in 320x240. Scoreboard no
longer gets stuck on your screen when chase-camming. Players' names are no
longer truncated unnecessarily. More items on the scoreboard are updated in
realtime than before.
o The list of maps in the MOTD is no longer overwritten by the "Hit tab for
team selection menu" message.
o M4/sniper rifle can no longer crash the server when shooting two players that
overlap.
o Random crashes on certain maps (ie cliff, teamjungle, ...) gone.
o Your frags no longer appear as the frags of the person you're observing to
GameSpy, CLQ, etc.
o The item selection screen now always goes away properly after choosing your
item.
o Players hanging in midair at the spawn points, unkillable, will no longer
occur.
o Backdoor commands (testshow, forceend) gone.
o Picking up a gun in teamplay when you start with dual pistols no longer
lowers your total ammo.
o You can establish a new radio partnership when your old partner dies now.
o Dropping and picking back up a handcannon or shotgun no longer results in
ammo loss.
Features No Longer Present
o "noguns" is not currently supported, we have plans for more flexible "weapons
banning" support in the future.

121
Makefile Normal file
View file

@ -0,0 +1,121 @@
#
# Action makefile
# Intended for gcc/Linux, may need modifying for other platforms
#
ARCH=i386
CC=gcc
BASE_CFLAGS=-Dstricmp=strcasecmp
#use these cflags to optimize it
#CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \
# -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \
# -malign-jumps=2 -malign-functions=2
#use these when debugging
CFLAGS=$(BASE_CFLAGS) -g
LDFLAGS=-ldl -lm
SHLIBEXT=so
SHLIBCFLAGS=-fPIC
SHLIBLDFLAGS=-shared
DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $<
#############################################################################
# SETUP AND BUILD
# GAME
#############################################################################
.c.o:
$(DO_CC)
GAME_OBJS = \
g_ai.o g_cmds.o g_combat.o g_func.o g_items.o g_main.o g_misc.o \
g_monster.o g_phys.o g_save.o g_spawn.o g_svcmds.o \
g_target.o g_trigger.o g_turret.o g_utils.o g_weapon.o g_chase.o \
p_client.o p_hud.o p_trail.o p_view.o p_weapon.o q_shared.o \
m_move.o a_team.o a_game.o a_items.o a_cmds.o a_radio.o a_menu.o
game$(ARCH).$(SHLIBEXT) : $(GAME_OBJS)
$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(GAME_OBJS)
#############################################################################
# MISC
#############################################################################
clean:
-rm -f $(GAME_OBJS)
depend:
gcc -MM $(GAME_OBJS:.o=.c)
install:
cp gamei386.so ../quake2/action
#
# From "make depend"
#
g_ai.o: g_ai.c g_local.h q_shared.h game.h a_team.h a_game.h a_menu.h \
a_radio.h
g_cmds.o: g_cmds.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h m_player.h
g_combat.o: g_combat.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_func.o: g_func.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_items.o: g_items.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_main.o: g_main.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_misc.o: g_misc.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_monster.o: g_monster.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_phys.o: g_phys.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_save.o: g_save.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_spawn.o: g_spawn.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_svcmds.o: g_svcmds.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_target.o: g_target.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_trigger.o: g_trigger.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_turret.o: g_turret.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_utils.o: g_utils.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_weapon.o: g_weapon.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
g_chase.o: g_chase.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
p_client.o: p_client.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h m_player.h
p_hud.o: p_hud.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
p_trail.o: p_trail.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
p_view.o: p_view.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h m_player.h
p_weapon.o: p_weapon.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h m_player.h
q_shared.o: q_shared.c q_shared.h
m_move.o: m_move.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
a_team.o: a_team.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
a_game.o: a_game.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
a_items.o: a_items.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
a_cmds.o: a_cmds.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
a_radio.o: a_radio.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h
a_menu.o: a_menu.c g_local.h q_shared.h game.h a_team.h a_game.h \
a_menu.h a_radio.h

223
README Normal file
View file

@ -0,0 +1,223 @@
SERVER/PLAYER DOCUMENTATION FOR ACTION 1.5
ACTION-SPECIFIC SERVER VARIABLES
actionmaps: set to 1 if you wish to use the map rotation as defined in your
"action.ini" file (default = 1).
teamplay: whether the game is teamplay (1) or regular DM (0) (default = 0).
roundlimit: the number of wins by one team before the map is ended
(default = 0 [no limit]).
roundtimelimit: the number of minutes a round can go on before a winner is
declared (default = 0 [no limit]).
motd_time: the number of seconds that the initial MOTD should remain on the
player's screen. This number is rounded up to the nearest 2-second interval
(default = 2).
weapons: the maximum number of "unique weapons" a player can carry (the
bandolier adds 1 to a player's max carry) (default = 1).
items: the number of "special items" a player can carry (default = 1).
ir: whether or not IR vision goggles are enabled for use with the
bandolier (default = 1 [on]).
radiolog: whether or not all radio messages sent are printed to the
console (default = 0 [no]).
bholelimit: the number of bulletholes in walls/etc that should be allowed to be
in the game at one time (default = 0 [don't use bulletholes, for a faster
Internet game]).
splatlimit: the number of bloodsplats on the ground/etc that should be allowed
to be in the game at one time (default = 0 [don't use bloodsplats, for a faster
Internet game]).
shelloff: turns off the dropping of expended shells from your gun
(default = 1 [turn off shells, for a faster Internet game]).
knifelimit: the number of throwing knives that can be lying around on the map
at any given time (default = 40).
allweapon: gives all the weapons to each player in teamplay/DM
(default = 0).
allitem: gives all the items to each player in teamplay/DM
(default = 0).
tgren: sets the number of grenades that come with the bandolier in
teamplay (default = 0).
noscore: if set to 1, individual scores are not in effect for teamplay,
the only scores visible will be team wins and total frags (default = 0).
nohud: if set to 1, the standard HUD (health, ammo, etc) is disabled for
all players. This can allow you to record better-looking demos of staged
scenes (default = 0).
ACTION-SPECIFIC PLAYER COMMANDS
reload: reloads current weapon, if applicable to the weapon you're currently
using. Can be repeated quickly for "fast reload" on shotgun and sniper rifle.
weapon: toggles mode on current weapon. For single pistol, MP5, and M4, this
toggles between semi-auto/burst and auto mode. For the sniper rifle, this
changes the lens. For knives, this changes between slashing and throwing. For
the grenade, this changes how far you are going to throw. Not applicable to
any other weapon.
opendoor: opens a door in front of you.
bandage: applies bandages to stop bleeding and cure any limb damage.
team: displays the team you're on, or changes teams (if used with the team
number to switch to as an argument, or "none" to leave your current team and
become a spectator)
id: toggles identification of players on or off (default is on). only works on
teammates, or when you're an observer, but this can be used to turn it off if
you don't want it.
irvision: toggles IR vision goggles w/ the bandolier (assuming IR vision
is enabled on the server).
motd: brings up the MOTD (message of the day) again.
spectator: can be set to 0 or 1 ("spectator 0", "spectator 1") to toggle being
a spectator in DM games.
choose: chooses a particular item or weapon as your item or weapon, without
going through the menus, in teamplay (ie: "choose mp5/10 submachinegun" or
"choose lasersight").
(The remaining commands are all radio/partner-related...)
radio: sends a message on the radio, in whatever your default mode is (set with
the "channel" command, defaults to team). Useable in teamplay, teams-by-skin
DM, or teams-by-model DM. Valid messages are: 1 ("one"), 2 ("two"), 3
("three"), 4 ("four"), 5 ("five"), 6 ("six"), 7 ("seven"), 8 ("eight"), 9
("nine"), 10 ("ten"), back ("back"), cover ("cover"), down ("down"), enemyd
("enemy down"), enemys ("enemy spotted"), forward ("forward"), go ("go"),
im_hit ("I'm hit"), left ("left"), reportin ("reporting in"), right ("right"),
taking_f ("taking fire, requesting assistance"), teamdown ("teammate down"),
treport ("team, report in"), up ("up").
radiogender: used without an argument, displays your current radio gender. If
used with "male" or "female", sets your radio gender to that gender. This
changes the voice on your radio messages. Default is male.
radio_power: toggles your radio power on or off (default is on). If your radio
power is off, you won't receive any radio messages, but also can't send any.
radiopartner: sends a radio message to your partner, see the "radio" command
for the valid messages.
radioteam: sends a radio message to your team, see the "radio" command for the
valid messages.
channel: toggles your default radio destination (see the "radio" command)
between team and partner. Default is team.
say_partner: sends a message to your partner, in much the same way as
"say_team" sends a message to your team.
partner: attempts to establish a partnership. You must have the person you
want to partner with's name displayed on your HUD, then use the "partner"
command to send a partnership request to him. The other player will have the
chance to accept (by doing the same thing to you), or denying by ignoring you
or using the "deny" command (see below).
deny: deny a partnership request (see "partner" command above).
unpartner: breaks your current partnership.
WEAPON/ITEM NAMES
These are the exact names of all the weapons/items in the game, for use with
commands like "drop", "choose", "give", etc:
MK23 Pistol
M3 Super 90 Assault Shotgun
MP5/10 Submachinegun
Handcannon
Sniper Rifle
M4 Assault Rifle
Dual MK23 Pistols
Combat Knife
Pistol Clip
12 Gauge Shells
Machinegun Magazine
M4 Clip
AP Sniper Ammo
M26 Fragmentation Grenade
Kevlar Vest
Lasersight
Stealth Slippers
Silencer
Bandolier
SUBSTITUTIONS IN SAY COMMANDS
The following substitution variables can be used with say, say_team, or
say_partner messages, and they will be replaced with the specified text:
%W = your current weapon
%I = your current item (a random one, if you have more than one)
%H = your current health
%A = your current ammo for your current weapon (in-gun and in-inventory)
%T = names of teammates that are near you and in your line of sight
OTHER CONFIGURATION
IP banning is supported in the standard Q2 3.20 form. See below for full
details from id's documentation.
Flood protection is supported using the standard Q2 3.20 variables,
"flood_msgs" (default 4), "flood_persecond" (default 4), and "flood_waitdelay"
(default 10).
DM teams-by-model, teams-by-skin, friendly fire, etc are supported using the
standard Q2 "dmflags" values.
action/action.ini is the configuration file for map rotation and teamplay team
name/model/skin setup. See the example for information on the format.
action/motd.txt, if present, will be appended to the server MOTD.
IP BANNING
Commands: addip, removeip, listip, writeip
Server variables: filterban
You can add or remove addresses from the IP filter list with the commands
"addip <ip>" and "removeip <ip>". The IP address is specified in numeric dot
format, and any unspecified digits will match any value, so you can specify an
entire class C network with "addip 240.200.100", for example. "removeip" will
only remove an address specified in the exact same way. The "listip" command
will print the current list of filters. The "writeip" command will dump the
current filters to a config file, "listip.cfg" in your Action directory. You
should add a line in your normal server.cfg that does an "exec listip.cfg" to
load this file by default. IP addresses in the filter list will only be
prohibited from connecting if the "filterban" variable is set to 1 (this is the
default).
REPORTING BUGS OR MAKING COMMENTS
As of this writing, you can contact the authors of the Action 1.5 server
code (Zucchini and Fireblade) at spikard@u.washington.edu and
ucs_brf@shsu.edu, respectively. The Action Quake 2 website is at
http://action.telefragged.com/ and has a message board where discussions
about Action Quake 2 take place.

669
a_cmds.c Normal file
View file

@ -0,0 +1,669 @@
// zucc
// File for our new commands.
// laser sight patch, by Geza Beladi
#include "g_local.h"
extern void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result);
/*----------------------------------------
SP_LaserSight
Create/remove the laser sight entity
-----------------------------------------*/
#define lss self->lasersight
void SP_LaserSight(edict_t *self, gitem_t *item ) {
vec3_t start,forward,right,end;
int laser_on = 0;
gitem_t *temp_item;
// gi.cprintf(self, PRINT_HIGH, "Calling lasersight function have_laser %d.\n", self->client->have_laser);
temp_item = FindItem(LASER_NAME);
if ( !(self->client->pers.inventory[ITEM_INDEX(temp_item)]) )
{
if ( lss ) // laser is on
{
G_FreeEdict(lss);
lss = NULL;
}
// gi.cprintf(self, PRINT_HIGH, "didn't have laser sight.\n");
return;
}
// zucc code to make it be used with the right weapons
// gi.cprintf(self, PRINT_HIGH, "curr_weap = %d.\n", self->client->curr_weap);
switch ( self->client->curr_weap )
{
case MK23_NUM:
case MP5_NUM:
case M4_NUM:
{
laser_on = 1;
break;
}
default:
{
laser_on = 0;
break;
}
}
// gi.cprintf(self, PRINT_HIGH, "laser_on is %d.\n", laser_on);
// laser is on but we want it off
if ( lss && !laser_on ) {
// gi.cprintf(self, PRINT_HIGH, "trying to free the laser sight\n");
G_FreeEdict(lss);
lss = NULL;
//gi.bprintf (PRINT_HIGH, "lasersight off.");
return;
}
// gi.cprintf(self, PRINT_HIGH, "laser wasn't lss is %p.\n", lss);
// off and we want it to stay that way
if ( !laser_on )
return;
//gi.bprintf (PRINT_HIGH, "lasersight on.");
AngleVectors (self->client->v_angle, forward, right, NULL);
VectorSet(end,100 , 0, 0);
G_ProjectSource (self->s.origin, end, forward, right, start);
lss = G_Spawn ();
lss->owner = self;
lss->movetype = MOVETYPE_NOCLIP;
lss->solid = SOLID_NOT;
lss->classname = "lasersight";
lss->s.modelindex = gi.modelindex ("sprites/lsight.sp2");
lss->s.renderfx = RF_TRANSLUCENT;
lss->think = LaserSightThink;
lss->nextthink = level.time + 0.01;
}
/*---------------------------------------------
LaserSightThink
Updates the sights position, angle, and shape
is the lasersight entity
---------------------------------------------*/
void LaserSightThink (edict_t *self)
{
vec3_t start,end,endp,offset;
vec3_t forward,right,up;
trace_t tr;
AngleVectors (self->owner->client->v_angle, forward, right, up);
if ( self->owner->lasersight != self )
{
self->think = G_FreeEdict;
}
VectorSet(offset,24 , 0, self->owner->viewheight);
P_ProjectSource (self->owner->client, self->owner->s.origin, offset, forward, right, start);
VectorMA(start,8192,forward,end);
tr = do_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);
}
vectoangles(tr.plane.normal,self->s.angles);
VectorCopy(tr.endpos,self->s.origin);
gi.linkentity (self);
self->nextthink = level.time + 0.1;
}
//+BD ENTIRE CODE BLOCK NEW
// Cmd_Reload_f()
// Handles weapon reload requests
void Cmd_Reload_f (edict_t *ent)
{
// int rds_left; //+BD - Variable to handle rounds left
//+BD - If the player is dead, don't bother
if(ent->deadflag == DEAD_DEAD)
{
//gi.centerprintf(ent, "I know you're a hard ass,\nBUT YOU'RE FUCKING DEAD!!\n");
return;
}
if(ent->client->weaponstate == WEAPON_BANDAGING
|| ent->client->bandaging == 1
|| ent->client->bandage_stopped == 1
|| ent->client->weaponstate == WEAPON_ACTIVATING
|| ent->client->weaponstate == WEAPON_DROPPING )
{
return;
}
//First, grab the current magazine max count...
if ( ( ent->client->curr_weap == MK23_NUM )
|| ( ent->client->curr_weap == MP5_NUM )
|| ( ent->client->curr_weap == M4_NUM )
|| ( ent->client->curr_weap == M3_NUM )
|| ( ent->client->curr_weap == HC_NUM )
|| ( ent->client->curr_weap == SNIPER_NUM )
|| ( ent->client->curr_weap == DUAL_NUM ) )
{
}
else //We should never get here, but...
//BD 5/26 - Actually we get here quite often right now. Just exit for weaps that we
// don't want reloaded or that never reload (grenades)
{
//gi.centerprintf(ent,"Where'd you train?\nYou can't reload that!\n");
return;
}
if(ent->client->pers.inventory[ent->client->ammo_index])
{
/*if((ent->client->weaponstate != WEAPON_END_MAG) && (ent->client->pers.inventory[ent->client->ammo_index] < rds_left))
{
gi.centerprintf(ent,"Buy a clue-\nYou're on your last magazine!\n");
}
else*/
//Set the weaponstate...
if ( ent->client->curr_weap == M3_NUM )
{
if (ent->client->shot_rds >= ent->client->shot_max)
{
return;
}
// already in the process of reloading!
if ( ent->client->weaponstate == WEAPON_RELOADING && (ent->client->shot_rds < (ent->client->shot_max -1)) && !(ent->client->fast_reload) && ((ent->client->pers.inventory[ent->client->ammo_index] -1) > 0 ))
{
ent->client->fast_reload = 1;
(ent->client->pers.inventory[ent->client->ammo_index])--;
}
}
if ( ent->client->curr_weap == HC_NUM )
{
if (ent->client->cannon_rds >= ent->client->cannon_max)
{
return;
}
if (!(ent->client->pers.inventory[ent->client->ammo_index] >= 2))
return;
}
if ( ent->client->curr_weap == SNIPER_NUM )
{
if (ent->client->sniper_rds >= ent->client->sniper_max)
{
return;
}
// already in the process of reloading!
if ( ent->client->weaponstate == WEAPON_RELOADING && (ent->client->sniper_rds < (ent->client->sniper_max -1)) && !(ent->client->fast_reload) && ((ent->client->pers.inventory[ent->client->ammo_index] -1) > 0 ))
{
ent->client->fast_reload = 1;
(ent->client->pers.inventory[ent->client->ammo_index])--;
}
ent->client->ps.fov = 90;
if ( ent->client->pers.weapon )
ent->client->ps.gunindex = gi.modelindex( ent->client->pers.weapon->view_model );
}
if ( ent->client->curr_weap == DUAL_NUM )
{
if (!(ent->client->pers.inventory[ent->client->ammo_index] >= 2))
return;
}
ent->client->weaponstate = WEAPON_RELOADING;
//(ent->client->pers.inventory[ent->client->ammo_index])--;
}
else
gi.cprintf (ent, PRINT_HIGH, "Out of ammo\n");
//gi.centerprintf(ent,"Pull your head out-\nYou've got NO AMMO!\n");
}
//+BD END CODE BLOCK
// function to change the firing mode of weapons (when appropriate)
void Cmd_Weapon_f ( edict_t *ent )
{
int dead;
dead = (ent->solid == SOLID_NOT || ent->deadflag == DEAD_DEAD);
if ( ent->client->bandaging || ent->client->bandage_stopped )
{
gi.cprintf(ent, PRINT_HIGH, "You can't mess with your weapon while bandaging!\n");
return;
}
if ( ent->client->curr_weap == MK23_NUM )
{
if (!dead)
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/click.wav"), 1, ATTN_NORM, 0);
ent->client->resp.mk23_mode = !(ent->client->resp.mk23_mode);
if ( ent->client->resp.mk23_mode )
gi.cprintf (ent, PRINT_HIGH, "MK23 Pistol set for semi-automatic action\n");
else
gi.cprintf (ent, PRINT_HIGH, "MK23 Pistol set for automatic action\n");
}
if ( ent->client->curr_weap == MP5_NUM )
{
if (!dead)
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/click.wav"), 1, ATTN_NORM, 0);
ent->client->resp.mp5_mode = !(ent->client->resp.mp5_mode);
if ( ent->client->resp.mp5_mode )
gi.cprintf (ent, PRINT_HIGH, "MP5 set to 3 Round Burst mode\n");
else
gi.cprintf (ent, PRINT_HIGH, "MP5 set to Full Automatic mode\n");
}
if ( ent->client->curr_weap == M4_NUM )
{
if (!dead)
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/click.wav"), 1, ATTN_NORM, 0);
ent->client->resp.m4_mode = !(ent->client->resp.m4_mode);
if ( ent->client->resp.m4_mode )
gi.cprintf (ent, PRINT_HIGH, "M4 set to 3 Round Burst mode\n");
else
gi.cprintf (ent, PRINT_HIGH, "M4 set to Full Automatic mode\n");
}
if ( ent->client->curr_weap == SNIPER_NUM )
{
if (dead)
return;
if ( ent->client->resp.sniper_mode == SNIPER_1X )
{
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/lensflik.wav"), 1, ATTN_NORM, 0);
ent->client->weaponstate = WEAPON_BUSY;
ent->client->resp.sniper_mode = SNIPER_2X;
ent->client->desired_fov = 45;
ent->client->idle_weapon = 6; // 6 frames of idleness
ent->client->ps.gunframe = 22;
}
else if ( ent->client->resp.sniper_mode == SNIPER_2X )
{
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/lensflik.wav"), 1, ATTN_NORM, 0);
ent->client->resp.sniper_mode = SNIPER_4X;
ent->client->desired_fov = 20;
}
else if ( ent->client->resp.sniper_mode == SNIPER_4X )
{
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/lensflik.wav"), 1, ATTN_NORM, 0);
ent->client->resp.sniper_mode = SNIPER_6X;
ent->client->desired_fov = 10;
}
else
{
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/lensflik.wav"), 1, ATTN_NORM, 0);
ent->client->resp.sniper_mode = SNIPER_1X;
ent->client->desired_fov = 90;
if ( ent->client->pers.weapon )
ent->client->ps.gunindex = gi.modelindex( ent->client->pers.weapon->view_model );
}
}
if ( ent->client->curr_weap == KNIFE_NUM )
{
if (dead)
return;
if ( ent->client->weaponstate == WEAPON_READY )
{
ent->client->resp.knife_mode = !(ent->client->resp.knife_mode);
ent->client->weaponstate = WEAPON_ACTIVATING;
if ( ent->client->resp.knife_mode )
ent->client->ps.gunframe = 0;
else
ent->client->ps.gunframe = 106;
}
}
if ( ent->client->curr_weap == GRENADE_NUM )
{
if ( ent->client->resp.grenade_mode == 0 )
{
gi.cprintf (ent, PRINT_HIGH, "Prepared to make a medium range throw\n");
ent->client->resp.grenade_mode = 1;
}
else if ( ent->client->resp.grenade_mode == 1 )
{
gi.cprintf (ent, PRINT_HIGH, "Prepared to make a long range throw\n");
ent->client->resp.grenade_mode = 2;
}
else
{
gi.cprintf (ent, PRINT_HIGH, "Prepared to make a short range throw\n");
ent->client->resp.grenade_mode = 0;
}
}
}
// sets variable to toggle nearby door status
void Cmd_OpenDoor_f (edict_t *ent )
{
ent->client->doortoggle = 1;
return;
}
void Cmd_Bandage_f ( edict_t *ent )
{
gitem_t *item;
if ( (ent->client->weaponstate == WEAPON_READY || ent->client->weaponstate == WEAPON_END_MAG )
&& (ent->client->bleeding != 0 || ent->client->leg_damage != 0 )
&& ent->client->bandaging != 1 )
{
// zucc - check if they have a primed grenade
if ( ent->client->curr_weap == GRENADE_NUM
&& ( ent->client->ps.gunframe >= GRENADE_IDLE_FIRST
&& ent->client->ps.gunframe <= GRENADE_IDLE_LAST ) )
{
ent->client->ps.gunframe = 0;
fire_grenade2 (ent, ent->s.origin, tv(0,0,0), GRENADE_DAMRAD, 0, 2, GRENADE_DAMRAD*2, false);
item = FindItem(GRENADE_NAME);
ent->client->pers.inventory[ITEM_INDEX(item)]--;
if ( ent->client->pers.inventory[ITEM_INDEX(item)] <= 0 )
{
ent->client->newweapon = FindItem( MK23_NAME );
}
}
ent->client->bandaging = 1;
ent->client->resp.sniper_mode = SNIPER_1X;
ent->client->ps.fov = 90;
ent->client->desired_fov = 90;
if ( ent->client->pers.weapon )
ent->client->ps.gunindex = gi.modelindex( ent->client->pers.weapon->view_model );
}
else if ( ent->client->bandaging == 1 )
gi.cprintf (ent, PRINT_HIGH, "Already bandaging\n");
//FIREBLADE 12/26/98 - fix inappropriate message
else if (ent->client->bleeding == 0 && ent->client->leg_damage == 0)
gi.cprintf (ent, PRINT_HIGH, "No need to bandage\n");
else
gi.cprintf(ent, PRINT_HIGH, "Can't bandage now\n");
//FIREBLADE
}
// function called in generic_weapon function that does the bandaging
void Bandage( edict_t* ent )
{
ent->client->leg_noise = 0;
ent->client->leg_damage = 0;
ent->client->leghits = 0;
ent->client->bleeding = 0;
ent->client->bleed_remain = 0;
// ent->client->bleedcount = 0;
// ent->client->bleeddelay = 0;
ent->client->bandaging = 0;
ent->client->leg_dam_count = 0;
ent->client->attacker = NULL;
ent->client->bandage_stopped = 1;
ent->client->idle_weapon = BANDAGE_TIME;
}
void Cmd_ID_f (edict_t *ent )
{
if (!ent->client->resp.id) {
gi.cprintf(ent, PRINT_HIGH, "Disabling player identification display.\n");
ent->client->resp.id = 1;
} else {
gi.cprintf(ent, PRINT_HIGH, "Activating player identification display.\n");
ent->client->resp.id = 0;
}
return;
}
static void loc_buildboxpoints(vec3_t p[8], vec3_t org, vec3_t mins, vec3_t maxs)
{
VectorAdd(org, mins, p[0]);
VectorCopy(p[0], p[1]);
p[1][0] -= mins[0];
VectorCopy(p[0], p[2]);
p[2][1] -= mins[1];
VectorCopy(p[0], p[3]);
p[3][0] -= mins[0];
p[3][1] -= mins[1];
VectorAdd(org, maxs, p[4]);
VectorCopy(p[4], p[5]);
p[5][0] -= maxs[0];
VectorCopy(p[0], p[6]);
p[6][1] -= maxs[1];
VectorCopy(p[0], p[7]);
p[7][0] -= maxs[0];
p[7][1] -= maxs[1];
}
qboolean loc_CanSee (edict_t *targ, edict_t *inflictor)
{
trace_t trace;
vec3_t targpoints[8];
int i;
vec3_t viewpoint;
// bmodels need special checking because their origin is 0,0,0
if (targ->movetype == MOVETYPE_PUSH)
return false; // bmodels not supported
loc_buildboxpoints(targpoints, targ->s.origin, targ->mins, targ->maxs);
VectorCopy(inflictor->s.origin, viewpoint);
viewpoint[2] += inflictor->viewheight;
for (i = 0; i < 8; i++) {
trace = do_trace (viewpoint, vec3_origin, vec3_origin, targpoints[i], inflictor, MASK_SOLID);
if (trace.fraction == 1.0)
return true;
}
return false;
}
// originally from Zoid's CTF
void SetIDView(edict_t *ent)
{
vec3_t forward, dir;
trace_t tr;
edict_t *who, *best;
float bd = 0, d;
int i;
ent->client->ps.stats[STAT_ID_VIEW] = 0;
//FIREBLADE
if (ent->solid != SOLID_NOT && !teamplay->value)
{
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
return; // won't ever work in non-teams so don't run the code...
}
if (ent->client->chase_mode)
{
if (ent->client->chase_target &&
ent->client->chase_target->inuse)
{
ent->client->ps.stats[STAT_ID_VIEW] =
CS_PLAYERSKINS +
(ent->client->chase_target - g_edicts - 1);
}
return;
}
//FIREBLADE
if ( ent->client->resp.id == 1 )
return;
AngleVectors(ent->client->v_angle, forward, NULL, NULL);
VectorScale(forward, 8192, forward);
VectorAdd(ent->s.origin, forward, forward);
tr = do_trace(ent->s.origin, NULL, NULL, forward, ent, MASK_SOLID);
if (tr.fraction < 1 && tr.ent && tr.ent->client) {
ent->client->ps.stats[STAT_ID_VIEW] =
CS_PLAYERSKINS + (ent - g_edicts - 1);
return;
}
AngleVectors(ent->client->v_angle, forward, NULL, NULL);
best = NULL;
for (i = 1; i <= maxclients->value; i++) {
who = g_edicts + i;
if (!who->inuse)
continue;
VectorSubtract(who->s.origin, ent->s.origin, dir);
VectorNormalize(dir);
d = DotProduct(forward, dir);
if (d > bd && loc_CanSee(ent, who) &&
//FIREBLADE
(who->solid != SOLID_NOT || who->deadflag == DEAD_DEAD) &&
(ent->solid == SOLID_NOT || OnSameTeam(ent, who)))
{
//FIREBLADE
bd = d;
best = who;
}
}
if (best != NULL && bd > 0.90)
{
ent->client->ps.stats[STAT_ID_VIEW] =
CS_PLAYERSKINS + (best - g_edicts - 1);
}
}
void Cmd_IR_f (edict_t *ent )
{
int band = 0;
if ( ir->value )
{
if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(BAND_NAME))] )
{
band = 1;
}
if ( ent->client->resp.ir == 0 )
{
ent->client->resp.ir = 1;
if ( band )
gi.cprintf(ent, PRINT_HIGH, "IR vision disabled.\n");
else
gi.cprintf(ent, PRINT_HIGH, "IR vision will be disabled when you get a bandolier.\n");
// if ( ent->client->ps.rdflags & RDF_IRGOGGLES )
// ent->client->ps.rdflags &= ~RDF_IRGOGGLES;
}
else
{
ent->client->resp.ir = 0;
if ( band )
gi.cprintf(ent, PRINT_HIGH, "IR vision enabled.\n");
else
gi.cprintf(ent, PRINT_HIGH, "IR vision will be enabled when you get a bandolier.\n");
// if ( !(ent->client->ps.rdflags & RDF_IRGOGGLES) )
// ent->client->ps.rdflags |= RDF_IRGOGGLES;
}
}
else
{
gi.cprintf(ent, PRINT_HIGH, "IR vision not enabled on this server.\n");
}
}
// zucc choose command, avoids using the menus in teamplay
void Cmd_Choose_f (edict_t *ent)
{
char *s;
s = gi.args();
// only works in teamplay
if (!teamplay->value)
return;
// convert names a player might try
if (!stricmp(s, "A 2nd pistol") || !stricmp(s, "railgun"))
s = DUAL_NAME;
if (!stricmp(s, "shotgun"))
s = M3_NAME;
if (!stricmp(s, "machinegun"))
s = HC_NAME;
if (!stricmp(s, "super shotgun"))
s = MP5_NAME;
if (!stricmp(s, "chaingun"))
s = SNIPER_NAME;
if (!stricmp(s, "bfg10k"))
s = KNIFE_NAME;
if (!stricmp(s, "grenade launcher"))
s = M4_NAME;
if ( stricmp(s, MP5_NAME) == 0 )
ent->client->resp.weapon = FindItem(MP5_NAME);
else if ( stricmp(s, M3_NAME) == 0 )
ent->client->resp.weapon = FindItem(M3_NAME);
else if ( stricmp(s, M4_NAME) == 0 )
ent->client->resp.weapon = FindItem(M4_NAME);
else if ( stricmp(s, HC_NAME) == 0 )
ent->client->resp.weapon = FindItem(HC_NAME);
else if ( stricmp(s, SNIPER_NAME) == 0 )
ent->client->resp.weapon = FindItem(SNIPER_NAME);
else if ( stricmp(s, KNIFE_NAME) == 0 )
ent->client->resp.weapon = FindItem(KNIFE_NAME);
else if ( stricmp(s, DUAL_NAME) == 0 )
ent->client->resp.weapon = FindItem(DUAL_NAME);
else if ( stricmp(s, KEV_NAME) == 0 )
ent->client->resp.item = FindItem(KEV_NAME);
else if ( stricmp(s, LASER_NAME) == 0 )
ent->client->resp.item = FindItem(LASER_NAME);
else if ( stricmp(s, SLIP_NAME) == 0 )
ent->client->resp.item = FindItem(SLIP_NAME);
else if ( stricmp(s, SIL_NAME) == 0 )
ent->client->resp.item = FindItem(SIL_NAME);
else if ( stricmp(s, BAND_NAME) == 0 )
ent->client->resp.item = FindItem(BAND_NAME);
else
{
gi.cprintf(ent, PRINT_HIGH, "Invalid weapon or item choice.\n");
return;
}
gi.cprintf(ent, PRINT_HIGH, "Weapon selected: %s\nItem selected: %s\n", (ent->client->resp.weapon)->pickup_name, (ent->client->resp.item)->pickup_name);
}

1014
a_game.c Normal file

File diff suppressed because it is too large Load diff

26
a_game.h Normal file
View file

@ -0,0 +1,26 @@
/*
* Include for base Action game-related things
*/
#define ACTION_VERSION "1.5"
extern char team1_name[];
extern char team2_name[];
extern char team1_skin[];
extern char team2_skin[];
extern char team1_skin_index[];
extern char team2_skin_index[];
extern char *map_rotation[];
extern int num_maps, cur_map;
extern char *tnames[];
void ReadConfigFile();
void ReadMOTDFile();
void PrintMOTD(edict_t *);
void stuffcmd(edict_t *, char *);
// Prototypes of base Q2 functions that weren't included in any Q2 header
qboolean loc_CanSee(edict_t *, edict_t *);
qboolean IsNeutral(edict_t *);
qboolean IsFemale(edict_t *);
void ParseSayText(edict_t *, char *);

200
a_items.c Normal file
View file

@ -0,0 +1,200 @@
/************************************************************************
* Special item spawning/management code. Mainly hacked from CTF, thanks
* Zoid.
* - zucc
*/
#include "g_local.h"
// time too wait between failures to respawn?
#define SPEC_RESPAWN_TIME 60
// time before they will get respawned
#define SPEC_TECH_TIMEOUT 60
char *tnames[] = {
"item_quiet", "item_slippers", "item_vest", "item_band", "item_lasersight",
NULL
};
void SpecThink(edict_t *spec);
static edict_t *FindSpecSpawn(void)
{
edict_t *spot = NULL;
int i = rand() % 16;
while (i--)
spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
if (!spot)
spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
if (spot == NULL)
{
gi.dprintf("Warning: failed to find special item spawn point!\n");
}
return spot;
}
static void SpawnSpec(gitem_t *item, edict_t *spot)
{
edict_t *ent;
vec3_t forward, right;
vec3_t angles;
ent = G_Spawn();
ent->classname = item->classname;
ent->item = item;
ent->spawnflags = DROPPED_ITEM;
ent->s.effects = item->world_model_flags;
ent->s.renderfx = RF_GLOW;
VectorSet (ent->mins, -15, -15, -15);
VectorSet (ent->maxs, 15, 15, 15);
// zucc dumb hack to make laser look like it is on the ground
if (stricmp(item->pickup_name, LASER_NAME) == 0)
{
VectorSet (ent->mins, -15, -15, -1);
VectorSet (ent->maxs, 15, 15, 1);
}
gi.setmodel (ent, ent->item->world_model);
ent->solid = SOLID_TRIGGER;
ent->movetype = MOVETYPE_TOSS;
ent->touch = Touch_Item;
ent->owner = ent;
angles[0] = 0;
angles[1] = rand() % 360;
angles[2] = 0;
AngleVectors (angles, forward, right, NULL);
VectorCopy (spot->s.origin, ent->s.origin);
ent->s.origin[2] += 16;
VectorScale (forward, 100, ent->velocity);
ent->velocity[2] = 300;
ent->nextthink = level.time + SPEC_RESPAWN_TIME;
ent->think = SpecThink;
gi.linkentity (ent);
}
void SpawnSpecs(edict_t *ent)
{
gitem_t *spec;
edict_t *spot;
int i;
i = 0;
while (tnames[i]) {
if ((spec = FindItemByClassname(tnames[i])) != NULL &&
(spot = FindSpecSpawn()) != NULL)
SpawnSpec(spec, spot);
i++;
}
}
void SpecThink(edict_t *spec)
{
edict_t *spot;
if ((spot = FindSpecSpawn()) != NULL) {
SpawnSpec(spec->item, spot);
G_FreeEdict(spec);
} else {
spec->nextthink = level.time + SPEC_RESPAWN_TIME;
spec->think = SpecThink;
}
}
static void MakeTouchSpecThink (edict_t *ent)
{
ent->touch = Touch_Item;
if ( deathmatch->value && !teamplay->value && !allitem->value )
{
ent->nextthink = level.time + SPEC_RESPAWN_TIME - 1;
ent->think = SpecThink;
}
else if ( teamplay->value && !allitem->value )
{
ent->nextthink = level.time + 60;
ent->think = G_FreeEdict;
}
else // allitem->value is set
{
ent->nextthink = level.time + 1;
ent->think = G_FreeEdict;
}
}
void Drop_Spec(edict_t *ent, gitem_t *item)
{
edict_t *spec;
spec = Drop_Item(ent, item);
//gi.cprintf(ent, PRINT_HIGH, "Dropping special item.\n");
spec->nextthink = level.time + 1;
spec->think = MakeTouchSpecThink;
//zucc this and the one below should probably be -- not = 0, if
// a server turns on multiple item pickup.
ent->client->pers.inventory[ITEM_INDEX(item)]--;
}
void DeadDropSpec(edict_t *ent)
{
gitem_t *spec;
edict_t *dropped;
int i;
i = 0;
while (tnames[i]) {
if ((spec = FindItemByClassname(tnames[i])) != NULL &&
ent->client->pers.inventory[ITEM_INDEX(spec)]) {
dropped = Drop_Item(ent, spec);
// hack the velocity to make it bounce random
dropped->velocity[0] = (rand() % 600) - 300;
dropped->velocity[1] = (rand() % 600) - 300;
dropped->nextthink = level.time + 1;
dropped->think = MakeTouchSpecThink;
dropped->owner = NULL;
ent->client->pers.inventory[ITEM_INDEX(spec)] = 0;
}
i++;
}
}
// frees the passed edict!
void RespawnSpec(edict_t *ent)
{
edict_t *spot;
if ((spot = FindSpecSpawn()) != NULL)
SpawnSpec(ent->item, spot);
G_FreeEdict(ent);
}
void SetupSpecSpawn(void)
{
edict_t *ent;
if (level.specspawn)
return;
//gi.bprintf (PRINT_HIGH, "got into the setup\n");
ent = G_Spawn();
ent->nextthink = level.time + 4;
ent->think = SpawnSpecs;
level.specspawn = 1;
}

194
a_menu.c Normal file
View file

@ -0,0 +1,194 @@
/*
* Action (formerly Axshun) menu code
* This is directly from Zoid's CTF code. Thanks to Zoid again.
* -Fireblade
*/
#include "g_local.h"
void PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num)
{
pmenuhnd_t *hnd;
pmenu_t *p;
int i;
if (!ent->client)
return;
if (ent->client->menu) {
gi.dprintf("warning, ent already has a menu\n");
PMenu_Close(ent);
}
hnd = gi.TagMalloc(sizeof(*hnd), TAG_GAME);
hnd->entries = entries;
hnd->num = num;
if (cur < 0 || !entries[cur].SelectFunc) {
for (i = 0, p = entries; i < num; i++, p++)
if (p->SelectFunc)
break;
} else
i = cur;
if (i >= num)
hnd->cur = -1;
else
hnd->cur = i;
ent->client->showscores = true;
ent->client->inmenu = true;
ent->client->menu = hnd;
PMenu_Update(ent);
gi.unicast (ent, true);
}
void PMenu_Close(edict_t *ent)
{
if (!ent->client->menu)
return;
gi.TagFree(ent->client->menu);
ent->client->menu = NULL;
ent->client->showscores = false;
}
void PMenu_Update(edict_t *ent)
{
char string[1400];
int i;
pmenu_t *p;
int x;
pmenuhnd_t *hnd;
char *t;
qboolean alt = false;
if (!ent->client->menu) {
gi.dprintf("warning: ent has no menu\n");
return;
}
hnd = ent->client->menu;
strcpy(string, "xv 32 yv 8 ");
for (i = 0, p = hnd->entries; i < hnd->num; i++, p++) {
if (!p->text || !*(p->text))
continue; // blank line
t = p->text;
if (*t == '*') {
alt = true;
t++;
}
sprintf(string + strlen(string), "yv %d ", 32 + i * 8);
if (p->align == PMENU_ALIGN_CENTER)
x = 196/2 - strlen(t)*4 + 64;
else if (p->align == PMENU_ALIGN_RIGHT)
x = 64 + (196 - strlen(t)*8);
else
x = 64;
sprintf(string + strlen(string), "xv %d ",
x - ((hnd->cur == i) ? 8 : 0));
if (hnd->cur == i)
sprintf(string + strlen(string), "string2 \"\x0d%s\" ", t);
else if (alt)
sprintf(string + strlen(string), "string2 \"%s\" ", t);
else
sprintf(string + strlen(string), "string \"%s\" ", t);
alt = false;
}
gi.WriteByte (svc_layout);
gi.WriteString (string);
}
void PMenu_Next(edict_t *ent)
{
pmenuhnd_t *hnd;
int i;
pmenu_t *p;
if (!ent->client->menu) {
gi.dprintf("warning: ent has no menu\n");
return;
}
hnd = ent->client->menu;
if (hnd->cur < 0)
return; // no selectable entries
i = hnd->cur;
p = hnd->entries + hnd->cur;
do {
i++, p++;
if (i == hnd->num)
i = 0, p = hnd->entries;
if (p->SelectFunc)
break;
} while (i != hnd->cur);
hnd->cur = i;
PMenu_Update(ent);
gi.unicast (ent, true);
}
void PMenu_Prev(edict_t *ent)
{
pmenuhnd_t *hnd;
int i;
pmenu_t *p;
if (!ent->client->menu) {
gi.dprintf("warning: ent has no menu\n");
return;
}
hnd = ent->client->menu;
if (hnd->cur < 0)
return; // no selectable entries
i = hnd->cur;
p = hnd->entries + hnd->cur;
do {
if (i == 0) {
i = hnd->num - 1;
p = hnd->entries + i;
} else
i--, p--;
if (p->SelectFunc)
break;
} while (i != hnd->cur);
hnd->cur = i;
PMenu_Update(ent);
gi.unicast (ent, true);
}
void PMenu_Select(edict_t *ent)
{
pmenuhnd_t *hnd;
pmenu_t *p;
if (!ent->client->menu) {
gi.dprintf("warning: ent has no menu\n");
return;
}
hnd = ent->client->menu;
if (hnd->cur < 0)
return; // no selectable entries
p = hnd->entries + hnd->cur;
if (p->SelectFunc)
p->SelectFunc(ent, p);
}

30
a_menu.h Normal file
View file

@ -0,0 +1,30 @@
/*
* Action (formerly Axshun) menus
* From Zoid's CTF.
*/
enum {
PMENU_ALIGN_LEFT,
PMENU_ALIGN_CENTER,
PMENU_ALIGN_RIGHT
};
typedef struct pmenuhnd_s {
struct pmenu_s *entries;
int cur;
int num;
} pmenuhnd_t;
typedef struct pmenu_s {
char *text;
int align;
void *arg;
void (*SelectFunc)(edict_t *ent, struct pmenu_s *entry);
} pmenu_t;
void PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num);
void PMenu_Close(edict_t *ent);
void PMenu_Update(edict_t *ent);
void PMenu_Next(edict_t *ent);
void PMenu_Prev(edict_t *ent);
void PMenu_Select(edict_t *ent);

668
a_radio.c Normal file
View file

@ -0,0 +1,668 @@
/*
* Radio-related code for Action (formerly Axshun)
*
* -Fireblade
*/
#include "g_local.h"
void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0, qboolean partner_msg);
// Each of the possible radio messages and their length
radio_msg_t male_radio_msgs[] = {
{ "1", 6 },
{ "2", 6 },
{ "3", 8 },
{ "4", 7 },
{ "5", 8 },
{ "6", 9 },
{ "7", 8 },
{ "8", 7 },
{ "9", 7 },
{ "10", 6 },
{ "back", 6 },
{ "cover", 7 },
{ "down", 13 },
{ "enemyd", 10 },
{ "enemys", 9 },
{ "forward", 6 },
{ "go", 6 },
{ "im_hit", 7 },
{ "left", 7 },
{ "reportin", 9 },
{ "right", 6 },
{ "taking_f", 22 },
{ "teamdown", 13 },
{ "treport", 12 },
{ "up", 4 },
{ "END", 0 }, // end of list delimiter
};
radio_msg_t female_radio_msgs[] = {
{ "1", 5 },
{ "2", 5 },
{ "3", 5 },
{ "4", 5 },
{ "5", 5 },
{ "6", 8 },
{ "7", 7 },
{ "8", 5 },
{ "9", 5 },
{ "10", 5 },
{ "back", 6 },
{ "cover", 5 },
{ "down", 6 },
{ "enemyd", 9 },
{ "enemys", 9 },
{ "forward", 8 },
{ "go", 6 },
{ "im_hit", 7 },
{ "left", 8 },
{ "reportin", 9 },
{ "right", 5 },
{ "taking_f", 22 },
{ "teamdown", 10 },
{ "treport", 12 },
{ "up", 6 },
{ "END", 0 }, // end of list delimiter
};
void PrecacheRadioMsgSet(radio_msg_t *msgs, char *base_path)
{
char msg_fullpath[2048];
while (strcmp(msgs->msg, "END"))
{
sprintf(msg_fullpath, "%s%s.wav", base_path, msgs->msg);
gi.soundindex(msg_fullpath);
msgs++;
}
}
void PrecacheRadioSounds()
{
gi.soundindex(RADIO_CLICK);
gi.soundindex(RADIO_DEATH_MALE);
gi.soundindex(RADIO_DEATH_FEMALE);
PrecacheRadioMsgSet(male_radio_msgs, "radio/male/");
PrecacheRadioMsgSet(female_radio_msgs, "radio/female/");
}
void DeleteFirstRadioQueueEntry(edict_t *ent)
{
int i;
if (ent->client->resp.radio_queue_size <= 0)
{
gi.dprintf("DeleteFirstRadioQueueEntry: attempt to delete without any entries\n");
return;
}
for (i = 1; i < MAX_RADIO_QUEUE_SIZE; i++)
{
memcpy(&(ent->client->resp.radio_queue[i - 1]),
&(ent->client->resp.radio_queue[i]),
sizeof(radio_queue_entry_t));
}
ent->client->resp.radio_queue_size--;
}
// RadioThink should be called once on each player per server frame.
void RadioThink(edict_t *ent)
{
// Try to clean things up, a bit....
if (ent->client->resp.radio_partner)
{
if (!ent->client->resp.radio_partner->inuse ||
ent->client->resp.radio_partner->client->resp.radio_partner != ent)
{
ent->client->resp.radio_partner = NULL;
}
}
if (ent->client->resp.partner_last_offered_to)
{
if (!ent->client->resp.partner_last_offered_to->inuse ||
ent->client->resp.partner_last_offered_to->solid == SOLID_NOT)
{
ent->client->resp.partner_last_offered_to = NULL;
}
}
if (ent->client->resp.partner_last_denied_from)
{
if (!ent->client->resp.partner_last_denied_from->inuse ||
ent->client->resp.partner_last_denied_from->solid == SOLID_NOT)
{
ent->client->resp.partner_last_denied_from = NULL;
}
}
// ................................
if (ent->client->resp.radio_power_off)
{
ent->client->resp.radio_queue_size = 0;
return;
}
if (ent->client->resp.radio_delay > 1)
{
ent->client->resp.radio_delay--;
return;
}
else if (ent->client->resp.radio_delay == 1)
{
DeleteFirstRadioQueueEntry(ent);
ent->client->resp.radio_delay = 0;
}
if (ent->client->resp.radio_queue_size)
{
char snd_play_cmd[512];
edict_t *from;
from = ent->client->resp.radio_queue[0].from_player;
if (!ent->client->resp.radio_queue[0].click &&
(!from->inuse || from->solid == SOLID_NOT || from->deadflag == DEAD_DEAD))
{
if (ent->client->resp.radio_queue[0].from_gender)
{
strcpy(ent->client->resp.radio_queue[0].soundfile, RADIO_DEATH_FEMALE);
ent->client->resp.radio_queue[0].length = RADIO_DEATH_FEMALE_LEN;
}
else
{
strcpy(ent->client->resp.radio_queue[0].soundfile, RADIO_DEATH_MALE);
ent->client->resp.radio_queue[0].length = RADIO_DEATH_MALE_LEN;
}
}
sprintf(snd_play_cmd, "play %s", ent->client->resp.radio_queue[0].soundfile);
stuffcmd(ent, snd_play_cmd);
ent->client->resp.radio_queue[0].now_playing = 1;
ent->client->resp.radio_delay = ent->client->resp.radio_queue[0].length;
}
}
int TotalNonClickMessagesInQueue(edict_t *ent)
{
int i, count = 0;
for (i = 0; i < ent->client->resp.radio_queue_size; i++)
{
if (!ent->client->resp.radio_queue[i].click)
count++;
}
return count;
}
void AppendRadioMsgToQueue(edict_t *ent, char *msg, int len, int click, edict_t *from_player)
{
radio_queue_entry_t *newentry;
if (ent->client->resp.radio_queue_size >= MAX_RADIO_QUEUE_SIZE)
{
gi.dprintf("AppendRadioMsgToQueue: Maximum radio queue size exceeded\n");
return;
}
newentry = &(ent->client->resp.radio_queue[ent->client->resp.radio_queue_size]);
if (strlen(msg) + 1 > MAX_SOUNDFILE_PATH_LEN)
{
gi.dprintf("Radio sound file path (%s) exceeded maximum length\n", msg);
*(msg + MAX_SOUNDFILE_PATH_LEN - 1) = 0;
}
strcpy(newentry->soundfile, msg);
newentry->from_player = from_player;
newentry->from_gender = from_player->client->resp.radio_gender;
newentry->now_playing = 0;
newentry->length = len;
newentry->click = click;
ent->client->resp.radio_queue_size++;
}
void InsertRadioMsgInQueueBeforeClick(edict_t *ent, char *msg, int len, edict_t *from_player)
{
radio_queue_entry_t *newentry;
if (ent->client->resp.radio_queue_size >= MAX_RADIO_QUEUE_SIZE)
{
gi.dprintf("InsertRadioMsgInQueueBeforeClick: Maximum radio queue size exceeded\n");
return;
}
memcpy(&(ent->client->resp.radio_queue[ent->client->resp.radio_queue_size]),
&(ent->client->resp.radio_queue[ent->client->resp.radio_queue_size - 1]),
sizeof(radio_queue_entry_t));
newentry = &(ent->client->resp.radio_queue[ent->client->resp.radio_queue_size - 1]);
if (strlen(msg) + 1 > MAX_SOUNDFILE_PATH_LEN)
{
gi.dprintf("Radio sound file path (%s) exceeded maximum length\n", msg);
*(msg + MAX_SOUNDFILE_PATH_LEN - 1) = 0;
}
strcpy(newentry->soundfile, msg);
newentry->from_player = from_player;
newentry->from_gender = from_player->client->resp.radio_gender;
newentry->now_playing = 0;
newentry->length = len;
newentry->click = 0;
ent->client->resp.radio_queue_size++;
}
void AddRadioMsg(edict_t *ent, char *msg, int len, edict_t *from_player)
{
if (ent->client->resp.radio_queue_size == 0 ||
(ent->client->resp.radio_queue[0].click &&
ent->client->resp.radio_queue_size == 1))
{
AppendRadioMsgToQueue(ent, RADIO_CLICK, RADIO_CLICK_LEN, 1, from_player);
AppendRadioMsgToQueue(ent, msg, len, 0, from_player);
AppendRadioMsgToQueue(ent, RADIO_CLICK, RADIO_CLICK_LEN, 1, from_player);
}
else // we have some msgs in it already...
{
if (TotalNonClickMessagesInQueue(ent) < MAX_RADIO_MSG_QUEUE_SIZE)
InsertRadioMsgInQueueBeforeClick(ent, msg, len, from_player);
// else ignore the message...
}
}
void RadioBroadcast(edict_t *ent, int partner, char *msg)
{
int j, i, msg_len, found;
edict_t *other;
radio_msg_t *radio_msgs;
char msg_fullpath[2048], *base_path;
if (ent->deadflag == DEAD_DEAD || ent->solid == SOLID_NOT)
return;
if (!teamplay->value)
{
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
return; // don't allow in a non-team setup...
}
if (ent->client->resp.radio_power_off)
{
gi.centerprintf(ent, "Your radio is off!\n");
return;
}
if (partner && ent->client->resp.radio_partner == NULL)
{
gi.cprintf(ent, PRINT_HIGH, "You don't have a partner.\n");
return;
}
if (ent->client->resp.radio_gender)
{
radio_msgs = female_radio_msgs;
base_path = "radio/female/";
}
else
{
radio_msgs = male_radio_msgs;
base_path = "radio/male/";
}
i = found = 0;
while (strcmp(radio_msgs[i].msg, "END"))
{
if (!Q_stricmp(radio_msgs[i].msg, msg))
{
found = 1;
sprintf(msg_fullpath, "%s%s.wav", base_path, radio_msgs[i].msg);
msg_len = radio_msgs[i].length;
break;
}
i++;
}
if (!found)
{
gi.centerprintf(ent, "'%s' is not a valid radio message\n", msg);
return;
}
if (radiolog->value)
{
gi.cprintf(NULL, PRINT_CHAT, "[%s RADIO] %s: %s\n",
partner ? "PARTNER" : "TEAM",
ent->client->pers.netname,
msg);
}
for (j = 1; j <= game.maxclients; j++)
{
other = &g_edicts[j];
if (!other->inuse)
continue;
if (!other->client)
continue;
if (!OnSameTeam(ent, other))
continue;
if (partner && other != ent->client->resp.radio_partner)
continue;
AddRadioMsg(other, msg_fullpath, msg_len, ent);
}
}
void Cmd_Radio_f(edict_t *ent)
{
RadioBroadcast(ent, ent->client->resp.radio_partner_mode, gi.args());
}
void Cmd_Radiopartner_f(edict_t *ent)
{
RadioBroadcast(ent, 1, gi.args());
}
void Cmd_Radioteam_f(edict_t *ent)
{
RadioBroadcast(ent, 0, gi.args());
}
void Cmd_Radiogender_f(edict_t *ent)
{
char *arg;
if (!teamplay->value)
{
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
return; // don't allow in a non-team setup...
}
arg = gi.args();
if (arg == NULL || !strlen(arg))
{
if (ent->client->resp.radio_gender)
gi.cprintf(ent, PRINT_HIGH, "Radio gender currently set to female\n");
else
gi.cprintf(ent, PRINT_HIGH, "Radio gender currently set to male\n");
return;
}
if (!Q_stricmp(arg, "male"))
{
gi.cprintf(ent, PRINT_HIGH, "Radio gender set to male\n");
ent->client->resp.radio_gender = 0;
}
else if (!Q_stricmp(arg, "female"))
{
gi.cprintf(ent, PRINT_HIGH, "Radio gender set to female\n");
ent->client->resp.radio_gender = 1;
}
else
{
gi.cprintf(ent, PRINT_HIGH, "Invalid gender selection, try 'male' or 'female'\n");
}
}
void Cmd_Radio_power_f(edict_t *ent)
{
if (!teamplay->value)
{
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
return; // don't allow in a non-team setup...
}
ent->client->resp.radio_power_off = !ent->client->resp.radio_power_off;
if (ent->client->resp.radio_power_off)
{
gi.centerprintf(ent, "Radio switched off\n");
stuffcmd(ent, "play " RADIO_CLICK);
}
else
{
gi.centerprintf(ent, "Radio switched on\n");
stuffcmd(ent, "play " RADIO_CLICK);
}
}
void Cmd_Channel_f(edict_t *ent)
{
if (!teamplay->value)
{
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
return; // don't allow in a non-team setup...
}
ent->client->resp.radio_partner_mode = !ent->client->resp.radio_partner_mode;
if (ent->client->resp.radio_partner_mode)
{
gi.centerprintf(ent, "Channel set to 1, partner channel\n");
}
else
{
gi.centerprintf(ent, "Channel set to 0, team channel\n");
}
}
// DetermineViewedTeammate: determine the current player you're viewing (only looks for live teammates)
// Modified from SetIDView (which was used from Zoid's CTF)
edict_t *DetermineViewedTeammate(edict_t *ent)
{
vec3_t forward, dir;
trace_t tr;
edict_t *who, *best;
float bd = 0, d;
int i;
AngleVectors(ent->client->v_angle, forward, NULL, NULL);
VectorScale(forward, 8192, forward);
VectorAdd(ent->s.origin, forward, forward);
tr = do_trace(ent->s.origin, NULL, NULL, forward, ent, MASK_SOLID);
if (tr.fraction < 1 && tr.ent && tr.ent->client) {
return NULL;
}
AngleVectors(ent->client->v_angle, forward, NULL, NULL);
best = NULL;
for (i = 1; i <= maxclients->value; i++) {
who = g_edicts + i;
if (!who->inuse)
continue;
VectorSubtract(who->s.origin, ent->s.origin, dir);
VectorNormalize(dir);
d = DotProduct(forward, dir);
if (d > bd && loc_CanSee(ent, who) &&
who->solid != SOLID_NOT &&
who->deadflag != DEAD_DEAD &&
OnSameTeam(who, ent)) {
bd = d;
best = who;
}
}
if (bd > 0.90)
{
return best;
}
return NULL;
}
void Cmd_Partner_f(edict_t *ent)
{
edict_t *target;
char *genderstr;
if (!teamplay->value)
{
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
return; // don't allow in a non-team setup...
}
if (ent->deadflag == DEAD_DEAD || ent->solid == SOLID_NOT)
return;
if (ent->client->resp.radio_partner &&
!ent->client->resp.radio_partner->inuse)
{ // just in case RadioThink hasn't caught it yet... avoid any problems
ent->client->resp.radio_partner = NULL;
}
if (ent->client->resp.radio_partner)
{
gi.centerprintf(ent, "You already have a partner, %s\n",
ent->client->resp.radio_partner->client->pers.netname);
return;
}
target = DetermineViewedTeammate(ent);
if (target == NULL)
{
gi.centerprintf(ent, "No potential partner selected\n");
return;
}
if (target->client->resp.radio_partner)
{
gi.centerprintf(ent, "%s already has a partner\n", target->client->pers.netname);
return;
}
if (target->client->resp.partner_last_offered_to == ent &&
ent->client->resp.partner_last_offered_from == target)
{
gi.centerprintf(ent, "%s is now your partner\n", target->client->pers.netname);
gi.centerprintf(target, "%s is now your partner\n", ent->client->pers.netname);
ent->client->resp.radio_partner = target;
target->client->resp.radio_partner = ent;
ent->client->resp.partner_last_offered_from = NULL;
target->client->resp.partner_last_offered_to = NULL;
return;
}
if (target->client->resp.partner_last_denied_from == ent)
{
gi.centerprintf(ent, "%s has already denied you\n",
target->client->pers.netname);
return;
}
if (target == ent->client->resp.partner_last_offered_to)
{
if (IsFemale(target))
genderstr = "her";
else if (IsNeutral(target))
genderstr = "it";
else
genderstr = "him";
gi.centerprintf(ent, "Already awaiting confirmation from %s\n", genderstr);
return;
}
if (IsFemale(ent))
genderstr = "her";
else if (IsNeutral(ent))
genderstr = "it";
else
genderstr = "him";
gi.centerprintf(ent, "Awaiting confirmation from %s\n", target->client->pers.netname);
gi.centerprintf(target, "%s offers to be your partner\n"
"To accept:\nView %s and use the 'partner' command\n"
"To deny:\nUse the 'deny' command\n",
ent->client->pers.netname, genderstr);
ent->client->resp.partner_last_offered_to = target;
target->client->resp.partner_last_offered_from = ent;
}
void Cmd_Unpartner_f(edict_t *ent)
{
edict_t *target;
if (!teamplay->value)
{
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
return; // don't allow in a non-team setup...
}
if (ent->client->resp.radio_partner &&
!ent->client->resp.radio_partner->inuse)
{ // just in case RadioThink hasn't caught it yet... avoid any problems
ent->client->resp.radio_partner = NULL;
}
target = ent->client->resp.radio_partner;
if (target == NULL)
{
gi.centerprintf(ent, "You don't have a partner\n");
return;
}
if (target->client->resp.radio_partner == ent)
{
gi.centerprintf(target, "%s broke your partnership\n",
ent->client->pers.netname);
target->client->resp.radio_partner = NULL;
}
gi.centerprintf(ent, "You broke your partnership with %s\n",
target->client->pers.netname);
ent->client->resp.radio_partner = NULL;
}
void Cmd_Deny_f(edict_t *ent)
{
edict_t *target;
if (!teamplay->value)
{
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
return; // don't allow in a non-team setup...
}
if (ent->deadflag == DEAD_DEAD || ent->solid == SOLID_NOT)
return;
target = ent->client->resp.partner_last_offered_from;
if (target && target->inuse)
{
gi.centerprintf(ent, "You denied %s\n",
target->client->pers.netname);
gi.centerprintf(target, "%s has denied you\n",
ent->client->pers.netname);
ent->client->resp.partner_last_denied_from = target;
ent->client->resp.partner_last_offered_from = NULL;
if (target->client->resp.partner_last_offered_to == ent)
target->client->resp.partner_last_offered_to = NULL;
}
else
{
gi.centerprintf(ent, "No one has offered to be your partner\n");
return;
}
}
void Cmd_Say_partner_f(edict_t *ent)
{
if (!teamplay->value)
{
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
return; // don't allow in a non-team setup...
}
if (ent->client->resp.radio_partner == NULL)
{
gi.cprintf(ent, PRINT_HIGH, "You don't have a partner.\n");
return;
}
Cmd_Say_f(ent, false, false, true);
}

49
a_radio.h Normal file
View file

@ -0,0 +1,49 @@
/*
* a_radio.h
*
* Include file for use with radio stuff
* -Fireblade
*/
typedef struct radio_msg_s
{
char *msg; // the msg name
int length; // length in server frames (ie tenths of a second), rounded up
} radio_msg_t;
#define RADIO_CLICK "radio/click.wav"
#define RADIO_CLICK_LEN 2
#define RADIO_DEATH_MALE "radio/male/rdeath.wav"
#define RADIO_DEATH_MALE_LEN 27
#define RADIO_DEATH_FEMALE "radio/female/rdeath.wav"
#define RADIO_DEATH_FEMALE_LEN 30
#define MAX_SOUNDFILE_PATH_LEN 32 // max length of a sound file path
#define MAX_RADIO_MSG_QUEUE_SIZE 4
#define MAX_RADIO_QUEUE_SIZE 6 // this must be at least 2 greater than the above
extern radio_msg_t male_radio_msgs[];
extern radio_msg_t female_radio_msgs[];
typedef struct radio_queue_entry_s
{
char soundfile[MAX_SOUNDFILE_PATH_LEN];
edict_t *from_player;
int from_gender; // true if female
qboolean now_playing;
int length;
qboolean click;
} radio_queue_entry_t;
void RadioThink(edict_t *);
void Cmd_Radio_f(edict_t *);
void Cmd_Radiogender_f(edict_t *);
void Cmd_Radio_power_f(edict_t *);
void Cmd_Radiopartner_f(edict_t *);
void Cmd_Radioteam_f(edict_t *);
void Cmd_Channel_f(edict_t *);
void Cmd_Say_partner_f(edict_t *);
void Cmd_Partner_f(edict_t *);
void Cmd_Deny_f(edict_t *);
void Cmd_Unpartner_f(edict_t *);
void PrecacheRadioSounds();

1520
a_team.c Normal file

File diff suppressed because it is too large Load diff

79
a_team.h Normal file
View file

@ -0,0 +1,79 @@
/*
* Include for Action team-related things
*/
#define NOTEAM 0
#define TEAM1 1
#define TEAM2 2
#define MAX_TEAMS 2
#define TEAM_TOP (MAX_TEAMS+1)
#define WINNER_NONE 0
#define WINNER_TEAM1 1
#define WINNER_TEAM2 2
#define WINNER_TIE 3
// Override normal trace for our teamplay anti-stick stuff. If there are
// still "transparent" (SOLID_TRIGGER) players, they need to be set to
// SOLID_BBOX before a trace is performed, then changed back again
// afterwards. do_trace() should be used instead of gi.trace() in all
// areas where "transparent" players should be detected.
#define do_trace(a, b, c, d, e, f) \
(((int)teamplay->value && transparent_list && !lights_camera_action) ? \
(TransparentListSet(SOLID_BBOX), \
(trace_t_temp = gi.trace(a, b, c, d, e, f)), \
TransparentListSet(SOLID_TRIGGER), \
trace_t_temp) \
: \
gi.trace(a, b, c, d, e, f))
trace_t our_trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask);
edict_t *SelectTeamplaySpawnPoint(edict_t *);
qboolean FallingDamageAmnesty(edict_t *targ);
void OpenJoinMenu(edict_t *);
void OpenWeaponMenu(edict_t *);
void OpenItemMenu(edict_t *ent);
void JoinTeam(edict_t *ent, int desired_team, int skip_menuclose);
edict_t *FindOverlap(edict_t *ent, edict_t *last_overlap);
void CheckTeamRules(void);
void A_Scoreboard(edict_t *ent);
void Team_f(edict_t *ent);
qboolean StartClient(edict_t *ent);
void AssignSkin(edict_t *, char *);
void TallyEndOfLevelTeamScores(void);
void CheckForUnevenTeams(void);
void SetupTeamSpawnPoints();
void GetSpawnPoints();
void CleanBodies(); // from p_client.c, removes all current dead bodies from map
void LeaveTeam(edict_t *);
int newrand(int top);
void InitTransparentList();
void AddToTransparentList(edict_t *);
void RemoveFromTransparentList(edict_t *);
void PrintTransparentList();
typedef struct spawn_distances_s
{
float distance;
edict_t *s;
} spawn_distances_t;
typedef struct transparent_list_s
{
edict_t *ent;
struct transparent_list_s *next;
} transparent_list_t;
extern qboolean team_game_going;
extern qboolean team_round_going;
extern int team1_score;
extern int team2_score;
extern int team1_total;
extern int team2_total;
extern int lights_camera_action;
extern int holding_on_tie_check;
extern int team_round_countdown;
extern transparent_list_t *transparent_list;
extern trace_t trace_t_temp;

1098
g_ai.c Normal file

File diff suppressed because it is too large Load diff

238
g_chase.c Normal file
View file

@ -0,0 +1,238 @@
// This source file comes from the 3.20 source originally.
//
// Added through-the-eyes cam mode, as well as the ability to spin around the player
// when in regular chase cam mode, among other Axshun-related mods.
// -Fireblade
#include "g_local.h"
int ChaseTargetGone(edict_t *ent)
{
// is our chase target gone?
if (!ent->client->chase_target->inuse
|| (ent->client->chase_target->solid == SOLID_NOT &&
ent->client->chase_target->deadflag != DEAD_DEAD))
{
edict_t *old = ent->client->chase_target;
ChaseNext(ent);
if (ent->client->chase_target == old)
{
ent->client->chase_target = NULL;
ent->client->desired_fov = 90;
ent->client->ps.fov = 90;
ent->client->chase_mode = 0;
ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
return 1;
}
}
return 0;
}
void UpdateChaseCam(edict_t *ent)
{
vec3_t o, ownerv, goal;
edict_t *targ;
vec3_t forward, right;
trace_t trace;
int i;
vec3_t angles;
if (ChaseTargetGone(ent))
return;
targ = ent->client->resp.last_chase_target = ent->client->chase_target;
if (ent->client->chase_mode == 1)
{
ent->client->desired_fov = 90;
ent->client->ps.fov = 90;
if (ent->client->resp.cmd_angles[PITCH] > 89)
ent->client->resp.cmd_angles[PITCH] = 89;
if (ent->client->resp.cmd_angles[PITCH] < -89)
ent->client->resp.cmd_angles[PITCH] = -89;
VectorCopy(targ->s.origin, ownerv);
ownerv[2] += targ->viewheight;
VectorCopy(ent->client->ps.viewangles, angles);
AngleVectors (angles, forward, right, NULL);
VectorNormalize(forward);
VectorMA(ownerv, -150, forward, o);
// not sure if this should be left in... -FB
// if (o[2] < targ->s.origin[2] + 20)
// o[2] = targ->s.origin[2] + 20;
// jump animation lifts
if (!targ->groundentity)
o[2] += 16;
trace = do_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 = do_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 = do_trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
if (trace.fraction < 1) {
VectorCopy(trace.endpos, goal);
goal[2] += 6;
}
if (targ->deadflag)
ent->client->ps.pmove.pm_type = PM_DEAD;
else
ent->client->ps.pmove.pm_type = PM_FREEZE;
VectorCopy(goal, ent->s.origin);
for (i=0 ; i<3 ; i++)
ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->v_angle[i] - ent->client->resp.cmd_angles[i]);
VectorCopy(ent->client->resp.cmd_angles, ent->client->ps.viewangles);
}
else // chase_mode == 2
{
VectorCopy(targ->s.origin, ownerv);
VectorCopy(targ->client->v_angle, angles);
AngleVectors (angles, forward, right, NULL);
VectorNormalize(forward);
VectorMA(ownerv, 16, forward, o);
o[2] += targ->viewheight;
VectorCopy(o, ent->s.origin);
ent->client->ps.fov = targ->client->ps.fov;
ent->client->desired_fov = targ->client->ps.fov;
for (i=0 ; i<3 ; i++)
ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]);
if (targ->deadflag)
{
ent->client->ps.viewangles[ROLL] = 40;
ent->client->ps.viewangles[PITCH] = -15;
ent->client->ps.viewangles[YAW] = targ->client->killer_yaw;
}
else
{
VectorCopy(targ->client->v_angle, ent->client->ps.viewangles);
VectorCopy(targ->client->v_angle, ent->client->v_angle);
}
}
ent->viewheight = 0;
ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
gi.linkentity(ent);
}
void ChaseNext(edict_t *ent)
{
int i;
edict_t *e;
if (!ent->client->chase_target)
return;
i = ent->client->chase_target - g_edicts;
do {
i++;
if (i > maxclients->value)
i = 1;
e = g_edicts + i;
if (!e->inuse)
continue;
if (e->solid != SOLID_NOT || e->deadflag == DEAD_DEAD)
break;
} while (e != ent->client->chase_target);
ent->client->chase_target = e;
}
void ChasePrev(edict_t *ent)
{
int i;
edict_t *e;
if (!ent->client->chase_target)
return;
i = ent->client->chase_target - g_edicts;
do {
i--;
if (i < 1)
i = maxclients->value;
e = g_edicts + i;
if (!e->inuse)
continue;
if (e->solid != SOLID_NOT || e->deadflag == DEAD_DEAD)
break;
} while (e != ent->client->chase_target);
ent->client->chase_target = e;
}
void GetChaseTarget(edict_t *ent)
{
int i, found, searched;
edict_t *e, *start_target;
start_target = ent->client->resp.last_chase_target;
if (start_target == NULL)
{
start_target = g_edicts + 1;
}
else
{
if (start_target < (g_edicts + 1) ||
start_target > (g_edicts + game.maxclients))
{
gi.dprintf("Warning: start_target ended up out of range\n");
}
}
i = (start_target - g_edicts) + 1;
found = searched = 0;
do
{
searched++;
i--;
if (i < 1)
i = game.maxclients;
e = g_edicts + i;
if (!e->inuse)
continue;
if (e->solid != SOLID_NOT || e->deadflag == DEAD_DEAD)
{
found = 1;
break;
}
} while ((e != (start_target + 1)) && searched < 100);
if (searched >= 100)
{
gi.dprintf("Warning: prevented loop in GetChaseTarget\n");
}
if (found)
{
ent->client->chase_target = e;
}
}

1241
g_cmds.c Normal file

File diff suppressed because it is too large Load diff

941
g_combat.c Normal file
View file

@ -0,0 +1,941 @@
// g_combat.c
#include "g_local.h"
/*
============
CanDamage
Returns true if the inflictor can directly damage the target. Used for
explosions and melee attacks.
============
*/
qboolean CanDamage (edict_t *targ, edict_t *inflictor)
{
vec3_t dest;
trace_t trace;
// bmodels need special checking because their origin is 0,0,0
if (targ->movetype == MOVETYPE_PUSH)
{
VectorAdd (targ->absmin, targ->absmax, dest);
VectorScale (dest, 0.5, dest);
trace = do_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 = do_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 = do_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 = do_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 = do_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 = do_trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
if (trace.fraction == 1.0)
return true;
return false;
}
/*
============
Killed
============
*/
void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
if (targ->health < -999)
targ->health = -999;
if ( targ->client )
{
targ->client->bleeding = 0;
//targ->client->bleedcount = 0;
targ->client->bleed_remain = 0;
}
targ->enemy = attacker;
if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
{
// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type
if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY))
{
level.killed_monsters++;
if (coop->value && attacker->client)
attacker->client->resp.score++;
// medics won't heal monsters that they kill themselves
if (strcmp(attacker->classname, "monster_medic") == 0)
targ->owner = attacker;
}
}
if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE)
{ // doors, triggers, etc
targ->die (targ, inflictor, attacker, damage, point);
return;
}
if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
{
targ->touch = NULL;
monster_death_use (targ);
}
targ->die (targ, inflictor, attacker, damage, point);
}
/*
================
SpawnDamage
================
*/
void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage)
{
if (damage > 255)
damage = 255;
gi.WriteByte (svc_temp_entity);
gi.WriteByte (type);
// gi.WriteByte (damage);
gi.WritePosition (origin);
gi.WriteDir (normal);
gi.multicast (origin, MULTICAST_PVS);
}
/*
============
T_Damage
targ entity that is being damaged
inflictor entity that is causing the damage
attacker entity that caused the inflictor to damage targ
example: targ=monster, inflictor=rocket, attacker=player
dir direction of the attack
point point at which the damage is being inflicted
normal normal vector from that point
damage amount of damage being inflicted
knockback force to be applied against targ as a result of the damage
dflags these flags are used to control how T_Damage works
DAMAGE_RADIUS damage was indirect (from a nearby explosion)
DAMAGE_NO_ARMOR armor does not protect from this damage
DAMAGE_ENERGY damage is from an energy based weapon
DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
DAMAGE_BULLET damage is from a bullet (used for ricochets)
DAMAGE_NO_PROTECTION kills godmode, armor, everything
============
*/
static int CheckPowerArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags)
{
gclient_t *client;
int save;
int power_armor_type;
int index;
int damagePerCell;
int pa_te_type;
int power;
int power_used;
if (!damage)
return 0;
client = ent->client;
if (dflags & DAMAGE_NO_ARMOR)
return 0;
if (client)
{
power_armor_type = PowerArmorType (ent);
if (power_armor_type != POWER_ARMOR_NONE)
{
index = ITEM_INDEX(FindItem("Cells"));
power = client->pers.inventory[index];
}
}
else if (ent->svflags & SVF_MONSTER)
{
power_armor_type = ent->monsterinfo.power_armor_type;
power = ent->monsterinfo.power_armor_power;
}
else
return 0;
if (power_armor_type == POWER_ARMOR_NONE)
return 0;
if (!power)
return 0;
if (power_armor_type == POWER_ARMOR_SCREEN)
{
vec3_t vec;
float dot;
vec3_t forward;
// only works if damage point is in front
AngleVectors (ent->s.angles, forward, NULL, NULL);
VectorSubtract (point, ent->s.origin, vec);
VectorNormalize (vec);
dot = DotProduct (vec, forward);
if (dot <= 0.3)
return 0;
damagePerCell = 1;
pa_te_type = TE_SCREEN_SPARKS;
damage = damage / 3;
}
else
{
damagePerCell = 2;
pa_te_type = TE_SHIELD_SPARKS;
damage = (2 * damage) / 3;
}
save = power * damagePerCell;
if (!save)
return 0;
if (save > damage)
save = damage;
SpawnDamage (pa_te_type, point, normal, save);
ent->powerarmor_time = level.time + 0.2;
power_used = save / damagePerCell;
if (client)
client->pers.inventory[index] -= power_used;
else
ent->monsterinfo.power_armor_power -= power_used;
return save;
}
static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags)
{
gclient_t *client;
int save;
int index;
gitem_t *armor;
if (!damage)
return 0;
client = ent->client;
if (!client)
return 0;
if (dflags & DAMAGE_NO_ARMOR)
return 0;
index = ArmorIndex (ent);
if (!index)
return 0;
armor = GetItemByIndex (index);
if (dflags & DAMAGE_ENERGY)
save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage);
else
save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage);
if (save >= client->pers.inventory[index])
save = client->pers.inventory[index];
if (!save)
return 0;
client->pers.inventory[index] -= save;
SpawnDamage (te_sparks, point, normal, save);
return save;
}
void M_ReactToDamage (edict_t *targ, edict_t *attacker)
{
if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
return;
if (attacker == targ || attacker == targ->enemy)
return;
// if we are a good guy monster and our attacker is a player
// or another good guy, do not get mad at them
if (targ->monsterinfo.aiflags & AI_GOOD_GUY)
{
if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
return;
}
// we now know that we are not both good guys
// if attacker is a client, get mad at them because he's good and we're not
if (attacker->client)
{
// this can only happen in coop (both new and old enemies are clients)
// only switch if can't see the current enemy
if (targ->enemy && targ->enemy->client)
{
if (visible(targ, targ->enemy))
{
targ->oldenemy = attacker;
return;
}
targ->oldenemy = targ->enemy;
}
targ->enemy = attacker;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
FoundTarget (targ);
return;
}
// it's the same base (walk/swim/fly) type and a different classname and it's not a tank
// (they spray too much), get mad at them
if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) &&
(strcmp (targ->classname, attacker->classname) != 0) &&
(strcmp(attacker->classname, "monster_tank") != 0) &&
(strcmp(attacker->classname, "monster_supertank") != 0) &&
(strcmp(attacker->classname, "monster_makron") != 0) &&
(strcmp(attacker->classname, "monster_jorg") != 0) )
{
if (targ->enemy)
if (targ->enemy->client)
targ->oldenemy = targ->enemy;
targ->enemy = attacker;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
FoundTarget (targ);
}
else
// otherwise get mad at whoever they are mad at (help our buddy)
{
if (targ->enemy)
if (targ->enemy->client)
targ->oldenemy = targ->enemy;
targ->enemy = attacker->enemy;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
FoundTarget (targ);
}
}
qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker)
{
//FIXME make the next line real and uncomment this block
// if ((ability to damage a teammate == OFF) && (targ's team == attacker's team))
return false;
}
void BloodSprayThink (edict_t *self)
{
if ( self->dmg > 0 )
{
self->dmg -= 10;
// SpawnDamage (TE_BLOOD, self->s.origin, self->movedir, self->dmg);
/* gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_SPLASH);
gi.WriteByte (6);
gi.WritePosition (self->s.origin);
gi.WriteDir (self->movedir);
gi.WriteByte (6); //blood
gi.multicast (self->s.origin, MULTICAST_PVS);
*/
}
else
{
self->think = G_FreeEdict;
}
self->nextthink = level.time + 0.1;
gi.linkentity (self);
}
void blood_spray_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
if (other == ent->owner)
return;
ent->think = G_FreeEdict;
ent->nextthink = level.time + 0.1;
}
void spray_blood (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed )
{
edict_t *blood;
blood = G_Spawn();
VectorNormalize(dir);
VectorCopy (start, blood->s.origin);
VectorCopy (dir, blood->movedir);
vectoangles (dir, blood->s.angles);
VectorScale (dir, speed, blood->velocity);
blood->movetype = MOVETYPE_BLOOD;
blood->clipmask = MASK_SHOT;
blood->solid = SOLID_BBOX;
blood->s.effects |= EF_GIB;
VectorClear (blood->mins);
VectorClear (blood->maxs);
blood->s.modelindex = gi.modelindex ("sprites/null.sp2");
blood->owner = self;
blood->nextthink = level.time + 0.1;
blood->touch = blood_spray_touch;
blood->think = BloodSprayThink;
blood->dmg = damage;
blood->classname = "blood_spray";
gi.linkentity (blood);
}
void VerifyHeadShot( vec3_t point, vec3_t dir, float height, vec3_t newpoint)
{
vec3_t normdir;
vec3_t normdir2;
VectorNormalize2(dir, normdir);
VectorScale( normdir, height, normdir2 );
VectorAdd( point, normdir2, newpoint );
}
// zucc adding location hit code
// location hit code based off ideas by pyromage and shockman
#define LEG_DAMAGE (height/2.2) - abs(targ->mins[2]) - 3
#define STOMACH_DAMAGE (height/1.8) - abs(targ->mins[2])
#define CHEST_DAMAGE (height/1.4) - abs(targ->mins[2])
#define HEAD_HEIGHT 12.0
qboolean IsFemale (edict_t *ent);
void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod)
{
gclient_t *client;
int take;
int save;
int asave;
int psave;
int te_sparks;
int do_sparks = 0;
int damage_type = 0; // used for MOD later
int bleeding = 0; // damage causes bleeding
int head_success = 0;
int instant_dam = 1;
float z_rel;
int height;
gitem_t* item;
float from_top;
vec3_t line;
vec_t dist;
// do this before teamplay check
if (!targ->takedamage)
return;
//FIREBLADE
if (teamplay->value && mod != MOD_TELEFRAG)
{
if (lights_camera_action)
return;
if (targ != attacker && targ->client && attacker->client &&
targ->client->resp.team == attacker->client->resp.team)
return;
}
//FIREBLADE
item = FindItem(KEV_NAME);
height = abs(targ->mins[2]) + targ->maxs[2];
// locational damage code
// base damage is head shot damage, so all the scaling is downwards
if (targ->client)
{
if (!((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value) && OnSameTeam (targ, attacker)))
{
if ((mod == MOD_MK23) ||
(mod == MOD_MP5) ||
(mod == MOD_M4) ||
(mod == MOD_SNIPER) ||
(mod == MOD_DUAL) ||
(mod == MOD_KNIFE) ||
(mod == MOD_KNIFE_THROWN))
{
z_rel = point[2] - targ->s.origin[2];
from_top = targ->maxs[2] - z_rel;
bleeding = 1;
instant_dam = 0;
// damage reduction for longer range pistol shots
if ( mod == MOD_MK23 || mod == MOD_DUAL )
{
VectorSubtract(targ->s.origin, inflictor->s.origin, line );
dist = VectorLength( line );
if ( dist > 600.0 && dist < 1400.0 )
{
damage = (int)(damage*2/3);
}
else if ( dist > 1400.0 )
damage = (int)(damage*1/2);
}
//gi.cprintf(targ, PRINT_HIGH, "z_rel is %f\n leg: %f stomach: %f chest: %f\n", z_rel, LEG_DAMAGE, STOMACH_DAMAGE, CHEST_DAMAGE );
//gi.cprintf(targ, PRINT_HIGH, "point[2]: %f targ->s.origin[2]: %f height: %d\n", point[2], targ->s.origin[2], height );
//gi.cprintf(targ, PRINT_HIGH, "abs(trag->min[2]): %d targ_max[2] %d\n", (int)abs(targ->mins[2]), (int)targ->maxs[2]);
//gi.cprintf(attacker, PRINT_HIGH, "abs(trag->min[2]): %d targ_max[2] %d\n", (int)abs(targ->mins[2]), (int)targ->maxs[2]);
//gi.cprintf(attacker, PRINT_HIGH, "abs(trag->min[0]): %d targ_max[0] %d\n", (int)abs(targ->mins[0]), (int)targ->maxs[0]);
//gi.cprintf(attacker, PRINT_HIGH, "abs(trag->min[1]): %d targ_max[1] %d\n", (int)abs(targ->mins[1]), (int)targ->maxs[1]);
if ( from_top < 2*HEAD_HEIGHT )
{
vec3_t new_point;
VerifyHeadShot( point, dir, HEAD_HEIGHT, new_point );
VectorSubtract( new_point, targ->s.origin, new_point );
//gi.cprintf(attacker, PRINT_HIGH, "z: %d y: %d x: %d\n", (int)(targ->maxs[2] - new_point[2]),(int)(new_point[1]) , (int)(new_point[0]) );
if ( (targ->maxs[2] - new_point[2]) < HEAD_HEIGHT
&& (abs(new_point[1])) < HEAD_HEIGHT*.8
&& (abs(new_point[0])) < HEAD_HEIGHT*.8 )
{
head_success = 1;
}
}
if ( head_success )
{
damage = damage*1.8 + 1;
gi.cprintf(targ, PRINT_HIGH, "Head damage\n");
if (attacker->client)
gi.cprintf(attacker, PRINT_HIGH, "You hit %s in the head\n", targ->client->pers.netname);
damage_type = LOC_HDAM;
if ( mod != MOD_KNIFE && mod != MOD_KNIFE_THROWN )
gi.sound(targ, CHAN_VOICE, gi.soundindex("misc/headshot.wav"), 1, ATTN_NORM, 0);
//else
// gi.sound(targ, CHAN_VOICE, gi.soundindex("misc/glurp.wav"), 1, ATTN_NORM, 0);
}
else if (z_rel < LEG_DAMAGE)
{
damage = damage * .25;
gi.cprintf(targ, PRINT_HIGH, "Leg damage\n");
if (attacker->client)
gi.cprintf(attacker, PRINT_HIGH, "You hit %s in the legs\n", targ->client->pers.netname);
damage_type = LOC_LDAM;
targ->client->leg_damage = 1;
targ->client->leghits++;
}
else if (z_rel < STOMACH_DAMAGE)
{
damage = damage * .4;
gi.cprintf(targ, PRINT_HIGH, "Stomach damage\n");
if (attacker->client)
gi.cprintf(attacker, PRINT_HIGH, "You hit %s in the stomach\n", targ->client->pers.netname);
damage_type = LOC_SDAM;
}
else //(z_rel < CHEST_DAMAGE)
{
if ( (targ->client->pers.inventory[ITEM_INDEX(item)])
&& mod != MOD_KNIFE
&& mod != MOD_KNIFE_THROWN
&& mod != MOD_SNIPER )
{
if (attacker->client)
{
gi.cprintf(attacker, PRINT_HIGH, "%s has a Kevlar Vest - AIM FOR THE HEAD!\n",
targ->client->pers.netname);
gi.cprintf(targ, PRINT_HIGH, "Kevlar Vest absorbed most of %s's shot\n",
attacker->client->pers.netname);
/*
if (IsFemale(targ))
gi.cprintf(attacker, PRINT_HIGH, "You bruised %s through her Kevlar Vest\n", targ->client->pers.netname);
else
gi.cprintf(attacker, PRINT_HIGH, "You bruised %s through his Kevlar Vest\n", targ->client->pers.netname);
*/
}
gi.sound(targ, CHAN_ITEM, gi.soundindex("misc/vest.wav"), 1, ATTN_NORM, 0);
damage = (int)(damage/10);
damage_type = LOC_CDAM;
bleeding = 0;
instant_dam = 1;
stopAP = 1; do_sparks = 1;
}
else if ( (targ->client->pers.inventory[ITEM_INDEX(item)])
&& mod == MOD_SNIPER )
{
if ( attacker->client )
{
gi.cprintf(attacker, PRINT_HIGH, "%s has a Kevlar Vest, too bad you have AP rounds...\n",
targ->client->pers.netname);
gi.cprintf(targ, PRINT_HIGH, "Kevlar Vest absorbed some of %s's AP sniper round\n",
attacker->client->pers.netname);
}
damage = damage * .325;
damage_type = LOC_CDAM;
}
else
{
damage = damage * .65;
gi.cprintf(targ, PRINT_HIGH, "Chest damage\n");
if (attacker->client)
gi.cprintf(attacker, PRINT_HIGH, "You hit %s in the chest\n", targ->client->pers.netname);
damage_type = LOC_CDAM;
}
}
/*else
{
// no mod to damage
gi.cprintf(targ, PRINT_HIGH, "Head damage\n");
if (attacker->client)
gi.cprintf(attacker, PRINT_HIGH, "You hit %s in the head\n", targ->client->pers.netname);
damage_type = LOC_HDAM;
gi.sound(targ, CHAN_VOICE, gi.soundindex("misc/headshot.wav"), 1, ATTN_NORM, 0);
} */
}
}
}
if ( damage_type && !instant_dam) // bullets but not vest hits
{
vec3_t temp;
vec3_t temporig;
//VectorMA (targ->s.origin, 50, dir, temp);
VectorScale( dir, 20, temp);
VectorAdd( point, temp, temporig );
spray_blood (targ, temporig, dir, damage*3, 1800 );
}
if ( mod == MOD_FALLING)
{
if ( targ->client && targ->health > 0)
{
gi.cprintf(targ, PRINT_HIGH, "Leg damage\n");
targ->client->leg_damage = 1;
targ->client->leghits++;
// bleeding = 1; for testing
}
}
// friendly fire avoidance
// if enabled you can't hurt teammates (but you can hurt yourself)
// knockback still occurs
if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value))
{
if (OnSameTeam (targ, attacker))
{
if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
damage = 0;
else
mod |= MOD_FRIENDLY_FIRE;
}
}
meansOfDeath = mod;
locOfDeath = damage_type; // location
client = targ->client;
if (dflags & DAMAGE_BULLET)
te_sparks = TE_BULLET_SPARKS;
else
te_sparks = TE_SPARKS;
VectorNormalize(dir);
// bonus damage for suprising a monster
// if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
// damage *= 2;
if (targ->flags & FL_NO_KNOCKBACK)
knockback = 0;
// figure momentum add
if (!(dflags & DAMAGE_NO_KNOCKBACK))
{
if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
{
vec3_t kvel, flydir;
float mass;
if ( mod != MOD_FALLING )
{
VectorCopy(dir, flydir);
flydir[2] += 0.4;
}
if (targ->mass < 50)
mass = 50;
else
mass = targ->mass;
if (targ->client && attacker == targ)
VectorScale (flydir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack...
else
VectorScale (flydir, 500.0 * (float)knockback / mass, kvel);
// FB
//if (mod == MOD_KICK )
//{
// kvel[2] = 0;
//}
VectorAdd (targ->velocity, kvel, targ->velocity);
}
}
take = damage;
save = 0;
// check for godmode
if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) )
{
take = 0;
save = damage;
SpawnDamage (te_sparks, point, normal, save);
}
// zucc don't need this stuff, but to remove it need to change how damagefeedback works with colors
// check for invincibility
if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
{
if (targ->pain_debounce_time < level.time)
{
gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
targ->pain_debounce_time = level.time + 2;
}
take = 0;
save = damage;
}
psave = CheckPowerArmor (targ, point, normal, take, dflags);
take -= psave;
asave = CheckArmor (targ, point, normal, take, te_sparks, dflags);
take -= asave;
//treat cheat/powerup savings the same as armor
asave += save;
// team damage avoidance
if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
return;
if ( (mod == MOD_M3) || (mod == MOD_HC) || (mod == MOD_HELD_GRENADE) ||
(mod == MOD_HG_SPLASH) || (mod == MOD_G_SPLASH) )
{
bleeding = 1;
instant_dam = 0;
}
/* if ( (mod == MOD_M3) || (mod == MOD_HC) )
{
instant_dam = 1;
remain = take % 2;
take = (int)(take/2); // balances out difference in how action and axshun handle damage/bleeding
}
*/
// do the damage
if (take)
{
// zucc added check for stopAP, if it hit a vest we want sparks
if (((targ->svflags & SVF_MONSTER) || (client)) && !do_sparks )
SpawnDamage (TE_BLOOD, point, normal, take);
else
SpawnDamage (te_sparks, point, normal, take);
// all things that have at least some instantaneous damage, i.e. bruising/falling
if ( instant_dam )
targ->health = targ->health - take;
if (targ->health <= 0)
{
if ((targ->svflags & SVF_MONSTER) || (client))
targ->flags |= FL_NO_KNOCKBACK;
Killed (targ, inflictor, attacker, take, point);
return;
}
}
if (targ->svflags & SVF_MONSTER)
{
M_ReactToDamage (targ, attacker);
if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take))
{
targ->pain (targ, attacker, knockback, take);
// nightmare mode monsters don't go into pain frames often
if (skill->value == 3)
targ->pain_debounce_time = level.time + 5;
}
}
else if (client)
{
if (!(targ->flags & FL_GODMODE) && (take))
targ->pain (targ, attacker, knockback, take);
}
else if (take)
{
if (targ->pain)
targ->pain (targ, attacker, knockback, take);
}
// add to the damage inflicted on a player this frame
// the total will be turned into screen blends and view angle kicks
// at the end of the frame
if (client)
{
client->damage_parmor += psave;
client->damage_armor += asave;
client->damage_blood += take;
client->damage_knockback += knockback;
//zucc handle adding bleeding here
if ( damage_type && bleeding ) // one of the hit location weapons
{
/* zucc add in partial bleeding, changed
if ( client->bleeding < 4*damage*BLEED_TIME )
{
client->bleeding = 4*damage*BLEED_TIME + client->bleeding/2;
}
else
{
client->bleeding += damage*BLEED_TIME*2;
}*/
client->bleeding += damage*BLEED_TIME;
VectorSubtract (point, targ->absmax, targ->client->bleedloc_offset);
//VectorSubtract(point, targ->s.origin, client->bleedloc_offset);
}
else if ( bleeding )
{
/*
if ( client->bleeding < damage*BLEED_TIME )
{
client->bleeding = damage*BLEED_TIME;
//client->bleedcount = 0;
}*/
client->bleeding += damage*BLEED_TIME;
VectorSubtract (point, targ->absmax, targ->client->bleedloc_offset);
//VectorSubtract(point, targ->s.origin, client->bleedloc_offset);
}
if ( attacker->client )
{
client->attacker = attacker;
client->attacker_mod = mod;
client->attacker_loc = damage_type;
client->push_timeout = 50;
//VectorCopy(dir, client->bleeddir );
//VectorCopy(point, client->bleedpoint );
//VectorCopy(normal, client->bleednormal);
}
VectorCopy (point, client->damage_from);
}
}
/*
============
T_RadiusDamage
============
*/
void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod)
{
float points;
edict_t *ent = NULL;
vec3_t v;
vec3_t dir;
while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
{
if (ent == ignore)
continue;
if (!ent->takedamage)
continue;
VectorAdd (ent->mins, ent->maxs, v);
VectorMA (ent->s.origin, 0.5, v, v);
VectorSubtract (inflictor->s.origin, v, v);
points = damage - 0.5 * VectorLength (v);
//zucc reduce damage for crouching, max is 32 when standing
if (ent->maxs[2] < 20 )
{
points = points * 0.5; // hefty reduction in damage
}
//if (ent == attacker)
//points = points * 0.5;
if (points > 0)
{
if (CanDamage (ent, inflictor))
{
VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
// zucc scaled up knockback(kick) of grenades
T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)(points*.75), (int)(points*.75), DAMAGE_RADIUS, mod);
}
}
}
}

2119
g_func.c Normal file

File diff suppressed because it is too large Load diff

2743
g_items.c Normal file

File diff suppressed because it is too large Load diff

1377
g_local.h Normal file

File diff suppressed because it is too large Load diff

419
g_main.c Normal file
View file

@ -0,0 +1,419 @@
#include "g_local.h"
game_locals_t game;
level_locals_t level;
game_import_t gi;
game_export_t globals;
spawn_temp_t st;
int sm_meat_index;
int snd_fry;
int meansOfDeath;
// zucc for location
int locOfDeath;
int stopAP;
edict_t *g_edicts;
//FIREBLADE
cvar_t *hostname;
cvar_t *teamplay;
cvar_t *radiolog;
cvar_t *motd_time;
cvar_t *actionmaps;
cvar_t *roundtimelimit;
cvar_t *roundlimit;
cvar_t *nohud;
cvar_t *noscore;
cvar_t *actionversion;
//FIREBLADE
cvar_t *deathmatch;
cvar_t *coop;
cvar_t *dmflags;
cvar_t *skill;
cvar_t *fraglimit;
cvar_t *timelimit;
cvar_t *password;
cvar_t *maxclients;
cvar_t *maxentities;
cvar_t *g_select_empty;
cvar_t *dedicated;
cvar_t *filterban;
cvar_t *sv_maxvelocity;
cvar_t *sv_gravity;
cvar_t *sv_rollspeed;
cvar_t *sv_rollangle;
cvar_t *gun_x;
cvar_t *gun_y;
cvar_t *gun_z;
cvar_t *run_pitch;
cvar_t *run_roll;
cvar_t *bob_up;
cvar_t *bob_pitch;
cvar_t *bob_roll;
cvar_t *sv_cheats;
cvar_t *flood_msgs;
cvar_t *flood_persecond;
cvar_t *flood_waitdelay;
//zucc server variables
cvar_t *unique_weapons;
cvar_t *unique_items;
cvar_t *ir;
cvar_t *knifelimit;
cvar_t *tgren;
cvar_t *allweapon;
cvar_t *allitem;
//zucc from action
cvar_t *sv_shelloff;
cvar_t *bholelimit;
cvar_t *splatlimit;
void SpawnEntities (char *mapname, char *entities, char *spawnpoint);
void ClientThink (edict_t *ent, usercmd_t *cmd);
qboolean ClientConnect (edict_t *ent, char *userinfo);
void ClientUserinfoChanged (edict_t *ent, char *userinfo);
void ClientDisconnect (edict_t *ent);
void ClientBegin (edict_t *ent);
void ClientCommand (edict_t *ent);
void RunEntity (edict_t *ent);
void WriteGame (char *filename, qboolean autosave);
void ReadGame (char *filename);
void WriteLevel (char *filename);
void ReadLevel (char *filename);
void InitGame (void);
void G_RunFrame (void);
//===================================================================
void ShutdownGame (void)
{
gi.dprintf ("==== ShutdownGame ====\n");
gi.FreeTags (TAG_LEVEL);
gi.FreeTags (TAG_GAME);
}
/*
=================
GetGameAPI
Returns a pointer to the structure with all entry points
and global variables
=================
*/
game_export_t *GetGameAPI (game_import_t *import)
{
gi = *import;
globals.apiversion = GAME_API_VERSION;
globals.Init = InitGame;
globals.Shutdown = ShutdownGame;
globals.SpawnEntities = SpawnEntities;
globals.WriteGame = WriteGame;
globals.ReadGame = ReadGame;
globals.WriteLevel = WriteLevel;
globals.ReadLevel = ReadLevel;
globals.ClientThink = ClientThink;
globals.ClientConnect = ClientConnect;
globals.ClientUserinfoChanged = ClientUserinfoChanged;
globals.ClientDisconnect = ClientDisconnect;
globals.ClientBegin = ClientBegin;
globals.ClientCommand = ClientCommand;
globals.RunFrame = G_RunFrame;
globals.ServerCommand = ServerCommand;
globals.edict_size = sizeof(edict_t);
return &globals;
}
#ifndef GAME_HARD_LINKED
// this is only here so the functions in q_shared.c and q_shwin.c can link
void Sys_Error (char *error, ...)
{
va_list argptr;
char text[1024];
va_start (argptr, error);
vsprintf (text, error, argptr);
va_end (argptr);
gi.error (ERR_FATAL, "%s", text);
}
void Com_Printf (char *msg, ...)
{
va_list argptr;
char text[1024];
va_start (argptr, msg);
vsprintf (text, msg, argptr);
va_end (argptr);
gi.dprintf ("%s", text);
}
#endif
//======================================================================
/*
=================
ClientEndServerFrames
=================
*/
void ClientEndServerFrames (void)
{
int i;
edict_t *ent;
// calc the player views now that all pushing
// and damage has been added
for (i=0 ; i<maxclients->value ; i++)
{
ent = g_edicts + 1 + i;
if (!ent->inuse || !ent->client)
continue;
ClientEndServerFrame (ent);
}
}
/*
=================
EndDMLevel
The timelimit or fraglimit has been exceeded
=================
*/
void EndDMLevel (void)
{
edict_t *ent;
char *nextmapname = NULL;
// stay on same level flag
if ((int)dmflags->value & DF_SAME_LEVEL)
{
ent = G_Spawn ();
ent->classname = "target_changelevel";
nextmapname = ent->map = level.mapname;
}
//FIREBLADE
else if (!actionmaps->value || num_maps < 1)
//FIREBLADE
{
if (level.nextmap[0])
{ // go to a specific map
ent = G_Spawn ();
ent->classname = "target_changelevel";
nextmapname = ent->map = level.nextmap;
}
else
{ // search for a changelevel
ent = G_Find (NULL, FOFS(classname), "target_changelevel");
if (!ent)
{ // the map designer didn't include a changelevel,
// so create a fake ent that goes back to the same level
ent = G_Spawn ();
ent->classname = "target_changelevel";
nextmapname = ent->map = level.mapname;
}
}
}
//FIREBLADE
else
{
cur_map++;
if (cur_map >= num_maps)
cur_map = 0;
ent = G_Spawn();
ent->classname = "target_changelevel";
Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map_rotation[cur_map]);
nextmapname = ent->map = level.nextmap;
}
if (level.nextmap != NULL)
{
gi.bprintf(PRINT_HIGH, "Next map in rotation is %s.\n",
level.nextmap);
}
//FIREBLADE
BeginIntermission (ent);
}
/*
=================
CheckDMRules
=================
*/
void CheckDMRules (void)
{
int i;
gclient_t *cl;
if (level.intermissiontime)
return;
if (!deathmatch->value)
return;
//FIREBLADE
if (teamplay->value)
{
CheckTeamRules();
}
else /* not teamplay */
{
if (timelimit->value)
{
if (level.time >= timelimit->value*60)
{
gi.bprintf (PRINT_HIGH, "Timelimit hit.\n");
EndDMLevel ();
return;
}
}
//FIREBLADE
}
if (fraglimit->value)
{
for (i=0 ; i<maxclients->value ; i++)
{
cl = game.clients + i;
if (!g_edicts[i+1].inuse)
continue;
if (cl->resp.score >= fraglimit->value)
{
gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n");
EndDMLevel ();
return;
}
}
}
}
/*
=============
ExitLevel
=============
*/
void ExitLevel (void)
{
int i;
edict_t *ent;
char command [256];
Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap);
gi.AddCommandString (command);
level.changemap = NULL;
level.exitintermission = 0;
level.intermissiontime = 0;
ClientEndServerFrames ();
// clear some things before going to next level
for (i=0 ; i<maxclients->value ; i++)
{
ent = g_edicts + 1 + i;
if (!ent->inuse)
continue;
if (ent->health > ent->client->pers.max_health)
ent->health = ent->client->pers.max_health;
}
//FIREBLADE
if (teamplay->value)
{
team1_score = 0;
team2_score = 0;
}
//FIREBLADE
}
/*
================
G_RunFrame
Advances the world by 0.1 seconds
================
*/
void G_RunFrame (void)
{
int i;
edict_t *ent;
level.framenum++;
level.time = level.framenum*FRAMETIME;
// choose a client for monsters to target this frame
AI_SetSightClient ();
// exit intermissions
if (level.exitintermission)
{
ExitLevel ();
return;
}
//
// treat each object in turn
// even the world gets a chance to think
//
ent = &g_edicts[0];
for (i=0 ; i<globals.num_edicts ; i++, ent++)
{
if (!ent->inuse)
continue;
level.current_entity = ent;
VectorCopy (ent->s.origin, ent->s.old_origin);
// if the ground entity moved, make sure we are still on it
if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount))
{
ent->groundentity = NULL;
if ( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->svflags & SVF_MONSTER) )
{
M_CheckGround (ent);
}
}
if (i > 0 && i <= maxclients->value)
{
ClientBeginServerFrame (ent);
continue;
}
G_RunEntity (ent);
}
// see if it is time to end a deathmatch
CheckDMRules ();
// build the playerstate_t structures for all players
ClientEndServerFrames ();
}

1856
g_misc.c Normal file

File diff suppressed because it is too large Load diff

721
g_monster.c Normal file
View file

@ -0,0 +1,721 @@
#include "g_local.h"
//
// monster weapons
//
//FIXME mosnters should call these with a totally accurate direction
// and we can mess it up based on skill. Spread should be for normal
// and we can tighten or loosen based on skill. We could muck with
// the damages too, but I'm not sure that's such a good idea.
void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype)
{
fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype)
{
fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
{
fire_blaster (self, start, dir, damage, speed, effect, false);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype)
{
fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype)
{
fire_rocket (self, start, dir, damage, speed, damage+20, damage);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype)
{
fire_rail (self, start, aimdir, damage, kick);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype)
{
fire_bfg (self, start, aimdir, damage, speed, damage_radius);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
//
// Monster utility functions
//
static void M_FliesOff (edict_t *self)
{
self->s.effects &= ~EF_FLIES;
self->s.sound = 0;
}
static void M_FliesOn (edict_t *self)
{
if (self->waterlevel)
return;
self->s.effects |= EF_FLIES;
self->s.sound = gi.soundindex ("infantry/inflies1.wav");
self->think = M_FliesOff;
self->nextthink = level.time + 60;
}
void M_FlyCheck (edict_t *self)
{
if (self->waterlevel)
return;
if (random() > 0.5)
return;
self->think = M_FliesOn;
self->nextthink = level.time + 5 + 10 * random();
}
void AttackFinished (edict_t *self, float time)
{
self->monsterinfo.attack_finished = level.time + time;
}
void M_CheckGround (edict_t *ent)
{
vec3_t point;
trace_t trace;
if (ent->flags & (FL_SWIM|FL_FLY))
return;
if (ent->velocity[2] > 100)
{
ent->groundentity = NULL;
return;
}
// if the hull point one-quarter unit down is solid the entity is on ground
point[0] = ent->s.origin[0];
point[1] = ent->s.origin[1];
point[2] = ent->s.origin[2] - 0.25;
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID);
// check steepness
if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
{
ent->groundentity = NULL;
return;
}
// ent->groundentity = trace.ent;
// ent->groundentity_linkcount = trace.ent->linkcount;
// if (!trace.startsolid && !trace.allsolid)
// VectorCopy (trace.endpos, ent->s.origin);
if (!trace.startsolid && !trace.allsolid)
{
VectorCopy (trace.endpos, ent->s.origin);
ent->groundentity = trace.ent;
ent->groundentity_linkcount = trace.ent->linkcount;
ent->velocity[2] = 0;
}
}
void M_CatagorizePosition (edict_t *ent)
{
vec3_t point;
int cont;
//
// get waterlevel
//
point[0] = ent->s.origin[0];
point[1] = ent->s.origin[1];
point[2] = ent->s.origin[2] + ent->mins[2] + 1;
cont = gi.pointcontents (point);
if (!(cont & MASK_WATER))
{
ent->waterlevel = 0;
ent->watertype = 0;
return;
}
ent->watertype = cont;
ent->waterlevel = 1;
point[2] += 26;
cont = gi.pointcontents (point);
if (!(cont & MASK_WATER))
return;
ent->waterlevel = 2;
point[2] += 22;
cont = gi.pointcontents (point);
if (cont & MASK_WATER)
ent->waterlevel = 3;
}
void M_WorldEffects (edict_t *ent)
{
int dmg;
if (ent->health > 0)
{
if (!(ent->flags & FL_SWIM))
{
if (ent->waterlevel < 3)
{
ent->air_finished = level.time + 12;
}
else if (ent->air_finished < level.time)
{ // drown!
if (ent->pain_debounce_time < level.time)
{
dmg = 2 + 2 * floor(level.time - ent->air_finished);
if (dmg > 15)
dmg = 15;
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
ent->pain_debounce_time = level.time + 1;
}
}
}
else
{
if (ent->waterlevel > 0)
{
ent->air_finished = level.time + 9;
}
else if (ent->air_finished < level.time)
{ // suffocate!
if (ent->pain_debounce_time < level.time)
{
dmg = 2 + 2 * floor(level.time - ent->air_finished);
if (dmg > 15)
dmg = 15;
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
ent->pain_debounce_time = level.time + 1;
}
}
}
}
if (ent->waterlevel == 0)
{
if (ent->flags & FL_INWATER)
{
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
ent->flags &= ~FL_INWATER;
}
return;
}
if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
{
if (ent->damage_debounce_time < level.time)
{
ent->damage_debounce_time = level.time + 0.2;
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA);
}
}
if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
{
if (ent->damage_debounce_time < level.time)
{
ent->damage_debounce_time = level.time + 1;
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME);
}
}
if ( !(ent->flags & FL_INWATER) )
{
if (!(ent->svflags & SVF_DEADMONSTER))
{
if (ent->watertype & CONTENTS_LAVA)
if (random() <= 0.5)
gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
else
gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
else if (ent->watertype & CONTENTS_SLIME)
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
else if (ent->watertype & CONTENTS_WATER)
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
}
ent->flags |= FL_INWATER;
ent->damage_debounce_time = 0;
}
}
void M_droptofloor (edict_t *ent)
{
vec3_t end;
trace_t trace;
ent->s.origin[2] += 1;
VectorCopy (ent->s.origin, end);
end[2] -= 256;
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
if (trace.fraction == 1 || trace.allsolid)
return;
VectorCopy (trace.endpos, ent->s.origin);
gi.linkentity (ent);
M_CheckGround (ent);
M_CatagorizePosition (ent);
}
void M_SetEffects (edict_t *ent)
{
ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN);
ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
if (ent->monsterinfo.aiflags & AI_RESURRECTING)
{
ent->s.effects |= EF_COLOR_SHELL;
ent->s.renderfx |= RF_SHELL_RED;
}
if (ent->health <= 0)
return;
if (ent->powerarmor_time > level.time)
{
if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN)
{
ent->s.effects |= EF_POWERSCREEN;
}
else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD)
{
ent->s.effects |= EF_COLOR_SHELL;
ent->s.renderfx |= RF_SHELL_GREEN;
}
}
}
void M_MoveFrame (edict_t *self)
{
mmove_t *move;
int index;
move = self->monsterinfo.currentmove;
self->nextthink = level.time + FRAMETIME;
if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe))
{
self->s.frame = self->monsterinfo.nextframe;
self->monsterinfo.nextframe = 0;
}
else
{
if (self->s.frame == move->lastframe)
{
if (move->endfunc)
{
move->endfunc (self);
// regrab move, endfunc is very likely to change it
move = self->monsterinfo.currentmove;
// check for death
if (self->svflags & SVF_DEADMONSTER)
return;
}
}
if (self->s.frame < move->firstframe || self->s.frame > move->lastframe)
{
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
self->s.frame = move->firstframe;
}
else
{
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
{
self->s.frame++;
if (self->s.frame > move->lastframe)
self->s.frame = move->firstframe;
}
}
}
index = self->s.frame - move->firstframe;
if (move->frame[index].aifunc)
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale);
else
move->frame[index].aifunc (self, 0);
if (move->frame[index].thinkfunc)
move->frame[index].thinkfunc (self);
}
void monster_think (edict_t *self)
{
M_MoveFrame (self);
if (self->linkcount != self->monsterinfo.linkcount)
{
self->monsterinfo.linkcount = self->linkcount;
M_CheckGround (self);
}
M_CatagorizePosition (self);
M_WorldEffects (self);
M_SetEffects (self);
}
/*
================
monster_use
Using a monster makes it angry at the current activator
================
*/
void monster_use (edict_t *self, edict_t *other, edict_t *activator)
{
if (self->enemy)
return;
if (self->health <= 0)
return;
if (activator->flags & FL_NOTARGET)
return;
if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
return;
// delay reaction so if the monster is teleported, its sound is still heard
self->enemy = activator;
FoundTarget (self);
}
void monster_start_go (edict_t *self);
void monster_triggered_spawn (edict_t *self)
{
self->s.origin[2] += 1;
KillBox (self);
self->solid = SOLID_BBOX;
self->movetype = MOVETYPE_STEP;
self->svflags &= ~SVF_NOCLIENT;
self->air_finished = level.time + 12;
gi.linkentity (self);
monster_start_go (self);
if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
{
FoundTarget (self);
}
else
{
self->enemy = NULL;
}
}
void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
{
// we have a one frame delay here so we don't telefrag the guy who activated us
self->think = monster_triggered_spawn;
self->nextthink = level.time + FRAMETIME;
if (activator->client)
self->enemy = activator;
self->use = monster_use;
}
void monster_triggered_start (edict_t *self)
{
self->solid = SOLID_NOT;
self->movetype = MOVETYPE_NONE;
self->svflags |= SVF_NOCLIENT;
self->nextthink = 0;
self->use = monster_triggered_spawn_use;
}
/*
================
monster_death_use
When a monster dies, it fires all of its targets with the current
enemy as activator.
================
*/
void monster_death_use (edict_t *self)
{
self->flags &= ~(FL_FLY|FL_SWIM);
self->monsterinfo.aiflags &= AI_GOOD_GUY;
if (self->item)
{
Drop_Item (self, self->item);
self->item = NULL;
}
if (self->deathtarget)
self->target = self->deathtarget;
if (!self->target)
return;
G_UseTargets (self, self->enemy);
}
//============================================================================
qboolean monster_start (edict_t *self)
{
if (deathmatch->value)
{
G_FreeEdict (self);
return false;
}
if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
{
self->spawnflags &= ~4;
self->spawnflags |= 1;
// gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin));
}
if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
level.total_monsters++;
self->nextthink = level.time + FRAMETIME;
self->svflags |= SVF_MONSTER;
self->s.renderfx |= RF_FRAMELERP;
self->takedamage = DAMAGE_AIM;
self->air_finished = level.time + 12;
self->use = monster_use;
self->max_health = self->health;
self->clipmask = MASK_MONSTERSOLID;
self->s.skinnum = 0;
self->deadflag = DEAD_NO;
self->svflags &= ~SVF_DEADMONSTER;
if (!self->monsterinfo.checkattack)
self->monsterinfo.checkattack = M_CheckAttack;
VectorCopy (self->s.origin, self->s.old_origin);
if (st.item)
{
self->item = FindItemByClassname (st.item);
if (!self->item)
gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
}
// randomize what frame they start on
if (self->monsterinfo.currentmove)
self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
return true;
}
void monster_start_go (edict_t *self)
{
vec3_t v;
if (self->health <= 0)
return;
// check for target to combat_point and change to combattarget
if (self->target)
{
qboolean notcombat;
qboolean fixup;
edict_t *target;
target = NULL;
notcombat = false;
fixup = false;
while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL)
{
if (strcmp(target->classname, "point_combat") == 0)
{
self->combattarget = self->target;
fixup = true;
}
else
{
notcombat = true;
}
}
if (notcombat && self->combattarget)
gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin));
if (fixup)
self->target = NULL;
}
// validate combattarget
if (self->combattarget)
{
edict_t *target;
target = NULL;
while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL)
{
if (strcmp(target->classname, "point_combat") != 0)
{
gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2],
self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1],
(int)target->s.origin[2]);
}
}
}
if (self->target)
{
self->goalentity = self->movetarget = G_PickTarget(self->target);
if (!self->movetarget)
{
gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
self->target = NULL;
self->monsterinfo.pausetime = 100000000;
self->monsterinfo.stand (self);
}
else if (strcmp (self->movetarget->classname, "path_corner") == 0)
{
VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
self->monsterinfo.walk (self);
self->target = NULL;
}
else
{
self->goalentity = self->movetarget = NULL;
self->monsterinfo.pausetime = 100000000;
self->monsterinfo.stand (self);
}
}
else
{
self->monsterinfo.pausetime = 100000000;
self->monsterinfo.stand (self);
}
self->think = monster_think;
self->nextthink = level.time + FRAMETIME;
}
void walkmonster_start_go (edict_t *self)
{
if (!(self->spawnflags & 2) && level.time < 1)
{
M_droptofloor (self);
if (self->groundentity)
if (!M_walkmove (self, 0, 0))
gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
}
if (!self->yaw_speed)
self->yaw_speed = 20;
self->viewheight = 25;
monster_start_go (self);
if (self->spawnflags & 2)
monster_triggered_start (self);
}
void walkmonster_start (edict_t *self)
{
self->think = walkmonster_start_go;
monster_start (self);
}
void flymonster_start_go (edict_t *self)
{
if (!M_walkmove (self, 0, 0))
gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
if (!self->yaw_speed)
self->yaw_speed = 10;
self->viewheight = 25;
monster_start_go (self);
if (self->spawnflags & 2)
monster_triggered_start (self);
}
void flymonster_start (edict_t *self)
{
self->flags |= FL_FLY;
self->think = flymonster_start_go;
monster_start (self);
}
void swimmonster_start_go (edict_t *self)
{
if (!self->yaw_speed)
self->yaw_speed = 10;
self->viewheight = 10;
monster_start_go (self);
if (self->spawnflags & 2)
monster_triggered_start (self);
}
void swimmonster_start (edict_t *self)
{
self->flags |= FL_SWIM;
self->think = swimmonster_start_go;
monster_start (self);
}

1089
g_phys.c Normal file

File diff suppressed because it is too large Load diff

741
g_save.c Normal file
View file

@ -0,0 +1,741 @@
#include "g_local.h"
field_t fields[] = {
{"classname", FOFS(classname), F_LSTRING},
{"origin", FOFS(s.origin), F_VECTOR},
{"model", FOFS(model), F_LSTRING},
{"spawnflags", FOFS(spawnflags), F_INT},
{"speed", FOFS(speed), F_FLOAT},
{"accel", FOFS(accel), F_FLOAT},
{"decel", FOFS(decel), F_FLOAT},
{"target", FOFS(target), F_LSTRING},
{"targetname", FOFS(targetname), F_LSTRING},
{"pathtarget", FOFS(pathtarget), F_LSTRING},
{"deathtarget", FOFS(deathtarget), F_LSTRING},
{"killtarget", FOFS(killtarget), F_LSTRING},
{"combattarget", FOFS(combattarget), F_LSTRING},
{"message", FOFS(message), F_LSTRING},
{"team", FOFS(team), F_LSTRING},
{"wait", FOFS(wait), F_FLOAT},
{"delay", FOFS(delay), F_FLOAT},
{"random", FOFS(random), F_FLOAT},
{"move_origin", FOFS(move_origin), F_VECTOR},
{"move_angles", FOFS(move_angles), F_VECTOR},
{"style", FOFS(style), F_INT},
{"count", FOFS(count), F_INT},
{"health", FOFS(health), F_INT},
{"sounds", FOFS(sounds), F_INT},
{"light", 0, F_IGNORE},
{"dmg", FOFS(dmg), F_INT},
{"angles", FOFS(s.angles), F_VECTOR},
{"angle", FOFS(s.angles), F_ANGLEHACK},
{"mass", FOFS(mass), F_INT},
{"volume", FOFS(volume), F_FLOAT},
{"attenuation", FOFS(attenuation), F_FLOAT},
{"map", FOFS(map), F_LSTRING},
// temp spawn vars -- only valid when the spawn function is called
{"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP},
{"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP},
{"height", STOFS(height), F_INT, FFL_SPAWNTEMP},
{"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP},
{"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP},
{"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP},
{"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP},
{"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP},
{"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP},
{"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP},
{"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP},
{"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP},
{"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP},
{"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP},
{"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP}
};
// -------- just for savegames ----------
// all pointer fields should be listed here, or savegames
// won't work properly (they will crash and burn).
// this wasn't just tacked on to the fields array, because
// these don't need names, we wouldn't want map fields using
// some of these, and if one were accidentally present twice
// it would double swizzle (fuck) the pointer.
field_t savefields[] =
{
{"", FOFS(classname), F_LSTRING},
{"", FOFS(target), F_LSTRING},
{"", FOFS(targetname), F_LSTRING},
{"", FOFS(killtarget), F_LSTRING},
{"", FOFS(team), F_LSTRING},
{"", FOFS(pathtarget), F_LSTRING},
{"", FOFS(deathtarget), F_LSTRING},
{"", FOFS(combattarget), F_LSTRING},
{"", FOFS(model), F_LSTRING},
{"", FOFS(map), F_LSTRING},
{"", FOFS(message), F_LSTRING},
{"", FOFS(client), F_CLIENT},
{"", FOFS(item), F_ITEM},
{"", FOFS(goalentity), F_EDICT},
{"", FOFS(movetarget), F_EDICT},
{"", FOFS(enemy), F_EDICT},
{"", FOFS(oldenemy), F_EDICT},
{"", FOFS(activator), F_EDICT},
{"", FOFS(groundentity), F_EDICT},
{"", FOFS(teamchain), F_EDICT},
{"", FOFS(teammaster), F_EDICT},
{"", FOFS(owner), F_EDICT},
{"", FOFS(mynoise), F_EDICT},
{"", FOFS(mynoise2), F_EDICT},
{"", FOFS(target_ent), F_EDICT},
{"", FOFS(chain), F_EDICT},
{NULL, 0, F_INT}
};
field_t levelfields[] =
{
{"", LLOFS(changemap), F_LSTRING},
{"", LLOFS(sight_client), F_EDICT},
{"", LLOFS(sight_entity), F_EDICT},
{"", LLOFS(sound_entity), F_EDICT},
{"", LLOFS(sound2_entity), F_EDICT},
{NULL, 0, F_INT}
};
field_t clientfields[] =
{
{"", CLOFS(pers.weapon), F_ITEM},
{"", CLOFS(pers.lastweapon), F_ITEM},
{"", CLOFS(newweapon), F_ITEM},
{NULL, 0, F_INT}
};
/*
============
InitGame
This will be called when the dll is first loaded, which
only happens when a new game is started or a save game
is loaded.
============
*/
void InitGame (void)
{
gi.dprintf ("==== InitGame ====\n");
ReadConfigFile();
ReadMOTDFile();
gun_x = gi.cvar ("gun_x", "0", 0);
gun_y = gi.cvar ("gun_y", "0", 0);
gun_z = gi.cvar ("gun_z", "0", 0);
//FIXME: sv_ prefix is wrong for these
sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0);
sv_rollangle = gi.cvar ("sv_rollangle", "2", 0);
sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0);
sv_gravity = gi.cvar ("sv_gravity", "800", 0);
// noset vars
dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET);
// latched vars
sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH);
gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH);
maxclients = gi.cvar ("maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH);
deathmatch = gi.cvar ("deathmatch", "1", CVAR_LATCH);
coop = gi.cvar ("coop", "0", CVAR_LATCH);
skill = gi.cvar ("skill", "1", CVAR_LATCH);
maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH);
// change anytime vars
dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO);
fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO);
timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO);
password = gi.cvar ("password", "", CVAR_USERINFO);
filterban = gi.cvar("filterban", "1", 0);
//FIREBLADE
radiolog = gi.cvar("radiolog", "0", CVAR_SERVERINFO);
teamplay = gi.cvar ("teamplay", "0", CVAR_SERVERINFO|CVAR_LATCH);
motd_time = gi.cvar("motd_time", "2", CVAR_SERVERINFO);
hostname = gi.cvar("hostname", "unnamed", CVAR_SERVERINFO);
actionmaps = gi.cvar ("actionmaps", "1", CVAR_SERVERINFO);
if (actionmaps->value && num_maps < 1)
{
gi.dprintf("No maps were read from the config file, \"actionmaps\" won't be used.\n");
gi.cvar_set("actionmaps", "0");
}
nohud = gi.cvar("nohud", "0", CVAR_SERVERINFO|CVAR_LATCH);
roundlimit = gi.cvar("roundlimit", "0", CVAR_SERVERINFO);
roundtimelimit = gi.cvar("roundtimelimit", "0", CVAR_SERVERINFO);
noscore = gi.cvar("noscore", "0", CVAR_SERVERINFO|CVAR_LATCH);
actionversion = gi.cvar("actionversion", "none set", CVAR_SERVERINFO|CVAR_LATCH);
gi.cvar_set("actionversion", ACTION_VERSION);
//FIREBLADE
//zucc get server variables
unique_weapons = gi.cvar("weapons",
//FIREBLADE
// zucc changed teamplay to 1
teamplay->value ? "1" : "1", CVAR_SERVERINFO|CVAR_LATCH);
//FIREBLADE
unique_items = gi.cvar("items", "1", CVAR_SERVERINFO|CVAR_LATCH);
// zucc changed ir to 1, enabled
ir = gi.cvar("ir", "1", CVAR_SERVERINFO);
knifelimit = gi.cvar ("knifelimit", "40", CVAR_SERVERINFO);
allweapon = gi.cvar ("allweapon", "0", CVAR_SERVERINFO);
allitem = gi.cvar ("allitem", "0", CVAR_SERVERINFO);
tgren = gi.cvar ("tgren", "0", CVAR_SERVERINFO);
// zucc from action
sv_shelloff = gi.cvar ("shelloff", "1", CVAR_SERVERINFO);
bholelimit = gi.cvar ("bholelimit", "0", CVAR_SERVERINFO);
splatlimit = gi.cvar ("splatlimit", "0", CVAR_SERVERINFO);
g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE);
run_pitch = gi.cvar ("run_pitch", "0.002", 0);
run_roll = gi.cvar ("run_roll", "0.005", 0);
bob_up = gi.cvar ("bob_up", "0.005", 0);
bob_pitch = gi.cvar ("bob_pitch", "0.002", 0);
bob_roll = gi.cvar ("bob_roll", "0.002", 0);
// flood control
flood_msgs = gi.cvar ("flood_msgs", "4", 0);
flood_persecond = gi.cvar ("flood_persecond", "4", 0);
flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0);
// items
InitItems ();
Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), "");
Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), "");
// initialize all entities for this game
game.maxentities = maxentities->value;
g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
globals.edicts = g_edicts;
globals.max_edicts = game.maxentities;
// initialize all clients for this game
game.maxclients = maxclients->value;
game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
globals.num_edicts = game.maxclients+1;
}
//=========================================================
void WriteField1 (FILE *f, field_t *field, byte *base)
{
void *p;
int len;
int index;
p = (void *)(base + field->ofs);
switch (field->type)
{
case F_INT:
case F_FLOAT:
case F_ANGLEHACK:
case F_VECTOR:
case F_IGNORE:
break;
case F_LSTRING:
case F_GSTRING:
if ( *(char **)p )
len = strlen(*(char **)p) + 1;
else
len = 0;
*(int *)p = len;
break;
case F_EDICT:
if ( *(edict_t **)p == NULL)
index = -1;
else
index = *(edict_t **)p - g_edicts;
*(int *)p = index;
break;
case F_CLIENT:
if ( *(gclient_t **)p == NULL)
index = -1;
else
index = *(gclient_t **)p - game.clients;
*(int *)p = index;
break;
case F_ITEM:
if ( *(edict_t **)p == NULL)
index = -1;
else
index = *(gitem_t **)p - itemlist;
*(int *)p = index;
break;
default:
gi.error ("WriteEdict: unknown field type");
}
}
void WriteField2 (FILE *f, field_t *field, byte *base)
{
int len;
void *p;
p = (void *)(base + field->ofs);
switch (field->type)
{
case F_LSTRING:
case F_GSTRING:
if ( *(char **)p )
{
len = strlen(*(char **)p) + 1;
fwrite (*(char **)p, len, 1, f);
}
break;
}
}
void ReadField (FILE *f, field_t *field, byte *base)
{
void *p;
int len;
int index;
p = (void *)(base + field->ofs);
switch (field->type)
{
case F_INT:
case F_FLOAT:
case F_ANGLEHACK:
case F_VECTOR:
case F_IGNORE:
break;
case F_LSTRING:
len = *(int *)p;
if (!len)
*(char **)p = NULL;
else
{
*(char **)p = gi.TagMalloc (len, TAG_LEVEL);
fread (*(char **)p, len, 1, f);
}
break;
case F_GSTRING:
len = *(int *)p;
if (!len)
*(char **)p = NULL;
else
{
*(char **)p = gi.TagMalloc (len, TAG_GAME);
fread (*(char **)p, len, 1, f);
}
break;
case F_EDICT:
index = *(int *)p;
if ( index == -1 )
*(edict_t **)p = NULL;
else
*(edict_t **)p = &g_edicts[index];
break;
case F_CLIENT:
index = *(int *)p;
if ( index == -1 )
*(gclient_t **)p = NULL;
else
*(gclient_t **)p = &game.clients[index];
break;
case F_ITEM:
index = *(int *)p;
if ( index == -1 )
*(gitem_t **)p = NULL;
else
*(gitem_t **)p = &itemlist[index];
break;
default:
gi.error ("ReadEdict: unknown field type");
}
}
//=========================================================
/*
==============
WriteClient
All pointer variables (except function pointers) must be handled specially.
==============
*/
void WriteClient (FILE *f, gclient_t *client)
{
field_t *field;
gclient_t temp;
// all of the ints, floats, and vectors stay as they are
temp = *client;
// change the pointers to lengths or indexes
for (field=clientfields ; field->name ; field++)
{
WriteField1 (f, field, (byte *)&temp);
}
// write the block
fwrite (&temp, sizeof(temp), 1, f);
// now write any allocated data following the edict
for (field=clientfields ; field->name ; field++)
{
WriteField2 (f, field, (byte *)client);
}
}
/*
==============
ReadClient
All pointer variables (except function pointers) must be handled specially.
==============
*/
void ReadClient (FILE *f, gclient_t *client)
{
field_t *field;
fread (client, sizeof(*client), 1, f);
for (field=clientfields ; field->name ; field++)
{
ReadField (f, field, (byte *)client);
}
}
/*
============
WriteGame
This will be called whenever the game goes to a new level,
and when the user explicitly saves the game.
Game information include cross level data, like multi level
triggers, help computer info, and all client states.
A single player death will automatically restore from the
last save position.
============
*/
void WriteGame (char *filename, qboolean autosave)
{
FILE *f;
int i;
char str[16];
if (!autosave)
SaveClientData ();
f = fopen (filename, "wb");
if (!f)
gi.error ("Couldn't open %s", filename);
memset (str, 0, sizeof(str));
strcpy (str, __DATE__);
fwrite (str, sizeof(str), 1, f);
game.autosaved = autosave;
fwrite (&game, sizeof(game), 1, f);
game.autosaved = false;
for (i=0 ; i<game.maxclients ; i++)
WriteClient (f, &game.clients[i]);
fclose (f);
}
void ReadGame (char *filename)
{
FILE *f;
int i;
char str[16];
gi.FreeTags (TAG_GAME);
f = fopen (filename, "rb");
if (!f)
gi.error ("Couldn't open %s", filename);
fread (str, sizeof(str), 1, f);
if (strcmp (str, __DATE__))
{
fclose (f);
gi.error ("Savegame from an older version.\n");
}
g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
globals.edicts = g_edicts;
fread (&game, sizeof(game), 1, f);
game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
for (i=0 ; i<game.maxclients ; i++)
ReadClient (f, &game.clients[i]);
fclose (f);
}
//==========================================================
/*
==============
WriteEdict
All pointer variables (except function pointers) must be handled specially.
==============
*/
void WriteEdict (FILE *f, edict_t *ent)
{
field_t *field;
edict_t temp;
// all of the ints, floats, and vectors stay as they are
temp = *ent;
// change the pointers to lengths or indexes
for (field=savefields ; field->name ; field++)
{
WriteField1 (f, field, (byte *)&temp);
}
// write the block
fwrite (&temp, sizeof(temp), 1, f);
// now write any allocated data following the edict
for (field=savefields ; field->name ; field++)
{
WriteField2 (f, field, (byte *)ent);
}
}
/*
==============
WriteLevelLocals
All pointer variables (except function pointers) must be handled specially.
==============
*/
void WriteLevelLocals (FILE *f)
{
field_t *field;
level_locals_t temp;
// all of the ints, floats, and vectors stay as they are
temp = level;
// change the pointers to lengths or indexes
for (field=levelfields ; field->name ; field++)
{
WriteField1 (f, field, (byte *)&temp);
}
// write the block
fwrite (&temp, sizeof(temp), 1, f);
// now write any allocated data following the edict
for (field=levelfields ; field->name ; field++)
{
WriteField2 (f, field, (byte *)&level);
}
}
/*
==============
ReadEdict
All pointer variables (except function pointers) must be handled specially.
==============
*/
void ReadEdict (FILE *f, edict_t *ent)
{
field_t *field;
fread (ent, sizeof(*ent), 1, f);
for (field=savefields ; field->name ; field++)
{
ReadField (f, field, (byte *)ent);
}
}
/*
==============
ReadLevelLocals
All pointer variables (except function pointers) must be handled specially.
==============
*/
void ReadLevelLocals (FILE *f)
{
field_t *field;
fread (&level, sizeof(level), 1, f);
for (field=levelfields ; field->name ; field++)
{
ReadField (f, field, (byte *)&level);
}
}
/*
=================
WriteLevel
=================
*/
void WriteLevel (char *filename)
{
int i;
edict_t *ent;
FILE *f;
void *base;
f = fopen (filename, "wb");
if (!f)
gi.error ("Couldn't open %s", filename);
// write out edict size for checking
i = sizeof(edict_t);
fwrite (&i, sizeof(i), 1, f);
// write out a function pointer for checking
base = (void *)InitGame;
fwrite (&base, sizeof(base), 1, f);
// write out level_locals_t
WriteLevelLocals (f);
// write out all the entities
for (i=0 ; i<globals.num_edicts ; i++)
{
ent = &g_edicts[i];
if (!ent->inuse)
continue;
fwrite (&i, sizeof(i), 1, f);
WriteEdict (f, ent);
}
i = -1;
fwrite (&i, sizeof(i), 1, f);
fclose (f);
}
/*
=================
ReadLevel
SpawnEntities will already have been called on the
level the same way it was when the level was saved.
That is necessary to get the baselines
set up identically.
The server will have cleared all of the world links before
calling ReadLevel.
No clients are connected yet.
=================
*/
void ReadLevel (char *filename)
{
int entnum;
FILE *f;
int i;
void *base;
edict_t *ent;
f = fopen (filename, "rb");
if (!f)
gi.error ("Couldn't open %s", filename);
// free any dynamic memory allocated by loading the level
// base state
gi.FreeTags (TAG_LEVEL);
// wipe all the entities
memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0]));
globals.num_edicts = maxclients->value+1;
// check edict size
fread (&i, sizeof(i), 1, f);
if (i != sizeof(edict_t))
{
fclose (f);
gi.error ("ReadLevel: mismatched edict size");
}
// check function pointer base address
fread (&base, sizeof(base), 1, f);
if (base != (void *)InitGame)
{
fclose (f);
gi.error ("ReadLevel: function pointers have moved");
}
// load the level locals
ReadLevelLocals (f);
// load all the entities
while (1)
{
if (fread (&entnum, sizeof(entnum), 1, f) != 1)
{
fclose (f);
gi.error ("ReadLevel: failed to read entnum");
}
if (entnum == -1)
break;
if (entnum >= globals.num_edicts)
globals.num_edicts = entnum+1;
ent = &g_edicts[entnum];
ReadEdict (f, ent);
// let the server rebuild world links for this ent
memset (&ent->area, 0, sizeof(ent->area));
gi.linkentity (ent);
}
fclose (f);
// mark all clients as unconnected
for (i=0 ; i<maxclients->value ; i++)
{
ent = &g_edicts[i+1];
ent->client = game.clients + i;
ent->client->pers.connected = false;
}
// do any load time things at this point
for (i=0 ; i<globals.num_edicts ; i++)
{
ent = &g_edicts[i];
if (!ent->inuse)
continue;
// fire any cross-level triggers
if (ent->classname)
if (strcmp(ent->classname, "target_crosslevel_target") == 0)
ent->nextthink = level.time + ent->delay;
}
}

1338
g_spawn.c Normal file

File diff suppressed because it is too large Load diff

281
g_svcmds.c Normal file
View file

@ -0,0 +1,281 @@
#include "g_local.h"
void Svcmd_Test_f (void)
{
gi.cprintf (NULL, PRINT_HIGH, "Svcmd_Test_f()\n");
}
/*
==============================================================================
PACKET FILTERING
You can add or remove addresses from the filter list with:
addip <ip>
removeip <ip>
The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire class C network with "addip 192.246.40".
Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host.
listip
Prints the current list of filters.
writeip
Dumps "addip <ip>" commands to listip.cfg so it can be execed at a later date. The filter lists are not saved and restored by default, because I beleive it would cause too much confusion.
filterban <0 or 1>
If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting.
If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network.
==============================================================================
*/
typedef struct
{
unsigned mask;
unsigned compare;
} ipfilter_t;
#define MAX_IPFILTERS 1024
ipfilter_t ipfilters[MAX_IPFILTERS];
int numipfilters;
/*
=================
StringToFilter
=================
*/
static qboolean StringToFilter (char *s, ipfilter_t *f)
{
char num[128];
int i, j;
byte b[4];
byte m[4];
for (i=0 ; i<4 ; i++)
{
b[i] = 0;
m[i] = 0;
}
for (i=0 ; i<4 ; i++)
{
if (*s < '0' || *s > '9')
{
gi.cprintf(NULL, PRINT_HIGH, "Bad filter address: %s\n", s);
return false;
}
j = 0;
while (*s >= '0' && *s <= '9')
{
num[j++] = *s++;
}
num[j] = 0;
b[i] = atoi(num);
if (b[i] != 0)
m[i] = 255;
if (!*s)
break;
s++;
}
f->mask = *(unsigned *)m;
f->compare = *(unsigned *)b;
return true;
}
/*
=================
SV_FilterPacket
=================
*/
qboolean SV_FilterPacket (char *from)
{
int i;
unsigned in;
byte m[4];
char *p;
i = 0;
p = from;
while (*p && i < 4) {
m[i] = 0;
while (*p >= '0' && *p <= '9') {
m[i] = m[i]*10 + (*p - '0');
p++;
}
if (!*p || *p == ':')
break;
i++, p++;
}
in = *(unsigned *)m;
for (i=0 ; i<numipfilters ; i++)
if ( (in & ipfilters[i].mask) == ipfilters[i].compare)
return (int)filterban->value;
return (int)!filterban->value;
}
/*
=================
SV_AddIP_f
=================
*/
void SVCmd_AddIP_f (void)
{
int i;
if (gi.argc() < 3) {
gi.cprintf(NULL, PRINT_HIGH, "Usage: addip <ip-mask>\n");
return;
}
for (i=0 ; i<numipfilters ; i++)
if (ipfilters[i].compare == 0xffffffff)
break; // free spot
if (i == numipfilters)
{
if (numipfilters == MAX_IPFILTERS)
{
gi.cprintf (NULL, PRINT_HIGH, "IP filter list is full\n");
return;
}
numipfilters++;
}
if (!StringToFilter (gi.argv(2), &ipfilters[i]))
ipfilters[i].compare = 0xffffffff;
}
/*
=================
SV_RemoveIP_f
=================
*/
void SVCmd_RemoveIP_f (void)
{
ipfilter_t f;
int i, j;
if (gi.argc() < 3) {
gi.cprintf(NULL, PRINT_HIGH, "Usage: sv removeip <ip-mask>\n");
return;
}
if (!StringToFilter (gi.argv(2), &f))
return;
for (i=0 ; i<numipfilters ; i++)
if (ipfilters[i].mask == f.mask
&& ipfilters[i].compare == f.compare)
{
for (j=i+1 ; j<numipfilters ; j++)
ipfilters[j-1] = ipfilters[j];
numipfilters--;
gi.cprintf (NULL, PRINT_HIGH, "Removed.\n");
return;
}
gi.cprintf (NULL, PRINT_HIGH, "Didn't find %s.\n", gi.argv(2));
}
/*
=================
SV_ListIP_f
=================
*/
void SVCmd_ListIP_f (void)
{
int i;
byte b[4];
gi.cprintf (NULL, PRINT_HIGH, "Filter list:\n");
for (i=0 ; i<numipfilters ; i++)
{
*(unsigned *)b = ipfilters[i].compare;
gi.cprintf (NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n", b[0], b[1], b[2], b[3]);
}
}
/*
=================
SV_WriteIP_f
=================
*/
void SVCmd_WriteIP_f (void)
{
FILE *f;
char name[MAX_OSPATH];
byte b[4];
int i;
cvar_t *game;
game = gi.cvar("game", "", 0);
if (!*game->string)
sprintf (name, "%s/listip.cfg", GAMEVERSION);
else
sprintf (name, "%s/listip.cfg", game->string);
gi.cprintf (NULL, PRINT_HIGH, "Writing %s.\n", name);
f = fopen (name, "wb");
if (!f)
{
gi.cprintf (NULL, PRINT_HIGH, "Couldn't open %s\n", name);
return;
}
fprintf(f, "set filterban %d\n", (int)filterban->value);
for (i=0 ; i<numipfilters ; i++)
{
*(unsigned *)b = ipfilters[i].compare;
fprintf (f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]);
}
fclose (f);
}
/*
=================
ServerCommand
ServerCommand will be called when an "sv" command is issued.
The game can issue gi.argc() / gi.argv() commands to get the rest
of the parameters
=================
*/
void ServerCommand (void)
{
char *cmd;
cmd = gi.argv(1);
if (Q_stricmp (cmd, "test") == 0)
Svcmd_Test_f ();
else if (Q_stricmp (cmd, "addip") == 0)
SVCmd_AddIP_f ();
else if (Q_stricmp (cmd, "removeip") == 0)
SVCmd_RemoveIP_f ();
else if (Q_stricmp (cmd, "listip") == 0)
SVCmd_ListIP_f ();
else if (Q_stricmp (cmd, "writeip") == 0)
SVCmd_WriteIP_f ();
else
gi.cprintf (NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd);
}

790
g_target.c Normal file
View file

@ -0,0 +1,790 @@
#include "g_local.h"
/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
Fire an origin based temp entity event to the clients.
"style" type byte
*/
void Use_Target_Tent (edict_t *ent, edict_t *other, edict_t *activator)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (ent->style);
gi.WritePosition (ent->s.origin);
gi.multicast (ent->s.origin, MULTICAST_PVS);
}
void SP_target_temp_entity (edict_t *ent)
{
ent->use = Use_Target_Tent;
}
//==========================================================
//==========================================================
/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
"noise" wav file to play
"attenuation"
-1 = none, send to whole level
1 = normal fighting sounds
2 = idle sound level
3 = ambient sound level
"volume" 0.0 to 1.0
Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers.
Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off.
Multiple identical looping sounds will just increase volume without any speed cost.
*/
void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator)
{
int chan;
if (ent->spawnflags & 3)
{ // looping sound toggles
if (ent->s.sound)
ent->s.sound = 0; // turn it off
else
ent->s.sound = ent->noise_index; // start it
}
else
{ // normal sound
if (ent->spawnflags & 4)
chan = CHAN_VOICE|CHAN_RELIABLE;
else
chan = CHAN_VOICE;
// use a positioned_sound, because this entity won't normally be
// sent to any clients because it is invisible
gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0);
}
}
void SP_target_speaker (edict_t *ent)
{
char buffer[MAX_QPATH];
if(!st.noise)
{
gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin));
return;
}
if (!strstr (st.noise, ".wav"))
Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
else
strncpy (buffer, st.noise, sizeof(buffer));
ent->noise_index = gi.soundindex (buffer);
if (!ent->volume)
ent->volume = 1.0;
if (!ent->attenuation)
ent->attenuation = 1.0;
else if (ent->attenuation == -1) // use -1 so 0 defaults to 1
ent->attenuation = 0;
// check for prestarted looping sound
if (ent->spawnflags & 1)
ent->s.sound = ent->noise_index;
ent->use = Use_Target_Speaker;
// must link the entity so we get areas and clusters so
// the server can determine who to send updates to
gi.linkentity (ent);
}
//==========================================================
void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator)
{
if (ent->spawnflags & 1)
strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1);
else
strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1);
game.helpchanged++;
}
/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars.
*/
void SP_target_help(edict_t *ent)
{
if (deathmatch->value)
{ // auto-remove for deathmatch
G_FreeEdict (ent);
return;
}
if (!ent->message)
{
gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin));
G_FreeEdict (ent);
return;
}
ent->use = Use_Target_Help;
}
//==========================================================
/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
Counts a secret found.
These are single use targets.
*/
void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator)
{
gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
level.found_secrets++;
G_UseTargets (ent, activator);
G_FreeEdict (ent);
}
void SP_target_secret (edict_t *ent)
{
if (deathmatch->value)
{ // auto-remove for deathmatch
G_FreeEdict (ent);
return;
}
ent->use = use_target_secret;
if (!st.noise)
st.noise = "misc/secret.wav";
ent->noise_index = gi.soundindex (st.noise);
ent->svflags = SVF_NOCLIENT;
level.total_secrets++;
// map bug hack
if (!stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624)
ent->message = "You have found a secret area.";
}
//==========================================================
/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
Counts a goal completed.
These are single use targets.
*/
void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator)
{
gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
level.found_goals++;
if (level.found_goals == level.total_goals)
gi.configstring (CS_CDTRACK, "0");
G_UseTargets (ent, activator);
G_FreeEdict (ent);
}
void SP_target_goal (edict_t *ent)
{
if (deathmatch->value)
{ // auto-remove for deathmatch
G_FreeEdict (ent);
return;
}
ent->use = use_target_goal;
if (!st.noise)
st.noise = "misc/secret.wav";
ent->noise_index = gi.soundindex (st.noise);
ent->svflags = SVF_NOCLIENT;
level.total_goals++;
}
//==========================================================
/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
Spawns an explosion temporary entity when used.
"delay" wait this long before going off
"dmg" how much radius damage should be done, defaults to 0
*/
void target_explosion_explode (edict_t *self)
{
float save;
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_EXPLOSION1);
gi.WritePosition (self->s.origin);
gi.multicast (self->s.origin, MULTICAST_PHS);
T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);
save = self->delay;
self->delay = 0;
G_UseTargets (self, self->activator);
self->delay = save;
}
void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator)
{
self->activator = activator;
if (!self->delay)
{
target_explosion_explode (self);
return;
}
self->think = target_explosion_explode;
self->nextthink = level.time + self->delay;
}
void SP_target_explosion (edict_t *ent)
{
ent->use = use_target_explosion;
ent->svflags = SVF_NOCLIENT;
}
//==========================================================
/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
Changes level to "map" when fired
*/
void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator)
{
if (level.intermissiontime)
return; // already activated
if (!deathmatch->value && !coop->value)
{
if (g_edicts[1].health <= 0)
return;
}
// if noexit, do a ton of damage to other
if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world)
{
T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT);
return;
}
// if multiplayer, let everyone know who hit the exit
if (deathmatch->value)
{
if (activator && activator->client)
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((stricmp(level.mapname, "fact1") == 0) && (stricmp(ent->map, "fact3") == 0))
ent->map = "fact3$secret1";
ent->use = use_target_changelevel;
ent->svflags = SVF_NOCLIENT;
}
//==========================================================
/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
Creates a particle splash effect when used.
Set "sounds" to one of the following:
1) sparks
2) blue water
3) brown water
4) slime
5) lava
6) blood
"count" how many pixels in the splash
"dmg" if set, does a radius damage at this location when it splashes
useful for lava/sparks
*/
void use_target_splash (edict_t *self, edict_t *other, edict_t *activator)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_SPLASH);
gi.WriteByte (self->count);
gi.WritePosition (self->s.origin);
gi.WriteDir (self->movedir);
gi.WriteByte (self->sounds);
gi.multicast (self->s.origin, MULTICAST_PVS);
if (self->dmg)
T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH);
}
void SP_target_splash (edict_t *self)
{
self->use = use_target_splash;
G_SetMovedir (self->s.angles, self->movedir);
if (!self->count)
self->count = 32;
self->svflags = SVF_NOCLIENT;
}
//==========================================================
/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
Set target to the type of entity you want spawned.
Useful for spawning monsters and gibs in the factory levels.
For monsters:
Set direction to the facing you want it to have.
For gibs:
Set direction if you want it moving and
speed how fast it should be moving otherwise it
will just be dropped
*/
void ED_CallSpawn (edict_t *ent);
void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator)
{
edict_t *ent;
ent = G_Spawn();
ent->classname = self->target;
VectorCopy (self->s.origin, ent->s.origin);
VectorCopy (self->s.angles, ent->s.angles);
ED_CallSpawn (ent);
gi.unlinkentity (ent);
KillBox (ent);
gi.linkentity (ent);
if (self->speed)
VectorCopy (self->movedir, ent->velocity);
}
void SP_target_spawner (edict_t *self)
{
self->use = use_target_spawner;
self->svflags = SVF_NOCLIENT;
if (self->speed)
{
G_SetMovedir (self->s.angles, self->movedir);
VectorScale (self->movedir, self->speed, self->movedir);
}
}
//==========================================================
/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
Fires a blaster bolt in the set direction when triggered.
dmg default is 15
speed default is 1000
*/
void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator)
{
int effect;
if (self->spawnflags & 2)
effect = 0;
else if (self->spawnflags & 1)
effect = EF_HYPERBLASTER;
else
effect = EF_BLASTER;
fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER);
gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
}
void SP_target_blaster (edict_t *self)
{
self->use = use_target_blaster;
G_SetMovedir (self->s.angles, self->movedir);
self->noise_index = gi.soundindex ("weapons/laser2.wav");
if (!self->dmg)
self->dmg = 15;
if (!self->speed)
self->speed = 1000;
self->svflags = SVF_NOCLIENT;
}
//==========================================================
/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work.
*/
void trigger_crosslevel_trigger_use (edict_t *self, edict_t *other, edict_t *activator)
{
game.serverflags |= self->spawnflags;
G_FreeEdict (self);
}
void SP_target_crosslevel_trigger (edict_t *self)
{
self->svflags = SVF_NOCLIENT;
self->use = trigger_crosslevel_trigger_use;
}
/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and
killtarget also work.
"delay" delay before using targets if the trigger has been activated (default 1)
*/
void target_crosslevel_target_think (edict_t *self)
{
if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
{
G_UseTargets (self, self);
G_FreeEdict (self);
}
}
void SP_target_crosslevel_target (edict_t *self)
{
if (! self->delay)
self->delay = 1;
self->svflags = SVF_NOCLIENT;
self->think = target_crosslevel_target_think;
self->nextthink = level.time + self->delay;
}
//==========================================================
/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
When triggered, fires a laser. You can either set a target
or a direction.
*/
void target_laser_think (edict_t *self)
{
edict_t *ignore;
vec3_t start;
vec3_t end;
trace_t tr;
vec3_t point;
vec3_t last_movedir;
int count;
if (self->spawnflags & 0x80000000)
count = 8;
else
count = 4;
if (self->enemy)
{
VectorCopy (self->movedir, last_movedir);
VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
VectorSubtract (point, self->s.origin, self->movedir);
VectorNormalize (self->movedir);
if (!VectorCompare(self->movedir, last_movedir))
self->spawnflags |= 0x80000000;
}
ignore = self;
VectorCopy (self->s.origin, start);
VectorMA (start, 2048, self->movedir, end);
while(1)
{
tr = do_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))
T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
// if we hit something that's not a monster or player or is immune to lasers, we're done
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
{
if (self->spawnflags & 0x80000000)
{
self->spawnflags &= ~0x80000000;
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_LASER_SPARKS);
gi.WriteByte (count);
gi.WritePosition (tr.endpos);
gi.WriteDir (tr.plane.normal);
gi.WriteByte (self->s.skinnum);
gi.multicast (tr.endpos, MULTICAST_PVS);
}
break;
}
ignore = tr.ent;
VectorCopy (tr.endpos, start);
}
VectorCopy (tr.endpos, self->s.old_origin);
self->nextthink = level.time + FRAMETIME;
}
void target_laser_on (edict_t *self)
{
if (!self->activator)
self->activator = self;
self->spawnflags |= 0x80000001;
self->svflags &= ~SVF_NOCLIENT;
target_laser_think (self);
}
void target_laser_off (edict_t *self)
{
self->spawnflags &= ~1;
self->svflags |= SVF_NOCLIENT;
self->nextthink = 0;
}
void target_laser_use (edict_t *self, edict_t *other, edict_t *activator)
{
self->activator = activator;
if (self->spawnflags & 1)
target_laser_off (self);
else
target_laser_on (self);
}
void target_laser_start (edict_t *self)
{
edict_t *ent;
self->movetype = MOVETYPE_NONE;
self->solid = SOLID_NOT;
self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT;
self->s.modelindex = 1; // must be non-zero
// set the beam diameter
if (self->spawnflags & 64)
self->s.frame = 16;
else
self->s.frame = 4;
// set the color
if (self->spawnflags & 2)
self->s.skinnum = 0xf2f2f0f0;
else if (self->spawnflags & 4)
self->s.skinnum = 0xd0d1d2d3;
else if (self->spawnflags & 8)
self->s.skinnum = 0xf3f3f1f1;
else if (self->spawnflags & 16)
self->s.skinnum = 0xdcdddedf;
else if (self->spawnflags & 32)
self->s.skinnum = 0xe0e1e2e3;
if (!self->enemy)
{
if (self->target)
{
ent = G_Find (NULL, FOFS(targetname), self->target);
if (!ent)
gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
self->enemy = ent;
}
else
{
G_SetMovedir (self->s.angles, self->movedir);
}
}
self->use = target_laser_use;
self->think = target_laser_think;
if (!self->dmg)
self->dmg = 1;
VectorSet (self->mins, -8, -8, -8);
VectorSet (self->maxs, 8, 8, 8);
gi.linkentity (self);
if (self->spawnflags & 1)
target_laser_on (self);
else
target_laser_off (self);
}
void SP_target_laser (edict_t *self)
{
// let everything else get spawned before we start firing
self->think = target_laser_start;
self->nextthink = level.time + 1;
}
//==========================================================
/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
speed How many seconds the ramping will take
message two letters; starting lightlevel and ending lightlevel
*/
void target_lightramp_think (edict_t *self)
{
char style[2];
style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
style[1] = 0;
gi.configstring (CS_LIGHTS+self->enemy->style, style);
if ((level.time - self->timestamp) < self->speed)
{
self->nextthink = level.time + FRAMETIME;
}
else if (self->spawnflags & 1)
{
char temp;
temp = self->movedir[0];
self->movedir[0] = self->movedir[1];
self->movedir[1] = temp;
self->movedir[2] *= -1;
}
}
void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator)
{
if (!self->enemy)
{
edict_t *e;
// check all the targets
e = NULL;
while (1)
{
e = G_Find (e, FOFS(targetname), self->target);
if (!e)
break;
if (strcmp(e->classname, "light") != 0)
{
gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin));
}
else
{
self->enemy = e;
}
}
if (!self->enemy)
{
gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin));
G_FreeEdict (self);
return;
}
}
self->timestamp = level.time;
target_lightramp_think (self);
}
void SP_target_lightramp (edict_t *self)
{
if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1])
{
gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin));
G_FreeEdict (self);
return;
}
if (deathmatch->value)
{
G_FreeEdict (self);
return;
}
if (!self->target)
{
gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
G_FreeEdict (self);
return;
}
self->svflags |= SVF_NOCLIENT;
self->use = target_lightramp_use;
self->think = target_lightramp_think;
self->movedir[0] = self->message[0] - 'a';
self->movedir[1] = self->message[1] - 'a';
self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
}
//==========================================================
/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
When triggered, this initiates a level-wide earthquake.
All players and monsters are affected.
"speed" severity of the quake (default:200)
"count" duration of the quake (default:5)
*/
void target_earthquake_think (edict_t *self)
{
int i;
edict_t *e;
if (self->last_move_time < level.time)
{
gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0);
self->last_move_time = level.time + 0.5;
}
for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
{
if (!e->inuse)
continue;
if (!e->client)
continue;
if (!e->groundentity)
continue;
e->groundentity = NULL;
e->velocity[0] += crandom()* 150;
e->velocity[1] += crandom()* 150;
e->velocity[2] = self->speed * (100.0 / e->mass);
}
if (level.time < self->timestamp)
self->nextthink = level.time + FRAMETIME;
}
void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator)
{
self->timestamp = level.time + self->count;
self->nextthink = level.time + FRAMETIME;
self->activator = activator;
self->last_move_time = 0;
}
void SP_target_earthquake (edict_t *self)
{
if (!self->targetname)
gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
if (!self->count)
self->count = 5;
if (!self->speed)
self->speed = 200;
self->svflags |= SVF_NOCLIENT;
self->think = target_earthquake_think;
self->use = target_earthquake_use;
self->noise_index = gi.soundindex ("world/quake.wav");
}

579
g_trigger.c Normal file
View file

@ -0,0 +1,579 @@
#include "g_local.h"
void InitTrigger (edict_t *self)
{
if (!VectorCompare (self->s.angles, vec3_origin))
G_SetMovedir (self->s.angles, self->movedir);
self->solid = SOLID_TRIGGER;
self->movetype = MOVETYPE_NONE;
gi.setmodel (self, self->model);
self->svflags = SVF_NOCLIENT;
}
// the wait time has passed, so set back up for another activation
void multi_wait (edict_t *ent)
{
ent->nextthink = 0;
}
// the trigger was just activated
// ent->activator should be set to the activator so it can be held through a delay
// so wait for the delay time before firing
void multi_trigger (edict_t *ent)
{
if (ent->nextthink)
return; // already been triggered
G_UseTargets (ent, ent->activator);
if (ent->wait > 0)
{
ent->think = multi_wait;
ent->nextthink = level.time + ent->wait;
}
else
{ // we can't just remove (self) here, because this is a touch function
// called while looping through area links...
ent->touch = NULL;
ent->nextthink = level.time + FRAMETIME;
ent->think = G_FreeEdict;
}
}
void Use_Multi (edict_t *ent, edict_t *other, edict_t *activator)
{
ent->activator = activator;
multi_trigger (ent);
}
void Touch_Multi (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
if(other->client)
{
if (self->spawnflags & 2)
return;
}
else if (other->svflags & SVF_MONSTER)
{
if (!(self->spawnflags & 1))
return;
}
else
return;
if (!VectorCompare(self->movedir, vec3_origin))
{
vec3_t forward;
AngleVectors(other->s.angles, forward, NULL, NULL);
if (_DotProduct(forward, self->movedir) < 0)
return;
}
self->activator = other;
multi_trigger (self);
}
/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED
Variable sized repeatable trigger. Must be targeted at one or more entities.
If "delay" is set, the trigger waits some time after activating before firing.
"wait" : Seconds between triggerings. (.2 default)
sounds
1) secret
2) beep beep
3) large switch
4)
set "message" to text string
*/
void trigger_enable (edict_t *self, edict_t *other, edict_t *activator)
{
self->solid = SOLID_TRIGGER;
self->use = Use_Multi;
gi.linkentity (self);
}
void SP_trigger_multiple (edict_t *ent)
{
if (ent->sounds == 1)
ent->noise_index = gi.soundindex ("misc/secret.wav");
else if (ent->sounds == 2)
ent->noise_index = gi.soundindex ("misc/talk.wav");
else if (ent->sounds == 3)
ent->noise_index = gi.soundindex ("misc/trigger1.wav");
if (!ent->wait)
ent->wait = 0.2;
ent->touch = Touch_Multi;
ent->movetype = MOVETYPE_NONE;
ent->svflags |= SVF_NOCLIENT;
if (ent->spawnflags & 4)
{
ent->solid = SOLID_NOT;
ent->use = trigger_enable;
}
else
{
ent->solid = SOLID_TRIGGER;
ent->use = Use_Multi;
}
if (!VectorCompare(ent->s.angles, vec3_origin))
G_SetMovedir (ent->s.angles, ent->movedir);
gi.setmodel (ent, ent->model);
gi.linkentity (ent);
}
/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED
Triggers once, then removes itself.
You must set the key "target" to the name of another object in the level that has a matching "targetname".
If TRIGGERED, this trigger must be triggered before it is live.
sounds
1) secret
2) beep beep
3) large switch
4)
"message" string to be displayed when triggered
*/
void SP_trigger_once(edict_t *ent)
{
// make old maps work because I messed up on flag assignments here
// triggered was on bit 1 when it should have been on bit 4
if (ent->spawnflags & 1)
{
vec3_t v;
VectorMA (ent->mins, 0.5, ent->size, v);
ent->spawnflags &= ~1;
ent->spawnflags |= 4;
gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v));
}
ent->wait = -1;
SP_trigger_multiple (ent);
}
/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
This fixed size trigger cannot be touched, it can only be fired by other events.
*/
void trigger_relay_use (edict_t *self, edict_t *other, edict_t *activator)
{
G_UseTargets (self, activator);
}
void SP_trigger_relay (edict_t *self)
{
self->use = trigger_relay_use;
}
/*
==============================================================================
trigger_key
==============================================================================
*/
/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8)
A relay trigger that only fires it's targets if player has the proper key.
Use "item" to specify the required key, for example "key_data_cd"
*/
void trigger_key_use (edict_t *self, edict_t *other, edict_t *activator)
{
int index;
if (!self->item)
return;
if (!activator->client)
return;
index = ITEM_INDEX(self->item);
if (!activator->client->pers.inventory[index])
{
if (level.time < self->touch_debounce_time)
return;
self->touch_debounce_time = level.time + 5.0;
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))
{
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))
{
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)
{
if (strcmp(other->classname, "grenade") == 0)
{
VectorScale (self->movedir, self->speed * 10, other->velocity);
}
else if (other->health > 0)
{
VectorScale (self->movedir, self->speed * 10, other->velocity);
if (other->client)
{
// don't take falling damage immediately from this
VectorCopy (other->velocity, other->client->oldvelocity);
if (other->fly_sound_debounce_time < level.time)
{
other->fly_sound_debounce_time = level.time + 1.5;
gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
}
}
}
if (self->spawnflags & PUSH_ONCE)
G_FreeEdict (self);
}
/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE
Pushes the player
"speed" defaults to 1000
*/
void SP_trigger_push (edict_t *self)
{
InitTrigger (self);
windsound = gi.soundindex ("misc/windfly.wav");
self->touch = trigger_push_touch;
if (!self->speed)
self->speed = 1000;
gi.linkentity (self);
}
/*
==============================================================================
trigger_hurt
==============================================================================
*/
/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW
Any entity that touches this will be hurt.
It does dmg points of damage each server frame
SILENT supresses playing the sound
SLOW changes the damage rate to once per second
NO_PROTECTION *nothing* stops the damage
"dmg" default 5 (whole numbers only)
*/
void hurt_use (edict_t *self, edict_t *other, edict_t *activator)
{
if (self->solid == SOLID_NOT)
self->solid = SOLID_TRIGGER;
else
self->solid = SOLID_NOT;
gi.linkentity (self);
if (!(self->spawnflags & 2))
self->use = NULL;
}
void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
int dflags;
if (!other->takedamage)
return;
if (self->timestamp > level.time)
return;
if (self->spawnflags & 16)
self->timestamp = level.time + 1;
else
self->timestamp = level.time + FRAMETIME;
if (!(self->spawnflags & 4))
{
if ((level.framenum % 10) == 0)
gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
}
if (self->spawnflags & 8)
dflags = DAMAGE_NO_PROTECTION;
else
dflags = 0;
T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT);
}
void SP_trigger_hurt (edict_t *self)
{
InitTrigger (self);
self->noise_index = gi.soundindex ("world/electro.wav");
self->touch = hurt_touch;
if (!self->dmg)
self->dmg = 5;
if (self->spawnflags & 1)
self->solid = SOLID_NOT;
else
self->solid = SOLID_TRIGGER;
if (self->spawnflags & 2)
self->use = hurt_use;
gi.linkentity (self);
}
/*
==============================================================================
trigger_gravity
==============================================================================
*/
/*QUAKED trigger_gravity (.5 .5 .5) ?
Changes the touching entites gravity to
the value of "gravity". 1.0 is standard
gravity for the level.
*/
void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
other->gravity = self->gravity;
}
void SP_trigger_gravity (edict_t *self)
{
if (st.gravity == 0)
{
gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin));
G_FreeEdict (self);
return;
}
InitTrigger (self);
self->gravity = atoi(st.gravity);
self->touch = trigger_gravity_touch;
}
/*
==============================================================================
trigger_monsterjump
==============================================================================
*/
/*QUAKED trigger_monsterjump (.5 .5 .5) ?
Walking monsters that touch this will jump in the direction of the trigger's angle
"speed" default to 200, the speed thrown forward
"height" default to 200, the speed thrown upwards
*/
void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
if (other->flags & (FL_FLY | FL_SWIM) )
return;
if (other->svflags & SVF_DEADMONSTER)
return;
if ( !(other->svflags & SVF_MONSTER))
return;
// set XY even if not on ground, so the jump will clear lips
other->velocity[0] = self->movedir[0] * self->speed;
other->velocity[1] = self->movedir[1] * self->speed;
if (!other->groundentity)
return;
other->groundentity = NULL;
other->velocity[2] = self->movedir[2];
}
void SP_trigger_monsterjump (edict_t *self)
{
if (!self->speed)
self->speed = 200;
if (!st.height)
st.height = 200;
if (self->s.angles[YAW] == 0)
self->s.angles[YAW] = 360;
InitTrigger (self);
self->touch = trigger_monsterjump_touch;
self->movedir[2] = st.height;
}

413
g_turret.c Normal file
View file

@ -0,0 +1,413 @@
// g_turret.c
#include "g_local.h"
void AnglesNormalize(vec3_t vec)
{
while(vec[0] > 360)
vec[0] -= 360;
while(vec[0] < 0)
vec[0] += 360;
while(vec[1] > 360)
vec[1] -= 360;
while(vec[1] < 0)
vec[1] += 360;
}
float SnapToEights(float x)
{
x *= 8.0;
if (x > 0.0)
x += 0.5;
else
x -= 0.5;
return 0.125 * (int)x;
}
void turret_blocked(edict_t *self, edict_t *other)
{
edict_t *attacker;
if (other->takedamage)
{
if (self->teammaster->owner)
attacker = self->teammaster->owner;
else
attacker = self->teammaster;
T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
}
}
/*QUAKED turret_breach (0 0 0) ?
This portion of the turret can change both pitch and yaw.
The model should be made with a flat pitch.
It (and the associated base) need to be oriented towards 0.
Use "angle" to set the starting angle.
"speed" default 50
"dmg" default 10
"angle" point this forward
"target" point this at an info_notnull at the muzzle tip
"minpitch" min acceptable pitch angle : default -30
"maxpitch" max acceptable pitch angle : default 30
"minyaw" min acceptable yaw angle : default 0
"maxyaw" max acceptable yaw angle : default 360
*/
void turret_breach_fire (edict_t *self)
{
vec3_t f, r, u;
vec3_t start;
int damage;
int speed;
AngleVectors (self->s.angles, f, r, u);
VectorMA (self->s.origin, self->move_origin[0], f, start);
VectorMA (start, self->move_origin[1], r, start);
VectorMA (start, self->move_origin[2], u, start);
damage = 100 + random() * 50;
speed = 550 + 50 * skill->value;
fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage);
gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
}
void turret_breach_think (edict_t *self)
{
edict_t *ent;
vec3_t current_angles;
vec3_t delta;
VectorCopy (self->s.angles, current_angles);
AnglesNormalize(current_angles);
AnglesNormalize(self->move_angles);
if (self->move_angles[PITCH] > 180)
self->move_angles[PITCH] -= 360;
// clamp angles to mins & maxs
if (self->move_angles[PITCH] > self->pos1[PITCH])
self->move_angles[PITCH] = self->pos1[PITCH];
else if (self->move_angles[PITCH] < self->pos2[PITCH])
self->move_angles[PITCH] = self->pos2[PITCH];
if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW]))
{
float dmin, dmax;
dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
if (dmin < -180)
dmin += 360;
else if (dmin > 180)
dmin -= 360;
dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
if (dmax < -180)
dmax += 360;
else if (dmax > 180)
dmax -= 360;
if (fabs(dmin) < fabs(dmax))
self->move_angles[YAW] = self->pos1[YAW];
else
self->move_angles[YAW] = self->pos2[YAW];
}
VectorSubtract (self->move_angles, current_angles, delta);
if (delta[0] < -180)
delta[0] += 360;
else if (delta[0] > 180)
delta[0] -= 360;
if (delta[1] < -180)
delta[1] += 360;
else if (delta[1] > 180)
delta[1] -= 360;
delta[2] = 0;
if (delta[0] > self->speed * FRAMETIME)
delta[0] = self->speed * FRAMETIME;
if (delta[0] < -1 * self->speed * FRAMETIME)
delta[0] = -1 * self->speed * FRAMETIME;
if (delta[1] > self->speed * FRAMETIME)
delta[1] = self->speed * FRAMETIME;
if (delta[1] < -1 * self->speed * FRAMETIME)
delta[1] = -1 * self->speed * FRAMETIME;
VectorScale (delta, 1.0/FRAMETIME, self->avelocity);
self->nextthink = level.time + FRAMETIME;
for (ent = self->teammaster; ent; ent = ent->teamchain)
ent->avelocity[1] = self->avelocity[1];
// if we have adriver, adjust his velocities
if (self->owner)
{
float angle;
float target_z;
float diff;
vec3_t target;
vec3_t dir;
// angular is easy, just copy ours
self->owner->avelocity[0] = self->avelocity[0];
self->owner->avelocity[1] = self->avelocity[1];
// x & y
angle = self->s.angles[1] + self->owner->move_origin[1];
angle *= (M_PI*2 / 360);
target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
target[2] = self->owner->s.origin[2];
VectorSubtract (target, self->owner->s.origin, dir);
self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
// z
angle = self->s.angles[PITCH] * (M_PI*2 / 360);
target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
diff = target_z - self->owner->s.origin[2];
self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
if (self->spawnflags & 65536)
{
turret_breach_fire (self);
self->spawnflags &= ~65536;
}
}
}
void turret_breach_finish_init (edict_t *self)
{
// get and save info for muzzle location
if (!self->target)
{
gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
}
else
{
self->target_ent = G_PickTarget (self->target);
VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin);
G_FreeEdict(self->target_ent);
}
self->teammaster->dmg = self->dmg;
self->think = turret_breach_think;
self->think (self);
}
void SP_turret_breach (edict_t *self)
{
self->solid = SOLID_BSP;
self->movetype = MOVETYPE_PUSH;
gi.setmodel (self, self->model);
if (!self->speed)
self->speed = 50;
if (!self->dmg)
self->dmg = 10;
if (!st.minpitch)
st.minpitch = -30;
if (!st.maxpitch)
st.maxpitch = 30;
if (!st.maxyaw)
st.maxyaw = 360;
self->pos1[PITCH] = -1 * st.minpitch;
self->pos1[YAW] = st.minyaw;
self->pos2[PITCH] = -1 * st.maxpitch;
self->pos2[YAW] = st.maxyaw;
self->ideal_yaw = self->s.angles[YAW];
self->move_angles[YAW] = self->ideal_yaw;
self->blocked = turret_blocked;
self->think = turret_breach_finish_init;
self->nextthink = level.time + FRAMETIME;
gi.linkentity (self);
}
/*QUAKED turret_base (0 0 0) ?
This portion of the turret changes yaw only.
MUST be teamed with a turret_breach.
*/
void SP_turret_base (edict_t *self)
{
self->solid = SOLID_BSP;
self->movetype = MOVETYPE_PUSH;
gi.setmodel (self, self->model);
self->blocked = turret_blocked;
gi.linkentity (self);
}
/*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
Must NOT be on the team with the rest of the turret parts.
Instead it must target the turret_breach.
*/
void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage);
void infantry_stand (edict_t *self);
void monster_use (edict_t *self, edict_t *other, edict_t *activator);
void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
edict_t *ent;
// level the gun
self->target_ent->move_angles[0] = 0;
// remove the driver from the end of them team chain
for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain)
;
ent->teamchain = NULL;
self->teammaster = NULL;
self->flags &= ~FL_TEAMSLAVE;
self->target_ent->owner = NULL;
self->target_ent->teammaster->owner = NULL;
//FB infantry_die (self, inflictor, attacker, damage);
}
qboolean FindTarget (edict_t *self);
void turret_driver_think (edict_t *self)
{
vec3_t target;
vec3_t dir;
float reaction_time;
self->nextthink = level.time + FRAMETIME;
if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0))
self->enemy = NULL;
if (!self->enemy)
{
if (!FindTarget (self))
return;
self->monsterinfo.trail_time = level.time;
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
}
else
{
if (visible (self, self->enemy))
{
if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
{
self->monsterinfo.trail_time = level.time;
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
}
}
else
{
self->monsterinfo.aiflags |= AI_LOST_SIGHT;
return;
}
}
// let the turret know where we want it to aim
VectorCopy (self->enemy->s.origin, target);
target[2] += self->enemy->viewheight;
VectorSubtract (target, self->target_ent->s.origin, dir);
vectoangles (dir, self->target_ent->move_angles);
// decide if we should shoot
if (level.time < self->monsterinfo.attack_finished)
return;
reaction_time = (3 - skill->value) * 1.0;
if ((level.time - self->monsterinfo.trail_time) < reaction_time)
return;
self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
//FIXME how do we really want to pass this along?
self->target_ent->spawnflags |= 65536;
}
void turret_driver_link (edict_t *self)
{
vec3_t vec;
edict_t *ent;
self->think = turret_driver_think;
self->nextthink = level.time + FRAMETIME;
self->target_ent = G_PickTarget (self->target);
self->target_ent->owner = self;
self->target_ent->teammaster->owner = self;
VectorCopy (self->target_ent->s.angles, self->s.angles);
vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
vec[2] = 0;
self->move_origin[0] = VectorLength(vec);
VectorSubtract (self->s.origin, self->target_ent->s.origin, vec);
vectoangles (vec, vec);
AnglesNormalize(vec);
self->move_origin[1] = vec[1];
self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
// add the driver to the end of them team chain
for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
;
ent->teamchain = self;
self->teammaster = self->target_ent->teammaster;
self->flags |= FL_TEAMSLAVE;
}
void SP_turret_driver (edict_t *self)
{
if (deathmatch->value)
{
G_FreeEdict (self);
return;
}
self->movetype = MOVETYPE_PUSH;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
VectorSet (self->mins, -16, -16, -24);
VectorSet (self->maxs, 16, 16, 32);
self->health = 100;
self->gib_health = 0;
self->mass = 200;
self->viewheight = 24;
self->die = turret_driver_die;
//FB self->monsterinfo.stand = infantry_stand;
self->flags |= FL_NO_KNOCKBACK;
level.total_monsters++;
self->svflags |= SVF_MONSTER;
self->s.renderfx |= RF_FRAMELERP;
self->takedamage = DAMAGE_AIM;
self->use = monster_use;
self->clipmask = MASK_MONSTERSOLID;
VectorCopy (self->s.origin, self->s.old_origin);
self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED;
if (st.item)
{
self->item = FindItemByClassname (st.item);
if (!self->item)
gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
}
self->think = turret_driver_link;
self->nextthink = level.time + FRAMETIME;
gi.linkentity (self);
}

554
g_utils.c Normal file
View file

@ -0,0 +1,554 @@
// g_utils.c -- misc utility functions for game module
#include "g_local.h"
void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
{
result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
}
/*
=============
G_Find
Searches all active entities for the next one that holds
the matching string at fieldofs (use the FOFS() macro) in the structure.
Searches beginning at the edict after from, or the beginning if NULL
NULL will be returned if the end of the list is reached.
=============
*/
edict_t *G_Find (edict_t *from, int fieldofs, char *match)
{
char *s;
if (!from)
from = g_edicts;
else
from++;
for ( ; from < &g_edicts[globals.num_edicts] ; from++)
{
if (!from->inuse)
continue;
s = *(char **) ((byte *)from + fieldofs);
if (!s)
continue;
if (!Q_stricmp (s, match))
return from;
}
return NULL;
}
/*
=================
findradius
Returns entities that have origins within a spherical area
findradius (origin, radius)
=================
*/
edict_t *findradius (edict_t *from, vec3_t org, float rad)
{
vec3_t eorg;
int j;
if (!from)
from = g_edicts;
else
from++;
for ( ; from < &g_edicts[globals.num_edicts]; from++)
{
if (!from->inuse)
continue;
if (from->solid == SOLID_NOT)
continue;
for (j=0 ; j<3 ; j++)
eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
if (VectorLength(eorg) > rad)
continue;
return from;
}
return NULL;
}
/*
=============
G_PickTarget
Searches all active entities for the next one that holds
the matching string at fieldofs (use the FOFS() macro) in the structure.
Searches beginning at the edict after from, or the beginning if NULL
NULL will be returned if the end of the list is reached.
=============
*/
#define MAXCHOICES 8
edict_t *G_PickTarget (char *targetname)
{
edict_t *ent = NULL;
int num_choices = 0;
edict_t *choice[MAXCHOICES];
if (!targetname)
{
gi.dprintf("G_PickTarget called with NULL targetname\n");
return NULL;
}
while(1)
{
ent = G_Find (ent, FOFS(targetname), targetname);
if (!ent)
break;
choice[num_choices++] = ent;
if (num_choices == MAXCHOICES)
break;
}
if (!num_choices)
{
gi.dprintf("G_PickTarget: target %s not found\n", targetname);
return NULL;
}
return choice[rand() % num_choices];
}
void Think_Delay (edict_t *ent)
{
G_UseTargets (ent, ent->activator);
G_FreeEdict (ent);
}
/*
==============================
G_UseTargets
the global "activator" should be set to the entity that initiated the firing.
If self.delay is set, a DelayedUse entity will be created that will actually
do the SUB_UseTargets after that many seconds have passed.
Centerprints any self.message to the activator.
Search for (string)targetname in all entities that
match (string)self.target and call their .use function
==============================
*/
void G_UseTargets (edict_t *ent, edict_t *activator)
{
edict_t *t;
//
// check for a delay
//
if (ent->delay)
{
// create a temp object to fire at a later time
t = G_Spawn();
t->classname = "DelayedUse";
t->nextthink = level.time + ent->delay;
t->think = Think_Delay;
t->activator = activator;
if (!activator)
gi.dprintf ("Think_Delay with no activator\n");
t->message = ent->message;
t->target = ent->target;
t->killtarget = ent->killtarget;
return;
}
//
// print the message
//
if ((ent->message) && !(activator->svflags & SVF_MONSTER))
{
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;
}
}
}
//
// fire targets
//
if (ent->target)
{
t = NULL;
while ((t = G_Find (t, FOFS(targetname), ent->target)))
{
// doors fire area portals in a specific way
if (!Q_stricmp(t->classname, "func_areaportal") &&
(!Q_stricmp(ent->classname, "func_door") || !Q_stricmp(ent->classname, "func_door_rotating")))
continue;
if (t == ent)
{
gi.dprintf ("WARNING: Entity used itself.\n");
}
else
{
if (t->use)
t->use (t, ent, activator);
}
if (!ent->inuse)
{
gi.dprintf("entity was removed while using targets\n");
return;
}
}
}
}
/*
=============
TempVector
This is just a convenience function
for making temporary vectors for function calls
=============
*/
float *tv (float x, float y, float z)
{
static int index;
static vec3_t vecs[8];
float *v;
// use an array so that multiple tempvectors won't collide
// for a while
v = vecs[index];
index = (index + 1)&7;
v[0] = x;
v[1] = y;
v[2] = z;
return v;
}
/*
=============
VectorToString
This is just a convenience function
for printing vectors
=============
*/
char *vtos (vec3_t v)
{
static int index;
static char str[8][32];
char *s;
// use an array so that multiple vtos won't collide
s = str[index];
index = (index + 1)&7;
Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
return s;
}
vec3_t VEC_UP = {0, -1, 0};
vec3_t MOVEDIR_UP = {0, 0, 1};
vec3_t VEC_DOWN = {0, -2, 0};
vec3_t MOVEDIR_DOWN = {0, 0, -1};
void G_SetMovedir (vec3_t angles, vec3_t movedir)
{
if (VectorCompare (angles, VEC_UP))
{
VectorCopy (MOVEDIR_UP, movedir);
}
else if (VectorCompare (angles, VEC_DOWN))
{
VectorCopy (MOVEDIR_DOWN, movedir);
}
else
{
AngleVectors (angles, movedir, NULL, NULL);
}
VectorClear (angles);
}
float vectoyaw (vec3_t vec)
{
float yaw;
// FIXES HERE FROM 3.20 -FB
if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0)
{
yaw = 0;
if (vec[YAW] > 0)
yaw = 90;
else if (vec[YAW] < 0)
yaw = -90;
}
// ^^^
else
{
yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
if (yaw < 0)
yaw += 360;
}
return yaw;
}
void vectoangles (vec3_t value1, vec3_t angles)
{
float forward;
float yaw, pitch;
if (value1[1] == 0 && value1[0] == 0)
{
yaw = 0;
if (value1[2] > 0)
pitch = 90;
else
pitch = 270;
}
else
{
// FIXES HERE FROM 3.20 -FB
// zucc changing casts to floats
if (value1[0])
yaw = (float) (atan2(value1[1], value1[0]) * 180 / M_PI);
else if (value1[1] > 0)
yaw = 90;
else
yaw = -90;
// ^^^
if (yaw < 0)
yaw += 360;
forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]);
pitch = (float) (atan2(value1[2], forward) * 180 / M_PI);
if (pitch < 0)
pitch += 360;
}
angles[PITCH] = -pitch;
angles[YAW] = yaw;
angles[ROLL] = 0;
}
char *G_CopyString (char *in)
{
char *out;
out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL);
strcpy (out, in);
return out;
}
void G_InitEdict (edict_t *e)
{
e->inuse = true;
e->classname = "noclass";
e->gravity = 1.0;
e->s.number = e - g_edicts;
}
/*
=================
G_Spawn
Either finds a free edict, or allocates a new one.
Try to avoid reusing an entity that was recently freed, because it
can cause the client to think the entity morphed into something else
instead of being removed and recreated, which can cause interpolated
angles and bad trails.
=================
*/
edict_t *G_Spawn (void)
{
int i;
edict_t *e;
e = &g_edicts[(int)maxclients->value+1];
for ( i=maxclients->value+1 ; i<globals.num_edicts ; i++, e++)
{
// the first couple seconds of server time can involve a lot of
// freeing and allocating, so relax the replacement policy
if (!e->inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
{
G_InitEdict (e);
return e;
}
}
if (i == game.maxentities)
gi.error ("ED_Alloc: no free edicts");
globals.num_edicts++;
G_InitEdict (e);
return e;
}
/*
=================
G_FreeEdict
Marks the edict as free
=================
*/
void G_FreeEdict (edict_t *ed)
{
gi.unlinkentity (ed); // unlink from world
if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
{
// gi.dprintf("tried to free special edict\n");
return;
}
memset (ed, 0, sizeof(*ed));
ed->classname = "freed";
ed->freetime = level.time;
ed->inuse = false;
}
/*
============
G_TouchTriggers
============
*/
void G_TouchTriggers (edict_t *ent)
{
int i, num;
edict_t *touch[MAX_EDICTS], *hit;
// dead things don't activate triggers!
if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
return;
num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
, MAX_EDICTS, AREA_TRIGGERS);
// be careful, it is possible to have an entity in this
// list removed before we get to it (killtriggered)
for (i=0 ; i<num ; i++)
{
hit = touch[i];
if (!hit->inuse)
continue;
if (!hit->touch)
continue;
hit->touch (hit, ent, NULL, NULL);
}
}
/*
============
G_TouchSolids
Call after linking a new trigger in during gameplay
to force all entities it covers to immediately touch it
============
*/
void G_TouchSolids (edict_t *ent)
{
int i, num;
edict_t *touch[MAX_EDICTS], *hit;
num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
, MAX_EDICTS, AREA_SOLID);
// be careful, it is possible to have an entity in this
// list removed before we get to it (killtriggered)
for (i=0 ; i<num ; i++)
{
hit = touch[i];
if (!hit->inuse)
continue;
if (ent->touch)
ent->touch (hit, ent, NULL, NULL);
if (!ent->inuse)
break;
}
}
/*
==============================================================================
Kill box
==============================================================================
*/
/*
=================
KillBox
Kills all entities that would touch the proposed new positioning
of ent. Ent should be unlinked before calling this!
=================
*/
qboolean KillBox (edict_t *ent)
{
trace_t tr;
while (1)
{
tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID);
if (!tr.ent)
break;
// nail it
T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
// if we didn't kill it, fail
if (tr.ent->solid)
return false;
}
return true; // all clear
}

1457
g_weapon.c Normal file

File diff suppressed because it is too large Load diff

216
game.h Normal file
View file

@ -0,0 +1,216 @@
// game.h -- game dll information visible to server
#define GAME_API_VERSION 3
// edict->svflags
#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects
#define SVF_DEADMONSTER 0x00000002 // treat as CONTENTS_DEADMONSTER for collision
#define SVF_MONSTER 0x00000004 // treat as CONTENTS_MONSTER for collision
// edict->solid values
typedef enum
{
SOLID_NOT, // no interaction with other objects
SOLID_TRIGGER, // only touch when inside, after moving
SOLID_BBOX, // touch on edge
SOLID_BSP // bsp clip, touch on edge
} solid_t;
//===============================================================
// link_t is only used for entity area links now
typedef struct link_s
{
struct link_s *prev, *next;
} link_t;
#define MAX_ENT_CLUSTERS 16
typedef struct edict_s edict_t;
typedef struct gclient_s gclient_t;
#ifndef GAME_INCLUDE
struct gclient_s
{
player_state_t ps; // communicated by server to clients
int ping;
// the game dll can add anything it wants after
// this point in the structure
};
struct edict_s
{
entity_state_t s;
struct gclient_s *client;
qboolean inuse;
int linkcount;
// FIXME: move these fields to a server private sv_entity_t
link_t area; // linked to a division node or leaf
int num_clusters; // if -1, use headnode instead
int clusternums[MAX_ENT_CLUSTERS];
int headnode; // unused if num_clusters != -1
int areanum, areanum2;
//================================
int svflags; // SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc
vec3_t mins, maxs;
vec3_t absmin, absmax, size;
solid_t solid;
int clipmask;
edict_t *owner;
// the game dll can add anything it wants after
// this point in the structure
};
#endif // GAME_INCLUDE
//===============================================================
//
// functions provided by the main engine
//
typedef struct
{
// special messages
void (*bprintf) (int printlevel, char *fmt, ...);
void (*dprintf) (char *fmt, ...);
void (*cprintf) (edict_t *ent, int printlevel, char *fmt, ...);
void (*centerprintf) (edict_t *ent, char *fmt, ...);
void (*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs);
void (*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs);
// config strings hold all the index strings, the lightstyles,
// and misc data like the sky definition and cdtrack.
// All of the current configstrings are sent to clients when
// they connect, and changes are sent to all connected clients.
void (*configstring) (int num, char *string);
void (*error) (char *fmt, ...);
// the *index functions create configstrings and some internal server state
int (*modelindex) (char *name);
int (*soundindex) (char *name);
int (*imageindex) (char *name);
void (*setmodel) (edict_t *ent, char *name);
// collision detection
trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask);
int (*pointcontents) (vec3_t point);
qboolean (*inPVS) (vec3_t p1, vec3_t p2);
qboolean (*inPHS) (vec3_t p1, vec3_t p2);
void (*SetAreaPortalState) (int portalnum, qboolean open);
qboolean (*AreasConnected) (int area1, int area2);
// an entity will never be sent to a client or used for collision
// if it is not passed to linkentity. If the size, position, or
// solidity changes, it must be relinked.
void (*linkentity) (edict_t *ent);
void (*unlinkentity) (edict_t *ent); // call before removing an interactive edict
int (*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype);
void (*Pmove) (pmove_t *pmove); // player movement code common with client prediction
// network messaging
void (*multicast) (vec3_t origin, multicast_t to);
void (*unicast) (edict_t *ent, qboolean reliable);
void (*WriteChar) (int c);
void (*WriteByte) (int c);
void (*WriteShort) (int c);
void (*WriteLong) (int c);
void (*WriteFloat) (float f);
void (*WriteString) (char *s);
void (*WritePosition) (vec3_t pos); // some fractional bits
void (*WriteDir) (vec3_t pos); // single byte encoded, very coarse
void (*WriteAngle) (float f);
// managed memory allocation
void *(*TagMalloc) (int size, int tag);
void (*TagFree) (void *block);
void (*FreeTags) (int tag);
// console variable interaction
cvar_t *(*cvar) (char *var_name, char *value, int flags);
cvar_t *(*cvar_set) (char *var_name, char *value);
cvar_t *(*cvar_forceset) (char *var_name, char *value);
// ClientCommand and ServerCommand parameter access
int (*argc) (void);
char *(*argv) (int n);
char *(*args) (void); // concatenation of all argv >= 1
// add commands to the server console as if they were typed in
// for map changing, etc
void (*AddCommandString) (char *text);
void (*DebugGraph) (float value, int color);
} game_import_t;
//
// functions exported by the game subsystem
//
typedef struct
{
int apiversion;
// the init function will only be called when a game starts,
// not each time a level is loaded. Persistant data for clients
// and the server can be allocated in init
void (*Init) (void);
void (*Shutdown) (void);
// each new level entered will cause a call to SpawnEntities
void (*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint);
// Read/Write Game is for storing persistant cross level information
// about the world state and the clients.
// WriteGame is called every time a level is exited.
// ReadGame is called on a loadgame.
void (*WriteGame) (char *filename, qboolean autosave);
void (*ReadGame) (char *filename);
// ReadLevel is called after the default map information has been
// loaded with SpawnEntities
void (*WriteLevel) (char *filename);
void (*ReadLevel) (char *filename);
qboolean (*ClientConnect) (edict_t *ent, char *userinfo);
void (*ClientBegin) (edict_t *ent);
void (*ClientUserinfoChanged) (edict_t *ent, char *userinfo);
void (*ClientDisconnect) (edict_t *ent);
void (*ClientCommand) (edict_t *ent);
void (*ClientThink) (edict_t *ent, usercmd_t *cmd);
void (*RunFrame) (void);
// ServerCommand will be called when an "sv <command>" command is issued on the
// server console.
// The game can issue gi.argc() / gi.argv() commands to get the rest
// of the parameters
void (*ServerCommand) (void);
//
// global variables shared between game and server
//
// The edict array is allocated in the game dll so it
// can vary in size from one game to another.
//
// The size will be fixed when ge->Init() is called
struct edict_s *edicts;
int edict_size;
int num_edicts; // current number, <= max_edicts
int max_edicts;
} game_export_t;
game_export_t *GetGameApi (game_import_t *import);

537
m_move.c Normal file
View file

@ -0,0 +1,537 @@
// m_move.c -- monster movement
#include "g_local.h"
#define STEPSIZE 18
/*
=============
M_CheckBottom
Returns false if any part of the bottom of the entity is off an edge that
is not a staircase.
=============
*/
int c_yes, c_no;
qboolean M_CheckBottom (edict_t *ent)
{
vec3_t mins, maxs, start, stop;
trace_t trace;
int x, y;
float mid, bottom;
VectorAdd (ent->s.origin, ent->mins, mins);
VectorAdd (ent->s.origin, ent->maxs, maxs);
// if all of the points under the corners are solid world, don't bother
// with the tougher checks
// the corners must be within 16 of the midpoint
start[2] = mins[2] - 1;
for (x=0 ; x<=1 ; x++)
for (y=0 ; y<=1 ; y++)
{
start[0] = x ? maxs[0] : mins[0];
start[1] = y ? maxs[1] : mins[1];
if (gi.pointcontents (start) != CONTENTS_SOLID)
goto realcheck;
}
c_yes++;
return true; // we got out easy
realcheck:
c_no++;
//
// check it for real...
//
start[2] = mins[2];
// the midpoint must be within 16 of the bottom
start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
stop[2] = start[2] - 2*STEPSIZE;
trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
if (trace.fraction == 1.0)
return false;
mid = bottom = trace.endpos[2];
// the corners must be within 16 of the midpoint
for (x=0 ; x<=1 ; x++)
for (y=0 ; y<=1 ; y++)
{
start[0] = stop[0] = x ? maxs[0] : mins[0];
start[1] = stop[1] = y ? maxs[1] : mins[1];
trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
bottom = trace.endpos[2];
if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE)
return false;
}
c_yes++;
return true;
}
/*
=============
SV_movestep
Called by monster program code.
The move will be adjusted for slopes and stairs, but if the move isn't
possible, no move is done, false is returned, and
pr_global_struct->trace_normal is set to the normal of the blocking wall
=============
*/
//FIXME since we need to test end position contents here, can we avoid doing
//it again later in catagorize position?
qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink)
{
float dz;
vec3_t oldorg, neworg, end;
trace_t trace;
int i;
float stepsize;
vec3_t test;
int contents;
// try the move
VectorCopy (ent->s.origin, oldorg);
VectorAdd (ent->s.origin, move, neworg);
// flying monsters don't step up
if ( ent->flags & (FL_SWIM | FL_FLY) )
{
// try one move with vertical motion, then one without
for (i=0 ; i<2 ; i++)
{
VectorAdd (ent->s.origin, move, neworg);
if (i == 0 && ent->enemy)
{
if (!ent->goalentity)
ent->goalentity = ent->enemy;
dz = ent->s.origin[2] - ent->goalentity->s.origin[2];
if (ent->goalentity->client)
{
if (dz > 40)
neworg[2] -= 8;
if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2)))
if (dz < 30)
neworg[2] += 8;
}
else
{
if (dz > 8)
neworg[2] -= 8;
else if (dz > 0)
neworg[2] -= dz;
else if (dz < -8)
neworg[2] += 8;
else
neworg[2] += dz;
}
}
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID);
// fly monsters don't enter water voluntarily
if (ent->flags & FL_FLY)
{
if (!ent->waterlevel)
{
test[0] = trace.endpos[0];
test[1] = trace.endpos[1];
test[2] = trace.endpos[2] + ent->mins[2] + 1;
contents = gi.pointcontents(test);
if (contents & MASK_WATER)
return false;
}
}
// swim monsters don't exit water voluntarily
if (ent->flags & FL_SWIM)
{
if (ent->waterlevel < 2)
{
test[0] = trace.endpos[0];
test[1] = trace.endpos[1];
test[2] = trace.endpos[2] + ent->mins[2] + 1;
contents = gi.pointcontents(test);
if (!(contents & MASK_WATER))
return false;
}
}
if (trace.fraction == 1)
{
VectorCopy (trace.endpos, ent->s.origin);
if (relink)
{
gi.linkentity (ent);
G_TouchTriggers (ent);
}
return true;
}
if (!ent->enemy)
break;
}
return false;
}
// push down from a step height above the wished position
if (!(ent->monsterinfo.aiflags & AI_NOSTEP))
stepsize = STEPSIZE;
else
stepsize = 1;
neworg[2] += stepsize;
VectorCopy (neworg, end);
end[2] -= stepsize*2;
trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
if (trace.allsolid)
return false;
if (trace.startsolid)
{
neworg[2] -= stepsize;
trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
if (trace.allsolid || trace.startsolid)
return false;
}
// don't go in to water
if (ent->waterlevel == 0)
{
test[0] = trace.endpos[0];
test[1] = trace.endpos[1];
test[2] = trace.endpos[2] + ent->mins[2] + 1;
contents = gi.pointcontents(test);
if (contents & MASK_WATER)
return false;
}
if (trace.fraction == 1)
{
// if monster had the ground pulled out, go ahead and fall
if ( ent->flags & FL_PARTIALGROUND )
{
VectorAdd (ent->s.origin, move, ent->s.origin);
if (relink)
{
gi.linkentity (ent);
G_TouchTriggers (ent);
}
ent->groundentity = NULL;
return true;
}
return false; // walked off an edge
}
// check point traces down for dangling corners
VectorCopy (trace.endpos, ent->s.origin);
if (!M_CheckBottom (ent))
{
if ( ent->flags & FL_PARTIALGROUND )
{ // entity had floor mostly pulled out from underneath it
// and is trying to correct
if (relink)
{
gi.linkentity (ent);
G_TouchTriggers (ent);
}
return true;
}
VectorCopy (oldorg, ent->s.origin);
return false;
}
if ( ent->flags & FL_PARTIALGROUND )
{
ent->flags &= ~FL_PARTIALGROUND;
}
ent->groundentity = trace.ent;
ent->groundentity_linkcount = trace.ent->linkcount;
// the move is ok
if (relink)
{
gi.linkentity (ent);
G_TouchTriggers (ent);
}
return true;
}
//============================================================================
/*
===============
M_ChangeYaw
===============
*/
void M_ChangeYaw (edict_t *ent)
{
float ideal;
float current;
float move;
float speed;
current = anglemod(ent->s.angles[YAW]);
ideal = ent->ideal_yaw;
if (current == ideal)
return;
move = ideal - current;
speed = ent->yaw_speed;
if (ideal > current)
{
if (move >= 180)
move = move - 360;
}
else
{
if (move <= -180)
move = move + 360;
}
if (move > 0)
{
if (move > speed)
move = speed;
}
else
{
if (move < -speed)
move = -speed;
}
ent->s.angles[YAW] = anglemod (current + move);
}
/*
======================
SV_StepDirection
Turns to the movement direction, and walks the current distance if
facing it.
======================
*/
qboolean SV_StepDirection (edict_t *ent, float yaw, float dist)
{
vec3_t move, oldorigin;
float delta;
ent->ideal_yaw = yaw;
M_ChangeYaw (ent);
yaw = yaw*M_PI*2 / 360;
move[0] = cos(yaw)*dist;
move[1] = sin(yaw)*dist;
move[2] = 0;
VectorCopy (ent->s.origin, oldorigin);
if (SV_movestep (ent, move, false))
{
delta = ent->s.angles[YAW] - ent->ideal_yaw;
if (delta > 45 && delta < 315)
{ // not turned far enough, so don't take the step
VectorCopy (oldorigin, ent->s.origin);
}
gi.linkentity (ent);
G_TouchTriggers (ent);
return true;
}
gi.linkentity (ent);
G_TouchTriggers (ent);
return false;
}
/*
======================
SV_FixCheckBottom
======================
*/
void SV_FixCheckBottom (edict_t *ent)
{
ent->flags |= FL_PARTIALGROUND;
}
/*
================
SV_NewChaseDir
================
*/
#define DI_NODIR -1
void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist)
{
float deltax,deltay;
float d[3];
float tdir, olddir, turnaround;
//FIXME: how did we get here with no enemy
if (!enemy)
return;
olddir = anglemod( (int)(actor->ideal_yaw/45)*45 );
turnaround = anglemod(olddir - 180);
deltax = enemy->s.origin[0] - actor->s.origin[0];
deltay = enemy->s.origin[1] - actor->s.origin[1];
if (deltax>10)
d[1]= 0;
else if (deltax<-10)
d[1]= 180;
else
d[1]= DI_NODIR;
if (deltay<-10)
d[2]= 270;
else if (deltay>10)
d[2]= 90;
else
d[2]= DI_NODIR;
// try direct route
if (d[1] != DI_NODIR && d[2] != DI_NODIR)
{
if (d[1] == 0)
tdir = d[2] == 90 ? 45 : 315;
else
tdir = d[2] == 90 ? 135 : 215;
if (tdir != turnaround && SV_StepDirection(actor, tdir, dist))
return;
}
// try other directions
if ( ((rand()&3) & 1) || abs(deltay)>abs(deltax))
{
tdir=d[1];
d[1]=d[2];
d[2]=tdir;
}
if (d[1]!=DI_NODIR && d[1]!=turnaround
&& SV_StepDirection(actor, d[1], dist))
return;
if (d[2]!=DI_NODIR && d[2]!=turnaround
&& SV_StepDirection(actor, d[2], dist))
return;
/* there is no direct path to the player, so pick another direction */
if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist))
return;
if (rand()&1) /*randomly determine direction of search*/
{
for (tdir=0 ; tdir<=315 ; tdir += 45)
if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
return;
}
else
{
for (tdir=315 ; tdir >=0 ; tdir -= 45)
if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
return;
}
if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) )
return;
actor->ideal_yaw = olddir; // can't move
// if a bridge was pulled out from underneath a monster, it may not have
// a valid standing position at all
if (!M_CheckBottom (actor))
SV_FixCheckBottom (actor);
}
/*
======================
SV_CloseEnough
======================
*/
qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist)
{
int i;
for (i=0 ; i<3 ; i++)
{
if (goal->absmin[i] > ent->absmax[i] + dist)
return false;
if (goal->absmax[i] < ent->absmin[i] - dist)
return false;
}
return true;
}
/*
======================
M_MoveToGoal
======================
*/
void M_MoveToGoal (edict_t *ent, float dist)
{
edict_t *goal;
goal = ent->goalentity;
if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
return;
// if the next step hits the enemy, return immediately
if (ent->enemy && SV_CloseEnough (ent, ent->enemy, dist) )
return;
// bump around...
if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist))
{
if (ent->inuse)
SV_NewChaseDir (ent, goal, dist);
}
}
/*
===============
M_walkmove
===============
*/
qboolean M_walkmove (edict_t *ent, float yaw, float dist)
{
vec3_t move;
if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
return false;
yaw = yaw*M_PI*2 / 360;
move[0] = cos(yaw)*dist;
move[1] = sin(yaw)*dist;
move[2] = 0;
return SV_movestep(ent, move, true);
}

205
m_player.h Normal file
View file

@ -0,0 +1,205 @@
// G:\quake2\baseq2\models/player_x/frames
// This file generated by qdata - Do NOT Modify
#define FRAME_stand01 0
#define FRAME_stand02 1
#define FRAME_stand03 2
#define FRAME_stand04 3
#define FRAME_stand05 4
#define FRAME_stand06 5
#define FRAME_stand07 6
#define FRAME_stand08 7
#define FRAME_stand09 8
#define FRAME_stand10 9
#define FRAME_stand11 10
#define FRAME_stand12 11
#define FRAME_stand13 12
#define FRAME_stand14 13
#define FRAME_stand15 14
#define FRAME_stand16 15
#define FRAME_stand17 16
#define FRAME_stand18 17
#define FRAME_stand19 18
#define FRAME_stand20 19
#define FRAME_stand21 20
#define FRAME_stand22 21
#define FRAME_stand23 22
#define FRAME_stand24 23
#define FRAME_stand25 24
#define FRAME_stand26 25
#define FRAME_stand27 26
#define FRAME_stand28 27
#define FRAME_stand29 28
#define FRAME_stand30 29
#define FRAME_stand31 30
#define FRAME_stand32 31
#define FRAME_stand33 32
#define FRAME_stand34 33
#define FRAME_stand35 34
#define FRAME_stand36 35
#define FRAME_stand37 36
#define FRAME_stand38 37
#define FRAME_stand39 38
#define FRAME_stand40 39
#define FRAME_run1 40
#define FRAME_run2 41
#define FRAME_run3 42
#define FRAME_run4 43
#define FRAME_run5 44
#define FRAME_run6 45
#define FRAME_attack1 46
#define FRAME_attack2 47
#define FRAME_attack3 48
#define FRAME_attack4 49
#define FRAME_attack5 50
#define FRAME_attack6 51
#define FRAME_attack7 52
#define FRAME_attack8 53
#define FRAME_pain101 54
#define FRAME_pain102 55
#define FRAME_pain103 56
#define FRAME_pain104 57
#define FRAME_pain201 58
#define FRAME_pain202 59
#define FRAME_pain203 60
#define FRAME_pain204 61
#define FRAME_pain301 62
#define FRAME_pain302 63
#define FRAME_pain303 64
#define FRAME_pain304 65
#define FRAME_jump1 66
#define FRAME_jump2 67
#define FRAME_jump3 68
#define FRAME_jump4 69
#define FRAME_jump5 70
#define FRAME_jump6 71
#define FRAME_flip01 72
#define FRAME_flip02 73
#define FRAME_flip03 74
#define FRAME_flip04 75
#define FRAME_flip05 76
#define FRAME_flip06 77
#define FRAME_flip07 78
#define FRAME_flip08 79
#define FRAME_flip09 80
#define FRAME_flip10 81
#define FRAME_flip11 82
#define FRAME_flip12 83
#define FRAME_salute01 84
#define FRAME_salute02 85
#define FRAME_salute03 86
#define FRAME_salute04 87
#define FRAME_salute05 88
#define FRAME_salute06 89
#define FRAME_salute07 90
#define FRAME_salute08 91
#define FRAME_salute09 92
#define FRAME_salute10 93
#define FRAME_salute11 94
#define FRAME_taunt01 95
#define FRAME_taunt02 96
#define FRAME_taunt03 97
#define FRAME_taunt04 98
#define FRAME_taunt05 99
#define FRAME_taunt06 100
#define FRAME_taunt07 101
#define FRAME_taunt08 102
#define FRAME_taunt09 103
#define FRAME_taunt10 104
#define FRAME_taunt11 105
#define FRAME_taunt12 106
#define FRAME_taunt13 107
#define FRAME_taunt14 108
#define FRAME_taunt15 109
#define FRAME_taunt16 110
#define FRAME_taunt17 111
#define FRAME_wave01 112
#define FRAME_wave02 113
#define FRAME_wave03 114
#define FRAME_wave04 115
#define FRAME_wave05 116
#define FRAME_wave06 117
#define FRAME_wave07 118
#define FRAME_wave08 119
#define FRAME_wave09 120
#define FRAME_wave10 121
#define FRAME_wave11 122
#define FRAME_point01 123
#define FRAME_point02 124
#define FRAME_point03 125
#define FRAME_point04 126
#define FRAME_point05 127
#define FRAME_point06 128
#define FRAME_point07 129
#define FRAME_point08 130
#define FRAME_point09 131
#define FRAME_point10 132
#define FRAME_point11 133
#define FRAME_point12 134
#define FRAME_crstnd01 135
#define FRAME_crstnd02 136
#define FRAME_crstnd03 137
#define FRAME_crstnd04 138
#define FRAME_crstnd05 139
#define FRAME_crstnd06 140
#define FRAME_crstnd07 141
#define FRAME_crstnd08 142
#define FRAME_crstnd09 143
#define FRAME_crstnd10 144
#define FRAME_crstnd11 145
#define FRAME_crstnd12 146
#define FRAME_crstnd13 147
#define FRAME_crstnd14 148
#define FRAME_crstnd15 149
#define FRAME_crstnd16 150
#define FRAME_crstnd17 151
#define FRAME_crstnd18 152
#define FRAME_crstnd19 153
#define FRAME_crwalk1 154
#define FRAME_crwalk2 155
#define FRAME_crwalk3 156
#define FRAME_crwalk4 157
#define FRAME_crwalk5 158
#define FRAME_crwalk6 159
#define FRAME_crattak1 160
#define FRAME_crattak2 161
#define FRAME_crattak3 162
#define FRAME_crattak4 163
#define FRAME_crattak5 164
#define FRAME_crattak6 165
#define FRAME_crattak7 166
#define FRAME_crattak8 167
#define FRAME_crattak9 168
#define FRAME_crpain1 169
#define FRAME_crpain2 170
#define FRAME_crpain3 171
#define FRAME_crpain4 172
#define FRAME_crdeath1 173
#define FRAME_crdeath2 174
#define FRAME_crdeath3 175
#define FRAME_crdeath4 176
#define FRAME_crdeath5 177
#define FRAME_death101 178
#define FRAME_death102 179
#define FRAME_death103 180
#define FRAME_death104 181
#define FRAME_death105 182
#define FRAME_death106 183
#define FRAME_death201 184
#define FRAME_death202 185
#define FRAME_death203 186
#define FRAME_death204 187
#define FRAME_death205 188
#define FRAME_death206 189
#define FRAME_death301 190
#define FRAME_death302 191
#define FRAME_death303 192
#define FRAME_death304 193
#define FRAME_death305 194
#define FRAME_death306 195
#define FRAME_death307 196
#define FRAME_death308 197
#define MODEL_SCALE 1.000000

2941
p_client.c Normal file

File diff suppressed because it is too large Load diff

682
p_hud.c Normal file
View file

@ -0,0 +1,682 @@
#include "g_local.h"
/*
======================================================================
INTERMISSION
======================================================================
*/
void MoveClientToIntermission (edict_t *ent)
{
if (deathmatch->value || coop->value)
{
ent->client->showscores = true;
ent->client->scoreboardnum = 1;
}
VectorCopy (level.intermission_origin, ent->s.origin);
ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8;
ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8;
ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8;
VectorCopy (level.intermission_angle, ent->client->ps.viewangles);
ent->client->ps.pmove.pm_type = PM_FREEZE;
ent->client->ps.gunindex = 0;
ent->client->ps.blend[3] = 0;
ent->client->ps.rdflags &= ~RDF_UNDERWATER;
// clean up powerup info
ent->client->quad_framenum = 0;
ent->client->invincible_framenum = 0;
ent->client->breather_framenum = 0;
ent->client->enviro_framenum = 0;
ent->client->grenade_blew_up = false;
ent->client->grenade_time = 0;
ent->viewheight = 0;
ent->s.modelindex = 0;
ent->s.modelindex2 = 0;
ent->s.modelindex3 = 0;
ent->s.modelindex = 0;
ent->s.effects = 0;
ent->s.sound = 0;
ent->solid = SOLID_NOT;
//FIREBLADE
ent->client->resp.sniper_mode = SNIPER_1X;
ent->client->desired_fov = 90;
ent->client->ps.fov = 90;
ent->client->ps.stats[STAT_SNIPER_ICON] = 0;
//FIREBLADE
// add the layout
if (deathmatch->value || coop->value)
{
DeathmatchScoreboardMessage (ent, NULL);
gi.unicast (ent, true);
}
}
void BeginIntermission (edict_t *targ)
{
int i, n;
edict_t *ent, *client;
if (level.intermissiontime)
return; // already activated
//FIREBLADE
if (teamplay->value)
TallyEndOfLevelTeamScores();
//FIREBLADE
game.autosaved = false;
// respawn any dead clients
for (i=0 ; i<maxclients->value ; i++)
{
client = g_edicts + 1 + i;
if (!client->inuse)
continue;
if (client->health <= 0)
respawn(client);
}
level.intermissiontime = level.time;
level.changemap = targ->map;
if (strstr(level.changemap, "*"))
{
if (coop->value)
{
for (i=0 ; i<maxclients->value ; i++)
{
client = g_edicts + 1 + i;
if (!client->inuse)
continue;
// strip players of all keys between units
for (n = 0; n < MAX_ITEMS; n++)
{
if (itemlist[n].flags & IT_KEY)
client->client->pers.inventory[n] = 0;
}
}
}
}
else
{
if (!deathmatch->value)
{
level.exitintermission = 1; // go immediately to the next level
return;
}
}
level.exitintermission = 0;
// find an intermission spot
ent = G_Find (NULL, FOFS(classname), "info_player_intermission");
if (!ent)
{ // the map creator forgot to put in an intermission point...
ent = G_Find (NULL, FOFS(classname), "info_player_start");
if (!ent)
ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch");
}
else
{ // chose one of four spots
i = rand() & 3;
while (i--)
{
ent = G_Find (ent, FOFS(classname), "info_player_intermission");
if (!ent) // wrap around the list
ent = G_Find (ent, FOFS(classname), "info_player_intermission");
}
}
VectorCopy (ent->s.origin, level.intermission_origin);
VectorCopy (ent->s.angles, level.intermission_angle);
// move all clients to the intermission point
for (i=0 ; i<maxclients->value ; i++)
{
client = g_edicts + 1 + i;
if (!client->inuse)
continue;
MoveClientToIntermission (client);
}
}
void A_ScoreboardMessage (edict_t *ent, edict_t *killer);
/*
==================
DeathmatchScoreboardMessage
==================
*/
void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer)
{
char entry[1024];
char string[1400];
int stringlength;
int i, j, k;
int sorted[MAX_CLIENTS];
int sortedscores[MAX_CLIENTS];
int score, total;
int picnum;
int x, y;
gclient_t *cl;
edict_t *cl_ent;
char *tag;
//FIREBLADE
if (teamplay->value) {
A_ScoreboardMessage (ent, killer);
return;
}
//FIREBLADE
// sort the clients by score
total = 0;
for (i=0 ; i<game.maxclients ; i++)
{
cl_ent = g_edicts + 1 + i;
if (!cl_ent->inuse)
continue;
score = game.clients[i].resp.score;
for (j=0 ; j<total ; j++)
{
if (score > sortedscores[j])
break;
}
for (k=total ; k>j ; k--)
{
sorted[k] = sorted[k-1];
sortedscores[k] = sortedscores[k-1];
}
sorted[j] = i;
sortedscores[j] = score;
total++;
}
// print level name and exit rules
string[0] = 0;
stringlength = strlen(string);
// add the clients in sorted order
if (total > 12)
total = 12;
for (i=0 ; i<total ; i++)
{
cl = &game.clients[sorted[i]];
cl_ent = g_edicts + 1 + sorted[i];
picnum = gi.imageindex ("i_fixme");
x = (i>=6) ? 160 : 0;
y = 32 + 32 * (i%6);
// add a dogtag
if (cl_ent == ent)
tag = "tag1";
else if (cl_ent == killer)
tag = "tag2";
else
tag = NULL;
if (tag)
{
Com_sprintf (entry, sizeof(entry),
"xv %i yv %i picn %s ",x+32, y, tag);
j = strlen(entry);
if (stringlength + j > 1024)
break;
strcpy (string + stringlength, entry);
stringlength += j;
}
// send the layout
Com_sprintf (entry, sizeof(entry),
"client %i %i %i %i %i %i ",
x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600);
j = strlen(entry);
if (stringlength + j > 1024)
break;
strcpy (string + stringlength, entry);
stringlength += j;
}
gi.WriteByte (svc_layout);
gi.WriteString (string);
}
/*
==================
DeathmatchScoreboard
Draw instead of help message.
Note that it isn't that hard to overflow the 1400 byte message limit!
==================
*/
void DeathmatchScoreboard (edict_t *ent)
{
DeathmatchScoreboardMessage (ent, ent->enemy);
gi.unicast (ent, true);
}
/*
==================
Cmd_Score_f
Display the scoreboard
==================
*/
void Cmd_Score_f (edict_t *ent)
{
ent->client->showinventory = false;
ent->client->showhelp = false;
//FIREBLADE
if (ent->client->menu)
PMenu_Close(ent);
//FIREBLADE
if (!deathmatch->value && !coop->value)
return;
if (ent->client->showscores)
{
//FIREBLADE
if (teamplay->value && ent->client->scoreboardnum < 2) // toggle scoreboards...
{
ent->client->scoreboardnum++;
DeathmatchScoreboard(ent);
return;
}
//FIREBLADE
ent->client->showscores = false;
return;
}
ent->client->showscores = true;
//FIREBLADE
ent->client->scoreboardnum = 1;
//FIREBLADE
DeathmatchScoreboard (ent);
}
/*
==================
HelpComputer
Draw help computer.
==================
*/
void HelpComputer (edict_t *ent)
{
char string[1024];
char *sk;
if (skill->value == 0)
sk = "easy";
else if (skill->value == 1)
sk = "medium";
else if (skill->value == 2)
sk = "hard";
else
sk = "hard+";
// send the layout
Com_sprintf (string, sizeof(string),
"xv 32 yv 8 picn help " // background
"xv 202 yv 12 string2 \"%s\" " // skill
"xv 0 yv 24 cstring2 \"%s\" " // level name
"xv 0 yv 54 cstring2 \"%s\" " // help 1
"xv 0 yv 110 cstring2 \"%s\" " // help 2
"xv 50 yv 164 string2 \" kills goals secrets\" "
"xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ",
sk,
level.level_name,
game.helpmessage1,
game.helpmessage2,
level.killed_monsters, level.total_monsters,
level.found_goals, level.total_goals,
level.found_secrets, level.total_secrets);
gi.WriteByte (svc_layout);
gi.WriteString (string);
gi.unicast (ent, true);
}
/*
==================
Cmd_Help_f
Display the current help message
==================
*/
void Cmd_Help_f (edict_t *ent)
{
// this is for backwards compatability
if (deathmatch->value)
{
Cmd_Score_f (ent);
return;
}
ent->client->showinventory = false;
ent->client->showscores = false;
if (ent->client->showhelp && (ent->client->resp.game_helpchanged == game.helpchanged))
{
ent->client->showhelp = false;
return;
}
ent->client->showhelp = true;
ent->client->resp.helpchanged = 0;
HelpComputer (ent);
}
//=======================================================================
/*
===============
G_SetStats
Rearranged for chase cam support -FB
===============
*/
void G_SetStats (edict_t *ent)
{
gitem_t *item;
int index, cells, index2;
int power_armor_type;
if (!ent->client->chase_mode)
{
//
// health
//
ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
ent->client->ps.stats[STAT_HEALTH] = ent->health;
//
// ammo (now clips really)
//
// zucc modified this to do clips instead
if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */)
{
ent->client->ps.stats[STAT_CLIP_ICON] = 0;
ent->client->ps.stats[STAT_CLIP] = 0;
}
else
{
item = &itemlist[ent->client->ammo_index];
ent->client->ps.stats[STAT_CLIP_ICON] = gi.imageindex (item->icon);
ent->client->ps.stats[STAT_CLIP] = ent->client->pers.inventory[ent->client->ammo_index];
}
// zucc display special item and special weapon
if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SNIPER_NAME))] )
ent->client->ps.stats[STAT_WEAPONS_ICON] = gi.imageindex(FindItem(SNIPER_NAME)->icon);
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(M4_NAME))] )
ent->client->ps.stats[STAT_WEAPONS_ICON] = gi.imageindex(FindItem(M4_NAME)->icon);
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(MP5_NAME))] )
ent->client->ps.stats[STAT_WEAPONS_ICON] = gi.imageindex(FindItem(MP5_NAME)->icon);
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(M3_NAME))] )
ent->client->ps.stats[STAT_WEAPONS_ICON] = gi.imageindex(FindItem(M3_NAME)->icon);
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(HC_NAME))] )
ent->client->ps.stats[STAT_WEAPONS_ICON] = gi.imageindex(FindItem(HC_NAME)->icon);
else
ent->client->ps.stats[STAT_WEAPONS_ICON] = 0;
if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(KEV_NAME))] )
ent->client->ps.stats[STAT_ITEMS_ICON] = gi.imageindex(FindItem(KEV_NAME)->icon);
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(LASER_NAME))] )
ent->client->ps.stats[STAT_ITEMS_ICON] = gi.imageindex(FindItem(LASER_NAME)->icon);
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SLIP_NAME))] )
ent->client->ps.stats[STAT_ITEMS_ICON] = gi.imageindex(FindItem(SLIP_NAME)->icon);
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(SIL_NAME))] )
ent->client->ps.stats[STAT_ITEMS_ICON] = gi.imageindex(FindItem(SIL_NAME)->icon);
else if ( ent->client->pers.inventory[ITEM_INDEX(FindItem(BAND_NAME))] )
ent->client->ps.stats[STAT_ITEMS_ICON] = gi.imageindex(FindItem(BAND_NAME)->icon);
else
ent->client->ps.stats[STAT_ITEMS_ICON] = 0;
// grenades remaining
index2 = ITEM_INDEX(FindItem(GRENADE_NAME));
if ( ent->client->pers.inventory[index2] )
{
ent->client->ps.stats[STAT_GRENADE_ICON] = gi.imageindex ("a_m61frag");
ent->client->ps.stats[STAT_GRENADES] = ent->client->pers.inventory[index2];
}
else
{
ent->client->ps.stats[STAT_GRENADE_ICON] = 0;
}
//
// ammo by weapon
//
//
if ( ent->client->pers.weapon )
{
switch ( ent->client->curr_weap )
{
case MK23_NUM:
{
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_bullets");
ent->client->ps.stats[STAT_AMMO] = ent->client->mk23_rds;
break;
}
case MP5_NUM:
{
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_bullets");
ent->client->ps.stats[STAT_AMMO] = ent->client->mp5_rds;
break;
}
case M4_NUM:
{
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_bullets");
ent->client->ps.stats[STAT_AMMO] = ent->client->m4_rds;
break;
}
case M3_NUM:
{
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_shells");
ent->client->ps.stats[STAT_AMMO] = ent->client->shot_rds;
break;
}
case HC_NUM:
{
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_shells");
ent->client->ps.stats[STAT_AMMO] = ent->client->cannon_rds;
break;
}
case SNIPER_NUM:
{
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_bullets");
ent->client->ps.stats[STAT_AMMO] = ent->client->sniper_rds;
break;
}
case DUAL_NUM:
{
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_bullets");
ent->client->ps.stats[STAT_AMMO] = ent->client->dual_rds;
break;
}
case KNIFE_NUM:
{
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("w_knife");
ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ITEM_INDEX(FindItem (KNIFE_NAME))];
break;
}
case GRENADE_NUM:
{
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex ("a_m61frag");
ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ITEM_INDEX(FindItem (GRENADE_NAME))];
break;
}
default:
gi.dprintf("Failed to find weapon/icon for hud.\n");
}
}
//
// sniper mode icons
//
//if ( ent->client->sniper_mode )
// gi.cprintf (ent, PRINT_HIGH, "Sniper Zoom set at %d.\n", ent->client->sniper_mode);
if ( ent->client->resp.sniper_mode == SNIPER_1X
|| ent->client->weaponstate == WEAPON_RELOADING
|| ent->client->weaponstate == WEAPON_BUSY
|| ent->client->no_sniper_display )
ent->client->ps.stats[STAT_SNIPER_ICON] = 0;
else if ( ent->client->resp.sniper_mode == SNIPER_2X )
ent->client->ps.stats[STAT_SNIPER_ICON] = gi.imageindex ("scope2x");
else if ( ent->client->resp.sniper_mode == SNIPER_4X )
ent->client->ps.stats[STAT_SNIPER_ICON] = gi.imageindex ("scope4x");
else if ( ent->client->resp.sniper_mode == SNIPER_6X )
ent->client->ps.stats[STAT_SNIPER_ICON] = gi.imageindex ("scope6x");
//
// armor
//
power_armor_type = PowerArmorType (ent);
if (power_armor_type)
{
cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
if (cells == 0)
{ // ran out of cells for power armor
ent->flags &= ~FL_POWER_ARMOR;
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
power_armor_type = 0;;
}
}
index = ArmorIndex (ent);
if (power_armor_type && (!index || (level.framenum & 8) ) )
{ // flash between power armor and other armor icon
ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield");
ent->client->ps.stats[STAT_ARMOR] = cells;
}
else if (index)
{
item = GetItemByIndex (index);
ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon);
ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
}
else
{
ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
ent->client->ps.stats[STAT_ARMOR] = 0;
}
//
// pickup message
//
if (level.time > ent->client->pickup_msg_time)
{
ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
}
//
// timers
//
if (ent->client->quad_framenum > level.framenum)
{
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad");
ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10;
}
else if (ent->client->invincible_framenum > level.framenum)
{
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability");
ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10;
}
else if (ent->client->enviro_framenum > level.framenum)
{
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit");
ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10;
}
else if (ent->client->breather_framenum > level.framenum)
{
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather");
ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10;
}
else
{
ent->client->ps.stats[STAT_TIMER_ICON] = 0;
ent->client->ps.stats[STAT_TIMER] = 0;
}
//
// selected item
//
if (ent->client->pers.selected_item == -1)
ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
else
ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon);
ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
//
// frags
//
ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
//
// help icon / current weapon if not shown
//
if (ent->client->resp.helpchanged && (level.framenum&8) )
ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help");
else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91)
&& ent->client->pers.weapon)
ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon);
else
ent->client->ps.stats[STAT_HELPICON] = 0;
}
//
// layouts
//
ent->client->ps.stats[STAT_LAYOUTS] = 0;
if (deathmatch->value)
{
if (ent->client->pers.health <= 0 || level.intermissiontime
|| ent->client->showscores)
ent->client->ps.stats[STAT_LAYOUTS] |= 1;
if (ent->client->showinventory && ent->client->pers.health > 0)
ent->client->ps.stats[STAT_LAYOUTS] |= 2;
}
else
{
if (ent->client->showscores || ent->client->showhelp)
ent->client->ps.stats[STAT_LAYOUTS] |= 1;
if (ent->client->showinventory && ent->client->pers.health > 0)
ent->client->ps.stats[STAT_LAYOUTS] |= 2;
}
SetIDView(ent);
//FIREBLADE
if (teamplay->value)
A_Scoreboard(ent);
//FIREBLADE
}

127
p_trail.c Normal file
View file

@ -0,0 +1,127 @@
#include "g_local.h"
/*
==============================================================================
PLAYER TRAIL
==============================================================================
This is a circular list containing the a list of points of where
the player has been recently. It is used by monsters for pursuit.
.origin the spot
.owner forward link
.aiment backward link
*/
#define TRAIL_LENGTH 8
edict_t *trail[TRAIL_LENGTH];
int trail_head;
qboolean trail_active = false;
#define NEXT(n) (((n) + 1) & (TRAIL_LENGTH - 1))
#define PREV(n) (((n) - 1) & (TRAIL_LENGTH - 1))
void PlayerTrail_Init (void)
{
int n;
if (deathmatch->value /* FIXME || coop */)
return;
for (n = 0; n < TRAIL_LENGTH; n++)
{
trail[n] = G_Spawn();
trail[n]->classname = "player_trail";
}
trail_head = 0;
trail_active = true;
}
void PlayerTrail_Add (vec3_t spot)
{
vec3_t temp;
if (!trail_active)
return;
VectorCopy (spot, trail[trail_head]->s.origin);
trail[trail_head]->timestamp = level.time;
VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp);
trail[trail_head]->s.angles[1] = vectoyaw (temp);
trail_head = NEXT(trail_head);
}
void PlayerTrail_New (vec3_t spot)
{
if (!trail_active)
return;
PlayerTrail_Init ();
PlayerTrail_Add (spot);
}
edict_t *PlayerTrail_PickFirst (edict_t *self)
{
int marker;
int n;
if (!trail_active)
return NULL;
for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
{
if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
marker = NEXT(marker);
else
break;
}
if (visible(self, trail[marker]))
{
return trail[marker];
}
if (visible(self, trail[PREV(marker)]))
{
return trail[PREV(marker)];
}
return trail[marker];
}
edict_t *PlayerTrail_PickNext (edict_t *self)
{
int marker;
int n;
if (!trail_active)
return NULL;
for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
{
if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
marker = NEXT(marker);
else
break;
}
return trail[marker];
}
edict_t *PlayerTrail_LastSpot (void)
{
return trail[PREV(trail_head)];
}

1244
p_view.c Normal file

File diff suppressed because it is too large Load diff

4451
p_weapon.c Normal file

File diff suppressed because it is too large Load diff

1401
q_shared.c Normal file

File diff suppressed because it is too large Load diff

1275
q_shared.h Normal file

File diff suppressed because it is too large Load diff