as released 1999-02-12
This commit is contained in:
commit
5004fddb4e
42 changed files with 38118 additions and 0 deletions
251
CHANGES
Normal file
251
CHANGES
Normal 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
121
Makefile
Normal 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
223
README
Normal 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
669
a_cmds.c
Normal 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);
|
||||
|
||||
}
|
||||
|
26
a_game.h
Normal file
26
a_game.h
Normal 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
200
a_items.c
Normal 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
194
a_menu.c
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Action (formerly Axshun) menu code
|
||||
* This is directly from Zoid's CTF code. Thanks to Zoid again.
|
||||
* -Fireblade
|
||||
*/
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
void PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num)
|
||||
{
|
||||
pmenuhnd_t *hnd;
|
||||
pmenu_t *p;
|
||||
int i;
|
||||
|
||||
if (!ent->client)
|
||||
return;
|
||||
|
||||
if (ent->client->menu) {
|
||||
gi.dprintf("warning, ent already has a menu\n");
|
||||
PMenu_Close(ent);
|
||||
}
|
||||
|
||||
hnd = gi.TagMalloc(sizeof(*hnd), TAG_GAME);
|
||||
|
||||
hnd->entries = entries;
|
||||
hnd->num = num;
|
||||
|
||||
if (cur < 0 || !entries[cur].SelectFunc) {
|
||||
for (i = 0, p = entries; i < num; i++, p++)
|
||||
if (p->SelectFunc)
|
||||
break;
|
||||
} else
|
||||
i = cur;
|
||||
|
||||
if (i >= num)
|
||||
hnd->cur = -1;
|
||||
else
|
||||
hnd->cur = i;
|
||||
|
||||
ent->client->showscores = true;
|
||||
ent->client->inmenu = true;
|
||||
ent->client->menu = hnd;
|
||||
|
||||
PMenu_Update(ent);
|
||||
gi.unicast (ent, true);
|
||||
}
|
||||
|
||||
void PMenu_Close(edict_t *ent)
|
||||
{
|
||||
if (!ent->client->menu)
|
||||
return;
|
||||
|
||||
gi.TagFree(ent->client->menu);
|
||||
ent->client->menu = NULL;
|
||||
ent->client->showscores = false;
|
||||
}
|
||||
|
||||
void PMenu_Update(edict_t *ent)
|
||||
{
|
||||
char string[1400];
|
||||
int i;
|
||||
pmenu_t *p;
|
||||
int x;
|
||||
pmenuhnd_t *hnd;
|
||||
char *t;
|
||||
qboolean alt = false;
|
||||
|
||||
if (!ent->client->menu) {
|
||||
gi.dprintf("warning: ent has no menu\n");
|
||||
return;
|
||||
}
|
||||
|
||||
hnd = ent->client->menu;
|
||||
|
||||
strcpy(string, "xv 32 yv 8 ");
|
||||
|
||||
for (i = 0, p = hnd->entries; i < hnd->num; i++, p++) {
|
||||
if (!p->text || !*(p->text))
|
||||
continue; // blank line
|
||||
t = p->text;
|
||||
if (*t == '*') {
|
||||
alt = true;
|
||||
t++;
|
||||
}
|
||||
sprintf(string + strlen(string), "yv %d ", 32 + i * 8);
|
||||
if (p->align == PMENU_ALIGN_CENTER)
|
||||
x = 196/2 - strlen(t)*4 + 64;
|
||||
else if (p->align == PMENU_ALIGN_RIGHT)
|
||||
x = 64 + (196 - strlen(t)*8);
|
||||
else
|
||||
x = 64;
|
||||
|
||||
sprintf(string + strlen(string), "xv %d ",
|
||||
x - ((hnd->cur == i) ? 8 : 0));
|
||||
|
||||
if (hnd->cur == i)
|
||||
sprintf(string + strlen(string), "string2 \"\x0d%s\" ", t);
|
||||
else if (alt)
|
||||
sprintf(string + strlen(string), "string2 \"%s\" ", t);
|
||||
else
|
||||
sprintf(string + strlen(string), "string \"%s\" ", t);
|
||||
alt = false;
|
||||
}
|
||||
|
||||
gi.WriteByte (svc_layout);
|
||||
gi.WriteString (string);
|
||||
}
|
||||
|
||||
void PMenu_Next(edict_t *ent)
|
||||
{
|
||||
pmenuhnd_t *hnd;
|
||||
int i;
|
||||
pmenu_t *p;
|
||||
|
||||
if (!ent->client->menu) {
|
||||
gi.dprintf("warning: ent has no menu\n");
|
||||
return;
|
||||
}
|
||||
|
||||
hnd = ent->client->menu;
|
||||
|
||||
if (hnd->cur < 0)
|
||||
return; // no selectable entries
|
||||
|
||||
i = hnd->cur;
|
||||
p = hnd->entries + hnd->cur;
|
||||
do {
|
||||
i++, p++;
|
||||
if (i == hnd->num)
|
||||
i = 0, p = hnd->entries;
|
||||
if (p->SelectFunc)
|
||||
break;
|
||||
} while (i != hnd->cur);
|
||||
|
||||
hnd->cur = i;
|
||||
|
||||
PMenu_Update(ent);
|
||||
gi.unicast (ent, true);
|
||||
}
|
||||
|
||||
void PMenu_Prev(edict_t *ent)
|
||||
{
|
||||
pmenuhnd_t *hnd;
|
||||
int i;
|
||||
pmenu_t *p;
|
||||
|
||||
if (!ent->client->menu) {
|
||||
gi.dprintf("warning: ent has no menu\n");
|
||||
return;
|
||||
}
|
||||
|
||||
hnd = ent->client->menu;
|
||||
|
||||
if (hnd->cur < 0)
|
||||
return; // no selectable entries
|
||||
|
||||
i = hnd->cur;
|
||||
p = hnd->entries + hnd->cur;
|
||||
do {
|
||||
if (i == 0) {
|
||||
i = hnd->num - 1;
|
||||
p = hnd->entries + i;
|
||||
} else
|
||||
i--, p--;
|
||||
if (p->SelectFunc)
|
||||
break;
|
||||
} while (i != hnd->cur);
|
||||
|
||||
hnd->cur = i;
|
||||
|
||||
PMenu_Update(ent);
|
||||
gi.unicast (ent, true);
|
||||
}
|
||||
|
||||
void PMenu_Select(edict_t *ent)
|
||||
{
|
||||
pmenuhnd_t *hnd;
|
||||
pmenu_t *p;
|
||||
|
||||
if (!ent->client->menu) {
|
||||
gi.dprintf("warning: ent has no menu\n");
|
||||
return;
|
||||
}
|
||||
|
||||
hnd = ent->client->menu;
|
||||
|
||||
if (hnd->cur < 0)
|
||||
return; // no selectable entries
|
||||
|
||||
p = hnd->entries + hnd->cur;
|
||||
|
||||
if (p->SelectFunc)
|
||||
p->SelectFunc(ent, p);
|
||||
}
|
30
a_menu.h
Normal file
30
a_menu.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Action (formerly Axshun) menus
|
||||
* From Zoid's CTF.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PMENU_ALIGN_LEFT,
|
||||
PMENU_ALIGN_CENTER,
|
||||
PMENU_ALIGN_RIGHT
|
||||
};
|
||||
|
||||
typedef struct pmenuhnd_s {
|
||||
struct pmenu_s *entries;
|
||||
int cur;
|
||||
int num;
|
||||
} pmenuhnd_t;
|
||||
|
||||
typedef struct pmenu_s {
|
||||
char *text;
|
||||
int align;
|
||||
void *arg;
|
||||
void (*SelectFunc)(edict_t *ent, struct pmenu_s *entry);
|
||||
} pmenu_t;
|
||||
|
||||
void PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num);
|
||||
void PMenu_Close(edict_t *ent);
|
||||
void PMenu_Update(edict_t *ent);
|
||||
void PMenu_Next(edict_t *ent);
|
||||
void PMenu_Prev(edict_t *ent);
|
||||
void PMenu_Select(edict_t *ent);
|
668
a_radio.c
Normal file
668
a_radio.c
Normal 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
49
a_radio.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* a_radio.h
|
||||
*
|
||||
* Include file for use with radio stuff
|
||||
* -Fireblade
|
||||
*/
|
||||
|
||||
typedef struct radio_msg_s
|
||||
{
|
||||
char *msg; // the msg name
|
||||
int length; // length in server frames (ie tenths of a second), rounded up
|
||||
} radio_msg_t;
|
||||
|
||||
#define RADIO_CLICK "radio/click.wav"
|
||||
#define RADIO_CLICK_LEN 2
|
||||
#define RADIO_DEATH_MALE "radio/male/rdeath.wav"
|
||||
#define RADIO_DEATH_MALE_LEN 27
|
||||
#define RADIO_DEATH_FEMALE "radio/female/rdeath.wav"
|
||||
#define RADIO_DEATH_FEMALE_LEN 30
|
||||
|
||||
#define MAX_SOUNDFILE_PATH_LEN 32 // max length of a sound file path
|
||||
#define MAX_RADIO_MSG_QUEUE_SIZE 4
|
||||
#define MAX_RADIO_QUEUE_SIZE 6 // this must be at least 2 greater than the above
|
||||
|
||||
extern radio_msg_t male_radio_msgs[];
|
||||
extern radio_msg_t female_radio_msgs[];
|
||||
|
||||
typedef struct radio_queue_entry_s
|
||||
{
|
||||
char soundfile[MAX_SOUNDFILE_PATH_LEN];
|
||||
edict_t *from_player;
|
||||
int from_gender; // true if female
|
||||
qboolean now_playing;
|
||||
int length;
|
||||
qboolean click;
|
||||
} radio_queue_entry_t;
|
||||
|
||||
void RadioThink(edict_t *);
|
||||
void Cmd_Radio_f(edict_t *);
|
||||
void Cmd_Radiogender_f(edict_t *);
|
||||
void Cmd_Radio_power_f(edict_t *);
|
||||
void Cmd_Radiopartner_f(edict_t *);
|
||||
void Cmd_Radioteam_f(edict_t *);
|
||||
void Cmd_Channel_f(edict_t *);
|
||||
void Cmd_Say_partner_f(edict_t *);
|
||||
void Cmd_Partner_f(edict_t *);
|
||||
void Cmd_Deny_f(edict_t *);
|
||||
void Cmd_Unpartner_f(edict_t *);
|
||||
void PrecacheRadioSounds();
|
79
a_team.h
Normal file
79
a_team.h
Normal 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;
|
238
g_chase.c
Normal file
238
g_chase.c
Normal 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;
|
||||
}
|
||||
}
|
||||
|
941
g_combat.c
Normal file
941
g_combat.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
419
g_main.c
Normal file
419
g_main.c
Normal 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 ();
|
||||
}
|
||||
|
721
g_monster.c
Normal file
721
g_monster.c
Normal file
|
@ -0,0 +1,721 @@
|
|||
#include "g_local.h"
|
||||
|
||||
|
||||
//
|
||||
// monster weapons
|
||||
//
|
||||
|
||||
//FIXME mosnters should call these with a totally accurate direction
|
||||
// and we can mess it up based on skill. Spread should be for normal
|
||||
// and we can tighten or loosen based on skill. We could muck with
|
||||
// the damages too, but I'm not sure that's such a good idea.
|
||||
void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype)
|
||||
{
|
||||
fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype)
|
||||
{
|
||||
fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
|
||||
{
|
||||
fire_blaster (self, start, dir, damage, speed, effect, false);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype)
|
||||
{
|
||||
fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype)
|
||||
{
|
||||
fire_rocket (self, start, dir, damage, speed, damage+20, damage);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype)
|
||||
{
|
||||
fire_rail (self, start, aimdir, damage, kick);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype)
|
||||
{
|
||||
fire_bfg (self, start, aimdir, damage, speed, damage_radius);
|
||||
|
||||
gi.WriteByte (svc_muzzleflash2);
|
||||
gi.WriteShort (self - g_edicts);
|
||||
gi.WriteByte (flashtype);
|
||||
gi.multicast (start, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Monster utility functions
|
||||
//
|
||||
|
||||
static void M_FliesOff (edict_t *self)
|
||||
{
|
||||
self->s.effects &= ~EF_FLIES;
|
||||
self->s.sound = 0;
|
||||
}
|
||||
|
||||
static void M_FliesOn (edict_t *self)
|
||||
{
|
||||
if (self->waterlevel)
|
||||
return;
|
||||
self->s.effects |= EF_FLIES;
|
||||
self->s.sound = gi.soundindex ("infantry/inflies1.wav");
|
||||
self->think = M_FliesOff;
|
||||
self->nextthink = level.time + 60;
|
||||
}
|
||||
|
||||
void M_FlyCheck (edict_t *self)
|
||||
{
|
||||
if (self->waterlevel)
|
||||
return;
|
||||
|
||||
if (random() > 0.5)
|
||||
return;
|
||||
|
||||
self->think = M_FliesOn;
|
||||
self->nextthink = level.time + 5 + 10 * random();
|
||||
}
|
||||
|
||||
void AttackFinished (edict_t *self, float time)
|
||||
{
|
||||
self->monsterinfo.attack_finished = level.time + time;
|
||||
}
|
||||
|
||||
|
||||
void M_CheckGround (edict_t *ent)
|
||||
{
|
||||
vec3_t point;
|
||||
trace_t trace;
|
||||
|
||||
if (ent->flags & (FL_SWIM|FL_FLY))
|
||||
return;
|
||||
|
||||
if (ent->velocity[2] > 100)
|
||||
{
|
||||
ent->groundentity = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// if the hull point one-quarter unit down is solid the entity is on ground
|
||||
point[0] = ent->s.origin[0];
|
||||
point[1] = ent->s.origin[1];
|
||||
point[2] = ent->s.origin[2] - 0.25;
|
||||
|
||||
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID);
|
||||
|
||||
// check steepness
|
||||
if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
|
||||
{
|
||||
ent->groundentity = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// ent->groundentity = trace.ent;
|
||||
// ent->groundentity_linkcount = trace.ent->linkcount;
|
||||
// if (!trace.startsolid && !trace.allsolid)
|
||||
// VectorCopy (trace.endpos, ent->s.origin);
|
||||
if (!trace.startsolid && !trace.allsolid)
|
||||
{
|
||||
VectorCopy (trace.endpos, ent->s.origin);
|
||||
ent->groundentity = trace.ent;
|
||||
ent->groundentity_linkcount = trace.ent->linkcount;
|
||||
ent->velocity[2] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void M_CatagorizePosition (edict_t *ent)
|
||||
{
|
||||
vec3_t point;
|
||||
int cont;
|
||||
|
||||
//
|
||||
// get waterlevel
|
||||
//
|
||||
point[0] = ent->s.origin[0];
|
||||
point[1] = ent->s.origin[1];
|
||||
point[2] = ent->s.origin[2] + ent->mins[2] + 1;
|
||||
cont = gi.pointcontents (point);
|
||||
|
||||
if (!(cont & MASK_WATER))
|
||||
{
|
||||
ent->waterlevel = 0;
|
||||
ent->watertype = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
ent->watertype = cont;
|
||||
ent->waterlevel = 1;
|
||||
point[2] += 26;
|
||||
cont = gi.pointcontents (point);
|
||||
if (!(cont & MASK_WATER))
|
||||
return;
|
||||
|
||||
ent->waterlevel = 2;
|
||||
point[2] += 22;
|
||||
cont = gi.pointcontents (point);
|
||||
if (cont & MASK_WATER)
|
||||
ent->waterlevel = 3;
|
||||
}
|
||||
|
||||
|
||||
void M_WorldEffects (edict_t *ent)
|
||||
{
|
||||
int dmg;
|
||||
|
||||
if (ent->health > 0)
|
||||
{
|
||||
if (!(ent->flags & FL_SWIM))
|
||||
{
|
||||
if (ent->waterlevel < 3)
|
||||
{
|
||||
ent->air_finished = level.time + 12;
|
||||
}
|
||||
else if (ent->air_finished < level.time)
|
||||
{ // drown!
|
||||
if (ent->pain_debounce_time < level.time)
|
||||
{
|
||||
dmg = 2 + 2 * floor(level.time - ent->air_finished);
|
||||
if (dmg > 15)
|
||||
dmg = 15;
|
||||
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
|
||||
ent->pain_debounce_time = level.time + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ent->waterlevel > 0)
|
||||
{
|
||||
ent->air_finished = level.time + 9;
|
||||
}
|
||||
else if (ent->air_finished < level.time)
|
||||
{ // suffocate!
|
||||
if (ent->pain_debounce_time < level.time)
|
||||
{
|
||||
dmg = 2 + 2 * floor(level.time - ent->air_finished);
|
||||
if (dmg > 15)
|
||||
dmg = 15;
|
||||
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
|
||||
ent->pain_debounce_time = level.time + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ent->waterlevel == 0)
|
||||
{
|
||||
if (ent->flags & FL_INWATER)
|
||||
{
|
||||
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
|
||||
ent->flags &= ~FL_INWATER;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
|
||||
{
|
||||
if (ent->damage_debounce_time < level.time)
|
||||
{
|
||||
ent->damage_debounce_time = level.time + 0.2;
|
||||
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA);
|
||||
}
|
||||
}
|
||||
if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
|
||||
{
|
||||
if (ent->damage_debounce_time < level.time)
|
||||
{
|
||||
ent->damage_debounce_time = level.time + 1;
|
||||
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME);
|
||||
}
|
||||
}
|
||||
|
||||
if ( !(ent->flags & FL_INWATER) )
|
||||
{
|
||||
if (!(ent->svflags & SVF_DEADMONSTER))
|
||||
{
|
||||
if (ent->watertype & CONTENTS_LAVA)
|
||||
if (random() <= 0.5)
|
||||
gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
|
||||
else
|
||||
gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
|
||||
else if (ent->watertype & CONTENTS_SLIME)
|
||||
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
|
||||
else if (ent->watertype & CONTENTS_WATER)
|
||||
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
|
||||
}
|
||||
|
||||
ent->flags |= FL_INWATER;
|
||||
ent->damage_debounce_time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void M_droptofloor (edict_t *ent)
|
||||
{
|
||||
vec3_t end;
|
||||
trace_t trace;
|
||||
|
||||
ent->s.origin[2] += 1;
|
||||
VectorCopy (ent->s.origin, end);
|
||||
end[2] -= 256;
|
||||
|
||||
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
|
||||
|
||||
if (trace.fraction == 1 || trace.allsolid)
|
||||
return;
|
||||
|
||||
VectorCopy (trace.endpos, ent->s.origin);
|
||||
|
||||
gi.linkentity (ent);
|
||||
M_CheckGround (ent);
|
||||
M_CatagorizePosition (ent);
|
||||
}
|
||||
|
||||
|
||||
void M_SetEffects (edict_t *ent)
|
||||
{
|
||||
ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN);
|
||||
ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
|
||||
|
||||
if (ent->monsterinfo.aiflags & AI_RESURRECTING)
|
||||
{
|
||||
ent->s.effects |= EF_COLOR_SHELL;
|
||||
ent->s.renderfx |= RF_SHELL_RED;
|
||||
}
|
||||
|
||||
if (ent->health <= 0)
|
||||
return;
|
||||
|
||||
if (ent->powerarmor_time > level.time)
|
||||
{
|
||||
if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN)
|
||||
{
|
||||
ent->s.effects |= EF_POWERSCREEN;
|
||||
}
|
||||
else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD)
|
||||
{
|
||||
ent->s.effects |= EF_COLOR_SHELL;
|
||||
ent->s.renderfx |= RF_SHELL_GREEN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void M_MoveFrame (edict_t *self)
|
||||
{
|
||||
mmove_t *move;
|
||||
int index;
|
||||
|
||||
move = self->monsterinfo.currentmove;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
|
||||
if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe))
|
||||
{
|
||||
self->s.frame = self->monsterinfo.nextframe;
|
||||
self->monsterinfo.nextframe = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (self->s.frame == move->lastframe)
|
||||
{
|
||||
if (move->endfunc)
|
||||
{
|
||||
move->endfunc (self);
|
||||
|
||||
// regrab move, endfunc is very likely to change it
|
||||
move = self->monsterinfo.currentmove;
|
||||
|
||||
// check for death
|
||||
if (self->svflags & SVF_DEADMONSTER)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (self->s.frame < move->firstframe || self->s.frame > move->lastframe)
|
||||
{
|
||||
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
||||
self->s.frame = move->firstframe;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
|
||||
{
|
||||
self->s.frame++;
|
||||
if (self->s.frame > move->lastframe)
|
||||
self->s.frame = move->firstframe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index = self->s.frame - move->firstframe;
|
||||
if (move->frame[index].aifunc)
|
||||
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
|
||||
move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale);
|
||||
else
|
||||
move->frame[index].aifunc (self, 0);
|
||||
|
||||
if (move->frame[index].thinkfunc)
|
||||
move->frame[index].thinkfunc (self);
|
||||
}
|
||||
|
||||
|
||||
void monster_think (edict_t *self)
|
||||
{
|
||||
M_MoveFrame (self);
|
||||
if (self->linkcount != self->monsterinfo.linkcount)
|
||||
{
|
||||
self->monsterinfo.linkcount = self->linkcount;
|
||||
M_CheckGround (self);
|
||||
}
|
||||
M_CatagorizePosition (self);
|
||||
M_WorldEffects (self);
|
||||
M_SetEffects (self);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
monster_use
|
||||
|
||||
Using a monster makes it angry at the current activator
|
||||
================
|
||||
*/
|
||||
void monster_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
if (self->enemy)
|
||||
return;
|
||||
if (self->health <= 0)
|
||||
return;
|
||||
if (activator->flags & FL_NOTARGET)
|
||||
return;
|
||||
if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
|
||||
return;
|
||||
|
||||
// delay reaction so if the monster is teleported, its sound is still heard
|
||||
self->enemy = activator;
|
||||
FoundTarget (self);
|
||||
}
|
||||
|
||||
|
||||
void monster_start_go (edict_t *self);
|
||||
|
||||
|
||||
void monster_triggered_spawn (edict_t *self)
|
||||
{
|
||||
self->s.origin[2] += 1;
|
||||
KillBox (self);
|
||||
|
||||
self->solid = SOLID_BBOX;
|
||||
self->movetype = MOVETYPE_STEP;
|
||||
self->svflags &= ~SVF_NOCLIENT;
|
||||
self->air_finished = level.time + 12;
|
||||
gi.linkentity (self);
|
||||
|
||||
monster_start_go (self);
|
||||
|
||||
if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
|
||||
{
|
||||
FoundTarget (self);
|
||||
}
|
||||
else
|
||||
{
|
||||
self->enemy = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
// we have a one frame delay here so we don't telefrag the guy who activated us
|
||||
self->think = monster_triggered_spawn;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
if (activator->client)
|
||||
self->enemy = activator;
|
||||
self->use = monster_use;
|
||||
}
|
||||
|
||||
void monster_triggered_start (edict_t *self)
|
||||
{
|
||||
self->solid = SOLID_NOT;
|
||||
self->movetype = MOVETYPE_NONE;
|
||||
self->svflags |= SVF_NOCLIENT;
|
||||
self->nextthink = 0;
|
||||
self->use = monster_triggered_spawn_use;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
monster_death_use
|
||||
|
||||
When a monster dies, it fires all of its targets with the current
|
||||
enemy as activator.
|
||||
================
|
||||
*/
|
||||
void monster_death_use (edict_t *self)
|
||||
{
|
||||
self->flags &= ~(FL_FLY|FL_SWIM);
|
||||
self->monsterinfo.aiflags &= AI_GOOD_GUY;
|
||||
|
||||
if (self->item)
|
||||
{
|
||||
Drop_Item (self, self->item);
|
||||
self->item = NULL;
|
||||
}
|
||||
|
||||
if (self->deathtarget)
|
||||
self->target = self->deathtarget;
|
||||
|
||||
if (!self->target)
|
||||
return;
|
||||
|
||||
G_UseTargets (self, self->enemy);
|
||||
}
|
||||
|
||||
|
||||
//============================================================================
|
||||
|
||||
qboolean monster_start (edict_t *self)
|
||||
{
|
||||
if (deathmatch->value)
|
||||
{
|
||||
G_FreeEdict (self);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
|
||||
{
|
||||
self->spawnflags &= ~4;
|
||||
self->spawnflags |= 1;
|
||||
// gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin));
|
||||
}
|
||||
|
||||
if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
|
||||
level.total_monsters++;
|
||||
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
self->svflags |= SVF_MONSTER;
|
||||
self->s.renderfx |= RF_FRAMELERP;
|
||||
self->takedamage = DAMAGE_AIM;
|
||||
self->air_finished = level.time + 12;
|
||||
self->use = monster_use;
|
||||
self->max_health = self->health;
|
||||
self->clipmask = MASK_MONSTERSOLID;
|
||||
|
||||
self->s.skinnum = 0;
|
||||
self->deadflag = DEAD_NO;
|
||||
self->svflags &= ~SVF_DEADMONSTER;
|
||||
|
||||
if (!self->monsterinfo.checkattack)
|
||||
self->monsterinfo.checkattack = M_CheckAttack;
|
||||
VectorCopy (self->s.origin, self->s.old_origin);
|
||||
|
||||
if (st.item)
|
||||
{
|
||||
self->item = FindItemByClassname (st.item);
|
||||
if (!self->item)
|
||||
gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
|
||||
}
|
||||
|
||||
// randomize what frame they start on
|
||||
if (self->monsterinfo.currentmove)
|
||||
self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void monster_start_go (edict_t *self)
|
||||
{
|
||||
vec3_t v;
|
||||
|
||||
if (self->health <= 0)
|
||||
return;
|
||||
|
||||
// check for target to combat_point and change to combattarget
|
||||
if (self->target)
|
||||
{
|
||||
qboolean notcombat;
|
||||
qboolean fixup;
|
||||
edict_t *target;
|
||||
|
||||
target = NULL;
|
||||
notcombat = false;
|
||||
fixup = false;
|
||||
while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL)
|
||||
{
|
||||
if (strcmp(target->classname, "point_combat") == 0)
|
||||
{
|
||||
self->combattarget = self->target;
|
||||
fixup = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
notcombat = true;
|
||||
}
|
||||
}
|
||||
if (notcombat && self->combattarget)
|
||||
gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin));
|
||||
if (fixup)
|
||||
self->target = NULL;
|
||||
}
|
||||
|
||||
// validate combattarget
|
||||
if (self->combattarget)
|
||||
{
|
||||
edict_t *target;
|
||||
|
||||
target = NULL;
|
||||
while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL)
|
||||
{
|
||||
if (strcmp(target->classname, "point_combat") != 0)
|
||||
{
|
||||
gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
|
||||
self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2],
|
||||
self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1],
|
||||
(int)target->s.origin[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self->target)
|
||||
{
|
||||
self->goalentity = self->movetarget = G_PickTarget(self->target);
|
||||
if (!self->movetarget)
|
||||
{
|
||||
gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
|
||||
self->target = NULL;
|
||||
self->monsterinfo.pausetime = 100000000;
|
||||
self->monsterinfo.stand (self);
|
||||
}
|
||||
else if (strcmp (self->movetarget->classname, "path_corner") == 0)
|
||||
{
|
||||
VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
|
||||
self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
|
||||
self->monsterinfo.walk (self);
|
||||
self->target = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->goalentity = self->movetarget = NULL;
|
||||
self->monsterinfo.pausetime = 100000000;
|
||||
self->monsterinfo.stand (self);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self->monsterinfo.pausetime = 100000000;
|
||||
self->monsterinfo.stand (self);
|
||||
}
|
||||
|
||||
self->think = monster_think;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
}
|
||||
|
||||
|
||||
void walkmonster_start_go (edict_t *self)
|
||||
{
|
||||
if (!(self->spawnflags & 2) && level.time < 1)
|
||||
{
|
||||
M_droptofloor (self);
|
||||
|
||||
if (self->groundentity)
|
||||
if (!M_walkmove (self, 0, 0))
|
||||
gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
|
||||
}
|
||||
|
||||
if (!self->yaw_speed)
|
||||
self->yaw_speed = 20;
|
||||
self->viewheight = 25;
|
||||
|
||||
monster_start_go (self);
|
||||
|
||||
if (self->spawnflags & 2)
|
||||
monster_triggered_start (self);
|
||||
}
|
||||
|
||||
void walkmonster_start (edict_t *self)
|
||||
{
|
||||
self->think = walkmonster_start_go;
|
||||
monster_start (self);
|
||||
}
|
||||
|
||||
|
||||
void flymonster_start_go (edict_t *self)
|
||||
{
|
||||
if (!M_walkmove (self, 0, 0))
|
||||
gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
|
||||
|
||||
if (!self->yaw_speed)
|
||||
self->yaw_speed = 10;
|
||||
self->viewheight = 25;
|
||||
|
||||
monster_start_go (self);
|
||||
|
||||
if (self->spawnflags & 2)
|
||||
monster_triggered_start (self);
|
||||
}
|
||||
|
||||
|
||||
void flymonster_start (edict_t *self)
|
||||
{
|
||||
self->flags |= FL_FLY;
|
||||
self->think = flymonster_start_go;
|
||||
monster_start (self);
|
||||
}
|
||||
|
||||
|
||||
void swimmonster_start_go (edict_t *self)
|
||||
{
|
||||
if (!self->yaw_speed)
|
||||
self->yaw_speed = 10;
|
||||
self->viewheight = 10;
|
||||
|
||||
monster_start_go (self);
|
||||
|
||||
if (self->spawnflags & 2)
|
||||
monster_triggered_start (self);
|
||||
}
|
||||
|
||||
void swimmonster_start (edict_t *self)
|
||||
{
|
||||
self->flags |= FL_SWIM;
|
||||
self->think = swimmonster_start_go;
|
||||
monster_start (self);
|
||||
}
|
741
g_save.c
Normal file
741
g_save.c
Normal 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;
|
||||
}
|
||||
}
|
281
g_svcmds.c
Normal file
281
g_svcmds.c
Normal 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
790
g_target.c
Normal 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
579
g_trigger.c
Normal file
|
@ -0,0 +1,579 @@
|
|||
#include "g_local.h"
|
||||
|
||||
|
||||
void InitTrigger (edict_t *self)
|
||||
{
|
||||
if (!VectorCompare (self->s.angles, vec3_origin))
|
||||
G_SetMovedir (self->s.angles, self->movedir);
|
||||
|
||||
self->solid = SOLID_TRIGGER;
|
||||
self->movetype = MOVETYPE_NONE;
|
||||
gi.setmodel (self, self->model);
|
||||
self->svflags = SVF_NOCLIENT;
|
||||
}
|
||||
|
||||
|
||||
// the wait time has passed, so set back up for another activation
|
||||
void multi_wait (edict_t *ent)
|
||||
{
|
||||
ent->nextthink = 0;
|
||||
}
|
||||
|
||||
|
||||
// the trigger was just activated
|
||||
// ent->activator should be set to the activator so it can be held through a delay
|
||||
// so wait for the delay time before firing
|
||||
void multi_trigger (edict_t *ent)
|
||||
{
|
||||
if (ent->nextthink)
|
||||
return; // already been triggered
|
||||
|
||||
G_UseTargets (ent, ent->activator);
|
||||
|
||||
if (ent->wait > 0)
|
||||
{
|
||||
ent->think = multi_wait;
|
||||
ent->nextthink = level.time + ent->wait;
|
||||
}
|
||||
else
|
||||
{ // we can't just remove (self) here, because this is a touch function
|
||||
// called while looping through area links...
|
||||
ent->touch = NULL;
|
||||
ent->nextthink = level.time + FRAMETIME;
|
||||
ent->think = G_FreeEdict;
|
||||
}
|
||||
}
|
||||
|
||||
void Use_Multi (edict_t *ent, edict_t *other, edict_t *activator)
|
||||
{
|
||||
ent->activator = activator;
|
||||
multi_trigger (ent);
|
||||
}
|
||||
|
||||
void Touch_Multi (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||
{
|
||||
if(other->client)
|
||||
{
|
||||
if (self->spawnflags & 2)
|
||||
return;
|
||||
}
|
||||
else if (other->svflags & SVF_MONSTER)
|
||||
{
|
||||
if (!(self->spawnflags & 1))
|
||||
return;
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
if (!VectorCompare(self->movedir, vec3_origin))
|
||||
{
|
||||
vec3_t forward;
|
||||
|
||||
AngleVectors(other->s.angles, forward, NULL, NULL);
|
||||
if (_DotProduct(forward, self->movedir) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
self->activator = other;
|
||||
multi_trigger (self);
|
||||
}
|
||||
|
||||
/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED
|
||||
Variable sized repeatable trigger. Must be targeted at one or more entities.
|
||||
If "delay" is set, the trigger waits some time after activating before firing.
|
||||
"wait" : Seconds between triggerings. (.2 default)
|
||||
sounds
|
||||
1) secret
|
||||
2) beep beep
|
||||
3) large switch
|
||||
4)
|
||||
set "message" to text string
|
||||
*/
|
||||
void trigger_enable (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
self->solid = SOLID_TRIGGER;
|
||||
self->use = Use_Multi;
|
||||
gi.linkentity (self);
|
||||
}
|
||||
|
||||
void SP_trigger_multiple (edict_t *ent)
|
||||
{
|
||||
if (ent->sounds == 1)
|
||||
ent->noise_index = gi.soundindex ("misc/secret.wav");
|
||||
else if (ent->sounds == 2)
|
||||
ent->noise_index = gi.soundindex ("misc/talk.wav");
|
||||
else if (ent->sounds == 3)
|
||||
ent->noise_index = gi.soundindex ("misc/trigger1.wav");
|
||||
|
||||
if (!ent->wait)
|
||||
ent->wait = 0.2;
|
||||
ent->touch = Touch_Multi;
|
||||
ent->movetype = MOVETYPE_NONE;
|
||||
ent->svflags |= SVF_NOCLIENT;
|
||||
|
||||
|
||||
if (ent->spawnflags & 4)
|
||||
{
|
||||
ent->solid = SOLID_NOT;
|
||||
ent->use = trigger_enable;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->solid = SOLID_TRIGGER;
|
||||
ent->use = Use_Multi;
|
||||
}
|
||||
|
||||
if (!VectorCompare(ent->s.angles, vec3_origin))
|
||||
G_SetMovedir (ent->s.angles, ent->movedir);
|
||||
|
||||
gi.setmodel (ent, ent->model);
|
||||
gi.linkentity (ent);
|
||||
}
|
||||
|
||||
|
||||
/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED
|
||||
Triggers once, then removes itself.
|
||||
You must set the key "target" to the name of another object in the level that has a matching "targetname".
|
||||
|
||||
If TRIGGERED, this trigger must be triggered before it is live.
|
||||
|
||||
sounds
|
||||
1) secret
|
||||
2) beep beep
|
||||
3) large switch
|
||||
4)
|
||||
|
||||
"message" string to be displayed when triggered
|
||||
*/
|
||||
|
||||
void SP_trigger_once(edict_t *ent)
|
||||
{
|
||||
// make old maps work because I messed up on flag assignments here
|
||||
// triggered was on bit 1 when it should have been on bit 4
|
||||
if (ent->spawnflags & 1)
|
||||
{
|
||||
vec3_t v;
|
||||
|
||||
VectorMA (ent->mins, 0.5, ent->size, v);
|
||||
ent->spawnflags &= ~1;
|
||||
ent->spawnflags |= 4;
|
||||
gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v));
|
||||
}
|
||||
|
||||
ent->wait = -1;
|
||||
SP_trigger_multiple (ent);
|
||||
}
|
||||
|
||||
/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
|
||||
This fixed size trigger cannot be touched, it can only be fired by other events.
|
||||
*/
|
||||
void trigger_relay_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
G_UseTargets (self, activator);
|
||||
}
|
||||
|
||||
void SP_trigger_relay (edict_t *self)
|
||||
{
|
||||
self->use = trigger_relay_use;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
trigger_key
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8)
|
||||
A relay trigger that only fires it's targets if player has the proper key.
|
||||
Use "item" to specify the required key, for example "key_data_cd"
|
||||
*/
|
||||
void trigger_key_use (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (!self->item)
|
||||
return;
|
||||
if (!activator->client)
|
||||
return;
|
||||
|
||||
index = ITEM_INDEX(self->item);
|
||||
if (!activator->client->pers.inventory[index])
|
||||
{
|
||||
if (level.time < self->touch_debounce_time)
|
||||
return;
|
||||
self->touch_debounce_time = level.time + 5.0;
|
||||
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
413
g_turret.c
Normal file
|
@ -0,0 +1,413 @@
|
|||
// g_turret.c
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
|
||||
void AnglesNormalize(vec3_t vec)
|
||||
{
|
||||
while(vec[0] > 360)
|
||||
vec[0] -= 360;
|
||||
while(vec[0] < 0)
|
||||
vec[0] += 360;
|
||||
while(vec[1] > 360)
|
||||
vec[1] -= 360;
|
||||
while(vec[1] < 0)
|
||||
vec[1] += 360;
|
||||
}
|
||||
|
||||
float SnapToEights(float x)
|
||||
{
|
||||
x *= 8.0;
|
||||
if (x > 0.0)
|
||||
x += 0.5;
|
||||
else
|
||||
x -= 0.5;
|
||||
return 0.125 * (int)x;
|
||||
}
|
||||
|
||||
|
||||
void turret_blocked(edict_t *self, edict_t *other)
|
||||
{
|
||||
edict_t *attacker;
|
||||
|
||||
if (other->takedamage)
|
||||
{
|
||||
if (self->teammaster->owner)
|
||||
attacker = self->teammaster->owner;
|
||||
else
|
||||
attacker = self->teammaster;
|
||||
T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
|
||||
}
|
||||
}
|
||||
|
||||
/*QUAKED turret_breach (0 0 0) ?
|
||||
This portion of the turret can change both pitch and yaw.
|
||||
The model should be made with a flat pitch.
|
||||
It (and the associated base) need to be oriented towards 0.
|
||||
Use "angle" to set the starting angle.
|
||||
|
||||
"speed" default 50
|
||||
"dmg" default 10
|
||||
"angle" point this forward
|
||||
"target" point this at an info_notnull at the muzzle tip
|
||||
"minpitch" min acceptable pitch angle : default -30
|
||||
"maxpitch" max acceptable pitch angle : default 30
|
||||
"minyaw" min acceptable yaw angle : default 0
|
||||
"maxyaw" max acceptable yaw angle : default 360
|
||||
*/
|
||||
|
||||
void turret_breach_fire (edict_t *self)
|
||||
{
|
||||
vec3_t f, r, u;
|
||||
vec3_t start;
|
||||
int damage;
|
||||
int speed;
|
||||
|
||||
AngleVectors (self->s.angles, f, r, u);
|
||||
VectorMA (self->s.origin, self->move_origin[0], f, start);
|
||||
VectorMA (start, self->move_origin[1], r, start);
|
||||
VectorMA (start, self->move_origin[2], u, start);
|
||||
|
||||
damage = 100 + random() * 50;
|
||||
speed = 550 + 50 * skill->value;
|
||||
fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage);
|
||||
gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
|
||||
}
|
||||
|
||||
void turret_breach_think (edict_t *self)
|
||||
{
|
||||
edict_t *ent;
|
||||
vec3_t current_angles;
|
||||
vec3_t delta;
|
||||
|
||||
VectorCopy (self->s.angles, current_angles);
|
||||
AnglesNormalize(current_angles);
|
||||
|
||||
AnglesNormalize(self->move_angles);
|
||||
if (self->move_angles[PITCH] > 180)
|
||||
self->move_angles[PITCH] -= 360;
|
||||
|
||||
// clamp angles to mins & maxs
|
||||
if (self->move_angles[PITCH] > self->pos1[PITCH])
|
||||
self->move_angles[PITCH] = self->pos1[PITCH];
|
||||
else if (self->move_angles[PITCH] < self->pos2[PITCH])
|
||||
self->move_angles[PITCH] = self->pos2[PITCH];
|
||||
|
||||
if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW]))
|
||||
{
|
||||
float dmin, dmax;
|
||||
|
||||
dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
|
||||
if (dmin < -180)
|
||||
dmin += 360;
|
||||
else if (dmin > 180)
|
||||
dmin -= 360;
|
||||
dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
|
||||
if (dmax < -180)
|
||||
dmax += 360;
|
||||
else if (dmax > 180)
|
||||
dmax -= 360;
|
||||
if (fabs(dmin) < fabs(dmax))
|
||||
self->move_angles[YAW] = self->pos1[YAW];
|
||||
else
|
||||
self->move_angles[YAW] = self->pos2[YAW];
|
||||
}
|
||||
|
||||
VectorSubtract (self->move_angles, current_angles, delta);
|
||||
if (delta[0] < -180)
|
||||
delta[0] += 360;
|
||||
else if (delta[0] > 180)
|
||||
delta[0] -= 360;
|
||||
if (delta[1] < -180)
|
||||
delta[1] += 360;
|
||||
else if (delta[1] > 180)
|
||||
delta[1] -= 360;
|
||||
delta[2] = 0;
|
||||
|
||||
if (delta[0] > self->speed * FRAMETIME)
|
||||
delta[0] = self->speed * FRAMETIME;
|
||||
if (delta[0] < -1 * self->speed * FRAMETIME)
|
||||
delta[0] = -1 * self->speed * FRAMETIME;
|
||||
if (delta[1] > self->speed * FRAMETIME)
|
||||
delta[1] = self->speed * FRAMETIME;
|
||||
if (delta[1] < -1 * self->speed * FRAMETIME)
|
||||
delta[1] = -1 * self->speed * FRAMETIME;
|
||||
|
||||
VectorScale (delta, 1.0/FRAMETIME, self->avelocity);
|
||||
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
|
||||
for (ent = self->teammaster; ent; ent = ent->teamchain)
|
||||
ent->avelocity[1] = self->avelocity[1];
|
||||
|
||||
// if we have adriver, adjust his velocities
|
||||
if (self->owner)
|
||||
{
|
||||
float angle;
|
||||
float target_z;
|
||||
float diff;
|
||||
vec3_t target;
|
||||
vec3_t dir;
|
||||
|
||||
// angular is easy, just copy ours
|
||||
self->owner->avelocity[0] = self->avelocity[0];
|
||||
self->owner->avelocity[1] = self->avelocity[1];
|
||||
|
||||
// x & y
|
||||
angle = self->s.angles[1] + self->owner->move_origin[1];
|
||||
angle *= (M_PI*2 / 360);
|
||||
target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
|
||||
target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
|
||||
target[2] = self->owner->s.origin[2];
|
||||
|
||||
VectorSubtract (target, self->owner->s.origin, dir);
|
||||
self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
|
||||
self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
|
||||
|
||||
// z
|
||||
angle = self->s.angles[PITCH] * (M_PI*2 / 360);
|
||||
target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
|
||||
|
||||
diff = target_z - self->owner->s.origin[2];
|
||||
self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
|
||||
|
||||
if (self->spawnflags & 65536)
|
||||
{
|
||||
turret_breach_fire (self);
|
||||
self->spawnflags &= ~65536;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void turret_breach_finish_init (edict_t *self)
|
||||
{
|
||||
// get and save info for muzzle location
|
||||
if (!self->target)
|
||||
{
|
||||
gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
|
||||
}
|
||||
else
|
||||
{
|
||||
self->target_ent = G_PickTarget (self->target);
|
||||
VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin);
|
||||
G_FreeEdict(self->target_ent);
|
||||
}
|
||||
|
||||
self->teammaster->dmg = self->dmg;
|
||||
self->think = turret_breach_think;
|
||||
self->think (self);
|
||||
}
|
||||
|
||||
void SP_turret_breach (edict_t *self)
|
||||
{
|
||||
self->solid = SOLID_BSP;
|
||||
self->movetype = MOVETYPE_PUSH;
|
||||
gi.setmodel (self, self->model);
|
||||
|
||||
if (!self->speed)
|
||||
self->speed = 50;
|
||||
if (!self->dmg)
|
||||
self->dmg = 10;
|
||||
|
||||
if (!st.minpitch)
|
||||
st.minpitch = -30;
|
||||
if (!st.maxpitch)
|
||||
st.maxpitch = 30;
|
||||
if (!st.maxyaw)
|
||||
st.maxyaw = 360;
|
||||
|
||||
self->pos1[PITCH] = -1 * st.minpitch;
|
||||
self->pos1[YAW] = st.minyaw;
|
||||
self->pos2[PITCH] = -1 * st.maxpitch;
|
||||
self->pos2[YAW] = st.maxyaw;
|
||||
|
||||
self->ideal_yaw = self->s.angles[YAW];
|
||||
self->move_angles[YAW] = self->ideal_yaw;
|
||||
|
||||
self->blocked = turret_blocked;
|
||||
|
||||
self->think = turret_breach_finish_init;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
gi.linkentity (self);
|
||||
}
|
||||
|
||||
|
||||
/*QUAKED turret_base (0 0 0) ?
|
||||
This portion of the turret changes yaw only.
|
||||
MUST be teamed with a turret_breach.
|
||||
*/
|
||||
|
||||
void SP_turret_base (edict_t *self)
|
||||
{
|
||||
self->solid = SOLID_BSP;
|
||||
self->movetype = MOVETYPE_PUSH;
|
||||
gi.setmodel (self, self->model);
|
||||
self->blocked = turret_blocked;
|
||||
gi.linkentity (self);
|
||||
}
|
||||
|
||||
|
||||
/*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
|
||||
Must NOT be on the team with the rest of the turret parts.
|
||||
Instead it must target the turret_breach.
|
||||
*/
|
||||
|
||||
void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage);
|
||||
void infantry_stand (edict_t *self);
|
||||
void monster_use (edict_t *self, edict_t *other, edict_t *activator);
|
||||
|
||||
void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
||||
{
|
||||
edict_t *ent;
|
||||
|
||||
// level the gun
|
||||
self->target_ent->move_angles[0] = 0;
|
||||
|
||||
// remove the driver from the end of them team chain
|
||||
for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain)
|
||||
;
|
||||
ent->teamchain = NULL;
|
||||
self->teammaster = NULL;
|
||||
self->flags &= ~FL_TEAMSLAVE;
|
||||
|
||||
self->target_ent->owner = NULL;
|
||||
self->target_ent->teammaster->owner = NULL;
|
||||
|
||||
//FB infantry_die (self, inflictor, attacker, damage);
|
||||
}
|
||||
|
||||
qboolean FindTarget (edict_t *self);
|
||||
|
||||
void turret_driver_think (edict_t *self)
|
||||
{
|
||||
vec3_t target;
|
||||
vec3_t dir;
|
||||
float reaction_time;
|
||||
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
|
||||
if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0))
|
||||
self->enemy = NULL;
|
||||
|
||||
if (!self->enemy)
|
||||
{
|
||||
if (!FindTarget (self))
|
||||
return;
|
||||
self->monsterinfo.trail_time = level.time;
|
||||
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (visible (self, self->enemy))
|
||||
{
|
||||
if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
|
||||
{
|
||||
self->monsterinfo.trail_time = level.time;
|
||||
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self->monsterinfo.aiflags |= AI_LOST_SIGHT;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// let the turret know where we want it to aim
|
||||
VectorCopy (self->enemy->s.origin, target);
|
||||
target[2] += self->enemy->viewheight;
|
||||
VectorSubtract (target, self->target_ent->s.origin, dir);
|
||||
vectoangles (dir, self->target_ent->move_angles);
|
||||
|
||||
// decide if we should shoot
|
||||
if (level.time < self->monsterinfo.attack_finished)
|
||||
return;
|
||||
|
||||
reaction_time = (3 - skill->value) * 1.0;
|
||||
if ((level.time - self->monsterinfo.trail_time) < reaction_time)
|
||||
return;
|
||||
|
||||
self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
|
||||
//FIXME how do we really want to pass this along?
|
||||
self->target_ent->spawnflags |= 65536;
|
||||
}
|
||||
|
||||
void turret_driver_link (edict_t *self)
|
||||
{
|
||||
vec3_t vec;
|
||||
edict_t *ent;
|
||||
|
||||
self->think = turret_driver_think;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
|
||||
self->target_ent = G_PickTarget (self->target);
|
||||
self->target_ent->owner = self;
|
||||
self->target_ent->teammaster->owner = self;
|
||||
VectorCopy (self->target_ent->s.angles, self->s.angles);
|
||||
|
||||
vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
|
||||
vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
|
||||
vec[2] = 0;
|
||||
self->move_origin[0] = VectorLength(vec);
|
||||
|
||||
VectorSubtract (self->s.origin, self->target_ent->s.origin, vec);
|
||||
vectoangles (vec, vec);
|
||||
AnglesNormalize(vec);
|
||||
self->move_origin[1] = vec[1];
|
||||
|
||||
self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
|
||||
|
||||
// add the driver to the end of them team chain
|
||||
for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
|
||||
;
|
||||
ent->teamchain = self;
|
||||
self->teammaster = self->target_ent->teammaster;
|
||||
self->flags |= FL_TEAMSLAVE;
|
||||
}
|
||||
|
||||
void SP_turret_driver (edict_t *self)
|
||||
{
|
||||
if (deathmatch->value)
|
||||
{
|
||||
G_FreeEdict (self);
|
||||
return;
|
||||
}
|
||||
|
||||
self->movetype = MOVETYPE_PUSH;
|
||||
self->solid = SOLID_BBOX;
|
||||
self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
|
||||
VectorSet (self->mins, -16, -16, -24);
|
||||
VectorSet (self->maxs, 16, 16, 32);
|
||||
|
||||
self->health = 100;
|
||||
self->gib_health = 0;
|
||||
self->mass = 200;
|
||||
self->viewheight = 24;
|
||||
|
||||
self->die = turret_driver_die;
|
||||
//FB self->monsterinfo.stand = infantry_stand;
|
||||
|
||||
self->flags |= FL_NO_KNOCKBACK;
|
||||
|
||||
level.total_monsters++;
|
||||
|
||||
self->svflags |= SVF_MONSTER;
|
||||
self->s.renderfx |= RF_FRAMELERP;
|
||||
self->takedamage = DAMAGE_AIM;
|
||||
self->use = monster_use;
|
||||
self->clipmask = MASK_MONSTERSOLID;
|
||||
VectorCopy (self->s.origin, self->s.old_origin);
|
||||
self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED;
|
||||
|
||||
if (st.item)
|
||||
{
|
||||
self->item = FindItemByClassname (st.item);
|
||||
if (!self->item)
|
||||
gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
|
||||
}
|
||||
|
||||
self->think = turret_driver_link;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
|
||||
gi.linkentity (self);
|
||||
}
|
554
g_utils.c
Normal file
554
g_utils.c
Normal file
|
@ -0,0 +1,554 @@
|
|||
// g_utils.c -- misc utility functions for game module
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
|
||||
void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
|
||||
{
|
||||
result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
|
||||
result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
|
||||
result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
G_Find
|
||||
|
||||
Searches all active entities for the next one that holds
|
||||
the matching string at fieldofs (use the FOFS() macro) in the structure.
|
||||
|
||||
Searches beginning at the edict after from, or the beginning if NULL
|
||||
NULL will be returned if the end of the list is reached.
|
||||
|
||||
=============
|
||||
*/
|
||||
edict_t *G_Find (edict_t *from, int fieldofs, char *match)
|
||||
{
|
||||
char *s;
|
||||
|
||||
if (!from)
|
||||
from = g_edicts;
|
||||
else
|
||||
from++;
|
||||
|
||||
for ( ; from < &g_edicts[globals.num_edicts] ; from++)
|
||||
{
|
||||
if (!from->inuse)
|
||||
continue;
|
||||
s = *(char **) ((byte *)from + fieldofs);
|
||||
if (!s)
|
||||
continue;
|
||||
if (!Q_stricmp (s, match))
|
||||
return from;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
findradius
|
||||
|
||||
Returns entities that have origins within a spherical area
|
||||
|
||||
findradius (origin, radius)
|
||||
=================
|
||||
*/
|
||||
edict_t *findradius (edict_t *from, vec3_t org, float rad)
|
||||
{
|
||||
vec3_t eorg;
|
||||
int j;
|
||||
|
||||
if (!from)
|
||||
from = g_edicts;
|
||||
else
|
||||
from++;
|
||||
for ( ; from < &g_edicts[globals.num_edicts]; from++)
|
||||
{
|
||||
if (!from->inuse)
|
||||
continue;
|
||||
if (from->solid == SOLID_NOT)
|
||||
continue;
|
||||
for (j=0 ; j<3 ; j++)
|
||||
eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
|
||||
if (VectorLength(eorg) > rad)
|
||||
continue;
|
||||
return from;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
G_PickTarget
|
||||
|
||||
Searches all active entities for the next one that holds
|
||||
the matching string at fieldofs (use the FOFS() macro) in the structure.
|
||||
|
||||
Searches beginning at the edict after from, or the beginning if NULL
|
||||
NULL will be returned if the end of the list is reached.
|
||||
|
||||
=============
|
||||
*/
|
||||
#define MAXCHOICES 8
|
||||
|
||||
edict_t *G_PickTarget (char *targetname)
|
||||
{
|
||||
edict_t *ent = NULL;
|
||||
int num_choices = 0;
|
||||
edict_t *choice[MAXCHOICES];
|
||||
|
||||
if (!targetname)
|
||||
{
|
||||
gi.dprintf("G_PickTarget called with NULL targetname\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while(1)
|
||||
{
|
||||
ent = G_Find (ent, FOFS(targetname), targetname);
|
||||
if (!ent)
|
||||
break;
|
||||
choice[num_choices++] = ent;
|
||||
if (num_choices == MAXCHOICES)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!num_choices)
|
||||
{
|
||||
gi.dprintf("G_PickTarget: target %s not found\n", targetname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return choice[rand() % num_choices];
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Think_Delay (edict_t *ent)
|
||||
{
|
||||
G_UseTargets (ent, ent->activator);
|
||||
G_FreeEdict (ent);
|
||||
}
|
||||
|
||||
/*
|
||||
==============================
|
||||
G_UseTargets
|
||||
|
||||
the global "activator" should be set to the entity that initiated the firing.
|
||||
|
||||
If self.delay is set, a DelayedUse entity will be created that will actually
|
||||
do the SUB_UseTargets after that many seconds have passed.
|
||||
|
||||
Centerprints any self.message to the activator.
|
||||
|
||||
Search for (string)targetname in all entities that
|
||||
match (string)self.target and call their .use function
|
||||
|
||||
==============================
|
||||
*/
|
||||
void G_UseTargets (edict_t *ent, edict_t *activator)
|
||||
{
|
||||
edict_t *t;
|
||||
|
||||
//
|
||||
// check for a delay
|
||||
//
|
||||
if (ent->delay)
|
||||
{
|
||||
// create a temp object to fire at a later time
|
||||
t = G_Spawn();
|
||||
t->classname = "DelayedUse";
|
||||
t->nextthink = level.time + ent->delay;
|
||||
t->think = Think_Delay;
|
||||
t->activator = activator;
|
||||
if (!activator)
|
||||
gi.dprintf ("Think_Delay with no activator\n");
|
||||
t->message = ent->message;
|
||||
t->target = ent->target;
|
||||
t->killtarget = ent->killtarget;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// print the message
|
||||
//
|
||||
if ((ent->message) && !(activator->svflags & SVF_MONSTER))
|
||||
{
|
||||
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
1457
g_weapon.c
Normal file
File diff suppressed because it is too large
Load diff
216
game.h
Normal file
216
game.h
Normal file
|
@ -0,0 +1,216 @@
|
|||
|
||||
// game.h -- game dll information visible to server
|
||||
|
||||
#define GAME_API_VERSION 3
|
||||
|
||||
// edict->svflags
|
||||
|
||||
#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects
|
||||
#define SVF_DEADMONSTER 0x00000002 // treat as CONTENTS_DEADMONSTER for collision
|
||||
#define SVF_MONSTER 0x00000004 // treat as CONTENTS_MONSTER for collision
|
||||
|
||||
// edict->solid values
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SOLID_NOT, // no interaction with other objects
|
||||
SOLID_TRIGGER, // only touch when inside, after moving
|
||||
SOLID_BBOX, // touch on edge
|
||||
SOLID_BSP // bsp clip, touch on edge
|
||||
} solid_t;
|
||||
|
||||
//===============================================================
|
||||
|
||||
// link_t is only used for entity area links now
|
||||
typedef struct link_s
|
||||
{
|
||||
struct link_s *prev, *next;
|
||||
} link_t;
|
||||
|
||||
#define MAX_ENT_CLUSTERS 16
|
||||
|
||||
|
||||
typedef struct edict_s edict_t;
|
||||
typedef struct gclient_s gclient_t;
|
||||
|
||||
|
||||
#ifndef GAME_INCLUDE
|
||||
|
||||
struct gclient_s
|
||||
{
|
||||
player_state_t ps; // communicated by server to clients
|
||||
int ping;
|
||||
// the game dll can add anything it wants after
|
||||
// this point in the structure
|
||||
};
|
||||
|
||||
|
||||
struct edict_s
|
||||
{
|
||||
entity_state_t s;
|
||||
struct gclient_s *client;
|
||||
qboolean inuse;
|
||||
int linkcount;
|
||||
|
||||
// FIXME: move these fields to a server private sv_entity_t
|
||||
link_t area; // linked to a division node or leaf
|
||||
|
||||
int num_clusters; // if -1, use headnode instead
|
||||
int clusternums[MAX_ENT_CLUSTERS];
|
||||
int headnode; // unused if num_clusters != -1
|
||||
int areanum, areanum2;
|
||||
|
||||
//================================
|
||||
|
||||
int svflags; // SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc
|
||||
vec3_t mins, maxs;
|
||||
vec3_t absmin, absmax, size;
|
||||
solid_t solid;
|
||||
int clipmask;
|
||||
edict_t *owner;
|
||||
|
||||
// the game dll can add anything it wants after
|
||||
// this point in the structure
|
||||
};
|
||||
|
||||
#endif // GAME_INCLUDE
|
||||
|
||||
//===============================================================
|
||||
|
||||
//
|
||||
// functions provided by the main engine
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
// special messages
|
||||
void (*bprintf) (int printlevel, char *fmt, ...);
|
||||
void (*dprintf) (char *fmt, ...);
|
||||
void (*cprintf) (edict_t *ent, int printlevel, char *fmt, ...);
|
||||
void (*centerprintf) (edict_t *ent, char *fmt, ...);
|
||||
void (*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs);
|
||||
void (*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs);
|
||||
|
||||
// config strings hold all the index strings, the lightstyles,
|
||||
// and misc data like the sky definition and cdtrack.
|
||||
// All of the current configstrings are sent to clients when
|
||||
// they connect, and changes are sent to all connected clients.
|
||||
void (*configstring) (int num, char *string);
|
||||
|
||||
void (*error) (char *fmt, ...);
|
||||
|
||||
// the *index functions create configstrings and some internal server state
|
||||
int (*modelindex) (char *name);
|
||||
int (*soundindex) (char *name);
|
||||
int (*imageindex) (char *name);
|
||||
|
||||
void (*setmodel) (edict_t *ent, char *name);
|
||||
|
||||
// collision detection
|
||||
trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask);
|
||||
int (*pointcontents) (vec3_t point);
|
||||
qboolean (*inPVS) (vec3_t p1, vec3_t p2);
|
||||
qboolean (*inPHS) (vec3_t p1, vec3_t p2);
|
||||
void (*SetAreaPortalState) (int portalnum, qboolean open);
|
||||
qboolean (*AreasConnected) (int area1, int area2);
|
||||
|
||||
// an entity will never be sent to a client or used for collision
|
||||
// if it is not passed to linkentity. If the size, position, or
|
||||
// solidity changes, it must be relinked.
|
||||
void (*linkentity) (edict_t *ent);
|
||||
void (*unlinkentity) (edict_t *ent); // call before removing an interactive edict
|
||||
int (*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype);
|
||||
void (*Pmove) (pmove_t *pmove); // player movement code common with client prediction
|
||||
|
||||
// network messaging
|
||||
void (*multicast) (vec3_t origin, multicast_t to);
|
||||
void (*unicast) (edict_t *ent, qboolean reliable);
|
||||
void (*WriteChar) (int c);
|
||||
void (*WriteByte) (int c);
|
||||
void (*WriteShort) (int c);
|
||||
void (*WriteLong) (int c);
|
||||
void (*WriteFloat) (float f);
|
||||
void (*WriteString) (char *s);
|
||||
void (*WritePosition) (vec3_t pos); // some fractional bits
|
||||
void (*WriteDir) (vec3_t pos); // single byte encoded, very coarse
|
||||
void (*WriteAngle) (float f);
|
||||
|
||||
// managed memory allocation
|
||||
void *(*TagMalloc) (int size, int tag);
|
||||
void (*TagFree) (void *block);
|
||||
void (*FreeTags) (int tag);
|
||||
|
||||
// console variable interaction
|
||||
cvar_t *(*cvar) (char *var_name, char *value, int flags);
|
||||
cvar_t *(*cvar_set) (char *var_name, char *value);
|
||||
cvar_t *(*cvar_forceset) (char *var_name, char *value);
|
||||
|
||||
// ClientCommand and ServerCommand parameter access
|
||||
int (*argc) (void);
|
||||
char *(*argv) (int n);
|
||||
char *(*args) (void); // concatenation of all argv >= 1
|
||||
|
||||
// add commands to the server console as if they were typed in
|
||||
// for map changing, etc
|
||||
void (*AddCommandString) (char *text);
|
||||
|
||||
void (*DebugGraph) (float value, int color);
|
||||
} game_import_t;
|
||||
|
||||
//
|
||||
// functions exported by the game subsystem
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
int apiversion;
|
||||
|
||||
// the init function will only be called when a game starts,
|
||||
// not each time a level is loaded. Persistant data for clients
|
||||
// and the server can be allocated in init
|
||||
void (*Init) (void);
|
||||
void (*Shutdown) (void);
|
||||
|
||||
// each new level entered will cause a call to SpawnEntities
|
||||
void (*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint);
|
||||
|
||||
// Read/Write Game is for storing persistant cross level information
|
||||
// about the world state and the clients.
|
||||
// WriteGame is called every time a level is exited.
|
||||
// ReadGame is called on a loadgame.
|
||||
void (*WriteGame) (char *filename, qboolean autosave);
|
||||
void (*ReadGame) (char *filename);
|
||||
|
||||
// ReadLevel is called after the default map information has been
|
||||
// loaded with SpawnEntities
|
||||
void (*WriteLevel) (char *filename);
|
||||
void (*ReadLevel) (char *filename);
|
||||
|
||||
qboolean (*ClientConnect) (edict_t *ent, char *userinfo);
|
||||
void (*ClientBegin) (edict_t *ent);
|
||||
void (*ClientUserinfoChanged) (edict_t *ent, char *userinfo);
|
||||
void (*ClientDisconnect) (edict_t *ent);
|
||||
void (*ClientCommand) (edict_t *ent);
|
||||
void (*ClientThink) (edict_t *ent, usercmd_t *cmd);
|
||||
|
||||
void (*RunFrame) (void);
|
||||
|
||||
// ServerCommand will be called when an "sv <command>" command is issued on the
|
||||
// server console.
|
||||
// The game can issue gi.argc() / gi.argv() commands to get the rest
|
||||
// of the parameters
|
||||
void (*ServerCommand) (void);
|
||||
|
||||
//
|
||||
// global variables shared between game and server
|
||||
//
|
||||
|
||||
// The edict array is allocated in the game dll so it
|
||||
// can vary in size from one game to another.
|
||||
//
|
||||
// The size will be fixed when ge->Init() is called
|
||||
struct edict_s *edicts;
|
||||
int edict_size;
|
||||
int num_edicts; // current number, <= max_edicts
|
||||
int max_edicts;
|
||||
} game_export_t;
|
||||
|
||||
game_export_t *GetGameApi (game_import_t *import);
|
537
m_move.c
Normal file
537
m_move.c
Normal file
|
@ -0,0 +1,537 @@
|
|||
// m_move.c -- monster movement
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
#define STEPSIZE 18
|
||||
|
||||
/*
|
||||
=============
|
||||
M_CheckBottom
|
||||
|
||||
Returns false if any part of the bottom of the entity is off an edge that
|
||||
is not a staircase.
|
||||
|
||||
=============
|
||||
*/
|
||||
int c_yes, c_no;
|
||||
|
||||
qboolean M_CheckBottom (edict_t *ent)
|
||||
{
|
||||
vec3_t mins, maxs, start, stop;
|
||||
trace_t trace;
|
||||
int x, y;
|
||||
float mid, bottom;
|
||||
|
||||
VectorAdd (ent->s.origin, ent->mins, mins);
|
||||
VectorAdd (ent->s.origin, ent->maxs, maxs);
|
||||
|
||||
// if all of the points under the corners are solid world, don't bother
|
||||
// with the tougher checks
|
||||
// the corners must be within 16 of the midpoint
|
||||
start[2] = mins[2] - 1;
|
||||
for (x=0 ; x<=1 ; x++)
|
||||
for (y=0 ; y<=1 ; y++)
|
||||
{
|
||||
start[0] = x ? maxs[0] : mins[0];
|
||||
start[1] = y ? maxs[1] : mins[1];
|
||||
if (gi.pointcontents (start) != CONTENTS_SOLID)
|
||||
goto realcheck;
|
||||
}
|
||||
|
||||
c_yes++;
|
||||
return true; // we got out easy
|
||||
|
||||
realcheck:
|
||||
c_no++;
|
||||
//
|
||||
// check it for real...
|
||||
//
|
||||
start[2] = mins[2];
|
||||
|
||||
// the midpoint must be within 16 of the bottom
|
||||
start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
|
||||
start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
|
||||
stop[2] = start[2] - 2*STEPSIZE;
|
||||
trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
|
||||
|
||||
if (trace.fraction == 1.0)
|
||||
return false;
|
||||
mid = bottom = trace.endpos[2];
|
||||
|
||||
// the corners must be within 16 of the midpoint
|
||||
for (x=0 ; x<=1 ; x++)
|
||||
for (y=0 ; y<=1 ; y++)
|
||||
{
|
||||
start[0] = stop[0] = x ? maxs[0] : mins[0];
|
||||
start[1] = stop[1] = y ? maxs[1] : mins[1];
|
||||
|
||||
trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
|
||||
|
||||
if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
|
||||
bottom = trace.endpos[2];
|
||||
if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE)
|
||||
return false;
|
||||
}
|
||||
|
||||
c_yes++;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
SV_movestep
|
||||
|
||||
Called by monster program code.
|
||||
The move will be adjusted for slopes and stairs, but if the move isn't
|
||||
possible, no move is done, false is returned, and
|
||||
pr_global_struct->trace_normal is set to the normal of the blocking wall
|
||||
=============
|
||||
*/
|
||||
//FIXME since we need to test end position contents here, can we avoid doing
|
||||
//it again later in catagorize position?
|
||||
qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink)
|
||||
{
|
||||
float dz;
|
||||
vec3_t oldorg, neworg, end;
|
||||
trace_t trace;
|
||||
int i;
|
||||
float stepsize;
|
||||
vec3_t test;
|
||||
int contents;
|
||||
|
||||
// try the move
|
||||
VectorCopy (ent->s.origin, oldorg);
|
||||
VectorAdd (ent->s.origin, move, neworg);
|
||||
|
||||
// flying monsters don't step up
|
||||
if ( ent->flags & (FL_SWIM | FL_FLY) )
|
||||
{
|
||||
// try one move with vertical motion, then one without
|
||||
for (i=0 ; i<2 ; i++)
|
||||
{
|
||||
VectorAdd (ent->s.origin, move, neworg);
|
||||
if (i == 0 && ent->enemy)
|
||||
{
|
||||
if (!ent->goalentity)
|
||||
ent->goalentity = ent->enemy;
|
||||
dz = ent->s.origin[2] - ent->goalentity->s.origin[2];
|
||||
if (ent->goalentity->client)
|
||||
{
|
||||
if (dz > 40)
|
||||
neworg[2] -= 8;
|
||||
if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2)))
|
||||
if (dz < 30)
|
||||
neworg[2] += 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dz > 8)
|
||||
neworg[2] -= 8;
|
||||
else if (dz > 0)
|
||||
neworg[2] -= dz;
|
||||
else if (dz < -8)
|
||||
neworg[2] += 8;
|
||||
else
|
||||
neworg[2] += dz;
|
||||
}
|
||||
}
|
||||
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID);
|
||||
|
||||
// fly monsters don't enter water voluntarily
|
||||
if (ent->flags & FL_FLY)
|
||||
{
|
||||
if (!ent->waterlevel)
|
||||
{
|
||||
test[0] = trace.endpos[0];
|
||||
test[1] = trace.endpos[1];
|
||||
test[2] = trace.endpos[2] + ent->mins[2] + 1;
|
||||
contents = gi.pointcontents(test);
|
||||
if (contents & MASK_WATER)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// swim monsters don't exit water voluntarily
|
||||
if (ent->flags & FL_SWIM)
|
||||
{
|
||||
if (ent->waterlevel < 2)
|
||||
{
|
||||
test[0] = trace.endpos[0];
|
||||
test[1] = trace.endpos[1];
|
||||
test[2] = trace.endpos[2] + ent->mins[2] + 1;
|
||||
contents = gi.pointcontents(test);
|
||||
if (!(contents & MASK_WATER))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (trace.fraction == 1)
|
||||
{
|
||||
VectorCopy (trace.endpos, ent->s.origin);
|
||||
if (relink)
|
||||
{
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ent->enemy)
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// push down from a step height above the wished position
|
||||
if (!(ent->monsterinfo.aiflags & AI_NOSTEP))
|
||||
stepsize = STEPSIZE;
|
||||
else
|
||||
stepsize = 1;
|
||||
|
||||
neworg[2] += stepsize;
|
||||
VectorCopy (neworg, end);
|
||||
end[2] -= stepsize*2;
|
||||
|
||||
trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
|
||||
|
||||
if (trace.allsolid)
|
||||
return false;
|
||||
|
||||
if (trace.startsolid)
|
||||
{
|
||||
neworg[2] -= stepsize;
|
||||
trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
|
||||
if (trace.allsolid || trace.startsolid)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// don't go in to water
|
||||
if (ent->waterlevel == 0)
|
||||
{
|
||||
test[0] = trace.endpos[0];
|
||||
test[1] = trace.endpos[1];
|
||||
test[2] = trace.endpos[2] + ent->mins[2] + 1;
|
||||
contents = gi.pointcontents(test);
|
||||
|
||||
if (contents & MASK_WATER)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trace.fraction == 1)
|
||||
{
|
||||
// if monster had the ground pulled out, go ahead and fall
|
||||
if ( ent->flags & FL_PARTIALGROUND )
|
||||
{
|
||||
VectorAdd (ent->s.origin, move, ent->s.origin);
|
||||
if (relink)
|
||||
{
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
}
|
||||
ent->groundentity = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // walked off an edge
|
||||
}
|
||||
|
||||
// check point traces down for dangling corners
|
||||
VectorCopy (trace.endpos, ent->s.origin);
|
||||
|
||||
if (!M_CheckBottom (ent))
|
||||
{
|
||||
if ( ent->flags & FL_PARTIALGROUND )
|
||||
{ // entity had floor mostly pulled out from underneath it
|
||||
// and is trying to correct
|
||||
if (relink)
|
||||
{
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
VectorCopy (oldorg, ent->s.origin);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ent->flags & FL_PARTIALGROUND )
|
||||
{
|
||||
ent->flags &= ~FL_PARTIALGROUND;
|
||||
}
|
||||
ent->groundentity = trace.ent;
|
||||
ent->groundentity_linkcount = trace.ent->linkcount;
|
||||
|
||||
// the move is ok
|
||||
if (relink)
|
||||
{
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//============================================================================
|
||||
|
||||
/*
|
||||
===============
|
||||
M_ChangeYaw
|
||||
|
||||
===============
|
||||
*/
|
||||
void M_ChangeYaw (edict_t *ent)
|
||||
{
|
||||
float ideal;
|
||||
float current;
|
||||
float move;
|
||||
float speed;
|
||||
|
||||
current = anglemod(ent->s.angles[YAW]);
|
||||
ideal = ent->ideal_yaw;
|
||||
|
||||
if (current == ideal)
|
||||
return;
|
||||
|
||||
move = ideal - current;
|
||||
speed = ent->yaw_speed;
|
||||
if (ideal > current)
|
||||
{
|
||||
if (move >= 180)
|
||||
move = move - 360;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (move <= -180)
|
||||
move = move + 360;
|
||||
}
|
||||
if (move > 0)
|
||||
{
|
||||
if (move > speed)
|
||||
move = speed;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (move < -speed)
|
||||
move = -speed;
|
||||
}
|
||||
|
||||
ent->s.angles[YAW] = anglemod (current + move);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
======================
|
||||
SV_StepDirection
|
||||
|
||||
Turns to the movement direction, and walks the current distance if
|
||||
facing it.
|
||||
|
||||
======================
|
||||
*/
|
||||
qboolean SV_StepDirection (edict_t *ent, float yaw, float dist)
|
||||
{
|
||||
vec3_t move, oldorigin;
|
||||
float delta;
|
||||
|
||||
ent->ideal_yaw = yaw;
|
||||
M_ChangeYaw (ent);
|
||||
|
||||
yaw = yaw*M_PI*2 / 360;
|
||||
move[0] = cos(yaw)*dist;
|
||||
move[1] = sin(yaw)*dist;
|
||||
move[2] = 0;
|
||||
|
||||
VectorCopy (ent->s.origin, oldorigin);
|
||||
if (SV_movestep (ent, move, false))
|
||||
{
|
||||
delta = ent->s.angles[YAW] - ent->ideal_yaw;
|
||||
if (delta > 45 && delta < 315)
|
||||
{ // not turned far enough, so don't take the step
|
||||
VectorCopy (oldorigin, ent->s.origin);
|
||||
}
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
return true;
|
||||
}
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
======================
|
||||
SV_FixCheckBottom
|
||||
|
||||
======================
|
||||
*/
|
||||
void SV_FixCheckBottom (edict_t *ent)
|
||||
{
|
||||
ent->flags |= FL_PARTIALGROUND;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
SV_NewChaseDir
|
||||
|
||||
================
|
||||
*/
|
||||
#define DI_NODIR -1
|
||||
void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist)
|
||||
{
|
||||
float deltax,deltay;
|
||||
float d[3];
|
||||
float tdir, olddir, turnaround;
|
||||
|
||||
//FIXME: how did we get here with no enemy
|
||||
if (!enemy)
|
||||
return;
|
||||
|
||||
olddir = anglemod( (int)(actor->ideal_yaw/45)*45 );
|
||||
turnaround = anglemod(olddir - 180);
|
||||
|
||||
deltax = enemy->s.origin[0] - actor->s.origin[0];
|
||||
deltay = enemy->s.origin[1] - actor->s.origin[1];
|
||||
if (deltax>10)
|
||||
d[1]= 0;
|
||||
else if (deltax<-10)
|
||||
d[1]= 180;
|
||||
else
|
||||
d[1]= DI_NODIR;
|
||||
if (deltay<-10)
|
||||
d[2]= 270;
|
||||
else if (deltay>10)
|
||||
d[2]= 90;
|
||||
else
|
||||
d[2]= DI_NODIR;
|
||||
|
||||
// try direct route
|
||||
if (d[1] != DI_NODIR && d[2] != DI_NODIR)
|
||||
{
|
||||
if (d[1] == 0)
|
||||
tdir = d[2] == 90 ? 45 : 315;
|
||||
else
|
||||
tdir = d[2] == 90 ? 135 : 215;
|
||||
|
||||
if (tdir != turnaround && SV_StepDirection(actor, tdir, dist))
|
||||
return;
|
||||
}
|
||||
|
||||
// try other directions
|
||||
if ( ((rand()&3) & 1) || abs(deltay)>abs(deltax))
|
||||
{
|
||||
tdir=d[1];
|
||||
d[1]=d[2];
|
||||
d[2]=tdir;
|
||||
}
|
||||
|
||||
if (d[1]!=DI_NODIR && d[1]!=turnaround
|
||||
&& SV_StepDirection(actor, d[1], dist))
|
||||
return;
|
||||
|
||||
if (d[2]!=DI_NODIR && d[2]!=turnaround
|
||||
&& SV_StepDirection(actor, d[2], dist))
|
||||
return;
|
||||
|
||||
/* there is no direct path to the player, so pick another direction */
|
||||
|
||||
if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist))
|
||||
return;
|
||||
|
||||
if (rand()&1) /*randomly determine direction of search*/
|
||||
{
|
||||
for (tdir=0 ; tdir<=315 ; tdir += 45)
|
||||
if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (tdir=315 ; tdir >=0 ; tdir -= 45)
|
||||
if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
|
||||
return;
|
||||
}
|
||||
|
||||
if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) )
|
||||
return;
|
||||
|
||||
actor->ideal_yaw = olddir; // can't move
|
||||
|
||||
// if a bridge was pulled out from underneath a monster, it may not have
|
||||
// a valid standing position at all
|
||||
|
||||
if (!M_CheckBottom (actor))
|
||||
SV_FixCheckBottom (actor);
|
||||
}
|
||||
|
||||
/*
|
||||
======================
|
||||
SV_CloseEnough
|
||||
|
||||
======================
|
||||
*/
|
||||
qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0 ; i<3 ; i++)
|
||||
{
|
||||
if (goal->absmin[i] > ent->absmax[i] + dist)
|
||||
return false;
|
||||
if (goal->absmax[i] < ent->absmin[i] - dist)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
======================
|
||||
M_MoveToGoal
|
||||
======================
|
||||
*/
|
||||
void M_MoveToGoal (edict_t *ent, float dist)
|
||||
{
|
||||
edict_t *goal;
|
||||
|
||||
goal = ent->goalentity;
|
||||
|
||||
if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
|
||||
return;
|
||||
|
||||
// if the next step hits the enemy, return immediately
|
||||
if (ent->enemy && SV_CloseEnough (ent, ent->enemy, dist) )
|
||||
return;
|
||||
|
||||
// bump around...
|
||||
if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist))
|
||||
{
|
||||
if (ent->inuse)
|
||||
SV_NewChaseDir (ent, goal, dist);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
M_walkmove
|
||||
===============
|
||||
*/
|
||||
qboolean M_walkmove (edict_t *ent, float yaw, float dist)
|
||||
{
|
||||
vec3_t move;
|
||||
|
||||
if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
|
||||
return false;
|
||||
|
||||
yaw = yaw*M_PI*2 / 360;
|
||||
|
||||
move[0] = cos(yaw)*dist;
|
||||
move[1] = sin(yaw)*dist;
|
||||
move[2] = 0;
|
||||
|
||||
return SV_movestep(ent, move, true);
|
||||
}
|
205
m_player.h
Normal file
205
m_player.h
Normal file
|
@ -0,0 +1,205 @@
|
|||
// G:\quake2\baseq2\models/player_x/frames
|
||||
|
||||
// This file generated by qdata - Do NOT Modify
|
||||
|
||||
#define FRAME_stand01 0
|
||||
#define FRAME_stand02 1
|
||||
#define FRAME_stand03 2
|
||||
#define FRAME_stand04 3
|
||||
#define FRAME_stand05 4
|
||||
#define FRAME_stand06 5
|
||||
#define FRAME_stand07 6
|
||||
#define FRAME_stand08 7
|
||||
#define FRAME_stand09 8
|
||||
#define FRAME_stand10 9
|
||||
#define FRAME_stand11 10
|
||||
#define FRAME_stand12 11
|
||||
#define FRAME_stand13 12
|
||||
#define FRAME_stand14 13
|
||||
#define FRAME_stand15 14
|
||||
#define FRAME_stand16 15
|
||||
#define FRAME_stand17 16
|
||||
#define FRAME_stand18 17
|
||||
#define FRAME_stand19 18
|
||||
#define FRAME_stand20 19
|
||||
#define FRAME_stand21 20
|
||||
#define FRAME_stand22 21
|
||||
#define FRAME_stand23 22
|
||||
#define FRAME_stand24 23
|
||||
#define FRAME_stand25 24
|
||||
#define FRAME_stand26 25
|
||||
#define FRAME_stand27 26
|
||||
#define FRAME_stand28 27
|
||||
#define FRAME_stand29 28
|
||||
#define FRAME_stand30 29
|
||||
#define FRAME_stand31 30
|
||||
#define FRAME_stand32 31
|
||||
#define FRAME_stand33 32
|
||||
#define FRAME_stand34 33
|
||||
#define FRAME_stand35 34
|
||||
#define FRAME_stand36 35
|
||||
#define FRAME_stand37 36
|
||||
#define FRAME_stand38 37
|
||||
#define FRAME_stand39 38
|
||||
#define FRAME_stand40 39
|
||||
#define FRAME_run1 40
|
||||
#define FRAME_run2 41
|
||||
#define FRAME_run3 42
|
||||
#define FRAME_run4 43
|
||||
#define FRAME_run5 44
|
||||
#define FRAME_run6 45
|
||||
#define FRAME_attack1 46
|
||||
#define FRAME_attack2 47
|
||||
#define FRAME_attack3 48
|
||||
#define FRAME_attack4 49
|
||||
#define FRAME_attack5 50
|
||||
#define FRAME_attack6 51
|
||||
#define FRAME_attack7 52
|
||||
#define FRAME_attack8 53
|
||||
#define FRAME_pain101 54
|
||||
#define FRAME_pain102 55
|
||||
#define FRAME_pain103 56
|
||||
#define FRAME_pain104 57
|
||||
#define FRAME_pain201 58
|
||||
#define FRAME_pain202 59
|
||||
#define FRAME_pain203 60
|
||||
#define FRAME_pain204 61
|
||||
#define FRAME_pain301 62
|
||||
#define FRAME_pain302 63
|
||||
#define FRAME_pain303 64
|
||||
#define FRAME_pain304 65
|
||||
#define FRAME_jump1 66
|
||||
#define FRAME_jump2 67
|
||||
#define FRAME_jump3 68
|
||||
#define FRAME_jump4 69
|
||||
#define FRAME_jump5 70
|
||||
#define FRAME_jump6 71
|
||||
#define FRAME_flip01 72
|
||||
#define FRAME_flip02 73
|
||||
#define FRAME_flip03 74
|
||||
#define FRAME_flip04 75
|
||||
#define FRAME_flip05 76
|
||||
#define FRAME_flip06 77
|
||||
#define FRAME_flip07 78
|
||||
#define FRAME_flip08 79
|
||||
#define FRAME_flip09 80
|
||||
#define FRAME_flip10 81
|
||||
#define FRAME_flip11 82
|
||||
#define FRAME_flip12 83
|
||||
#define FRAME_salute01 84
|
||||
#define FRAME_salute02 85
|
||||
#define FRAME_salute03 86
|
||||
#define FRAME_salute04 87
|
||||
#define FRAME_salute05 88
|
||||
#define FRAME_salute06 89
|
||||
#define FRAME_salute07 90
|
||||
#define FRAME_salute08 91
|
||||
#define FRAME_salute09 92
|
||||
#define FRAME_salute10 93
|
||||
#define FRAME_salute11 94
|
||||
#define FRAME_taunt01 95
|
||||
#define FRAME_taunt02 96
|
||||
#define FRAME_taunt03 97
|
||||
#define FRAME_taunt04 98
|
||||
#define FRAME_taunt05 99
|
||||
#define FRAME_taunt06 100
|
||||
#define FRAME_taunt07 101
|
||||
#define FRAME_taunt08 102
|
||||
#define FRAME_taunt09 103
|
||||
#define FRAME_taunt10 104
|
||||
#define FRAME_taunt11 105
|
||||
#define FRAME_taunt12 106
|
||||
#define FRAME_taunt13 107
|
||||
#define FRAME_taunt14 108
|
||||
#define FRAME_taunt15 109
|
||||
#define FRAME_taunt16 110
|
||||
#define FRAME_taunt17 111
|
||||
#define FRAME_wave01 112
|
||||
#define FRAME_wave02 113
|
||||
#define FRAME_wave03 114
|
||||
#define FRAME_wave04 115
|
||||
#define FRAME_wave05 116
|
||||
#define FRAME_wave06 117
|
||||
#define FRAME_wave07 118
|
||||
#define FRAME_wave08 119
|
||||
#define FRAME_wave09 120
|
||||
#define FRAME_wave10 121
|
||||
#define FRAME_wave11 122
|
||||
#define FRAME_point01 123
|
||||
#define FRAME_point02 124
|
||||
#define FRAME_point03 125
|
||||
#define FRAME_point04 126
|
||||
#define FRAME_point05 127
|
||||
#define FRAME_point06 128
|
||||
#define FRAME_point07 129
|
||||
#define FRAME_point08 130
|
||||
#define FRAME_point09 131
|
||||
#define FRAME_point10 132
|
||||
#define FRAME_point11 133
|
||||
#define FRAME_point12 134
|
||||
#define FRAME_crstnd01 135
|
||||
#define FRAME_crstnd02 136
|
||||
#define FRAME_crstnd03 137
|
||||
#define FRAME_crstnd04 138
|
||||
#define FRAME_crstnd05 139
|
||||
#define FRAME_crstnd06 140
|
||||
#define FRAME_crstnd07 141
|
||||
#define FRAME_crstnd08 142
|
||||
#define FRAME_crstnd09 143
|
||||
#define FRAME_crstnd10 144
|
||||
#define FRAME_crstnd11 145
|
||||
#define FRAME_crstnd12 146
|
||||
#define FRAME_crstnd13 147
|
||||
#define FRAME_crstnd14 148
|
||||
#define FRAME_crstnd15 149
|
||||
#define FRAME_crstnd16 150
|
||||
#define FRAME_crstnd17 151
|
||||
#define FRAME_crstnd18 152
|
||||
#define FRAME_crstnd19 153
|
||||
#define FRAME_crwalk1 154
|
||||
#define FRAME_crwalk2 155
|
||||
#define FRAME_crwalk3 156
|
||||
#define FRAME_crwalk4 157
|
||||
#define FRAME_crwalk5 158
|
||||
#define FRAME_crwalk6 159
|
||||
#define FRAME_crattak1 160
|
||||
#define FRAME_crattak2 161
|
||||
#define FRAME_crattak3 162
|
||||
#define FRAME_crattak4 163
|
||||
#define FRAME_crattak5 164
|
||||
#define FRAME_crattak6 165
|
||||
#define FRAME_crattak7 166
|
||||
#define FRAME_crattak8 167
|
||||
#define FRAME_crattak9 168
|
||||
#define FRAME_crpain1 169
|
||||
#define FRAME_crpain2 170
|
||||
#define FRAME_crpain3 171
|
||||
#define FRAME_crpain4 172
|
||||
#define FRAME_crdeath1 173
|
||||
#define FRAME_crdeath2 174
|
||||
#define FRAME_crdeath3 175
|
||||
#define FRAME_crdeath4 176
|
||||
#define FRAME_crdeath5 177
|
||||
#define FRAME_death101 178
|
||||
#define FRAME_death102 179
|
||||
#define FRAME_death103 180
|
||||
#define FRAME_death104 181
|
||||
#define FRAME_death105 182
|
||||
#define FRAME_death106 183
|
||||
#define FRAME_death201 184
|
||||
#define FRAME_death202 185
|
||||
#define FRAME_death203 186
|
||||
#define FRAME_death204 187
|
||||
#define FRAME_death205 188
|
||||
#define FRAME_death206 189
|
||||
#define FRAME_death301 190
|
||||
#define FRAME_death302 191
|
||||
#define FRAME_death303 192
|
||||
#define FRAME_death304 193
|
||||
#define FRAME_death305 194
|
||||
#define FRAME_death306 195
|
||||
#define FRAME_death307 196
|
||||
#define FRAME_death308 197
|
||||
|
||||
#define MODEL_SCALE 1.000000
|
||||
|
2941
p_client.c
Normal file
2941
p_client.c
Normal file
File diff suppressed because it is too large
Load diff
682
p_hud.c
Normal file
682
p_hud.c
Normal 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
127
p_trail.c
Normal file
|
@ -0,0 +1,127 @@
|
|||
#include "g_local.h"
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
PLAYER TRAIL
|
||||
|
||||
==============================================================================
|
||||
|
||||
This is a circular list containing the a list of points of where
|
||||
the player has been recently. It is used by monsters for pursuit.
|
||||
|
||||
.origin the spot
|
||||
.owner forward link
|
||||
.aiment backward link
|
||||
*/
|
||||
|
||||
|
||||
#define TRAIL_LENGTH 8
|
||||
|
||||
edict_t *trail[TRAIL_LENGTH];
|
||||
int trail_head;
|
||||
qboolean trail_active = false;
|
||||
|
||||
#define NEXT(n) (((n) + 1) & (TRAIL_LENGTH - 1))
|
||||
#define PREV(n) (((n) - 1) & (TRAIL_LENGTH - 1))
|
||||
|
||||
|
||||
void PlayerTrail_Init (void)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (deathmatch->value /* FIXME || coop */)
|
||||
return;
|
||||
|
||||
for (n = 0; n < TRAIL_LENGTH; n++)
|
||||
{
|
||||
trail[n] = G_Spawn();
|
||||
trail[n]->classname = "player_trail";
|
||||
}
|
||||
|
||||
trail_head = 0;
|
||||
trail_active = true;
|
||||
}
|
||||
|
||||
|
||||
void PlayerTrail_Add (vec3_t spot)
|
||||
{
|
||||
vec3_t temp;
|
||||
|
||||
if (!trail_active)
|
||||
return;
|
||||
|
||||
VectorCopy (spot, trail[trail_head]->s.origin);
|
||||
|
||||
trail[trail_head]->timestamp = level.time;
|
||||
|
||||
VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp);
|
||||
trail[trail_head]->s.angles[1] = vectoyaw (temp);
|
||||
|
||||
trail_head = NEXT(trail_head);
|
||||
}
|
||||
|
||||
|
||||
void PlayerTrail_New (vec3_t spot)
|
||||
{
|
||||
if (!trail_active)
|
||||
return;
|
||||
|
||||
PlayerTrail_Init ();
|
||||
PlayerTrail_Add (spot);
|
||||
}
|
||||
|
||||
|
||||
edict_t *PlayerTrail_PickFirst (edict_t *self)
|
||||
{
|
||||
int marker;
|
||||
int n;
|
||||
|
||||
if (!trail_active)
|
||||
return NULL;
|
||||
|
||||
for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
|
||||
{
|
||||
if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
|
||||
marker = NEXT(marker);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (visible(self, trail[marker]))
|
||||
{
|
||||
return trail[marker];
|
||||
}
|
||||
|
||||
if (visible(self, trail[PREV(marker)]))
|
||||
{
|
||||
return trail[PREV(marker)];
|
||||
}
|
||||
|
||||
return trail[marker];
|
||||
}
|
||||
|
||||
edict_t *PlayerTrail_PickNext (edict_t *self)
|
||||
{
|
||||
int marker;
|
||||
int n;
|
||||
|
||||
if (!trail_active)
|
||||
return NULL;
|
||||
|
||||
for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
|
||||
{
|
||||
if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
|
||||
marker = NEXT(marker);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return trail[marker];
|
||||
}
|
||||
|
||||
edict_t *PlayerTrail_LastSpot (void)
|
||||
{
|
||||
return trail[PREV(trail_head)];
|
||||
}
|
4451
p_weapon.c
Normal file
4451
p_weapon.c
Normal file
File diff suppressed because it is too large
Load diff
1401
q_shared.c
Normal file
1401
q_shared.c
Normal file
File diff suppressed because it is too large
Load diff
1275
q_shared.h
Normal file
1275
q_shared.h
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue