as released 2001-12-22
107
3.15_Changes.txt
Normal file
|
@ -0,0 +1,107 @@
|
|||
|
||||
Quake2 3.15 Upgrade
|
||||
-------------------
|
||||
|
||||
This upgrade addresses several features, including security, playability, and
|
||||
enhancements.
|
||||
|
||||
A new map is also included (in baseq2\pak3.pak) called match1, Reckless
|
||||
Abandon. This map is designed for one on one deathmatch play. It was built
|
||||
by American McGee and Dave "Zoid" Kirsch.
|
||||
|
||||
This patch replaces the following files:
|
||||
|
||||
quake2.exe
|
||||
3dfxgl.dll
|
||||
pvrgl.dll
|
||||
ref_gl.dll
|
||||
ref_soft.dll
|
||||
baseq2\gamex86.dll
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Added visible weapons support. This is precached with a special symbol, i.e.
|
||||
gi.modelindex("#w_shotgun.md2") which causes the client to autobind it to
|
||||
the players current weapon model. Plug in player models can optionally
|
||||
support the visible weapons. Any that do not support it will use their
|
||||
default weapon.md2 files automatically.
|
||||
Visible weapons files for plug in player models are not downloaded
|
||||
automatically--only the default weapon.md2 (and skin) is.
|
||||
The Visible weapon models themselves are not included. They can be
|
||||
downloaded from http://www.telefragged.com/vwep/
|
||||
- Rewrote the some of the net code to use optimized network packets for
|
||||
projectiles. This is transparent to the game code, but improves netplay
|
||||
substancially. The hyperblaster doesn't flood modem players anymore.
|
||||
- Rewrote the packet checksum code to be more portable and defeat proxy bots
|
||||
yet again.
|
||||
- Autodownload support is in. The following items will be automatcally
|
||||
downloaded as needed:
|
||||
- world map (and textures)
|
||||
- models
|
||||
- sounds (precached ones)
|
||||
- plug in player model, skin, skin_i and weapon.md2
|
||||
downloads go to a temp file (maps/blah.tmp for example) and get renamed
|
||||
when done. autoresume is supported (if you lose connect during the
|
||||
download, just reconnect and resume). Server has fine control over
|
||||
the downloads with the following new cvars:
|
||||
allow_download - global download on/off
|
||||
allow_download_players - players download on/off
|
||||
allow_download_models - models download on/off
|
||||
allow_download_sounds - sounds download on/off
|
||||
allow_download_maps - maps download on/off
|
||||
maps that are in pak files will _not_ autodownload from the server, this
|
||||
is for copyright considerations.
|
||||
The QuakeWorld bug of the server map changing while download a map has
|
||||
been fixed.
|
||||
- New option in the Multiplayer/Player Setup menu for setting your connection
|
||||
speed. This sets a default rate for the player and can improve net
|
||||
performance for modem connections.
|
||||
- Rewrote some of the save game code to make it more portable. I wanted to
|
||||
completely rewrite the entire save game system and make it portable across
|
||||
versions and operating systems, but this would require an enormous amount
|
||||
of work.
|
||||
- Added another 512 configure strings for general usage for mod makers.
|
||||
This gives lots of room for general string displays on the HUD and in other
|
||||
data.
|
||||
- Player movement code re-written to be similiar to that of NetQuake and
|
||||
later versions of QuakeWorld. Player has more control in the air and
|
||||
gets a boost in vertical speed when jumping off the top of ramps.
|
||||
- Fixed up serverrecord so that it works correctly with the later versions.
|
||||
serverrecord lets the server do a recording of the current game that
|
||||
demo editors can use to make demos from any PVS in the level. Server
|
||||
recorded demos are BIG. Will look at using delta compression in them
|
||||
to cut down the size.
|
||||
- Copy protection CD check has been removed.
|
||||
- Quake2 3.15 has changed the protocol (so old servers will not run) but
|
||||
all existing game dlls can run on the new version (albiet without the
|
||||
new features such as visible weapons).
|
||||
- Added flood protection. Controlled from the following cvars:
|
||||
flood_msgs - maximum number of messages allowed in the time period
|
||||
specified by flood_persecond
|
||||
flood_persecond - time period that a maximum of flood_msgs messages are
|
||||
permitted
|
||||
flood_waitdelay - amount of time a client gets muzzled for flooding
|
||||
- fixed it so blaster/hyperblaster shots aren't treated as solid when
|
||||
predicting--you aren't clipped against them now.
|
||||
- gender support is now in. The userinfo cvar "gender" can be set to
|
||||
male/female/none (none for neutral messages). This doesn't affect sounds
|
||||
but does affect death messages in the game. The models male and cyborg
|
||||
default to gender male, and female and crackhor default to female.
|
||||
Everything else defaults to none, but you can set it by typing
|
||||
"gender male" or "gender female" as appropriate.
|
||||
- IP banning support ala QW. It's built into the game dll as 'sv' console
|
||||
commands. This list is:
|
||||
sv addip <ip-mask> - adds an ip to the ban list
|
||||
sv listip <ip-mask> - removes an ip from the ban list
|
||||
sv writeip - writes the ban list to <gamedir>/listip.cfg. You can
|
||||
exec this on a server load to load the list on subsequent server runs.
|
||||
like so: quake2 +set dedicated 1 +exec listip.cfg
|
||||
sv removeip <ip-mask> - remove an ip from the list
|
||||
the ip list is a simple mask system. Adding 192.168 to the list
|
||||
would block out everyone in the 192.168.*.* net block. You get 1024 bans,
|
||||
if you need more, recompile the game dll. :)
|
||||
A new cvar is also supported called 'filterban'. It defaults to one which
|
||||
means "allow everyone to connect _except_ those matching in the ban list."
|
||||
If you set it to zero, the meaning reverses like so, "don't allow anyone
|
||||
to connect unless they are in the list."
|
127
3.16_Changes.txt
Normal file
|
@ -0,0 +1,127 @@
|
|||
|
||||
Quake2 3.16 Upgrade
|
||||
-------------------
|
||||
|
||||
This upgrade addresses several features, including security, playability, and
|
||||
enhancements.
|
||||
|
||||
A new map is also included (in baseq2\pak3.pak) called match1, Reckless
|
||||
Abandon. This map is designed for one on one deathmatch play. It was built
|
||||
by American McGee and Dave "Zoid" Kirsch.
|
||||
|
||||
Changes for 3.16
|
||||
----------------
|
||||
|
||||
- Fixed infinite grenade bug
|
||||
- Fixed autodownloading to actually download sounds and console pics
|
||||
- Fixed autodownload to not create empty directories for files not on
|
||||
the server.
|
||||
- Added customized client downloading. cvars are the same as the server side:
|
||||
allow_download - global download on/off
|
||||
allow_download_players - players download on/off
|
||||
allow_download_models - models download on/off
|
||||
allow_download_sounds - sounds download on/off
|
||||
allow_download_maps - maps download on/off
|
||||
They can also be (more easily) set with a new Download Options menu
|
||||
accessible in Multiplayer/Player Setup/Download Options
|
||||
- Changed checksumming code to be more portable and faster.
|
||||
The checksum in 3.15 was seriously broken.
|
||||
This change makes 3.16 incompatible with previous servers.
|
||||
- Fixed it so sounds played for PPMs that default to male are only checked
|
||||
on disk once.
|
||||
- Fixed player 'warping' present in 3.15 (this was an artifact of the
|
||||
hyperblaster optimizations).
|
||||
- Fixed the autodownload in 3.15 so that stuff like skins for models are
|
||||
downloaded as well as pics.
|
||||
|
||||
Changes for 3.15
|
||||
----------------
|
||||
|
||||
- Added visible weapons support. This is precached with a special symbol, i.e.
|
||||
gi.modelindex("#w_shotgun.md2") which causes the client to autobind it to
|
||||
the players current weapon model. Plug in player models can optionally
|
||||
support the visible weapons. Any that do not support it will use their
|
||||
default weapon.md2 files automatically.
|
||||
Visible weapons files for plug in player models are not downloaded
|
||||
automatically--only the default weapon.md2 (and skin) is.
|
||||
The Visible weapon models themselves are not included. They can be
|
||||
downloaded from http://www.telefragged.com/vwep/
|
||||
- Rewrote the some of the net code to use optimized network packets for
|
||||
projectiles. This is transparent to the game code, but improves netplay
|
||||
substancially. The hyperblaster doesn't flood modem players anymore.
|
||||
- Rewrote the packet checksum code to be more portable and defeat proxy bots
|
||||
yet again.
|
||||
- Autodownload support is in. The following items will be automatcally
|
||||
downloaded as needed:
|
||||
- world map (and textures)
|
||||
- models
|
||||
- sounds (precached ones)
|
||||
- plug in player model, skin, skin_i and weapon.md2
|
||||
downloads go to a temp file (maps/blah.tmp for example) and get renamed
|
||||
when done. autoresume is supported (if you lose connect during the
|
||||
download, just reconnect and resume). Server has fine control over
|
||||
the downloads with the following new cvars:
|
||||
allow_download - global download on/off
|
||||
allow_download_players - players download on/off
|
||||
allow_download_models - models download on/off
|
||||
allow_download_sounds - sounds download on/off
|
||||
allow_download_maps - maps download on/off
|
||||
maps that are in pak files will _not_ autodownload from the server, this
|
||||
is for copyright considerations.
|
||||
The QuakeWorld bug of the server map changing while download a map has
|
||||
been fixed.
|
||||
- New option in the Multiplayer/Player Setup menu for setting your connection
|
||||
speed. This sets a default rate for the player and can improve net
|
||||
performance for modem connections.
|
||||
- Rewrote some of the save game code to make it more portable. I wanted to
|
||||
completely rewrite the entire save game system and make it portable across
|
||||
versions and operating systems, but this would require an enormous amount
|
||||
of work.
|
||||
- Added another 512 configure strings for general usage for mod makers.
|
||||
This gives lots of room for general string displays on the HUD and in other
|
||||
data.
|
||||
- Player movement code re-written to be similiar to that of NetQuake and
|
||||
later versions of QuakeWorld. Player has more control in the air and
|
||||
gets a boost in vertical speed when jumping off the top of ramps.
|
||||
- Fixed up serverrecord so that it works correctly with the later versions.
|
||||
serverrecord lets the server do a recording of the current game that
|
||||
demo editors can use to make demos from any PVS in the level. Server
|
||||
recorded demos are BIG. Will look at using delta compression in them
|
||||
to cut down the size.
|
||||
- Copy protection CD check has been removed.
|
||||
- Quake2 3.15 has changed the protocol (so old servers will not run) but
|
||||
all existing game dlls can run on the new version (albiet without the
|
||||
new features such as visible weapons).
|
||||
- Added flood protection. Controlled from the following cvars:
|
||||
flood_msgs - maximum number of messages allowed in the time period
|
||||
specified by flood_persecond
|
||||
flood_persecond - time period that a maximum of flood_msgs messages are
|
||||
permitted
|
||||
flood_waitdelay - amount of time a client gets muzzled for flooding
|
||||
(gamex86 DLL specific)
|
||||
- fixed it so blaster/hyperblaster shots aren't treated as solid when
|
||||
predicting--you aren't clipped against them now.
|
||||
(gamex86 DLL specific, the SVF_DEADMONSTER flag is set on projectiles)
|
||||
- gender support is now in. The userinfo cvar "gender" can be set to
|
||||
male/female/none (none for neutral messages). This doesn't affect sounds
|
||||
but does affect death messages in the game. The models male and cyborg
|
||||
default to gender male, and female and crackhor default to female.
|
||||
Everything else defaults to none, but you can set it by typing
|
||||
"gender male" or "gender female" as appropriate.
|
||||
- IP banning support ala QW. It's built into the game dll as 'sv' console
|
||||
commands. This list is:
|
||||
sv addip <ip-mask> - adds an ip to the ban list
|
||||
sv listip <ip-mask> - removes an ip from the ban list
|
||||
sv writeip - writes the ban list to <gamedir>/listip.cfg. You can
|
||||
exec this on a server load to load the list on subsequent server runs.
|
||||
like so: quake2 +set dedicated 1 +exec listip.cfg
|
||||
sv removeip <ip-mask> - remove an ip from the list
|
||||
the ip list is a simple mask system. Adding 192.168 to the list
|
||||
would block out everyone in the 192.168.*.* net block. You get 1024 bans,
|
||||
if you need more, recompile the game dll. :)
|
||||
A new cvar is also supported called 'filterban'. It defaults to one which
|
||||
means "allow everyone to connect _except_ those matching in the ban list."
|
||||
If you set it to zero, the meaning reverses like so, "don't allow anyone
|
||||
to connect unless they are in the list."
|
||||
(gamex86 DLL specific)
|
||||
|
158
3.17_Changes.txt
Normal file
|
@ -0,0 +1,158 @@
|
|||
|
||||
Quake2 3.17 Upgrade
|
||||
-------------------
|
||||
|
||||
This upgrade addresses several features, including security, playability, and
|
||||
enhancements.
|
||||
|
||||
Changes for 3.17
|
||||
----------------
|
||||
|
||||
- Fixed possible NAN resulting from handing zero to second arg of atan2
|
||||
- Autodownloading is now DISABLED by DEFAULT. It must be enabled by typing
|
||||
'allow_download 1' at the console, or using the download options menu
|
||||
in Multiplayer/PlayerSetup/Download Options
|
||||
- Server demos now include a svc_serverdata block at the beginning with the
|
||||
attractloop byte set to '2' to indicate server demo (byte before gamedir
|
||||
in the svc_serverdata block). This allows easy identification of
|
||||
serverrecorded demos (serverrecord demos are only for demo editors, they
|
||||
can not be played back in Quake2 without being first edited).
|
||||
- New options for setting texture formats in ref_gl:
|
||||
gl_texturealphamode: default, GL_RGBA, GL_RGBA8, GL_RGB5_A1, GL_RGBA4,
|
||||
GL_RGBA2
|
||||
gl_texturesolidmode: default, GL_RGB, GL_RGB8, GL_RGB5, GL_RGB4,
|
||||
GL_R3_G3_B2, GL_RGB2 (SGI only)
|
||||
- Player movement during Air acceleration changed to reflect more real-world
|
||||
physics while airborne.
|
||||
- Fixed a bug when riding trains that caused drift in a southwest direction
|
||||
(Thanks to Jim Dose at Ritual for pointing this one out).
|
||||
- Linux: Now correctly reports out of memory rather than segfaulting (mmap
|
||||
returns (void *)-1 and not NULL on error).
|
||||
- Fixed autodownloading to not create paths for files that can't be downloaded
|
||||
(this was creating many empty directories in baseq2/players).
|
||||
- When downloading a file from a server that doesn't have it, the message is
|
||||
now "Server does not have this file" rather than "File not found."
|
||||
- Fixed some coop keys in 3.15 weren't being handled correctly (pyramid key).
|
||||
- Highbits are now stripped from console when using condump
|
||||
- Restored support for gl_modulate in multiplayer play
|
||||
- Fixed it so that players with a model/skin you don't have aren't checked for
|
||||
on disk more than once.
|
||||
- Fixed it so sounds played for PPMs that default to male are only checked
|
||||
on disk once.
|
||||
- Byte ordering/portability fixes in cinematics, PCX and other file handling.
|
||||
- Client state during static image cinematic (PCX image) so that client can
|
||||
continue to next unit.
|
||||
- Fixed it so that dedicated coop servers no longer get stuck at victory.pcx,
|
||||
if a server is in coop mode, hitting a button at the victory.pcx screen
|
||||
while cause the server to restart at base1
|
||||
- Fixed infinite grenade bug
|
||||
- Fixed autodownloading to actually download sounds and console pics
|
||||
- Fixed autodownload to not create empty directories for files not on
|
||||
the server.
|
||||
- Added customized client downloading. cvars are the same as the server side:
|
||||
allow_download - global download on/off
|
||||
allow_download_players - players download on/off
|
||||
allow_download_models - models download on/off
|
||||
allow_download_sounds - sounds download on/off
|
||||
allow_download_maps - maps download on/off
|
||||
They can also be (more easily) set with a new Download Options menu
|
||||
accessible in Multiplayer/Player Setup/Download Options
|
||||
- Changed checksumming code to be more portable and faster.
|
||||
The checksum in 3.15 was seriously broken.
|
||||
This change makes 3.17 incompatible with previous servers.
|
||||
- Fixed player 'warping' present in 3.15 (this was an artifact of the
|
||||
hyperblaster optimizations).
|
||||
- Fixed the autodownload in 3.15 so that stuff like skins for models are
|
||||
downloaded as well as pics.
|
||||
|
||||
Changes for 3.15
|
||||
----------------
|
||||
|
||||
- Added visible weapons support. This is precached with a special symbol, i.e.
|
||||
gi.modelindex("#w_shotgun.md2") which causes the client to autobind it to
|
||||
the players current weapon model. Plug in player models can optionally
|
||||
support the visible weapons. Any that do not support it will use their
|
||||
default weapon.md2 files automatically.
|
||||
Visible weapons files for plug in player models are not downloaded
|
||||
automatically--only the default weapon.md2 (and skin) is.
|
||||
The Visible weapon models themselves are not included. They can be
|
||||
downloaded from http://www.telefragged.com/vwep/
|
||||
- Rewrote the some of the net code to use optimized network packets for
|
||||
projectiles. This is transparent to the game code, but improves netplay
|
||||
substancially. The hyperblaster doesn't flood modem players anymore.
|
||||
- Rewrote the packet checksum code to be more portable and defeat proxy bots
|
||||
yet again.
|
||||
- Autodownload support is in. The following items will be automatcally
|
||||
downloaded as needed:
|
||||
- world map (and textures)
|
||||
- models
|
||||
- sounds (precached ones)
|
||||
- plug in player model, skin, skin_i and weapon.md2
|
||||
downloads go to a temp file (maps/blah.tmp for example) and get renamed
|
||||
when done. autoresume is supported (if you lose connect during the
|
||||
download, just reconnect and resume). Server has fine control over
|
||||
the downloads with the following new cvars:
|
||||
allow_download - global download on/off
|
||||
allow_download_players - players download on/off
|
||||
allow_download_models - models download on/off
|
||||
allow_download_sounds - sounds download on/off
|
||||
allow_download_maps - maps download on/off
|
||||
maps that are in pak files will _not_ autodownload from the server, this
|
||||
is for copyright considerations.
|
||||
The QuakeWorld bug of the server map changing while download a map has
|
||||
been fixed.
|
||||
- New option in the Multiplayer/Player Setup menu for setting your connection
|
||||
speed. This sets a default rate for the player and can improve net
|
||||
performance for modem connections.
|
||||
- Rewrote some of the save game code to make it more portable. I wanted to
|
||||
completely rewrite the entire save game system and make it portable across
|
||||
versions and operating systems, but this would require an enormous amount
|
||||
of work.
|
||||
- Added another 512 configure strings for general usage for mod makers.
|
||||
This gives lots of room for general string displays on the HUD and in other
|
||||
data.
|
||||
- Player movement code re-written to be similiar to that of NetQuake and
|
||||
later versions of QuakeWorld. Player has more control in the air and
|
||||
gets a boost in vertical speed when jumping off the top of ramps.
|
||||
- Fixed up serverrecord so that it works correctly with the later versions.
|
||||
serverrecord lets the server do a recording of the current game that
|
||||
demo editors can use to make demos from any PVS in the level. Server
|
||||
recorded demos are BIG. Will look at using delta compression in them
|
||||
to cut down the size.
|
||||
- Copy protection CD check has been removed.
|
||||
- Quake2 3.15 has changed the protocol (so old servers will not run) but
|
||||
all existing game dlls can run on the new version (albiet without the
|
||||
new features such as visible weapons).
|
||||
- Added flood protection. Controlled from the following cvars:
|
||||
flood_msgs - maximum number of messages allowed in the time period
|
||||
specified by flood_persecond
|
||||
flood_persecond - time period that a maximum of flood_msgs messages are
|
||||
permitted
|
||||
flood_waitdelay - amount of time a client gets muzzled for flooding
|
||||
(gamex86 DLL specific)
|
||||
- fixed it so blaster/hyperblaster shots aren't treated as solid when
|
||||
predicting--you aren't clipped against them now.
|
||||
(gamex86 DLL specific, the SVF_DEADMONSTER flag is set on projectiles)
|
||||
- gender support is now in. The userinfo cvar "gender" can be set to
|
||||
male/female/none (none for neutral messages). This doesn't affect sounds
|
||||
but does affect death messages in the game. The models male and cyborg
|
||||
default to gender male, and female and crackhor default to female.
|
||||
Everything else defaults to none, but you can set it by typing
|
||||
"gender male" or "gender female" as appropriate.
|
||||
- IP banning support ala QW. It's built into the game dll as 'sv' console
|
||||
commands. This list is:
|
||||
sv addip <ip-mask> - adds an ip to the ban list
|
||||
sv listip <ip-mask> - removes an ip from the ban list
|
||||
sv writeip - writes the ban list to <gamedir>/listip.cfg. You can
|
||||
exec this on a server load to load the list on subsequent server runs.
|
||||
like so: quake2 +set dedicated 1 +exec listip.cfg
|
||||
sv removeip <ip-mask> - remove an ip from the list
|
||||
the ip list is a simple mask system. Adding 192.168 to the list
|
||||
would block out everyone in the 192.168.*.* net block. You get 1024 bans,
|
||||
if you need more, recompile the game dll. :)
|
||||
A new cvar is also supported called 'filterban'. It defaults to one which
|
||||
means "allow everyone to connect _except_ those matching in the ban list."
|
||||
If you set it to zero, the meaning reverses like so, "don't allow anyone
|
||||
to connect unless they are in the list."
|
||||
(gamex86 DLL specific)
|
||||
|
53
3.18_changes.txt
Normal file
|
@ -0,0 +1,53 @@
|
|||
3.18 Changes
|
||||
|
||||
- "Water surfing" that was present in 3.17 has been fixed (holding jump while
|
||||
on the surface of water let you swim at full speed).
|
||||
- Environment maps (env) are now autodownloaded (if allow_download_maps is set).
|
||||
- Spectator support added. A new cvar is built into the client, "spectator"
|
||||
Setting it to value other than "0" will allow you join a game as a spectator.
|
||||
While in spectator mode, you can press the attack button to enter a chasecam
|
||||
mode and follow other players. Using the inventory keys (by default the
|
||||
left and right square brackets) you can switch between players in the game
|
||||
while using the chasecam.
|
||||
You may enter and leave spectator mode while connected. Doing so resets
|
||||
your score to zero.
|
||||
***The new spectator support requires a new game.dll and may not work for
|
||||
user mods until they update their code. The default game.dll that comes
|
||||
with 3.18 supports chasecam as well as the new included Xatrix game.dll.
|
||||
- Fixed it so that when a model defaults to male/grunt (don't have the
|
||||
necessary model or skin for the player), VWep support is still enabled.
|
||||
- New console command for players, "playerlist". This will cause the server
|
||||
to give you a text list of the players on the server, including their
|
||||
connect time, score, ping and spectator status. This is handy if not
|
||||
everyone fits on the scoreboard on busy servers.
|
||||
- New cvar for the game.dll: spectator_password. If set to a value (other
|
||||
than "none"), users must set their spectator variable to this value in order
|
||||
to join the server as a spectator. This password is independant of the
|
||||
normal user password.
|
||||
- New cvar for the game.dll: maxspectators (defaults to 4). This value is
|
||||
not seperate from maxclients (a spectator is still a client).
|
||||
- New cvar for the game.dll: sv_maplist. This can be set to a list of map
|
||||
names that the server should autorotate through, rather than using the
|
||||
nextmap set in the actual map files themselves.
|
||||
For example: set sv_maplist "base1 q2dm1 q2dm3 fact3" will cause the server
|
||||
to rotate through those maps.
|
||||
***This requires a game.dll update and will not work with user mods until
|
||||
they update their code.
|
||||
- A new facility has been added to ClientConnect() in the game.dll to allow
|
||||
the game.dll to pass a message back to the user for the reason of disallowing
|
||||
a connection. It is done by setting a key of "rejmsg" in the passed userinfo.
|
||||
For example:
|
||||
Info_SetValueforKey(userinfo, "rejmsg", "Password required or incorrect.");
|
||||
- The server cvar, password, may be set to "none" to clear the password. This
|
||||
is needed because rcon can not set a blank password.
|
||||
- New server cvar: sv_airaccelerate. This controls the optional air
|
||||
acceleration facility. The default value is 0, which disables air control.
|
||||
The usual value to replicate the air control seen in the original Quake and
|
||||
later versions of Quakeworld is 10. 10 allows for much more
|
||||
air control (as was seen in 3.15). This value is ignored in single player
|
||||
and coop.
|
||||
- Fixed NoSuchFrame/BAD_MODELTYPE errors when doing a vid_restart while
|
||||
connected.
|
||||
- NoSuchFrame errors now include model name to assist in debugging user mods.
|
||||
- Fixed the remote status query response (ServerInfo) to not include error
|
||||
messages and be more consistent.
|
60
3.19_Changes.txt
Normal file
|
@ -0,0 +1,60 @@
|
|||
Changes since v3.17
|
||||
-------------------
|
||||
|
||||
- Updated VWEP models
|
||||
- Fixed sound config strings getting out of sync on loadgame. This caused
|
||||
the wrong sound to be played.
|
||||
- Fixed a bug in ref_soft that caused the menu system's bottom of screen
|
||||
help text not to be displayed
|
||||
- Added a missing Rogue DM option
|
||||
- "Water surfing" that was present in 3.17 has been fixed (holding jump while
|
||||
on the surface of water let you swim at full speed).
|
||||
- Environment maps (env) are now autodownloaded (if allow_download_maps is set).
|
||||
- Spectator support added. A new cvar is built into the client, "spectator"
|
||||
Setting it to value other than "0" will allow you join a game as a spectator.
|
||||
While in spectator mode, you can press the attack button to enter a chasecam
|
||||
mode and follow other players. Using the inventory keys (by default the
|
||||
left and right square brackets) you can switch between players in the game
|
||||
while using the chasecam.
|
||||
You may enter and leave spectator mode while connected. Doing so resets
|
||||
your score to zero.
|
||||
***The new spectator support requires a new game.dll and may not work for
|
||||
user mods until they update their code. The default game.dll that comes
|
||||
with 3.19 supports chasecam as well as the new included Xatrix game.dll.
|
||||
- Fixed it so that when a model defaults to male/grunt (don't have the
|
||||
necessary model or skin for the player), VWep support is still enabled.
|
||||
- New console command for players, "playerlist". This will cause the server
|
||||
to give you a text list of the players on the server, including their
|
||||
connect time, score, ping and spectator status. This is handy if not
|
||||
everyone fits on the scoreboard on busy servers.
|
||||
- New cvar for the game.dll: spectator_password. If set to a value (other
|
||||
than "none"), users must set their spectator variable to this value in order
|
||||
to join the server as a spectator. This password is independant of the
|
||||
normal user password.
|
||||
- New cvar for the game.dll: maxspectators (defaults to 4). This value is
|
||||
not seperate from maxclients (a spectator is still a client).
|
||||
- New cvar for the game.dll: sv_maplist. This can be set to a list of map
|
||||
names that the server should autorotate through, rather than using the
|
||||
nextmap set in the actual map files themselves.
|
||||
For example: set sv_maplist "base1 q2dm1 q2dm3 fact3" will cause the server
|
||||
to rotate through those maps.
|
||||
***This requires a game.dll update and will not work with user mods until
|
||||
they update their code.
|
||||
- A new facility has been added to ClientConnect() in the game.dll to allow
|
||||
the game.dll to pass a message back to the user for the reason of disallowing
|
||||
a connection. It is done by setting a key of "rejmsg" in the passed userinfo.
|
||||
For example:
|
||||
Info_SetValueforKey(userinfo, "rejmsg", "Password required or incorrect.");
|
||||
- The server cvar, password, may be set to "none" to clear the password. This
|
||||
is needed because rcon can not set a blank password.
|
||||
- New server cvar: sv_airaccelerate. This controls the optional air
|
||||
acceleration facility. The default value is 0, which disables air control.
|
||||
The usual value to replicate the air control seen in the original Quake and
|
||||
later versions of Quakeworld is 10. 10 allows for much more
|
||||
air control (as was seen in 3.15). This value is ignored in single player
|
||||
and coop.
|
||||
- Fixed NoSuchFrame/BAD_MODELTYPE errors when doing a vid_restart while
|
||||
connected.
|
||||
- NoSuchFrame errors now include model name to assist in debugging user mods.
|
||||
- Fixed the remote status query response (ServerInfo) to not include error
|
||||
messages and be more consistent.
|
69
3.20_Changes.txt
Normal file
|
@ -0,0 +1,69 @@
|
|||
3.20 Changes:
|
||||
- Fixed a network problem where and oversize packet could cause a client
|
||||
crash.
|
||||
- Fixed the long standing Quake2 bug of where you would occasionally spawn
|
||||
or teleport and find yourself either looking straight at the ceiling or
|
||||
down at the floor.
|
||||
- Changed it so that the function keys (F1 through F12) now get executed when
|
||||
depressed during demo playback or attract modes. This allows you to take
|
||||
screen shots (F12) during demos and other features. An example of other
|
||||
features is a fast forward for demos:
|
||||
alias +ff "timedemo 1"
|
||||
alias -ff "timedemo 0"
|
||||
bind f7 +ff
|
||||
This binding will cause the current demo playing to zip into timedemo mode
|
||||
while F7 is depressed, effectively acting like a fast forward key.
|
||||
- Wrong packaging of 3.19 patch. The Rogue CD has a 45k pak2 and vwep .md2
|
||||
files in baseq2/players, but the 3.19 x86 patch we released has a 2.7MB
|
||||
pak2. 3.20 has a 45k pak2 and the vwep models go in baseq2/players.
|
||||
- VWep code has been added to Xatrix dll, new VWep models for the Xatrix
|
||||
specific weapons (Ion Ripper and Phalanx) have been included.
|
||||
- Rogue Linux game library was wrong version and had some unlinked symbols,
|
||||
this has been corrected.
|
||||
- Occasional error of: "D_SCAlloc: bad cache width 16384" in software renderer.
|
||||
This had to do with surfaces to SURF_FLOWING and were transparent. This
|
||||
has been fixed in the refs now so flowing transparent textures now works.
|
||||
- [Unix] Net_ErrorToString calls were wrong, was using %i and not %s resulting
|
||||
in random numbers being printed for error messages.
|
||||
- Color shell mixing restored to the same blends as previous versions. This
|
||||
was changed in 3.19 for the new color shells the Rogue mission pack
|
||||
introduced.
|
||||
- Fixed a possible server crash in the new "playerlist" command.
|
||||
- Fixed a case where a person joining a server could be invisible (left over
|
||||
setting of SVF_NOCLIENT from previous spectator).
|
||||
- Invalid pak files no longer cause a crash and are just ignored
|
||||
- Fixed a 3.19 bug where linked models (modelindex2) who's modelindex was
|
||||
greater than 0x7f causes the wrong model to be drawn (in some cases, the
|
||||
world would be drawn twice). This was the cause of many of the "extreme"
|
||||
frame lag people were seeing in 3.19 on servers using old-style VWep code.
|
||||
- Linux: Complete rewrite of the OpenGL library handling. This was needed
|
||||
to cleanly integrate OpenGL extension checking. Linux now supports
|
||||
extensions such as multitexture and better dynamic loading of libraries.
|
||||
It's cleaner now in that you don't have to preload hack stuff to use the
|
||||
3DFX Miniport rather than libMesa3D. The Linux version now uses the
|
||||
gl_driver to specify the 3D library to dynamically load. For example, to
|
||||
use the lib3dfxgl.so miniport, one would now use:
|
||||
./quake2 +set vid_ref gl +set gl_driver lib3dfxgl.so
|
||||
This change fixes several bugs that were apparant in the older method, such
|
||||
as a segfault occasionally when connecting to a server with a different game
|
||||
directory.
|
||||
The vid menu in the Linux version has been changed to reflect the new
|
||||
options, the current list of supported video drivers are now: software,
|
||||
software X11, Mesa 3-D 3DFX, 3DFXGL Miniport, OpenGL glX, and Mesa 3-D glX,
|
||||
- Railgun shots now go through gibs as well as other players.
|
||||
- New server variable, "needpass" that can been seen with server browser
|
||||
tools such as GameSpy. This variable indicates whether a password or
|
||||
spectator password is needed to get onto a server. Bit 0 is password and
|
||||
bit 1 is spectator password.
|
||||
- Quake2 will no longer look for gamex86.dll in the main Quake2 directory.
|
||||
It will always load out of the game directory first.
|
||||
- Players joining a server during an intermission are now moved to the
|
||||
intermission position.
|
||||
- The "logfile" cvar has been extended with the following values:
|
||||
0 - don't log (default)
|
||||
1 - overwrite qconsole.log and use buffered writes
|
||||
2 - overwrite qconsole.log and flush write every line
|
||||
3 - append to existing qconsole.log and flush write every line
|
||||
- Several minor bug fixes to the Rogue mission pack gamex86.dll
|
||||
- Linux: Rebuild of Rogue mission pack shared library to correct some
|
||||
dynamic symbol errors (is NAN errors).
|
80
3.21_Changes.txt
Normal file
|
@ -0,0 +1,80 @@
|
|||
12-22-2001, for source release under GPL licensing:
|
||||
- Tweaked linux/Makefile for easier build
|
||||
added linux/README-3.21-RELEASE
|
||||
|
||||
3.21 Changes:
|
||||
- Support for GL_ARB_multitexture added. This supports the new multitexture
|
||||
extensions and deprecates GL_SGIS_multitexture.
|
||||
- Linux OpenGL X11 handling completely rewritten. Support for XF86DGA Mouse
|
||||
and fullscreen resolution support added. Please see the README file for
|
||||
Linux about the new features of this handling.
|
||||
|
||||
3.20 Changes:
|
||||
- Fixed a network problem where and oversize packet could cause a client
|
||||
crash.
|
||||
- Fixed the long standing Quake2 bug of where you would occasionally spawn
|
||||
or teleport and find yourself either looking straight at the ceiling or
|
||||
down at the floor.
|
||||
- Changed it so that the function keys (F1 through F12) now get executed when
|
||||
depressed during demo playback or attract modes. This allows you to take
|
||||
screen shots (F12) during demos and other features. An example of other
|
||||
features is a fast forward for demos:
|
||||
alias +ff "timedemo 1"
|
||||
alias -ff "timedemo 0"
|
||||
bind f7 +ff
|
||||
This binding will cause the current demo playing to zip into timedemo mode
|
||||
while F7 is depressed, effectively acting like a fast forward key.
|
||||
- Wrong packaging of 3.19 patch. The Rogue CD has a 45k pak2 and vwep .md2
|
||||
files in baseq2/players, but the 3.19 x86 patch we released has a 2.7MB
|
||||
pak2. 3.20 has a 45k pak2 and the vwep models go in baseq2/players.
|
||||
- VWep code has been added to Xatrix dll, new VWep models for the Xatrix
|
||||
specific weapons (Ion Ripper and Phalanx) have been included.
|
||||
- Rogue Linux game library was wrong version and had some unlinked symbols,
|
||||
this has been corrected.
|
||||
- Occasional error of: "D_SCAlloc: bad cache width 16384" in software renderer.
|
||||
This had to do with surfaces to SURF_FLOWING and were transparent. This
|
||||
has been fixed in the refs now so flowing transparent textures now works.
|
||||
- [Unix] Net_ErrorToString calls were wrong, was using %i and not %s resulting
|
||||
in random numbers being printed for error messages.
|
||||
- Color shell mixing restored to the same blends as previous versions. This
|
||||
was changed in 3.19 for the new color shells the Rogue mission pack
|
||||
introduced.
|
||||
- Fixed a possible server crash in the new "playerlist" command.
|
||||
- Fixed a case where a person joining a server could be invisible (left over
|
||||
setting of SVF_NOCLIENT from previous spectator).
|
||||
- Invalid pak files no longer cause a crash and are just ignored
|
||||
- Fixed a 3.19 bug where linked models (modelindex2) who's modelindex was
|
||||
greater than 0x7f causes the wrong model to be drawn (in some cases, the
|
||||
world would be drawn twice). This was the cause of many of the "extreme"
|
||||
frame lag people were seeing in 3.19 on servers using old-style VWep code.
|
||||
- Linux: Complete rewrite of the OpenGL library handling. This was needed
|
||||
to cleanly integrate OpenGL extension checking. Linux now supports
|
||||
extensions such as multitexture and better dynamic loading of libraries.
|
||||
It's cleaner now in that you don't have to preload hack stuff to use the
|
||||
3DFX Miniport rather than libMesa3D. The Linux version now uses the
|
||||
gl_driver to specify the 3D library to dynamically load. For example, to
|
||||
use the lib3dfxgl.so miniport, one would now use:
|
||||
./quake2 +set vid_ref gl +set gl_driver lib3dfxgl.so
|
||||
This change fixes several bugs that were apparant in the older method, such
|
||||
as a segfault occasionally when connecting to a server with a different game
|
||||
directory.
|
||||
The vid menu in the Linux version has been changed to reflect the new
|
||||
options, the current list of supported video drivers are now: software,
|
||||
software X11, Mesa 3-D 3DFX, 3DFXGL Miniport, OpenGL glX, and Mesa 3-D glX,
|
||||
- Railgun shots now go through gibs as well as other players.
|
||||
- New server variable, "needpass" that can been seen with server browser
|
||||
tools such as GameSpy. This variable indicates whether a password or
|
||||
spectator password is needed to get onto a server. Bit 0 is password and
|
||||
bit 1 is spectator password.
|
||||
- Quake2 will no longer look for gamex86.dll in the main Quake2 directory.
|
||||
It will always load out of the game directory first.
|
||||
- Players joining a server during an intermission are now moved to the
|
||||
intermission position.
|
||||
- The "logfile" cvar has been extended with the following values:
|
||||
0 - don't log (default)
|
||||
1 - overwrite qconsole.log and use buffered writes
|
||||
2 - overwrite qconsole.log and flush write every line
|
||||
3 - append to existing qconsole.log and flush write every line
|
||||
- Several minor bug fixes to the Rogue mission pack gamex86.dll
|
||||
- Linux: Rebuild of Rogue mission pack shared library to correct some
|
||||
dynamic symbol errors (is NAN errors).
|
166
changes.txt
Normal file
|
@ -0,0 +1,166 @@
|
|||
|
||||
Quake2 3.16 changes:
|
||||
|
||||
- Fixed infinite grenade bug
|
||||
- Fixed autodownloading to actually download sounds and console pics
|
||||
- Fixed autodownload to not create empty directories for files not on
|
||||
the server.
|
||||
- Added customized client downloading. cvars are the same as the server side:
|
||||
allow_download - global download on/off
|
||||
allow_download_players - players download on/off
|
||||
allow_download_models - models download on/off
|
||||
allow_download_sounds - sounds download on/off
|
||||
allow_download_maps - maps download on/off
|
||||
They can also be (more easily) set with a new Download Options menu
|
||||
accessible in Multiplayer/Player Setup/Download Options
|
||||
- Changed checksumming code to be more portable and faster.
|
||||
|
||||
|
||||
Quake2 3.15 changes:
|
||||
|
||||
- Added visible weapons support. This is precached with a special symbol, i.e.
|
||||
gi.modelindex("#w_shotgun.md2") which causes the client to autobind it to
|
||||
the players current weapon model. Plug in player models can optionally
|
||||
support the visible weapons. Any that do not support it will use their
|
||||
default weapon.md2 files automatically.
|
||||
Visible weapons files for plug in player models are not downloaded
|
||||
automatically--only the default weapon.md2 (and skin) is.
|
||||
- New cvar cl_vwep controls whether visible weapons is enabled on the client.
|
||||
If you turn it off, the visible weapons models are not loaded. This can offer
|
||||
a speed up on slow or memory starved machines.
|
||||
- Rewrote the some of the net code to use optimized network packets for
|
||||
projectiles. This is transparent to the game code, but improves netplay
|
||||
substancially. The hyperblaster doesn't flood modem players anymore.
|
||||
- Rewrote the packet checksum code to be more portable and defeat proxy bots
|
||||
yet again.
|
||||
- Autodownload support is in. The following items will be automatcally
|
||||
downloaded as needed:
|
||||
- world map (and textures)
|
||||
- models
|
||||
- sounds (precached ones)
|
||||
- plug in player model, skin, skin_i and weapon.md2
|
||||
downloads go to a temp file (maps/blah.tmp for example) and get renamed
|
||||
when done. autoresume is supported (if you lose connect during the
|
||||
download, just reconnect and resume). Server has fine control over
|
||||
the downloads with the following new cvars:
|
||||
allow_download - global download on/off
|
||||
allow_download_players - players download on/off
|
||||
allow_download_models - models download on/off
|
||||
allow_download_sounds - sounds download on/off
|
||||
allow_download_maps - maps download on/off
|
||||
maps that are in pak files will _not_ autodownload from the server, this
|
||||
is for copyright considerations.
|
||||
The QuakeWorld bug of the server map changing while download a map has
|
||||
been fixed.
|
||||
- New option in the Multiplayer/Player Setup menu for setting your connection
|
||||
speed. This sets a default rate for the player and can improve net
|
||||
performance for modem connections.
|
||||
- Rewrote some of the save game code to make it more portable. I wanted to
|
||||
completely rewrite the entire save game system and make it portable across
|
||||
versions and operating systems, but this would require an enormous amount
|
||||
of work.
|
||||
- Added another 512 configure strings for general usage for mod makers.
|
||||
This gives lots of room for general string displays on the HUD and in other
|
||||
data.
|
||||
- Player movement code re-written to be similiar to that of NetQuake and
|
||||
later versions of QuakeWorld. Player has more control in the air and
|
||||
gets a boost in vertical speed when jumping off the top of ramps.
|
||||
- Fixed up serverrecord so that it works correctly with the later versions.
|
||||
serverrecord lets the server do a recording of the current game that
|
||||
demo editors can use to make demos from any PVS in the level. Server
|
||||
recorded demos are BIG. Will look at using delta compression in them
|
||||
to cut down the size.
|
||||
- Copy protection CD check has been removed.
|
||||
- Quake2 3.15 has changed the protocol (so old servers will not run) but
|
||||
all existing game dlls can run on the new version (albiet without the
|
||||
new features such as visible weapons).
|
||||
- Added flood protection. Controlled from the following cvars:
|
||||
flood_msgs - maximum number of messages allowed in the time period
|
||||
specified by flood_persecond
|
||||
flood_persecond - time period that a maximum of flood_msgs messages are
|
||||
permitted
|
||||
flood_waitdelay - amount of time a client gets muzzled for flooding
|
||||
- fixed it so blaster/hyperblaster shots aren't treated as solid when
|
||||
predicting--you aren't clipped against them now.
|
||||
- gender support is now in. The userinfo cvar "gender" can be set to
|
||||
male/female/none (none for neutral messages). This doesn't affect sounds
|
||||
but does affect death messages in the game. The models male and cyborg
|
||||
default to gender male, and female and crackhor default to female.
|
||||
Everything else defaults to none, but you can set it by typing
|
||||
"gender male" or "gender female" as appropriate.
|
||||
- IP banning support ala QW. It's built into the game dll as 'sv' console
|
||||
commands. This list is:
|
||||
sv addip <ip-mask> - adds an ip to the ban list
|
||||
sv listip <ip-mask> - removes an ip from the ban list
|
||||
sv writeip - writes the ban list to <gamedir>/listip.cfg. You can
|
||||
exec this on a server load to load the list on subsequent server runs.
|
||||
like so: quake2 +set dedicated 1 +exec listip.cfg
|
||||
sv removeip <ip-mask> - remove an ip from the list
|
||||
the ip list is a simple mask system. Adding 192.168 to the list
|
||||
would block out everyone in the 192.168.*.* net block. You get 1024 bans,
|
||||
if you need more, recompile the game dll. :)
|
||||
A new cvar is also supported called 'filterban'. It defaults to one which
|
||||
means "allow everyone to connect _except_ those matching in the ban list."
|
||||
If you set it to zero, the meaning reverses like so, "don't allow anyone
|
||||
to connect unless they are in the list."
|
||||
|
||||
Quake2 CTF 1.09a Changes:
|
||||
|
||||
- Q2CTF 1.09 requires 3.15 now.
|
||||
- Competition Match mode added. Server can be reset into a timed match mode.
|
||||
Includes a pregame setup time, countdown until game start, timed match,
|
||||
statistics on players, admin functions and a post game time.
|
||||
- The server command 'gamemap' now works correctly. On a server, you can
|
||||
change maps with two commands: map and gamemap. Map will cause all teams
|
||||
to reset, gamemap will change maps with the teams intact.
|
||||
- New console commands:
|
||||
yes - vote yes on an election
|
||||
no - vote no on an election
|
||||
ready - ready oneself for a match
|
||||
notready - remove oneself from the ready list (stop the clock)
|
||||
ghost - ghost back into a match if connection was lost
|
||||
admin - become an admin or access the admin menu
|
||||
stats - show statistics on players in a match
|
||||
warp - warp to a new level
|
||||
boot - kick a player of the server (you must be an admin)
|
||||
playerlist - show player list and connect times
|
||||
- New cvars:
|
||||
competition - set to 1 to allow the server to be voted by players into
|
||||
competition mode. Set to 3 for a dedicated competition server.
|
||||
The default, 0, disables competition features.
|
||||
matchlock - controls whether players are allowed into a match in progress
|
||||
in competition mode. Defaults to on (1).
|
||||
electpercentage - the precentage of yes votes needed to win an election.
|
||||
Defaults to 66%.
|
||||
matchtime - length of a match, defaulting to 20 minutes. Can be changed
|
||||
by admins.
|
||||
matchsetuptime - length of time allowed to setup a match (after which
|
||||
the server will reset itself back into normal pickup play). Defaults
|
||||
to 10 mins.
|
||||
matchstarttime - The countdown after match setup has been completed
|
||||
until the match begins. Defaults to 20 seconds.
|
||||
admin_password - Password for admin access (allowing access to the admin
|
||||
menu without needing to be elected).
|
||||
- Minor bug fixes in team selection to help balance the teams better (the
|
||||
default option on the menu is now the team with the fewer players).
|
||||
- Don't get base defenses for telefragging your teammates in base.
|
||||
- Telefrags at start of game no longer count (to help with game spawning).
|
||||
- Instant weapon changing is now a server option (and can be changed by
|
||||
admin menu).
|
||||
- New admin menu that allows remote changes of the following items:
|
||||
Match length (time)
|
||||
Match setup length (time)
|
||||
Match start length (time)
|
||||
Weapons Stay
|
||||
Instant Items
|
||||
Quad Drop
|
||||
Instant Weapons
|
||||
- As part of the match code, a new 'ghost' option is included. When a match
|
||||
begins, all players are printed a randomly generated five digit ghost code
|
||||
in their consoles. If the player loses connection for some reason during
|
||||
the match, they can reconnect and reenter the game keeping their score
|
||||
intact at the time of disconnection.
|
||||
- Visible weapon support (as with the 3.15 release).
|
||||
- Some minor changes to the pmenu code to allow more flexability
|
||||
|
||||
|
1058
client/adivtab.h
Normal file
181
client/anorms.h
Normal file
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
{-0.525731, 0.000000, 0.850651},
|
||||
{-0.442863, 0.238856, 0.864188},
|
||||
{-0.295242, 0.000000, 0.955423},
|
||||
{-0.309017, 0.500000, 0.809017},
|
||||
{-0.162460, 0.262866, 0.951056},
|
||||
{0.000000, 0.000000, 1.000000},
|
||||
{0.000000, 0.850651, 0.525731},
|
||||
{-0.147621, 0.716567, 0.681718},
|
||||
{0.147621, 0.716567, 0.681718},
|
||||
{0.000000, 0.525731, 0.850651},
|
||||
{0.309017, 0.500000, 0.809017},
|
||||
{0.525731, 0.000000, 0.850651},
|
||||
{0.295242, 0.000000, 0.955423},
|
||||
{0.442863, 0.238856, 0.864188},
|
||||
{0.162460, 0.262866, 0.951056},
|
||||
{-0.681718, 0.147621, 0.716567},
|
||||
{-0.809017, 0.309017, 0.500000},
|
||||
{-0.587785, 0.425325, 0.688191},
|
||||
{-0.850651, 0.525731, 0.000000},
|
||||
{-0.864188, 0.442863, 0.238856},
|
||||
{-0.716567, 0.681718, 0.147621},
|
||||
{-0.688191, 0.587785, 0.425325},
|
||||
{-0.500000, 0.809017, 0.309017},
|
||||
{-0.238856, 0.864188, 0.442863},
|
||||
{-0.425325, 0.688191, 0.587785},
|
||||
{-0.716567, 0.681718, -0.147621},
|
||||
{-0.500000, 0.809017, -0.309017},
|
||||
{-0.525731, 0.850651, 0.000000},
|
||||
{0.000000, 0.850651, -0.525731},
|
||||
{-0.238856, 0.864188, -0.442863},
|
||||
{0.000000, 0.955423, -0.295242},
|
||||
{-0.262866, 0.951056, -0.162460},
|
||||
{0.000000, 1.000000, 0.000000},
|
||||
{0.000000, 0.955423, 0.295242},
|
||||
{-0.262866, 0.951056, 0.162460},
|
||||
{0.238856, 0.864188, 0.442863},
|
||||
{0.262866, 0.951056, 0.162460},
|
||||
{0.500000, 0.809017, 0.309017},
|
||||
{0.238856, 0.864188, -0.442863},
|
||||
{0.262866, 0.951056, -0.162460},
|
||||
{0.500000, 0.809017, -0.309017},
|
||||
{0.850651, 0.525731, 0.000000},
|
||||
{0.716567, 0.681718, 0.147621},
|
||||
{0.716567, 0.681718, -0.147621},
|
||||
{0.525731, 0.850651, 0.000000},
|
||||
{0.425325, 0.688191, 0.587785},
|
||||
{0.864188, 0.442863, 0.238856},
|
||||
{0.688191, 0.587785, 0.425325},
|
||||
{0.809017, 0.309017, 0.500000},
|
||||
{0.681718, 0.147621, 0.716567},
|
||||
{0.587785, 0.425325, 0.688191},
|
||||
{0.955423, 0.295242, 0.000000},
|
||||
{1.000000, 0.000000, 0.000000},
|
||||
{0.951056, 0.162460, 0.262866},
|
||||
{0.850651, -0.525731, 0.000000},
|
||||
{0.955423, -0.295242, 0.000000},
|
||||
{0.864188, -0.442863, 0.238856},
|
||||
{0.951056, -0.162460, 0.262866},
|
||||
{0.809017, -0.309017, 0.500000},
|
||||
{0.681718, -0.147621, 0.716567},
|
||||
{0.850651, 0.000000, 0.525731},
|
||||
{0.864188, 0.442863, -0.238856},
|
||||
{0.809017, 0.309017, -0.500000},
|
||||
{0.951056, 0.162460, -0.262866},
|
||||
{0.525731, 0.000000, -0.850651},
|
||||
{0.681718, 0.147621, -0.716567},
|
||||
{0.681718, -0.147621, -0.716567},
|
||||
{0.850651, 0.000000, -0.525731},
|
||||
{0.809017, -0.309017, -0.500000},
|
||||
{0.864188, -0.442863, -0.238856},
|
||||
{0.951056, -0.162460, -0.262866},
|
||||
{0.147621, 0.716567, -0.681718},
|
||||
{0.309017, 0.500000, -0.809017},
|
||||
{0.425325, 0.688191, -0.587785},
|
||||
{0.442863, 0.238856, -0.864188},
|
||||
{0.587785, 0.425325, -0.688191},
|
||||
{0.688191, 0.587785, -0.425325},
|
||||
{-0.147621, 0.716567, -0.681718},
|
||||
{-0.309017, 0.500000, -0.809017},
|
||||
{0.000000, 0.525731, -0.850651},
|
||||
{-0.525731, 0.000000, -0.850651},
|
||||
{-0.442863, 0.238856, -0.864188},
|
||||
{-0.295242, 0.000000, -0.955423},
|
||||
{-0.162460, 0.262866, -0.951056},
|
||||
{0.000000, 0.000000, -1.000000},
|
||||
{0.295242, 0.000000, -0.955423},
|
||||
{0.162460, 0.262866, -0.951056},
|
||||
{-0.442863, -0.238856, -0.864188},
|
||||
{-0.309017, -0.500000, -0.809017},
|
||||
{-0.162460, -0.262866, -0.951056},
|
||||
{0.000000, -0.850651, -0.525731},
|
||||
{-0.147621, -0.716567, -0.681718},
|
||||
{0.147621, -0.716567, -0.681718},
|
||||
{0.000000, -0.525731, -0.850651},
|
||||
{0.309017, -0.500000, -0.809017},
|
||||
{0.442863, -0.238856, -0.864188},
|
||||
{0.162460, -0.262866, -0.951056},
|
||||
{0.238856, -0.864188, -0.442863},
|
||||
{0.500000, -0.809017, -0.309017},
|
||||
{0.425325, -0.688191, -0.587785},
|
||||
{0.716567, -0.681718, -0.147621},
|
||||
{0.688191, -0.587785, -0.425325},
|
||||
{0.587785, -0.425325, -0.688191},
|
||||
{0.000000, -0.955423, -0.295242},
|
||||
{0.000000, -1.000000, 0.000000},
|
||||
{0.262866, -0.951056, -0.162460},
|
||||
{0.000000, -0.850651, 0.525731},
|
||||
{0.000000, -0.955423, 0.295242},
|
||||
{0.238856, -0.864188, 0.442863},
|
||||
{0.262866, -0.951056, 0.162460},
|
||||
{0.500000, -0.809017, 0.309017},
|
||||
{0.716567, -0.681718, 0.147621},
|
||||
{0.525731, -0.850651, 0.000000},
|
||||
{-0.238856, -0.864188, -0.442863},
|
||||
{-0.500000, -0.809017, -0.309017},
|
||||
{-0.262866, -0.951056, -0.162460},
|
||||
{-0.850651, -0.525731, 0.000000},
|
||||
{-0.716567, -0.681718, -0.147621},
|
||||
{-0.716567, -0.681718, 0.147621},
|
||||
{-0.525731, -0.850651, 0.000000},
|
||||
{-0.500000, -0.809017, 0.309017},
|
||||
{-0.238856, -0.864188, 0.442863},
|
||||
{-0.262866, -0.951056, 0.162460},
|
||||
{-0.864188, -0.442863, 0.238856},
|
||||
{-0.809017, -0.309017, 0.500000},
|
||||
{-0.688191, -0.587785, 0.425325},
|
||||
{-0.681718, -0.147621, 0.716567},
|
||||
{-0.442863, -0.238856, 0.864188},
|
||||
{-0.587785, -0.425325, 0.688191},
|
||||
{-0.309017, -0.500000, 0.809017},
|
||||
{-0.147621, -0.716567, 0.681718},
|
||||
{-0.425325, -0.688191, 0.587785},
|
||||
{-0.162460, -0.262866, 0.951056},
|
||||
{0.442863, -0.238856, 0.864188},
|
||||
{0.162460, -0.262866, 0.951056},
|
||||
{0.309017, -0.500000, 0.809017},
|
||||
{0.147621, -0.716567, 0.681718},
|
||||
{0.000000, -0.525731, 0.850651},
|
||||
{0.425325, -0.688191, 0.587785},
|
||||
{0.587785, -0.425325, 0.688191},
|
||||
{0.688191, -0.587785, 0.425325},
|
||||
{-0.955423, 0.295242, 0.000000},
|
||||
{-0.951056, 0.162460, 0.262866},
|
||||
{-1.000000, 0.000000, 0.000000},
|
||||
{-0.850651, 0.000000, 0.525731},
|
||||
{-0.955423, -0.295242, 0.000000},
|
||||
{-0.951056, -0.162460, 0.262866},
|
||||
{-0.864188, 0.442863, -0.238856},
|
||||
{-0.951056, 0.162460, -0.262866},
|
||||
{-0.809017, 0.309017, -0.500000},
|
||||
{-0.864188, -0.442863, -0.238856},
|
||||
{-0.951056, -0.162460, -0.262866},
|
||||
{-0.809017, -0.309017, -0.500000},
|
||||
{-0.681718, 0.147621, -0.716567},
|
||||
{-0.681718, -0.147621, -0.716567},
|
||||
{-0.850651, 0.000000, -0.525731},
|
||||
{-0.688191, 0.587785, -0.425325},
|
||||
{-0.587785, 0.425325, -0.688191},
|
||||
{-0.425325, 0.688191, -0.587785},
|
||||
{-0.425325, -0.688191, -0.587785},
|
||||
{-0.587785, -0.425325, -0.688191},
|
||||
{-0.688191, -0.587785, -0.425325},
|
81
client/asm_i386.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
|
||||
#ifndef __ASM_I386__
|
||||
#define __ASM_I386__
|
||||
|
||||
#ifdef ELF
|
||||
#define C(label) label
|
||||
#else
|
||||
#define C(label) _##label
|
||||
#endif
|
||||
|
||||
//
|
||||
// !!! note that this file must match the corresponding C structures at all
|
||||
// times !!!
|
||||
//
|
||||
|
||||
// plane_t structure
|
||||
// !!! if this is changed, it must be changed in model.h too !!!
|
||||
// !!! if the size of this is changed, the array lookup in SV_HullPointContents
|
||||
// must be changed too !!!
|
||||
#define pl_normal 0
|
||||
#define pl_dist 12
|
||||
#define pl_type 16
|
||||
#define pl_signbits 17
|
||||
#define pl_pad 18
|
||||
#define pl_size 20
|
||||
|
||||
// hull_t structure
|
||||
// !!! if this is changed, it must be changed in model.h too !!!
|
||||
#define hu_clipnodes 0
|
||||
#define hu_planes 4
|
||||
#define hu_firstclipnode 8
|
||||
#define hu_lastclipnode 12
|
||||
#define hu_clip_mins 16
|
||||
#define hu_clip_maxs 28
|
||||
#define hu_size 40
|
||||
|
||||
// dnode_t structure
|
||||
// !!! if this is changed, it must be changed in bspfile.h too !!!
|
||||
#define nd_planenum 0
|
||||
#define nd_children 4
|
||||
#define nd_mins 8
|
||||
#define nd_maxs 20
|
||||
#define nd_firstface 32
|
||||
#define nd_numfaces 36
|
||||
#define nd_size 40
|
||||
|
||||
// sfxcache_t structure
|
||||
// !!! if this is changed, it much be changed in sound.h too !!!
|
||||
#define sfxc_length 0
|
||||
#define sfxc_loopstart 4
|
||||
#define sfxc_speed 8
|
||||
#define sfxc_width 12
|
||||
#define sfxc_stereo 16
|
||||
#define sfxc_data 20
|
||||
|
||||
// channel_t structure
|
||||
// !!! if this is changed, it much be changed in sound.h too !!!
|
||||
#define ch_sfx 0
|
||||
#define ch_leftvol 4
|
||||
#define ch_rightvol 8
|
||||
#define ch_end 12
|
||||
#define ch_pos 16
|
||||
#define ch_looping 20
|
||||
#define ch_entnum 24
|
||||
#define ch_entchannel 28
|
||||
#define ch_origin 32
|
||||
#define ch_dist_mult 44
|
||||
#define ch_master_vol 48
|
||||
#define ch_size 52
|
||||
|
||||
// portable_samplepair_t structure
|
||||
// !!! if this is changed, it much be changed in sound.h too !!!
|
||||
#define psp_left 0
|
||||
#define psp_right 4
|
||||
#define psp_size 8
|
||||
|
||||
// !!! must be kept the same as in d_iface.h !!!
|
||||
#define TRANSPARENT_COLOR 255
|
||||
|
||||
#endif
|
||||
|
123
client/block16.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
LEnter16_16:
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movw 0x12345678(,%eax,2),%ax
|
||||
LBPatch0:
|
||||
addl %ebp,%edx
|
||||
movw %ax,(%edi)
|
||||
movw 0x12345678(,%ecx,2),%cx
|
||||
LBPatch1:
|
||||
movw %cx,2(%edi)
|
||||
addl $0x4,%edi
|
||||
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movw 0x12345678(,%eax,2),%ax
|
||||
LBPatch2:
|
||||
addl %ebp,%edx
|
||||
movw %ax,(%edi)
|
||||
movw 0x12345678(,%ecx,2),%cx
|
||||
LBPatch3:
|
||||
movw %cx,2(%edi)
|
||||
addl $0x4,%edi
|
||||
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movw 0x12345678(,%eax,2),%ax
|
||||
LBPatch4:
|
||||
addl %ebp,%edx
|
||||
movw %ax,(%edi)
|
||||
movw 0x12345678(,%ecx,2),%cx
|
||||
LBPatch5:
|
||||
movw %cx,2(%edi)
|
||||
addl $0x4,%edi
|
||||
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movw 0x12345678(,%eax,2),%ax
|
||||
LBPatch6:
|
||||
addl %ebp,%edx
|
||||
movw %ax,(%edi)
|
||||
movw 0x12345678(,%ecx,2),%cx
|
||||
LBPatch7:
|
||||
movw %cx,2(%edi)
|
||||
addl $0x4,%edi
|
||||
|
||||
LEnter8_16:
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movw 0x12345678(,%eax,2),%ax
|
||||
LBPatch8:
|
||||
addl %ebp,%edx
|
||||
movw %ax,(%edi)
|
||||
movw 0x12345678(,%ecx,2),%cx
|
||||
LBPatch9:
|
||||
movw %cx,2(%edi)
|
||||
addl $0x4,%edi
|
||||
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movw 0x12345678(,%eax,2),%ax
|
||||
LBPatch10:
|
||||
addl %ebp,%edx
|
||||
movw %ax,(%edi)
|
||||
movw 0x12345678(,%ecx,2),%cx
|
||||
LBPatch11:
|
||||
movw %cx,2(%edi)
|
||||
addl $0x4,%edi
|
||||
|
||||
LEnter4_16:
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movw 0x12345678(,%eax,2),%ax
|
||||
LBPatch12:
|
||||
addl %ebp,%edx
|
||||
movw %ax,(%edi)
|
||||
movw 0x12345678(,%ecx,2),%cx
|
||||
LBPatch13:
|
||||
movw %cx,2(%edi)
|
||||
addl $0x4,%edi
|
||||
|
||||
LEnter2_16:
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movw 0x12345678(,%eax,2),%ax
|
||||
LBPatch14:
|
||||
addl %ebp,%edx
|
||||
movw %ax,(%edi)
|
||||
movw 0x12345678(,%ecx,2),%cx
|
||||
LBPatch15:
|
||||
movw %cx,2(%edi)
|
||||
addl $0x4,%edi
|
124
client/block8.h
Normal file
|
@ -0,0 +1,124 @@
|
|||
LEnter16_8:
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movb 0x12345678(%eax),%al
|
||||
LBPatch0:
|
||||
addl %ebp,%edx
|
||||
movb %al,(%edi)
|
||||
movb 0x12345678(%ecx),%cl
|
||||
LBPatch1:
|
||||
movb %cl,1(%edi)
|
||||
addl $0x2,%edi
|
||||
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movb 0x12345678(%eax),%al
|
||||
LBPatch2:
|
||||
addl %ebp,%edx
|
||||
movb %al,(%edi)
|
||||
movb 0x12345678(%ecx),%cl
|
||||
LBPatch3:
|
||||
movb %cl,1(%edi)
|
||||
addl $0x2,%edi
|
||||
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movb 0x12345678(%eax),%al
|
||||
LBPatch4:
|
||||
addl %ebp,%edx
|
||||
movb %al,(%edi)
|
||||
movb 0x12345678(%ecx),%cl
|
||||
LBPatch5:
|
||||
movb %cl,1(%edi)
|
||||
addl $0x2,%edi
|
||||
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movb 0x12345678(%eax),%al
|
||||
LBPatch6:
|
||||
addl %ebp,%edx
|
||||
movb %al,(%edi)
|
||||
movb 0x12345678(%ecx),%cl
|
||||
LBPatch7:
|
||||
movb %cl,1(%edi)
|
||||
addl $0x2,%edi
|
||||
|
||||
LEnter8_8:
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movb 0x12345678(%eax),%al
|
||||
LBPatch8:
|
||||
addl %ebp,%edx
|
||||
movb %al,(%edi)
|
||||
movb 0x12345678(%ecx),%cl
|
||||
LBPatch9:
|
||||
movb %cl,1(%edi)
|
||||
addl $0x2,%edi
|
||||
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movb 0x12345678(%eax),%al
|
||||
LBPatch10:
|
||||
addl %ebp,%edx
|
||||
movb %al,(%edi)
|
||||
movb 0x12345678(%ecx),%cl
|
||||
LBPatch11:
|
||||
movb %cl,1(%edi)
|
||||
addl $0x2,%edi
|
||||
|
||||
LEnter4_8:
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movb 0x12345678(%eax),%al
|
||||
LBPatch12:
|
||||
addl %ebp,%edx
|
||||
movb %al,(%edi)
|
||||
movb 0x12345678(%ecx),%cl
|
||||
LBPatch13:
|
||||
movb %cl,1(%edi)
|
||||
addl $0x2,%edi
|
||||
|
||||
LEnter2_8:
|
||||
movb (%esi),%al
|
||||
movb (%esi,%ebx,),%cl
|
||||
movb %dh,%ah
|
||||
addl %ebp,%edx
|
||||
movb %dh,%ch
|
||||
leal (%esi,%ebx,2),%esi
|
||||
movb 0x12345678(%eax),%al
|
||||
LBPatch14:
|
||||
addl %ebp,%edx
|
||||
movb %al,(%edi)
|
||||
movb 0x12345678(%ecx),%cl
|
||||
LBPatch15:
|
||||
movb %cl,1(%edi)
|
||||
addl $0x2,%edi
|
||||
|
26
client/cdaudio.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
int CDAudio_Init(void);
|
||||
void CDAudio_Shutdown(void);
|
||||
void CDAudio_Play(int track, qboolean looping);
|
||||
void CDAudio_Stop(void);
|
||||
void CDAudio_Update(void);
|
||||
void CDAudio_Activate (qboolean active);
|
650
client/cl_cin.c
Normal file
|
@ -0,0 +1,650 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#include "client.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
byte *data;
|
||||
int count;
|
||||
} cblock_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
qboolean restart_sound;
|
||||
int s_rate;
|
||||
int s_width;
|
||||
int s_channels;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
byte *pic;
|
||||
byte *pic_pending;
|
||||
|
||||
// order 1 huffman stuff
|
||||
int *hnodes1; // [256][256][2];
|
||||
int numhnodes1[256];
|
||||
|
||||
int h_used[512];
|
||||
int h_count[512];
|
||||
} cinematics_t;
|
||||
|
||||
cinematics_t cin;
|
||||
|
||||
/*
|
||||
=================================================================
|
||||
|
||||
PCX LOADING
|
||||
|
||||
=================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
==============
|
||||
SCR_LoadPCX
|
||||
==============
|
||||
*/
|
||||
void SCR_LoadPCX (char *filename, byte **pic, byte **palette, int *width, int *height)
|
||||
{
|
||||
byte *raw;
|
||||
pcx_t *pcx;
|
||||
int x, y;
|
||||
int len;
|
||||
int dataByte, runLength;
|
||||
byte *out, *pix;
|
||||
|
||||
*pic = NULL;
|
||||
|
||||
//
|
||||
// load the file
|
||||
//
|
||||
len = FS_LoadFile (filename, (void **)&raw);
|
||||
if (!raw)
|
||||
return; // Com_Printf ("Bad pcx file %s\n", filename);
|
||||
|
||||
//
|
||||
// parse the PCX file
|
||||
//
|
||||
pcx = (pcx_t *)raw;
|
||||
raw = &pcx->data;
|
||||
|
||||
if (pcx->manufacturer != 0x0a
|
||||
|| pcx->version != 5
|
||||
|| pcx->encoding != 1
|
||||
|| pcx->bits_per_pixel != 8
|
||||
|| pcx->xmax >= 640
|
||||
|| pcx->ymax >= 480)
|
||||
{
|
||||
Com_Printf ("Bad pcx file %s\n", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
out = Z_Malloc ( (pcx->ymax+1) * (pcx->xmax+1) );
|
||||
|
||||
*pic = out;
|
||||
|
||||
pix = out;
|
||||
|
||||
if (palette)
|
||||
{
|
||||
*palette = Z_Malloc(768);
|
||||
memcpy (*palette, (byte *)pcx + len - 768, 768);
|
||||
}
|
||||
|
||||
if (width)
|
||||
*width = pcx->xmax+1;
|
||||
if (height)
|
||||
*height = pcx->ymax+1;
|
||||
|
||||
for (y=0 ; y<=pcx->ymax ; y++, pix += pcx->xmax+1)
|
||||
{
|
||||
for (x=0 ; x<=pcx->xmax ; )
|
||||
{
|
||||
dataByte = *raw++;
|
||||
|
||||
if((dataByte & 0xC0) == 0xC0)
|
||||
{
|
||||
runLength = dataByte & 0x3F;
|
||||
dataByte = *raw++;
|
||||
}
|
||||
else
|
||||
runLength = 1;
|
||||
|
||||
while(runLength-- > 0)
|
||||
pix[x++] = dataByte;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( raw - (byte *)pcx > len)
|
||||
{
|
||||
Com_Printf ("PCX file %s was malformed", filename);
|
||||
Z_Free (*pic);
|
||||
*pic = NULL;
|
||||
}
|
||||
|
||||
FS_FreeFile (pcx);
|
||||
}
|
||||
|
||||
//=============================================================
|
||||
|
||||
/*
|
||||
==================
|
||||
SCR_StopCinematic
|
||||
==================
|
||||
*/
|
||||
void SCR_StopCinematic (void)
|
||||
{
|
||||
cl.cinematictime = 0; // done
|
||||
if (cin.pic)
|
||||
{
|
||||
Z_Free (cin.pic);
|
||||
cin.pic = NULL;
|
||||
}
|
||||
if (cin.pic_pending)
|
||||
{
|
||||
Z_Free (cin.pic_pending);
|
||||
cin.pic_pending = NULL;
|
||||
}
|
||||
if (cl.cinematicpalette_active)
|
||||
{
|
||||
re.CinematicSetPalette(NULL);
|
||||
cl.cinematicpalette_active = false;
|
||||
}
|
||||
if (cl.cinematic_file)
|
||||
{
|
||||
fclose (cl.cinematic_file);
|
||||
cl.cinematic_file = NULL;
|
||||
}
|
||||
if (cin.hnodes1)
|
||||
{
|
||||
Z_Free (cin.hnodes1);
|
||||
cin.hnodes1 = NULL;
|
||||
}
|
||||
|
||||
// switch back down to 11 khz sound if necessary
|
||||
if (cin.restart_sound)
|
||||
{
|
||||
cin.restart_sound = false;
|
||||
CL_Snd_Restart_f ();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
SCR_FinishCinematic
|
||||
|
||||
Called when either the cinematic completes, or it is aborted
|
||||
====================
|
||||
*/
|
||||
void SCR_FinishCinematic (void)
|
||||
{
|
||||
// tell the server to advance to the next map / cinematic
|
||||
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
|
||||
SZ_Print (&cls.netchan.message, va("nextserver %i\n", cl.servercount));
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
||||
/*
|
||||
==================
|
||||
SmallestNode1
|
||||
==================
|
||||
*/
|
||||
int SmallestNode1 (int numhnodes)
|
||||
{
|
||||
int i;
|
||||
int best, bestnode;
|
||||
|
||||
best = 99999999;
|
||||
bestnode = -1;
|
||||
for (i=0 ; i<numhnodes ; i++)
|
||||
{
|
||||
if (cin.h_used[i])
|
||||
continue;
|
||||
if (!cin.h_count[i])
|
||||
continue;
|
||||
if (cin.h_count[i] < best)
|
||||
{
|
||||
best = cin.h_count[i];
|
||||
bestnode = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestnode == -1)
|
||||
return -1;
|
||||
|
||||
cin.h_used[bestnode] = true;
|
||||
return bestnode;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
Huff1TableInit
|
||||
|
||||
Reads the 64k counts table and initializes the node trees
|
||||
==================
|
||||
*/
|
||||
void Huff1TableInit (void)
|
||||
{
|
||||
int prev;
|
||||
int j;
|
||||
int *node, *nodebase;
|
||||
byte counts[256];
|
||||
int numhnodes;
|
||||
|
||||
cin.hnodes1 = Z_Malloc (256*256*2*4);
|
||||
memset (cin.hnodes1, 0, 256*256*2*4);
|
||||
|
||||
for (prev=0 ; prev<256 ; prev++)
|
||||
{
|
||||
memset (cin.h_count,0,sizeof(cin.h_count));
|
||||
memset (cin.h_used,0,sizeof(cin.h_used));
|
||||
|
||||
// read a row of counts
|
||||
FS_Read (counts, sizeof(counts), cl.cinematic_file);
|
||||
for (j=0 ; j<256 ; j++)
|
||||
cin.h_count[j] = counts[j];
|
||||
|
||||
// build the nodes
|
||||
numhnodes = 256;
|
||||
nodebase = cin.hnodes1 + prev*256*2;
|
||||
|
||||
while (numhnodes != 511)
|
||||
{
|
||||
node = nodebase + (numhnodes-256)*2;
|
||||
|
||||
// pick two lowest counts
|
||||
node[0] = SmallestNode1 (numhnodes);
|
||||
if (node[0] == -1)
|
||||
break; // no more
|
||||
|
||||
node[1] = SmallestNode1 (numhnodes);
|
||||
if (node[1] == -1)
|
||||
break;
|
||||
|
||||
cin.h_count[numhnodes] = cin.h_count[node[0]] + cin.h_count[node[1]];
|
||||
numhnodes++;
|
||||
}
|
||||
|
||||
cin.numhnodes1[prev] = numhnodes-1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
Huff1Decompress
|
||||
==================
|
||||
*/
|
||||
cblock_t Huff1Decompress (cblock_t in)
|
||||
{
|
||||
byte *input;
|
||||
byte *out_p;
|
||||
int nodenum;
|
||||
int count;
|
||||
cblock_t out;
|
||||
int inbyte;
|
||||
int *hnodes, *hnodesbase;
|
||||
//int i;
|
||||
|
||||
// get decompressed count
|
||||
count = in.data[0] + (in.data[1]<<8) + (in.data[2]<<16) + (in.data[3]<<24);
|
||||
input = in.data + 4;
|
||||
out_p = out.data = Z_Malloc (count);
|
||||
|
||||
// read bits
|
||||
|
||||
hnodesbase = cin.hnodes1 - 256*2; // nodes 0-255 aren't stored
|
||||
|
||||
hnodes = hnodesbase;
|
||||
nodenum = cin.numhnodes1[0];
|
||||
while (count)
|
||||
{
|
||||
inbyte = *input++;
|
||||
//-----------
|
||||
if (nodenum < 256)
|
||||
{
|
||||
hnodes = hnodesbase + (nodenum<<9);
|
||||
*out_p++ = nodenum;
|
||||
if (!--count)
|
||||
break;
|
||||
nodenum = cin.numhnodes1[nodenum];
|
||||
}
|
||||
nodenum = hnodes[nodenum*2 + (inbyte&1)];
|
||||
inbyte >>=1;
|
||||
//-----------
|
||||
if (nodenum < 256)
|
||||
{
|
||||
hnodes = hnodesbase + (nodenum<<9);
|
||||
*out_p++ = nodenum;
|
||||
if (!--count)
|
||||
break;
|
||||
nodenum = cin.numhnodes1[nodenum];
|
||||
}
|
||||
nodenum = hnodes[nodenum*2 + (inbyte&1)];
|
||||
inbyte >>=1;
|
||||
//-----------
|
||||
if (nodenum < 256)
|
||||
{
|
||||
hnodes = hnodesbase + (nodenum<<9);
|
||||
*out_p++ = nodenum;
|
||||
if (!--count)
|
||||
break;
|
||||
nodenum = cin.numhnodes1[nodenum];
|
||||
}
|
||||
nodenum = hnodes[nodenum*2 + (inbyte&1)];
|
||||
inbyte >>=1;
|
||||
//-----------
|
||||
if (nodenum < 256)
|
||||
{
|
||||
hnodes = hnodesbase + (nodenum<<9);
|
||||
*out_p++ = nodenum;
|
||||
if (!--count)
|
||||
break;
|
||||
nodenum = cin.numhnodes1[nodenum];
|
||||
}
|
||||
nodenum = hnodes[nodenum*2 + (inbyte&1)];
|
||||
inbyte >>=1;
|
||||
//-----------
|
||||
if (nodenum < 256)
|
||||
{
|
||||
hnodes = hnodesbase + (nodenum<<9);
|
||||
*out_p++ = nodenum;
|
||||
if (!--count)
|
||||
break;
|
||||
nodenum = cin.numhnodes1[nodenum];
|
||||
}
|
||||
nodenum = hnodes[nodenum*2 + (inbyte&1)];
|
||||
inbyte >>=1;
|
||||
//-----------
|
||||
if (nodenum < 256)
|
||||
{
|
||||
hnodes = hnodesbase + (nodenum<<9);
|
||||
*out_p++ = nodenum;
|
||||
if (!--count)
|
||||
break;
|
||||
nodenum = cin.numhnodes1[nodenum];
|
||||
}
|
||||
nodenum = hnodes[nodenum*2 + (inbyte&1)];
|
||||
inbyte >>=1;
|
||||
//-----------
|
||||
if (nodenum < 256)
|
||||
{
|
||||
hnodes = hnodesbase + (nodenum<<9);
|
||||
*out_p++ = nodenum;
|
||||
if (!--count)
|
||||
break;
|
||||
nodenum = cin.numhnodes1[nodenum];
|
||||
}
|
||||
nodenum = hnodes[nodenum*2 + (inbyte&1)];
|
||||
inbyte >>=1;
|
||||
//-----------
|
||||
if (nodenum < 256)
|
||||
{
|
||||
hnodes = hnodesbase + (nodenum<<9);
|
||||
*out_p++ = nodenum;
|
||||
if (!--count)
|
||||
break;
|
||||
nodenum = cin.numhnodes1[nodenum];
|
||||
}
|
||||
nodenum = hnodes[nodenum*2 + (inbyte&1)];
|
||||
inbyte >>=1;
|
||||
}
|
||||
|
||||
if (input - in.data != in.count && input - in.data != in.count+1)
|
||||
{
|
||||
Com_Printf ("Decompression overread by %i", (input - in.data) - in.count);
|
||||
}
|
||||
out.count = out_p - out.data;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SCR_ReadNextFrame
|
||||
==================
|
||||
*/
|
||||
byte *SCR_ReadNextFrame (void)
|
||||
{
|
||||
int r;
|
||||
int command;
|
||||
byte samples[22050/14*4];
|
||||
byte compressed[0x20000];
|
||||
int size;
|
||||
byte *pic;
|
||||
cblock_t in, huf1;
|
||||
int start, end, count;
|
||||
|
||||
// read the next frame
|
||||
r = fread (&command, 4, 1, cl.cinematic_file);
|
||||
if (r == 0) // we'll give it one more chance
|
||||
r = fread (&command, 4, 1, cl.cinematic_file);
|
||||
|
||||
if (r != 1)
|
||||
return NULL;
|
||||
command = LittleLong(command);
|
||||
if (command == 2)
|
||||
return NULL; // last frame marker
|
||||
|
||||
if (command == 1)
|
||||
{ // read palette
|
||||
FS_Read (cl.cinematicpalette, sizeof(cl.cinematicpalette), cl.cinematic_file);
|
||||
cl.cinematicpalette_active=0; // dubious.... exposes an edge case
|
||||
}
|
||||
|
||||
// decompress the next frame
|
||||
FS_Read (&size, 4, cl.cinematic_file);
|
||||
size = LittleLong(size);
|
||||
if (size > sizeof(compressed) || size < 1)
|
||||
Com_Error (ERR_DROP, "Bad compressed frame size");
|
||||
FS_Read (compressed, size, cl.cinematic_file);
|
||||
|
||||
// read sound
|
||||
start = cl.cinematicframe*cin.s_rate/14;
|
||||
end = (cl.cinematicframe+1)*cin.s_rate/14;
|
||||
count = end - start;
|
||||
|
||||
FS_Read (samples, count*cin.s_width*cin.s_channels, cl.cinematic_file);
|
||||
|
||||
S_RawSamples (count, cin.s_rate, cin.s_width, cin.s_channels, samples);
|
||||
|
||||
in.data = compressed;
|
||||
in.count = size;
|
||||
|
||||
huf1 = Huff1Decompress (in);
|
||||
|
||||
pic = huf1.data;
|
||||
|
||||
cl.cinematicframe++;
|
||||
|
||||
return pic;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SCR_RunCinematic
|
||||
|
||||
==================
|
||||
*/
|
||||
void SCR_RunCinematic (void)
|
||||
{
|
||||
int frame;
|
||||
|
||||
if (cl.cinematictime <= 0)
|
||||
{
|
||||
SCR_StopCinematic ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cl.cinematicframe == -1)
|
||||
return; // static image
|
||||
|
||||
if (cls.key_dest != key_game)
|
||||
{ // pause if menu or console is up
|
||||
cl.cinematictime = cls.realtime - cl.cinematicframe*1000/14;
|
||||
return;
|
||||
}
|
||||
|
||||
frame = (cls.realtime - cl.cinematictime)*14.0/1000;
|
||||
if (frame <= cl.cinematicframe)
|
||||
return;
|
||||
if (frame > cl.cinematicframe+1)
|
||||
{
|
||||
Com_Printf ("Dropped frame: %i > %i\n", frame, cl.cinematicframe+1);
|
||||
cl.cinematictime = cls.realtime - cl.cinematicframe*1000/14;
|
||||
}
|
||||
if (cin.pic)
|
||||
Z_Free (cin.pic);
|
||||
cin.pic = cin.pic_pending;
|
||||
cin.pic_pending = NULL;
|
||||
cin.pic_pending = SCR_ReadNextFrame ();
|
||||
if (!cin.pic_pending)
|
||||
{
|
||||
SCR_StopCinematic ();
|
||||
SCR_FinishCinematic ();
|
||||
cl.cinematictime = 1; // hack to get the black screen behind loading
|
||||
SCR_BeginLoadingPlaque ();
|
||||
cl.cinematictime = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SCR_DrawCinematic
|
||||
|
||||
Returns true if a cinematic is active, meaning the view rendering
|
||||
should be skipped
|
||||
==================
|
||||
*/
|
||||
qboolean SCR_DrawCinematic (void)
|
||||
{
|
||||
if (cl.cinematictime <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cls.key_dest == key_menu)
|
||||
{ // blank screen and pause if menu is up
|
||||
re.CinematicSetPalette(NULL);
|
||||
cl.cinematicpalette_active = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!cl.cinematicpalette_active)
|
||||
{
|
||||
re.CinematicSetPalette(cl.cinematicpalette);
|
||||
cl.cinematicpalette_active = true;
|
||||
}
|
||||
|
||||
if (!cin.pic)
|
||||
return true;
|
||||
|
||||
re.DrawStretchRaw (0, 0, viddef.width, viddef.height,
|
||||
cin.width, cin.height, cin.pic);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SCR_PlayCinematic
|
||||
|
||||
==================
|
||||
*/
|
||||
void SCR_PlayCinematic (char *arg)
|
||||
{
|
||||
int width, height;
|
||||
byte *palette;
|
||||
char name[MAX_OSPATH], *dot;
|
||||
int old_khz;
|
||||
|
||||
// make sure CD isn't playing music
|
||||
CDAudio_Stop();
|
||||
|
||||
cl.cinematicframe = 0;
|
||||
dot = strstr (arg, ".");
|
||||
if (dot && !strcmp (dot, ".pcx"))
|
||||
{ // static pcx image
|
||||
Com_sprintf (name, sizeof(name), "pics/%s", arg);
|
||||
SCR_LoadPCX (name, &cin.pic, &palette, &cin.width, &cin.height);
|
||||
cl.cinematicframe = -1;
|
||||
cl.cinematictime = 1;
|
||||
SCR_EndLoadingPlaque ();
|
||||
cls.state = ca_active;
|
||||
if (!cin.pic)
|
||||
{
|
||||
Com_Printf ("%s not found.\n", name);
|
||||
cl.cinematictime = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy (cl.cinematicpalette, palette, sizeof(cl.cinematicpalette));
|
||||
Z_Free (palette);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Com_sprintf (name, sizeof(name), "video/%s", arg);
|
||||
FS_FOpenFile (name, &cl.cinematic_file);
|
||||
if (!cl.cinematic_file)
|
||||
{
|
||||
// Com_Error (ERR_DROP, "Cinematic %s not found.\n", name);
|
||||
SCR_FinishCinematic ();
|
||||
cl.cinematictime = 0; // done
|
||||
return;
|
||||
}
|
||||
|
||||
SCR_EndLoadingPlaque ();
|
||||
|
||||
cls.state = ca_active;
|
||||
|
||||
FS_Read (&width, 4, cl.cinematic_file);
|
||||
FS_Read (&height, 4, cl.cinematic_file);
|
||||
cin.width = LittleLong(width);
|
||||
cin.height = LittleLong(height);
|
||||
|
||||
FS_Read (&cin.s_rate, 4, cl.cinematic_file);
|
||||
cin.s_rate = LittleLong(cin.s_rate);
|
||||
FS_Read (&cin.s_width, 4, cl.cinematic_file);
|
||||
cin.s_width = LittleLong(cin.s_width);
|
||||
FS_Read (&cin.s_channels, 4, cl.cinematic_file);
|
||||
cin.s_channels = LittleLong(cin.s_channels);
|
||||
|
||||
Huff1TableInit ();
|
||||
|
||||
// switch up to 22 khz sound if necessary
|
||||
old_khz = Cvar_VariableValue ("s_khz");
|
||||
if (old_khz != cin.s_rate/1000)
|
||||
{
|
||||
cin.restart_sound = true;
|
||||
Cvar_SetValue ("s_khz", cin.s_rate/1000);
|
||||
CL_Snd_Restart_f ();
|
||||
Cvar_SetValue ("s_khz", old_khz);
|
||||
}
|
||||
|
||||
cl.cinematicframe = 0;
|
||||
cin.pic = SCR_ReadNextFrame ();
|
||||
cl.cinematictime = Sys_Milliseconds ();
|
||||
}
|
1543
client/cl_ents.c
Normal file
2298
client/cl_fx.c
Normal file
548
client/cl_input.c
Normal file
|
@ -0,0 +1,548 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// cl.input.c -- builds an intended movement command to send to the server
|
||||
|
||||
#include "client.h"
|
||||
|
||||
cvar_t *cl_nodelta;
|
||||
|
||||
extern unsigned sys_frame_time;
|
||||
unsigned frame_msec;
|
||||
unsigned old_sys_frame_time;
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
KEY BUTTONS
|
||||
|
||||
Continuous button event tracking is complicated by the fact that two different
|
||||
input sources (say, mouse button 1 and the control key) can both press the
|
||||
same button, but the button should only be released when both of the
|
||||
pressing key have been released.
|
||||
|
||||
When a key event issues a button command (+forward, +attack, etc), it appends
|
||||
its key number as a parameter to the command so it can be matched up with
|
||||
the release.
|
||||
|
||||
state bit 0 is the current state of the key
|
||||
state bit 1 is edge triggered on the up to down transition
|
||||
state bit 2 is edge triggered on the down to up transition
|
||||
|
||||
|
||||
Key_Event (int key, qboolean down, unsigned time);
|
||||
|
||||
+mlook src time
|
||||
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
|
||||
kbutton_t in_klook;
|
||||
kbutton_t in_left, in_right, in_forward, in_back;
|
||||
kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright;
|
||||
kbutton_t in_strafe, in_speed, in_use, in_attack;
|
||||
kbutton_t in_up, in_down;
|
||||
|
||||
int in_impulse;
|
||||
|
||||
|
||||
void KeyDown (kbutton_t *b)
|
||||
{
|
||||
int k;
|
||||
char *c;
|
||||
|
||||
c = Cmd_Argv(1);
|
||||
if (c[0])
|
||||
k = atoi(c);
|
||||
else
|
||||
k = -1; // typed manually at the console for continuous down
|
||||
|
||||
if (k == b->down[0] || k == b->down[1])
|
||||
return; // repeating key
|
||||
|
||||
if (!b->down[0])
|
||||
b->down[0] = k;
|
||||
else if (!b->down[1])
|
||||
b->down[1] = k;
|
||||
else
|
||||
{
|
||||
Com_Printf ("Three keys down for a button!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (b->state & 1)
|
||||
return; // still down
|
||||
|
||||
// save timestamp
|
||||
c = Cmd_Argv(2);
|
||||
b->downtime = atoi(c);
|
||||
if (!b->downtime)
|
||||
b->downtime = sys_frame_time - 100;
|
||||
|
||||
b->state |= 1 + 2; // down + impulse down
|
||||
}
|
||||
|
||||
void KeyUp (kbutton_t *b)
|
||||
{
|
||||
int k;
|
||||
char *c;
|
||||
unsigned uptime;
|
||||
|
||||
c = Cmd_Argv(1);
|
||||
if (c[0])
|
||||
k = atoi(c);
|
||||
else
|
||||
{ // typed manually at the console, assume for unsticking, so clear all
|
||||
b->down[0] = b->down[1] = 0;
|
||||
b->state = 4; // impulse up
|
||||
return;
|
||||
}
|
||||
|
||||
if (b->down[0] == k)
|
||||
b->down[0] = 0;
|
||||
else if (b->down[1] == k)
|
||||
b->down[1] = 0;
|
||||
else
|
||||
return; // key up without coresponding down (menu pass through)
|
||||
if (b->down[0] || b->down[1])
|
||||
return; // some other key is still holding it down
|
||||
|
||||
if (!(b->state & 1))
|
||||
return; // still up (this should not happen)
|
||||
|
||||
// save timestamp
|
||||
c = Cmd_Argv(2);
|
||||
uptime = atoi(c);
|
||||
if (uptime)
|
||||
b->msec += uptime - b->downtime;
|
||||
else
|
||||
b->msec += 10;
|
||||
|
||||
b->state &= ~1; // now up
|
||||
b->state |= 4; // impulse up
|
||||
}
|
||||
|
||||
void IN_KLookDown (void) {KeyDown(&in_klook);}
|
||||
void IN_KLookUp (void) {KeyUp(&in_klook);}
|
||||
void IN_UpDown(void) {KeyDown(&in_up);}
|
||||
void IN_UpUp(void) {KeyUp(&in_up);}
|
||||
void IN_DownDown(void) {KeyDown(&in_down);}
|
||||
void IN_DownUp(void) {KeyUp(&in_down);}
|
||||
void IN_LeftDown(void) {KeyDown(&in_left);}
|
||||
void IN_LeftUp(void) {KeyUp(&in_left);}
|
||||
void IN_RightDown(void) {KeyDown(&in_right);}
|
||||
void IN_RightUp(void) {KeyUp(&in_right);}
|
||||
void IN_ForwardDown(void) {KeyDown(&in_forward);}
|
||||
void IN_ForwardUp(void) {KeyUp(&in_forward);}
|
||||
void IN_BackDown(void) {KeyDown(&in_back);}
|
||||
void IN_BackUp(void) {KeyUp(&in_back);}
|
||||
void IN_LookupDown(void) {KeyDown(&in_lookup);}
|
||||
void IN_LookupUp(void) {KeyUp(&in_lookup);}
|
||||
void IN_LookdownDown(void) {KeyDown(&in_lookdown);}
|
||||
void IN_LookdownUp(void) {KeyUp(&in_lookdown);}
|
||||
void IN_MoveleftDown(void) {KeyDown(&in_moveleft);}
|
||||
void IN_MoveleftUp(void) {KeyUp(&in_moveleft);}
|
||||
void IN_MoverightDown(void) {KeyDown(&in_moveright);}
|
||||
void IN_MoverightUp(void) {KeyUp(&in_moveright);}
|
||||
|
||||
void IN_SpeedDown(void) {KeyDown(&in_speed);}
|
||||
void IN_SpeedUp(void) {KeyUp(&in_speed);}
|
||||
void IN_StrafeDown(void) {KeyDown(&in_strafe);}
|
||||
void IN_StrafeUp(void) {KeyUp(&in_strafe);}
|
||||
|
||||
void IN_AttackDown(void) {KeyDown(&in_attack);}
|
||||
void IN_AttackUp(void) {KeyUp(&in_attack);}
|
||||
|
||||
void IN_UseDown (void) {KeyDown(&in_use);}
|
||||
void IN_UseUp (void) {KeyUp(&in_use);}
|
||||
|
||||
void IN_Impulse (void) {in_impulse=atoi(Cmd_Argv(1));}
|
||||
|
||||
/*
|
||||
===============
|
||||
CL_KeyState
|
||||
|
||||
Returns the fraction of the frame that the key was down
|
||||
===============
|
||||
*/
|
||||
float CL_KeyState (kbutton_t *key)
|
||||
{
|
||||
float val;
|
||||
int msec;
|
||||
|
||||
key->state &= 1; // clear impulses
|
||||
|
||||
msec = key->msec;
|
||||
key->msec = 0;
|
||||
|
||||
if (key->state)
|
||||
{ // still down
|
||||
msec += sys_frame_time - key->downtime;
|
||||
key->downtime = sys_frame_time;
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (msec)
|
||||
{
|
||||
Com_Printf ("%i ", msec);
|
||||
}
|
||||
#endif
|
||||
|
||||
val = (float)msec / frame_msec;
|
||||
if (val < 0)
|
||||
val = 0;
|
||||
if (val > 1)
|
||||
val = 1;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//==========================================================================
|
||||
|
||||
cvar_t *cl_upspeed;
|
||||
cvar_t *cl_forwardspeed;
|
||||
cvar_t *cl_sidespeed;
|
||||
|
||||
cvar_t *cl_yawspeed;
|
||||
cvar_t *cl_pitchspeed;
|
||||
|
||||
cvar_t *cl_run;
|
||||
|
||||
cvar_t *cl_anglespeedkey;
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
CL_AdjustAngles
|
||||
|
||||
Moves the local angle positions
|
||||
================
|
||||
*/
|
||||
void CL_AdjustAngles (void)
|
||||
{
|
||||
float speed;
|
||||
float up, down;
|
||||
|
||||
if (in_speed.state & 1)
|
||||
speed = cls.frametime * cl_anglespeedkey->value;
|
||||
else
|
||||
speed = cls.frametime;
|
||||
|
||||
if (!(in_strafe.state & 1))
|
||||
{
|
||||
cl.viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right);
|
||||
cl.viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left);
|
||||
}
|
||||
if (in_klook.state & 1)
|
||||
{
|
||||
cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_forward);
|
||||
cl.viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_back);
|
||||
}
|
||||
|
||||
up = CL_KeyState (&in_lookup);
|
||||
down = CL_KeyState(&in_lookdown);
|
||||
|
||||
cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * up;
|
||||
cl.viewangles[PITCH] += speed*cl_pitchspeed->value * down;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
CL_BaseMove
|
||||
|
||||
Send the intended movement message to the server
|
||||
================
|
||||
*/
|
||||
void CL_BaseMove (usercmd_t *cmd)
|
||||
{
|
||||
CL_AdjustAngles ();
|
||||
|
||||
memset (cmd, 0, sizeof(*cmd));
|
||||
|
||||
VectorCopy (cl.viewangles, cmd->angles);
|
||||
if (in_strafe.state & 1)
|
||||
{
|
||||
cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_right);
|
||||
cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_left);
|
||||
}
|
||||
|
||||
cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright);
|
||||
cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_moveleft);
|
||||
|
||||
cmd->upmove += cl_upspeed->value * CL_KeyState (&in_up);
|
||||
cmd->upmove -= cl_upspeed->value * CL_KeyState (&in_down);
|
||||
|
||||
if (! (in_klook.state & 1) )
|
||||
{
|
||||
cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward);
|
||||
cmd->forwardmove -= cl_forwardspeed->value * CL_KeyState (&in_back);
|
||||
}
|
||||
|
||||
//
|
||||
// adjust for speed key / running
|
||||
//
|
||||
if ( (in_speed.state & 1) ^ (int)(cl_run->value) )
|
||||
{
|
||||
cmd->forwardmove *= 2;
|
||||
cmd->sidemove *= 2;
|
||||
cmd->upmove *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
void CL_ClampPitch (void)
|
||||
{
|
||||
float pitch;
|
||||
|
||||
pitch = SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[PITCH]);
|
||||
if (pitch > 180)
|
||||
pitch -= 360;
|
||||
|
||||
if (cl.viewangles[PITCH] + pitch < -360)
|
||||
cl.viewangles[PITCH] += 360; // wrapped
|
||||
if (cl.viewangles[PITCH] + pitch > 360)
|
||||
cl.viewangles[PITCH] -= 360; // wrapped
|
||||
|
||||
if (cl.viewangles[PITCH] + pitch > 89)
|
||||
cl.viewangles[PITCH] = 89 - pitch;
|
||||
if (cl.viewangles[PITCH] + pitch < -89)
|
||||
cl.viewangles[PITCH] = -89 - pitch;
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
CL_FinishMove
|
||||
==============
|
||||
*/
|
||||
void CL_FinishMove (usercmd_t *cmd)
|
||||
{
|
||||
int ms;
|
||||
int i;
|
||||
|
||||
//
|
||||
// figure button bits
|
||||
//
|
||||
if ( in_attack.state & 3 )
|
||||
cmd->buttons |= BUTTON_ATTACK;
|
||||
in_attack.state &= ~2;
|
||||
|
||||
if (in_use.state & 3)
|
||||
cmd->buttons |= BUTTON_USE;
|
||||
in_use.state &= ~2;
|
||||
|
||||
if (anykeydown && cls.key_dest == key_game)
|
||||
cmd->buttons |= BUTTON_ANY;
|
||||
|
||||
// send milliseconds of time to apply the move
|
||||
ms = cls.frametime * 1000;
|
||||
if (ms > 250)
|
||||
ms = 100; // time was unreasonable
|
||||
cmd->msec = ms;
|
||||
|
||||
CL_ClampPitch ();
|
||||
for (i=0 ; i<3 ; i++)
|
||||
cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]);
|
||||
|
||||
cmd->impulse = in_impulse;
|
||||
in_impulse = 0;
|
||||
|
||||
// send the ambient light level at the player's current position
|
||||
cmd->lightlevel = (byte)cl_lightlevel->value;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CL_CreateCmd
|
||||
=================
|
||||
*/
|
||||
usercmd_t CL_CreateCmd (void)
|
||||
{
|
||||
usercmd_t cmd;
|
||||
|
||||
frame_msec = sys_frame_time - old_sys_frame_time;
|
||||
if (frame_msec < 1)
|
||||
frame_msec = 1;
|
||||
if (frame_msec > 200)
|
||||
frame_msec = 200;
|
||||
|
||||
// get basic movement from keyboard
|
||||
CL_BaseMove (&cmd);
|
||||
|
||||
// allow mice or other external controllers to add to the move
|
||||
IN_Move (&cmd);
|
||||
|
||||
CL_FinishMove (&cmd);
|
||||
|
||||
old_sys_frame_time = sys_frame_time;
|
||||
|
||||
//cmd.impulse = cls.framecount;
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
|
||||
void IN_CenterView (void)
|
||||
{
|
||||
cl.viewangles[PITCH] = -SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[PITCH]);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
CL_InitInput
|
||||
============
|
||||
*/
|
||||
void CL_InitInput (void)
|
||||
{
|
||||
Cmd_AddCommand ("centerview",IN_CenterView);
|
||||
|
||||
Cmd_AddCommand ("+moveup",IN_UpDown);
|
||||
Cmd_AddCommand ("-moveup",IN_UpUp);
|
||||
Cmd_AddCommand ("+movedown",IN_DownDown);
|
||||
Cmd_AddCommand ("-movedown",IN_DownUp);
|
||||
Cmd_AddCommand ("+left",IN_LeftDown);
|
||||
Cmd_AddCommand ("-left",IN_LeftUp);
|
||||
Cmd_AddCommand ("+right",IN_RightDown);
|
||||
Cmd_AddCommand ("-right",IN_RightUp);
|
||||
Cmd_AddCommand ("+forward",IN_ForwardDown);
|
||||
Cmd_AddCommand ("-forward",IN_ForwardUp);
|
||||
Cmd_AddCommand ("+back",IN_BackDown);
|
||||
Cmd_AddCommand ("-back",IN_BackUp);
|
||||
Cmd_AddCommand ("+lookup", IN_LookupDown);
|
||||
Cmd_AddCommand ("-lookup", IN_LookupUp);
|
||||
Cmd_AddCommand ("+lookdown", IN_LookdownDown);
|
||||
Cmd_AddCommand ("-lookdown", IN_LookdownUp);
|
||||
Cmd_AddCommand ("+strafe", IN_StrafeDown);
|
||||
Cmd_AddCommand ("-strafe", IN_StrafeUp);
|
||||
Cmd_AddCommand ("+moveleft", IN_MoveleftDown);
|
||||
Cmd_AddCommand ("-moveleft", IN_MoveleftUp);
|
||||
Cmd_AddCommand ("+moveright", IN_MoverightDown);
|
||||
Cmd_AddCommand ("-moveright", IN_MoverightUp);
|
||||
Cmd_AddCommand ("+speed", IN_SpeedDown);
|
||||
Cmd_AddCommand ("-speed", IN_SpeedUp);
|
||||
Cmd_AddCommand ("+attack", IN_AttackDown);
|
||||
Cmd_AddCommand ("-attack", IN_AttackUp);
|
||||
Cmd_AddCommand ("+use", IN_UseDown);
|
||||
Cmd_AddCommand ("-use", IN_UseUp);
|
||||
Cmd_AddCommand ("impulse", IN_Impulse);
|
||||
Cmd_AddCommand ("+klook", IN_KLookDown);
|
||||
Cmd_AddCommand ("-klook", IN_KLookUp);
|
||||
|
||||
cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
CL_SendCmd
|
||||
=================
|
||||
*/
|
||||
void CL_SendCmd (void)
|
||||
{
|
||||
sizebuf_t buf;
|
||||
byte data[128];
|
||||
int i;
|
||||
usercmd_t *cmd, *oldcmd;
|
||||
usercmd_t nullcmd;
|
||||
int checksumIndex;
|
||||
|
||||
// build a command even if not connected
|
||||
|
||||
// save this command off for prediction
|
||||
i = cls.netchan.outgoing_sequence & (CMD_BACKUP-1);
|
||||
cmd = &cl.cmds[i];
|
||||
cl.cmd_time[i] = cls.realtime; // for netgraph ping calculation
|
||||
|
||||
*cmd = CL_CreateCmd ();
|
||||
|
||||
cl.cmd = *cmd;
|
||||
|
||||
if (cls.state == ca_disconnected || cls.state == ca_connecting)
|
||||
return;
|
||||
|
||||
if ( cls.state == ca_connected)
|
||||
{
|
||||
if (cls.netchan.message.cursize || curtime - cls.netchan.last_sent > 1000 )
|
||||
Netchan_Transmit (&cls.netchan, 0, buf.data);
|
||||
return;
|
||||
}
|
||||
|
||||
// send a userinfo update if needed
|
||||
if (userinfo_modified)
|
||||
{
|
||||
CL_FixUpGender();
|
||||
userinfo_modified = false;
|
||||
MSG_WriteByte (&cls.netchan.message, clc_userinfo);
|
||||
MSG_WriteString (&cls.netchan.message, Cvar_Userinfo() );
|
||||
}
|
||||
|
||||
SZ_Init (&buf, data, sizeof(data));
|
||||
|
||||
if (cmd->buttons && cl.cinematictime > 0 && !cl.attractloop
|
||||
&& cls.realtime - cl.cinematictime > 1000)
|
||||
{ // skip the rest of the cinematic
|
||||
SCR_FinishCinematic ();
|
||||
}
|
||||
|
||||
// begin a client move command
|
||||
MSG_WriteByte (&buf, clc_move);
|
||||
|
||||
// save the position for a checksum byte
|
||||
checksumIndex = buf.cursize;
|
||||
MSG_WriteByte (&buf, 0);
|
||||
|
||||
// let the server know what the last frame we
|
||||
// got was, so the next message can be delta compressed
|
||||
if (cl_nodelta->value || !cl.frame.valid || cls.demowaiting)
|
||||
MSG_WriteLong (&buf, -1); // no compression
|
||||
else
|
||||
MSG_WriteLong (&buf, cl.frame.serverframe);
|
||||
|
||||
// send this and the previous cmds in the message, so
|
||||
// if the last packet was dropped, it can be recovered
|
||||
i = (cls.netchan.outgoing_sequence-2) & (CMD_BACKUP-1);
|
||||
cmd = &cl.cmds[i];
|
||||
memset (&nullcmd, 0, sizeof(nullcmd));
|
||||
MSG_WriteDeltaUsercmd (&buf, &nullcmd, cmd);
|
||||
oldcmd = cmd;
|
||||
|
||||
i = (cls.netchan.outgoing_sequence-1) & (CMD_BACKUP-1);
|
||||
cmd = &cl.cmds[i];
|
||||
MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd);
|
||||
oldcmd = cmd;
|
||||
|
||||
i = (cls.netchan.outgoing_sequence) & (CMD_BACKUP-1);
|
||||
cmd = &cl.cmds[i];
|
||||
MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd);
|
||||
|
||||
// calculate a checksum over the move commands
|
||||
buf.data[checksumIndex] = COM_BlockSequenceCRCByte(
|
||||
buf.data + checksumIndex + 1, buf.cursize - checksumIndex - 1,
|
||||
cls.netchan.outgoing_sequence);
|
||||
|
||||
//
|
||||
// deliver the message
|
||||
//
|
||||
Netchan_Transmit (&cls.netchan, buf.cursize, buf.data);
|
||||
}
|
||||
|
||||
|
142
client/cl_inv.c
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// cl_inv.c -- client inventory screen
|
||||
|
||||
#include "client.h"
|
||||
|
||||
/*
|
||||
================
|
||||
CL_ParseInventory
|
||||
================
|
||||
*/
|
||||
void CL_ParseInventory (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0 ; i<MAX_ITEMS ; i++)
|
||||
cl.inventory[i] = MSG_ReadShort (&net_message);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
Inv_DrawString
|
||||
================
|
||||
*/
|
||||
void Inv_DrawString (int x, int y, char *string)
|
||||
{
|
||||
while (*string)
|
||||
{
|
||||
re.DrawChar (x, y, *string);
|
||||
x+=8;
|
||||
string++;
|
||||
}
|
||||
}
|
||||
|
||||
void SetStringHighBit (char *s)
|
||||
{
|
||||
while (*s)
|
||||
*s++ |= 128;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
CL_DrawInventory
|
||||
================
|
||||
*/
|
||||
#define DISPLAY_ITEMS 17
|
||||
|
||||
void CL_DrawInventory (void)
|
||||
{
|
||||
int i, j;
|
||||
int num, selected_num, item;
|
||||
int index[MAX_ITEMS];
|
||||
char string[1024];
|
||||
int x, y;
|
||||
char binding[1024];
|
||||
char *bind;
|
||||
int selected;
|
||||
int top;
|
||||
|
||||
selected = cl.frame.playerstate.stats[STAT_SELECTED_ITEM];
|
||||
|
||||
num = 0;
|
||||
selected_num = 0;
|
||||
for (i=0 ; i<MAX_ITEMS ; i++)
|
||||
{
|
||||
if (i==selected)
|
||||
selected_num = num;
|
||||
if (cl.inventory[i])
|
||||
{
|
||||
index[num] = i;
|
||||
num++;
|
||||
}
|
||||
}
|
||||
|
||||
// determine scroll point
|
||||
top = selected_num - DISPLAY_ITEMS/2;
|
||||
if (num - top < DISPLAY_ITEMS)
|
||||
top = num - DISPLAY_ITEMS;
|
||||
if (top < 0)
|
||||
top = 0;
|
||||
|
||||
x = (viddef.width-256)/2;
|
||||
y = (viddef.height-240)/2;
|
||||
|
||||
// repaint everything next frame
|
||||
SCR_DirtyScreen ();
|
||||
|
||||
re.DrawPic (x, y+8, "inventory");
|
||||
|
||||
y += 24;
|
||||
x += 24;
|
||||
Inv_DrawString (x, y, "hotkey ### item");
|
||||
Inv_DrawString (x, y+8, "------ --- ----");
|
||||
y += 16;
|
||||
for (i=top ; i<num && i < top+DISPLAY_ITEMS ; i++)
|
||||
{
|
||||
item = index[i];
|
||||
// search for a binding
|
||||
Com_sprintf (binding, sizeof(binding), "use %s", cl.configstrings[CS_ITEMS+item]);
|
||||
bind = "";
|
||||
for (j=0 ; j<256 ; j++)
|
||||
if (keybindings[j] && !Q_stricmp (keybindings[j], binding))
|
||||
{
|
||||
bind = Key_KeynumToString(j);
|
||||
break;
|
||||
}
|
||||
|
||||
Com_sprintf (string, sizeof(string), "%6s %3i %s", bind, cl.inventory[item],
|
||||
cl.configstrings[CS_ITEMS+item] );
|
||||
if (item != selected)
|
||||
SetStringHighBit (string);
|
||||
else // draw a blinky cursor by the selected item
|
||||
{
|
||||
if ( (int)(cls.realtime*10) & 1)
|
||||
re.DrawChar (x-8, y, 15);
|
||||
}
|
||||
Inv_DrawString (x, y, string);
|
||||
y += 8;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
1844
client/cl_main.c
Normal file
1323
client/cl_newfx.c
Normal file
811
client/cl_parse.c
Normal file
|
@ -0,0 +1,811 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// cl_parse.c -- parse a message received from the server
|
||||
|
||||
#include "client.h"
|
||||
|
||||
char *svc_strings[256] =
|
||||
{
|
||||
"svc_bad",
|
||||
|
||||
"svc_muzzleflash",
|
||||
"svc_muzzlflash2",
|
||||
"svc_temp_entity",
|
||||
"svc_layout",
|
||||
"svc_inventory",
|
||||
|
||||
"svc_nop",
|
||||
"svc_disconnect",
|
||||
"svc_reconnect",
|
||||
"svc_sound",
|
||||
"svc_print",
|
||||
"svc_stufftext",
|
||||
"svc_serverdata",
|
||||
"svc_configstring",
|
||||
"svc_spawnbaseline",
|
||||
"svc_centerprint",
|
||||
"svc_download",
|
||||
"svc_playerinfo",
|
||||
"svc_packetentities",
|
||||
"svc_deltapacketentities",
|
||||
"svc_frame"
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
|
||||
void CL_DownloadFileName(char *dest, int destlen, char *fn)
|
||||
{
|
||||
if (strncmp(fn, "players", 7) == 0)
|
||||
Com_sprintf (dest, destlen, "%s/%s", BASEDIRNAME, fn);
|
||||
else
|
||||
Com_sprintf (dest, destlen, "%s/%s", FS_Gamedir(), fn);
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
CL_CheckOrDownloadFile
|
||||
|
||||
Returns true if the file exists, otherwise it attempts
|
||||
to start a download from the server.
|
||||
===============
|
||||
*/
|
||||
qboolean CL_CheckOrDownloadFile (char *filename)
|
||||
{
|
||||
FILE *fp;
|
||||
char name[MAX_OSPATH];
|
||||
|
||||
if (strstr (filename, ".."))
|
||||
{
|
||||
Com_Printf ("Refusing to download a path with ..\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (FS_LoadFile (filename, NULL) != -1)
|
||||
{ // it exists, no need to download
|
||||
return true;
|
||||
}
|
||||
|
||||
strcpy (cls.downloadname, filename);
|
||||
|
||||
// download to a temp name, and only rename
|
||||
// to the real name when done, so if interrupted
|
||||
// a runt file wont be left
|
||||
COM_StripExtension (cls.downloadname, cls.downloadtempname);
|
||||
strcat (cls.downloadtempname, ".tmp");
|
||||
|
||||
//ZOID
|
||||
// check to see if we already have a tmp for this file, if so, try to resume
|
||||
// open the file if not opened yet
|
||||
CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);
|
||||
|
||||
// FS_CreatePath (name);
|
||||
|
||||
fp = fopen (name, "r+b");
|
||||
if (fp) { // it exists
|
||||
int len;
|
||||
fseek(fp, 0, SEEK_END);
|
||||
len = ftell(fp);
|
||||
|
||||
cls.download = fp;
|
||||
|
||||
// give the server an offset to start the download
|
||||
Com_Printf ("Resuming %s\n", cls.downloadname);
|
||||
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
|
||||
MSG_WriteString (&cls.netchan.message,
|
||||
va("download %s %i", cls.downloadname, len));
|
||||
} else {
|
||||
Com_Printf ("Downloading %s\n", cls.downloadname);
|
||||
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
|
||||
MSG_WriteString (&cls.netchan.message,
|
||||
va("download %s", cls.downloadname));
|
||||
}
|
||||
|
||||
cls.downloadnumber++;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
CL_Download_f
|
||||
|
||||
Request a download from the server
|
||||
===============
|
||||
*/
|
||||
void CL_Download_f (void)
|
||||
{
|
||||
char filename[MAX_OSPATH];
|
||||
|
||||
if (Cmd_Argc() != 2) {
|
||||
Com_Printf("Usage: download <filename>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Com_sprintf(filename, sizeof(filename), "%s", Cmd_Argv(1));
|
||||
|
||||
if (strstr (filename, ".."))
|
||||
{
|
||||
Com_Printf ("Refusing to download a path with ..\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (FS_LoadFile (filename, NULL) != -1)
|
||||
{ // it exists, no need to download
|
||||
Com_Printf("File already exists.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
strcpy (cls.downloadname, filename);
|
||||
Com_Printf ("Downloading %s\n", cls.downloadname);
|
||||
|
||||
// download to a temp name, and only rename
|
||||
// to the real name when done, so if interrupted
|
||||
// a runt file wont be left
|
||||
COM_StripExtension (cls.downloadname, cls.downloadtempname);
|
||||
strcat (cls.downloadtempname, ".tmp");
|
||||
|
||||
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
|
||||
MSG_WriteString (&cls.netchan.message,
|
||||
va("download %s", cls.downloadname));
|
||||
|
||||
cls.downloadnumber++;
|
||||
}
|
||||
|
||||
/*
|
||||
======================
|
||||
CL_RegisterSounds
|
||||
======================
|
||||
*/
|
||||
void CL_RegisterSounds (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
S_BeginRegistration ();
|
||||
CL_RegisterTEntSounds ();
|
||||
for (i=1 ; i<MAX_SOUNDS ; i++)
|
||||
{
|
||||
if (!cl.configstrings[CS_SOUNDS+i][0])
|
||||
break;
|
||||
cl.sound_precache[i] = S_RegisterSound (cl.configstrings[CS_SOUNDS+i]);
|
||||
Sys_SendKeyEvents (); // pump message loop
|
||||
}
|
||||
S_EndRegistration ();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=====================
|
||||
CL_ParseDownload
|
||||
|
||||
A download message has been received from the server
|
||||
=====================
|
||||
*/
|
||||
void CL_ParseDownload (void)
|
||||
{
|
||||
int size, percent;
|
||||
char name[MAX_OSPATH];
|
||||
int r;
|
||||
|
||||
// read the data
|
||||
size = MSG_ReadShort (&net_message);
|
||||
percent = MSG_ReadByte (&net_message);
|
||||
if (size == -1)
|
||||
{
|
||||
Com_Printf ("Server does not have this file.\n");
|
||||
if (cls.download)
|
||||
{
|
||||
// if here, we tried to resume a file but the server said no
|
||||
fclose (cls.download);
|
||||
cls.download = NULL;
|
||||
}
|
||||
CL_RequestNextDownload ();
|
||||
return;
|
||||
}
|
||||
|
||||
// open the file if not opened yet
|
||||
if (!cls.download)
|
||||
{
|
||||
CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);
|
||||
|
||||
FS_CreatePath (name);
|
||||
|
||||
cls.download = fopen (name, "wb");
|
||||
if (!cls.download)
|
||||
{
|
||||
net_message.readcount += size;
|
||||
Com_Printf ("Failed to open %s\n", cls.downloadtempname);
|
||||
CL_RequestNextDownload ();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fwrite (net_message.data + net_message.readcount, 1, size, cls.download);
|
||||
net_message.readcount += size;
|
||||
|
||||
if (percent != 100)
|
||||
{
|
||||
// request next block
|
||||
// change display routines by zoid
|
||||
#if 0
|
||||
Com_Printf (".");
|
||||
if (10*(percent/10) != cls.downloadpercent)
|
||||
{
|
||||
cls.downloadpercent = 10*(percent/10);
|
||||
Com_Printf ("%i%%", cls.downloadpercent);
|
||||
}
|
||||
#endif
|
||||
cls.downloadpercent = percent;
|
||||
|
||||
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
|
||||
SZ_Print (&cls.netchan.message, "nextdl");
|
||||
}
|
||||
else
|
||||
{
|
||||
char oldn[MAX_OSPATH];
|
||||
char newn[MAX_OSPATH];
|
||||
|
||||
// Com_Printf ("100%%\n");
|
||||
|
||||
fclose (cls.download);
|
||||
|
||||
// rename the temp file to it's final name
|
||||
CL_DownloadFileName(oldn, sizeof(oldn), cls.downloadtempname);
|
||||
CL_DownloadFileName(newn, sizeof(newn), cls.downloadname);
|
||||
r = rename (oldn, newn);
|
||||
if (r)
|
||||
Com_Printf ("failed to rename.\n");
|
||||
|
||||
cls.download = NULL;
|
||||
cls.downloadpercent = 0;
|
||||
|
||||
// get another file if needed
|
||||
|
||||
CL_RequestNextDownload ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=====================================================================
|
||||
|
||||
SERVER CONNECTING MESSAGES
|
||||
|
||||
=====================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
==================
|
||||
CL_ParseServerData
|
||||
==================
|
||||
*/
|
||||
void CL_ParseServerData (void)
|
||||
{
|
||||
extern cvar_t *fs_gamedirvar;
|
||||
char *str;
|
||||
int i;
|
||||
|
||||
Com_DPrintf ("Serverdata packet received.\n");
|
||||
//
|
||||
// wipe the client_state_t struct
|
||||
//
|
||||
CL_ClearState ();
|
||||
cls.state = ca_connected;
|
||||
|
||||
// parse protocol version number
|
||||
i = MSG_ReadLong (&net_message);
|
||||
cls.serverProtocol = i;
|
||||
|
||||
// BIG HACK to let demos from release work with the 3.0x patch!!!
|
||||
if (Com_ServerState() && PROTOCOL_VERSION == 34)
|
||||
{
|
||||
}
|
||||
else if (i != PROTOCOL_VERSION)
|
||||
Com_Error (ERR_DROP,"Server returned version %i, not %i", i, PROTOCOL_VERSION);
|
||||
|
||||
cl.servercount = MSG_ReadLong (&net_message);
|
||||
cl.attractloop = MSG_ReadByte (&net_message);
|
||||
|
||||
// game directory
|
||||
str = MSG_ReadString (&net_message);
|
||||
strncpy (cl.gamedir, str, sizeof(cl.gamedir)-1);
|
||||
|
||||
// set gamedir
|
||||
if ((*str && (!fs_gamedirvar->string || !*fs_gamedirvar->string || strcmp(fs_gamedirvar->string, str))) || (!*str && (fs_gamedirvar->string || *fs_gamedirvar->string)))
|
||||
Cvar_Set("game", str);
|
||||
|
||||
// parse player entity number
|
||||
cl.playernum = MSG_ReadShort (&net_message);
|
||||
|
||||
// get the full level name
|
||||
str = MSG_ReadString (&net_message);
|
||||
|
||||
if (cl.playernum == -1)
|
||||
{ // playing a cinematic or showing a pic, not a level
|
||||
SCR_PlayCinematic (str);
|
||||
}
|
||||
else
|
||||
{
|
||||
// seperate the printfs so the server message can have a color
|
||||
Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
|
||||
Com_Printf ("%c%s\n", 2, str);
|
||||
|
||||
// need to prep refresh at next oportunity
|
||||
cl.refresh_prepped = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CL_ParseBaseline
|
||||
==================
|
||||
*/
|
||||
void CL_ParseBaseline (void)
|
||||
{
|
||||
entity_state_t *es;
|
||||
int bits;
|
||||
int newnum;
|
||||
entity_state_t nullstate;
|
||||
|
||||
memset (&nullstate, 0, sizeof(nullstate));
|
||||
|
||||
newnum = CL_ParseEntityBits (&bits);
|
||||
es = &cl_entities[newnum].baseline;
|
||||
CL_ParseDelta (&nullstate, es, newnum, bits);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
CL_LoadClientinfo
|
||||
|
||||
================
|
||||
*/
|
||||
void CL_LoadClientinfo (clientinfo_t *ci, char *s)
|
||||
{
|
||||
int i;
|
||||
char *t;
|
||||
char model_name[MAX_QPATH];
|
||||
char skin_name[MAX_QPATH];
|
||||
char model_filename[MAX_QPATH];
|
||||
char skin_filename[MAX_QPATH];
|
||||
char weapon_filename[MAX_QPATH];
|
||||
|
||||
strncpy(ci->cinfo, s, sizeof(ci->cinfo));
|
||||
ci->cinfo[sizeof(ci->cinfo)-1] = 0;
|
||||
|
||||
// isolate the player's name
|
||||
strncpy(ci->name, s, sizeof(ci->name));
|
||||
ci->name[sizeof(ci->name)-1] = 0;
|
||||
t = strstr (s, "\\");
|
||||
if (t)
|
||||
{
|
||||
ci->name[t-s] = 0;
|
||||
s = t+1;
|
||||
}
|
||||
|
||||
if (cl_noskins->value || *s == 0)
|
||||
{
|
||||
Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2");
|
||||
Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/male/weapon.md2");
|
||||
Com_sprintf (skin_filename, sizeof(skin_filename), "players/male/grunt.pcx");
|
||||
Com_sprintf (ci->iconname, sizeof(ci->iconname), "/players/male/grunt_i.pcx");
|
||||
ci->model = re.RegisterModel (model_filename);
|
||||
memset(ci->weaponmodel, 0, sizeof(ci->weaponmodel));
|
||||
ci->weaponmodel[0] = re.RegisterModel (weapon_filename);
|
||||
ci->skin = re.RegisterSkin (skin_filename);
|
||||
ci->icon = re.RegisterPic (ci->iconname);
|
||||
}
|
||||
else
|
||||
{
|
||||
// isolate the model name
|
||||
strcpy (model_name, s);
|
||||
t = strstr(model_name, "/");
|
||||
if (!t)
|
||||
t = strstr(model_name, "\\");
|
||||
if (!t)
|
||||
t = model_name;
|
||||
*t = 0;
|
||||
|
||||
// isolate the skin name
|
||||
strcpy (skin_name, s + strlen(model_name) + 1);
|
||||
|
||||
// model file
|
||||
Com_sprintf (model_filename, sizeof(model_filename), "players/%s/tris.md2", model_name);
|
||||
ci->model = re.RegisterModel (model_filename);
|
||||
if (!ci->model)
|
||||
{
|
||||
strcpy(model_name, "male");
|
||||
Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2");
|
||||
ci->model = re.RegisterModel (model_filename);
|
||||
}
|
||||
|
||||
// skin file
|
||||
Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", model_name, skin_name);
|
||||
ci->skin = re.RegisterSkin (skin_filename);
|
||||
|
||||
// if we don't have the skin and the model wasn't male,
|
||||
// see if the male has it (this is for CTF's skins)
|
||||
if (!ci->skin && Q_stricmp(model_name, "male"))
|
||||
{
|
||||
// change model to male
|
||||
strcpy(model_name, "male");
|
||||
Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2");
|
||||
ci->model = re.RegisterModel (model_filename);
|
||||
|
||||
// see if the skin exists for the male model
|
||||
Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", model_name, skin_name);
|
||||
ci->skin = re.RegisterSkin (skin_filename);
|
||||
}
|
||||
|
||||
// if we still don't have a skin, it means that the male model didn't have
|
||||
// it, so default to grunt
|
||||
if (!ci->skin) {
|
||||
// see if the skin exists for the male model
|
||||
Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/grunt.pcx", model_name, skin_name);
|
||||
ci->skin = re.RegisterSkin (skin_filename);
|
||||
}
|
||||
|
||||
// weapon file
|
||||
for (i = 0; i < num_cl_weaponmodels; i++) {
|
||||
Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/%s/%s", model_name, cl_weaponmodels[i]);
|
||||
ci->weaponmodel[i] = re.RegisterModel(weapon_filename);
|
||||
if (!ci->weaponmodel[i] && strcmp(model_name, "cyborg") == 0) {
|
||||
// try male
|
||||
Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/male/%s", cl_weaponmodels[i]);
|
||||
ci->weaponmodel[i] = re.RegisterModel(weapon_filename);
|
||||
}
|
||||
if (!cl_vwep->value)
|
||||
break; // only one when vwep is off
|
||||
}
|
||||
|
||||
// icon file
|
||||
Com_sprintf (ci->iconname, sizeof(ci->iconname), "/players/%s/%s_i.pcx", model_name, skin_name);
|
||||
ci->icon = re.RegisterPic (ci->iconname);
|
||||
}
|
||||
|
||||
// must have loaded all data types to be valud
|
||||
if (!ci->skin || !ci->icon || !ci->model || !ci->weaponmodel[0])
|
||||
{
|
||||
ci->skin = NULL;
|
||||
ci->icon = NULL;
|
||||
ci->model = NULL;
|
||||
ci->weaponmodel[0] = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
CL_ParseClientinfo
|
||||
|
||||
Load the skin, icon, and model for a client
|
||||
================
|
||||
*/
|
||||
void CL_ParseClientinfo (int player)
|
||||
{
|
||||
char *s;
|
||||
clientinfo_t *ci;
|
||||
|
||||
s = cl.configstrings[player+CS_PLAYERSKINS];
|
||||
|
||||
ci = &cl.clientinfo[player];
|
||||
|
||||
CL_LoadClientinfo (ci, s);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
CL_ParseConfigString
|
||||
================
|
||||
*/
|
||||
void CL_ParseConfigString (void)
|
||||
{
|
||||
int i;
|
||||
char *s;
|
||||
char olds[MAX_QPATH];
|
||||
|
||||
i = MSG_ReadShort (&net_message);
|
||||
if (i < 0 || i >= MAX_CONFIGSTRINGS)
|
||||
Com_Error (ERR_DROP, "configstring > MAX_CONFIGSTRINGS");
|
||||
s = MSG_ReadString(&net_message);
|
||||
|
||||
strncpy (olds, cl.configstrings[i], sizeof(olds));
|
||||
olds[sizeof(olds) - 1] = 0;
|
||||
|
||||
strcpy (cl.configstrings[i], s);
|
||||
|
||||
// do something apropriate
|
||||
|
||||
if (i >= CS_LIGHTS && i < CS_LIGHTS+MAX_LIGHTSTYLES)
|
||||
CL_SetLightstyle (i - CS_LIGHTS);
|
||||
else if (i == CS_CDTRACK)
|
||||
{
|
||||
if (cl.refresh_prepped)
|
||||
CDAudio_Play (atoi(cl.configstrings[CS_CDTRACK]), true);
|
||||
}
|
||||
else if (i >= CS_MODELS && i < CS_MODELS+MAX_MODELS)
|
||||
{
|
||||
if (cl.refresh_prepped)
|
||||
{
|
||||
cl.model_draw[i-CS_MODELS] = re.RegisterModel (cl.configstrings[i]);
|
||||
if (cl.configstrings[i][0] == '*')
|
||||
cl.model_clip[i-CS_MODELS] = CM_InlineModel (cl.configstrings[i]);
|
||||
else
|
||||
cl.model_clip[i-CS_MODELS] = NULL;
|
||||
}
|
||||
}
|
||||
else if (i >= CS_SOUNDS && i < CS_SOUNDS+MAX_MODELS)
|
||||
{
|
||||
if (cl.refresh_prepped)
|
||||
cl.sound_precache[i-CS_SOUNDS] = S_RegisterSound (cl.configstrings[i]);
|
||||
}
|
||||
else if (i >= CS_IMAGES && i < CS_IMAGES+MAX_MODELS)
|
||||
{
|
||||
if (cl.refresh_prepped)
|
||||
cl.image_precache[i-CS_IMAGES] = re.RegisterPic (cl.configstrings[i]);
|
||||
}
|
||||
else if (i >= CS_PLAYERSKINS && i < CS_PLAYERSKINS+MAX_CLIENTS)
|
||||
{
|
||||
if (cl.refresh_prepped && strcmp(olds, s))
|
||||
CL_ParseClientinfo (i-CS_PLAYERSKINS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=====================================================================
|
||||
|
||||
ACTION MESSAGES
|
||||
|
||||
=====================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
==================
|
||||
CL_ParseStartSoundPacket
|
||||
==================
|
||||
*/
|
||||
void CL_ParseStartSoundPacket(void)
|
||||
{
|
||||
vec3_t pos_v;
|
||||
float *pos;
|
||||
int channel, ent;
|
||||
int sound_num;
|
||||
float volume;
|
||||
float attenuation;
|
||||
int flags;
|
||||
float ofs;
|
||||
|
||||
flags = MSG_ReadByte (&net_message);
|
||||
sound_num = MSG_ReadByte (&net_message);
|
||||
|
||||
if (flags & SND_VOLUME)
|
||||
volume = MSG_ReadByte (&net_message) / 255.0;
|
||||
else
|
||||
volume = DEFAULT_SOUND_PACKET_VOLUME;
|
||||
|
||||
if (flags & SND_ATTENUATION)
|
||||
attenuation = MSG_ReadByte (&net_message) / 64.0;
|
||||
else
|
||||
attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;
|
||||
|
||||
if (flags & SND_OFFSET)
|
||||
ofs = MSG_ReadByte (&net_message) / 1000.0;
|
||||
else
|
||||
ofs = 0;
|
||||
|
||||
if (flags & SND_ENT)
|
||||
{ // entity reletive
|
||||
channel = MSG_ReadShort(&net_message);
|
||||
ent = channel>>3;
|
||||
if (ent > MAX_EDICTS)
|
||||
Com_Error (ERR_DROP,"CL_ParseStartSoundPacket: ent = %i", ent);
|
||||
|
||||
channel &= 7;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent = 0;
|
||||
channel = 0;
|
||||
}
|
||||
|
||||
if (flags & SND_POS)
|
||||
{ // positioned in space
|
||||
MSG_ReadPos (&net_message, pos_v);
|
||||
|
||||
pos = pos_v;
|
||||
}
|
||||
else // use entity number
|
||||
pos = NULL;
|
||||
|
||||
if (!cl.sound_precache[sound_num])
|
||||
return;
|
||||
|
||||
S_StartSound (pos, ent, channel, cl.sound_precache[sound_num], volume, attenuation, ofs);
|
||||
}
|
||||
|
||||
|
||||
void SHOWNET(char *s)
|
||||
{
|
||||
if (cl_shownet->value>=2)
|
||||
Com_Printf ("%3i:%s\n", net_message.readcount-1, s);
|
||||
}
|
||||
|
||||
/*
|
||||
=====================
|
||||
CL_ParseServerMessage
|
||||
=====================
|
||||
*/
|
||||
void CL_ParseServerMessage (void)
|
||||
{
|
||||
int cmd;
|
||||
char *s;
|
||||
int i;
|
||||
|
||||
//
|
||||
// if recording demos, copy the message out
|
||||
//
|
||||
if (cl_shownet->value == 1)
|
||||
Com_Printf ("%i ",net_message.cursize);
|
||||
else if (cl_shownet->value >= 2)
|
||||
Com_Printf ("------------------\n");
|
||||
|
||||
|
||||
//
|
||||
// parse the message
|
||||
//
|
||||
while (1)
|
||||
{
|
||||
if (net_message.readcount > net_message.cursize)
|
||||
{
|
||||
Com_Error (ERR_DROP,"CL_ParseServerMessage: Bad server message");
|
||||
break;
|
||||
}
|
||||
|
||||
cmd = MSG_ReadByte (&net_message);
|
||||
|
||||
if (cmd == -1)
|
||||
{
|
||||
SHOWNET("END OF MESSAGE");
|
||||
break;
|
||||
}
|
||||
|
||||
if (cl_shownet->value>=2)
|
||||
{
|
||||
if (!svc_strings[cmd])
|
||||
Com_Printf ("%3i:BAD CMD %i\n", net_message.readcount-1,cmd);
|
||||
else
|
||||
SHOWNET(svc_strings[cmd]);
|
||||
}
|
||||
|
||||
// other commands
|
||||
switch (cmd)
|
||||
{
|
||||
default:
|
||||
Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n");
|
||||
break;
|
||||
|
||||
case svc_nop:
|
||||
// Com_Printf ("svc_nop\n");
|
||||
break;
|
||||
|
||||
case svc_disconnect:
|
||||
Com_Error (ERR_DISCONNECT,"Server disconnected\n");
|
||||
break;
|
||||
|
||||
case svc_reconnect:
|
||||
Com_Printf ("Server disconnected, reconnecting\n");
|
||||
if (cls.download) {
|
||||
//ZOID, close download
|
||||
fclose (cls.download);
|
||||
cls.download = NULL;
|
||||
}
|
||||
cls.state = ca_connecting;
|
||||
cls.connect_time = -99999; // CL_CheckForResend() will fire immediately
|
||||
break;
|
||||
|
||||
case svc_print:
|
||||
i = MSG_ReadByte (&net_message);
|
||||
if (i == PRINT_CHAT)
|
||||
{
|
||||
S_StartLocalSound ("misc/talk.wav");
|
||||
con.ormask = 128;
|
||||
}
|
||||
Com_Printf ("%s", MSG_ReadString (&net_message));
|
||||
con.ormask = 0;
|
||||
break;
|
||||
|
||||
case svc_centerprint:
|
||||
SCR_CenterPrint (MSG_ReadString (&net_message));
|
||||
break;
|
||||
|
||||
case svc_stufftext:
|
||||
s = MSG_ReadString (&net_message);
|
||||
Com_DPrintf ("stufftext: %s\n", s);
|
||||
Cbuf_AddText (s);
|
||||
break;
|
||||
|
||||
case svc_serverdata:
|
||||
Cbuf_Execute (); // make sure any stuffed commands are done
|
||||
CL_ParseServerData ();
|
||||
break;
|
||||
|
||||
case svc_configstring:
|
||||
CL_ParseConfigString ();
|
||||
break;
|
||||
|
||||
case svc_sound:
|
||||
CL_ParseStartSoundPacket();
|
||||
break;
|
||||
|
||||
case svc_spawnbaseline:
|
||||
CL_ParseBaseline ();
|
||||
break;
|
||||
|
||||
case svc_temp_entity:
|
||||
CL_ParseTEnt ();
|
||||
break;
|
||||
|
||||
case svc_muzzleflash:
|
||||
CL_ParseMuzzleFlash ();
|
||||
break;
|
||||
|
||||
case svc_muzzleflash2:
|
||||
CL_ParseMuzzleFlash2 ();
|
||||
break;
|
||||
|
||||
case svc_download:
|
||||
CL_ParseDownload ();
|
||||
break;
|
||||
|
||||
case svc_frame:
|
||||
CL_ParseFrame ();
|
||||
break;
|
||||
|
||||
case svc_inventory:
|
||||
CL_ParseInventory ();
|
||||
break;
|
||||
|
||||
case svc_layout:
|
||||
s = MSG_ReadString (&net_message);
|
||||
strncpy (cl.layout, s, sizeof(cl.layout)-1);
|
||||
break;
|
||||
|
||||
case svc_playerinfo:
|
||||
case svc_packetentities:
|
||||
case svc_deltapacketentities:
|
||||
Com_Error (ERR_DROP, "Out of place frame data");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CL_AddNetgraph ();
|
||||
|
||||
//
|
||||
// we don't know if it is ok to save a demo message until
|
||||
// after we have parsed the frame
|
||||
//
|
||||
if (cls.demorecording && !cls.demowaiting)
|
||||
CL_WriteDemoMessage ();
|
||||
|
||||
}
|
||||
|
||||
|
278
client/cl_pred.c
Normal file
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
#include "client.h"
|
||||
|
||||
|
||||
/*
|
||||
===================
|
||||
CL_CheckPredictionError
|
||||
===================
|
||||
*/
|
||||
void CL_CheckPredictionError (void)
|
||||
{
|
||||
int frame;
|
||||
int delta[3];
|
||||
int i;
|
||||
int len;
|
||||
|
||||
if (!cl_predict->value || (cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION))
|
||||
return;
|
||||
|
||||
// calculate the last usercmd_t we sent that the server has processed
|
||||
frame = cls.netchan.incoming_acknowledged;
|
||||
frame &= (CMD_BACKUP-1);
|
||||
|
||||
// compare what the server returned with what we had predicted it to be
|
||||
VectorSubtract (cl.frame.playerstate.pmove.origin, cl.predicted_origins[frame], delta);
|
||||
|
||||
// save the prediction error for interpolation
|
||||
len = abs(delta[0]) + abs(delta[1]) + abs(delta[2]);
|
||||
if (len > 640) // 80 world units
|
||||
{ // a teleport or something
|
||||
VectorClear (cl.prediction_error);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cl_showmiss->value && (delta[0] || delta[1] || delta[2]) )
|
||||
Com_Printf ("prediction miss on %i: %i\n", cl.frame.serverframe,
|
||||
delta[0] + delta[1] + delta[2]);
|
||||
|
||||
VectorCopy (cl.frame.playerstate.pmove.origin, cl.predicted_origins[frame]);
|
||||
|
||||
// save for error itnerpolation
|
||||
for (i=0 ; i<3 ; i++)
|
||||
cl.prediction_error[i] = delta[i]*0.125;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
====================
|
||||
CL_ClipMoveToEntities
|
||||
|
||||
====================
|
||||
*/
|
||||
void CL_ClipMoveToEntities ( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, trace_t *tr )
|
||||
{
|
||||
int i, x, zd, zu;
|
||||
trace_t trace;
|
||||
int headnode;
|
||||
float *angles;
|
||||
entity_state_t *ent;
|
||||
int num;
|
||||
cmodel_t *cmodel;
|
||||
vec3_t bmins, bmaxs;
|
||||
|
||||
for (i=0 ; i<cl.frame.num_entities ; i++)
|
||||
{
|
||||
num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
|
||||
ent = &cl_parse_entities[num];
|
||||
|
||||
if (!ent->solid)
|
||||
continue;
|
||||
|
||||
if (ent->number == cl.playernum+1)
|
||||
continue;
|
||||
|
||||
if (ent->solid == 31)
|
||||
{ // special value for bmodel
|
||||
cmodel = cl.model_clip[ent->modelindex];
|
||||
if (!cmodel)
|
||||
continue;
|
||||
headnode = cmodel->headnode;
|
||||
angles = ent->angles;
|
||||
}
|
||||
else
|
||||
{ // encoded bbox
|
||||
x = 8*(ent->solid & 31);
|
||||
zd = 8*((ent->solid>>5) & 31);
|
||||
zu = 8*((ent->solid>>10) & 63) - 32;
|
||||
|
||||
bmins[0] = bmins[1] = -x;
|
||||
bmaxs[0] = bmaxs[1] = x;
|
||||
bmins[2] = -zd;
|
||||
bmaxs[2] = zu;
|
||||
|
||||
headnode = CM_HeadnodeForBox (bmins, bmaxs);
|
||||
angles = vec3_origin; // boxes don't rotate
|
||||
}
|
||||
|
||||
if (tr->allsolid)
|
||||
return;
|
||||
|
||||
trace = CM_TransformedBoxTrace (start, end,
|
||||
mins, maxs, headnode, MASK_PLAYERSOLID,
|
||||
ent->origin, angles);
|
||||
|
||||
if (trace.allsolid || trace.startsolid ||
|
||||
trace.fraction < tr->fraction)
|
||||
{
|
||||
trace.ent = (struct edict_s *)ent;
|
||||
if (tr->startsolid)
|
||||
{
|
||||
*tr = trace;
|
||||
tr->startsolid = true;
|
||||
}
|
||||
else
|
||||
*tr = trace;
|
||||
}
|
||||
else if (trace.startsolid)
|
||||
tr->startsolid = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
CL_PMTrace
|
||||
================
|
||||
*/
|
||||
trace_t CL_PMTrace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
|
||||
{
|
||||
trace_t t;
|
||||
|
||||
// check against world
|
||||
t = CM_BoxTrace (start, end, mins, maxs, 0, MASK_PLAYERSOLID);
|
||||
if (t.fraction < 1.0)
|
||||
t.ent = (struct edict_s *)1;
|
||||
|
||||
// check all other solid models
|
||||
CL_ClipMoveToEntities (start, mins, maxs, end, &t);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
int CL_PMpointcontents (vec3_t point)
|
||||
{
|
||||
int i;
|
||||
entity_state_t *ent;
|
||||
int num;
|
||||
cmodel_t *cmodel;
|
||||
int contents;
|
||||
|
||||
contents = CM_PointContents (point, 0);
|
||||
|
||||
for (i=0 ; i<cl.frame.num_entities ; i++)
|
||||
{
|
||||
num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
|
||||
ent = &cl_parse_entities[num];
|
||||
|
||||
if (ent->solid != 31) // special value for bmodel
|
||||
continue;
|
||||
|
||||
cmodel = cl.model_clip[ent->modelindex];
|
||||
if (!cmodel)
|
||||
continue;
|
||||
|
||||
contents |= CM_TransformedPointContents (point, cmodel->headnode, ent->origin, ent->angles);
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
CL_PredictMovement
|
||||
|
||||
Sets cl.predicted_origin and cl.predicted_angles
|
||||
=================
|
||||
*/
|
||||
void CL_PredictMovement (void)
|
||||
{
|
||||
int ack, current;
|
||||
int frame;
|
||||
int oldframe;
|
||||
usercmd_t *cmd;
|
||||
pmove_t pm;
|
||||
int i;
|
||||
int step;
|
||||
int oldz;
|
||||
|
||||
if (cls.state != ca_active)
|
||||
return;
|
||||
|
||||
if (cl_paused->value)
|
||||
return;
|
||||
|
||||
if (!cl_predict->value || (cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION))
|
||||
{ // just set angles
|
||||
for (i=0 ; i<3 ; i++)
|
||||
{
|
||||
cl.predicted_angles[i] = cl.viewangles[i] + SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ack = cls.netchan.incoming_acknowledged;
|
||||
current = cls.netchan.outgoing_sequence;
|
||||
|
||||
// if we are too far out of date, just freeze
|
||||
if (current - ack >= CMD_BACKUP)
|
||||
{
|
||||
if (cl_showmiss->value)
|
||||
Com_Printf ("exceeded CMD_BACKUP\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// copy current state to pmove
|
||||
memset (&pm, 0, sizeof(pm));
|
||||
pm.trace = CL_PMTrace;
|
||||
pm.pointcontents = CL_PMpointcontents;
|
||||
|
||||
pm_airaccelerate = atof(cl.configstrings[CS_AIRACCEL]);
|
||||
|
||||
pm.s = cl.frame.playerstate.pmove;
|
||||
|
||||
// SCR_DebugGraph (current - ack - 1, 0);
|
||||
|
||||
frame = 0;
|
||||
|
||||
// run frames
|
||||
while (++ack < current)
|
||||
{
|
||||
frame = ack & (CMD_BACKUP-1);
|
||||
cmd = &cl.cmds[frame];
|
||||
|
||||
pm.cmd = *cmd;
|
||||
Pmove (&pm);
|
||||
|
||||
// save for debug checking
|
||||
VectorCopy (pm.s.origin, cl.predicted_origins[frame]);
|
||||
}
|
||||
|
||||
oldframe = (ack-2) & (CMD_BACKUP-1);
|
||||
oldz = cl.predicted_origins[oldframe][2];
|
||||
step = pm.s.origin[2] - oldz;
|
||||
if (step > 63 && step < 160 && (pm.s.pm_flags & PMF_ON_GROUND) )
|
||||
{
|
||||
cl.predicted_step = step * 0.125;
|
||||
cl.predicted_step_time = cls.realtime - cls.frametime * 500;
|
||||
}
|
||||
|
||||
|
||||
// copy results out for rendering
|
||||
cl.predicted_origin[0] = pm.s.origin[0]*0.125;
|
||||
cl.predicted_origin[1] = pm.s.origin[1]*0.125;
|
||||
cl.predicted_origin[2] = pm.s.origin[2]*0.125;
|
||||
|
||||
VectorCopy (pm.viewangles, cl.predicted_angles);
|
||||
}
|
1401
client/cl_scrn.c
Normal file
1745
client/cl_tent.c
Normal file
584
client/cl_view.c
Normal file
|
@ -0,0 +1,584 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// cl_view.c -- player rendering positioning
|
||||
|
||||
#include "client.h"
|
||||
|
||||
//=============
|
||||
//
|
||||
// development tools for weapons
|
||||
//
|
||||
int gun_frame;
|
||||
struct model_s *gun_model;
|
||||
|
||||
//=============
|
||||
|
||||
cvar_t *crosshair;
|
||||
cvar_t *cl_testparticles;
|
||||
cvar_t *cl_testentities;
|
||||
cvar_t *cl_testlights;
|
||||
cvar_t *cl_testblend;
|
||||
|
||||
cvar_t *cl_stats;
|
||||
|
||||
|
||||
int r_numdlights;
|
||||
dlight_t r_dlights[MAX_DLIGHTS];
|
||||
|
||||
int r_numentities;
|
||||
entity_t r_entities[MAX_ENTITIES];
|
||||
|
||||
int r_numparticles;
|
||||
particle_t r_particles[MAX_PARTICLES];
|
||||
|
||||
lightstyle_t r_lightstyles[MAX_LIGHTSTYLES];
|
||||
|
||||
char cl_weaponmodels[MAX_CLIENTWEAPONMODELS][MAX_QPATH];
|
||||
int num_cl_weaponmodels;
|
||||
|
||||
/*
|
||||
====================
|
||||
V_ClearScene
|
||||
|
||||
Specifies the model that will be used as the world
|
||||
====================
|
||||
*/
|
||||
void V_ClearScene (void)
|
||||
{
|
||||
r_numdlights = 0;
|
||||
r_numentities = 0;
|
||||
r_numparticles = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=====================
|
||||
V_AddEntity
|
||||
|
||||
=====================
|
||||
*/
|
||||
void V_AddEntity (entity_t *ent)
|
||||
{
|
||||
if (r_numentities >= MAX_ENTITIES)
|
||||
return;
|
||||
r_entities[r_numentities++] = *ent;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=====================
|
||||
V_AddParticle
|
||||
|
||||
=====================
|
||||
*/
|
||||
void V_AddParticle (vec3_t org, int color, float alpha)
|
||||
{
|
||||
particle_t *p;
|
||||
|
||||
if (r_numparticles >= MAX_PARTICLES)
|
||||
return;
|
||||
p = &r_particles[r_numparticles++];
|
||||
VectorCopy (org, p->origin);
|
||||
p->color = color;
|
||||
p->alpha = alpha;
|
||||
}
|
||||
|
||||
/*
|
||||
=====================
|
||||
V_AddLight
|
||||
|
||||
=====================
|
||||
*/
|
||||
void V_AddLight (vec3_t org, float intensity, float r, float g, float b)
|
||||
{
|
||||
dlight_t *dl;
|
||||
|
||||
if (r_numdlights >= MAX_DLIGHTS)
|
||||
return;
|
||||
dl = &r_dlights[r_numdlights++];
|
||||
VectorCopy (org, dl->origin);
|
||||
dl->intensity = intensity;
|
||||
dl->color[0] = r;
|
||||
dl->color[1] = g;
|
||||
dl->color[2] = b;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=====================
|
||||
V_AddLightStyle
|
||||
|
||||
=====================
|
||||
*/
|
||||
void V_AddLightStyle (int style, float r, float g, float b)
|
||||
{
|
||||
lightstyle_t *ls;
|
||||
|
||||
if (style < 0 || style > MAX_LIGHTSTYLES)
|
||||
Com_Error (ERR_DROP, "Bad light style %i", style);
|
||||
ls = &r_lightstyles[style];
|
||||
|
||||
ls->white = r+g+b;
|
||||
ls->rgb[0] = r;
|
||||
ls->rgb[1] = g;
|
||||
ls->rgb[2] = b;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
V_TestParticles
|
||||
|
||||
If cl_testparticles is set, create 4096 particles in the view
|
||||
================
|
||||
*/
|
||||
void V_TestParticles (void)
|
||||
{
|
||||
particle_t *p;
|
||||
int i, j;
|
||||
float d, r, u;
|
||||
|
||||
r_numparticles = MAX_PARTICLES;
|
||||
for (i=0 ; i<r_numparticles ; i++)
|
||||
{
|
||||
d = i*0.25;
|
||||
r = 4*((i&7)-3.5);
|
||||
u = 4*(((i>>3)&7)-3.5);
|
||||
p = &r_particles[i];
|
||||
|
||||
for (j=0 ; j<3 ; j++)
|
||||
p->origin[j] = cl.refdef.vieworg[j] + cl.v_forward[j]*d +
|
||||
cl.v_right[j]*r + cl.v_up[j]*u;
|
||||
|
||||
p->color = 8;
|
||||
p->alpha = cl_testparticles->value;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
V_TestEntities
|
||||
|
||||
If cl_testentities is set, create 32 player models
|
||||
================
|
||||
*/
|
||||
void V_TestEntities (void)
|
||||
{
|
||||
int i, j;
|
||||
float f, r;
|
||||
entity_t *ent;
|
||||
|
||||
r_numentities = 32;
|
||||
memset (r_entities, 0, sizeof(r_entities));
|
||||
|
||||
for (i=0 ; i<r_numentities ; i++)
|
||||
{
|
||||
ent = &r_entities[i];
|
||||
|
||||
r = 64 * ( (i%4) - 1.5 );
|
||||
f = 64 * (i/4) + 128;
|
||||
|
||||
for (j=0 ; j<3 ; j++)
|
||||
ent->origin[j] = cl.refdef.vieworg[j] + cl.v_forward[j]*f +
|
||||
cl.v_right[j]*r;
|
||||
|
||||
ent->model = cl.baseclientinfo.model;
|
||||
ent->skin = cl.baseclientinfo.skin;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
V_TestLights
|
||||
|
||||
If cl_testlights is set, create 32 lights models
|
||||
================
|
||||
*/
|
||||
void V_TestLights (void)
|
||||
{
|
||||
int i, j;
|
||||
float f, r;
|
||||
dlight_t *dl;
|
||||
|
||||
r_numdlights = 32;
|
||||
memset (r_dlights, 0, sizeof(r_dlights));
|
||||
|
||||
for (i=0 ; i<r_numdlights ; i++)
|
||||
{
|
||||
dl = &r_dlights[i];
|
||||
|
||||
r = 64 * ( (i%4) - 1.5 );
|
||||
f = 64 * (i/4) + 128;
|
||||
|
||||
for (j=0 ; j<3 ; j++)
|
||||
dl->origin[j] = cl.refdef.vieworg[j] + cl.v_forward[j]*f +
|
||||
cl.v_right[j]*r;
|
||||
dl->color[0] = ((i%6)+1) & 1;
|
||||
dl->color[1] = (((i%6)+1) & 2)>>1;
|
||||
dl->color[2] = (((i%6)+1) & 4)>>2;
|
||||
dl->intensity = 200;
|
||||
}
|
||||
}
|
||||
|
||||
//===================================================================
|
||||
|
||||
/*
|
||||
=================
|
||||
CL_PrepRefresh
|
||||
|
||||
Call before entering a new level, or after changing dlls
|
||||
=================
|
||||
*/
|
||||
void CL_PrepRefresh (void)
|
||||
{
|
||||
char mapname[32];
|
||||
int i;
|
||||
char name[MAX_QPATH];
|
||||
float rotate;
|
||||
vec3_t axis;
|
||||
|
||||
if (!cl.configstrings[CS_MODELS+1][0])
|
||||
return; // no map loaded
|
||||
|
||||
SCR_AddDirtyPoint (0, 0);
|
||||
SCR_AddDirtyPoint (viddef.width-1, viddef.height-1);
|
||||
|
||||
// let the render dll load the map
|
||||
strcpy (mapname, cl.configstrings[CS_MODELS+1] + 5); // skip "maps/"
|
||||
mapname[strlen(mapname)-4] = 0; // cut off ".bsp"
|
||||
|
||||
// register models, pics, and skins
|
||||
Com_Printf ("Map: %s\r", mapname);
|
||||
SCR_UpdateScreen ();
|
||||
re.BeginRegistration (mapname);
|
||||
Com_Printf (" \r");
|
||||
|
||||
// precache status bar pics
|
||||
Com_Printf ("pics\r");
|
||||
SCR_UpdateScreen ();
|
||||
SCR_TouchPics ();
|
||||
Com_Printf (" \r");
|
||||
|
||||
CL_RegisterTEntModels ();
|
||||
|
||||
num_cl_weaponmodels = 1;
|
||||
strcpy(cl_weaponmodels[0], "weapon.md2");
|
||||
|
||||
for (i=1 ; i<MAX_MODELS && cl.configstrings[CS_MODELS+i][0] ; i++)
|
||||
{
|
||||
strcpy (name, cl.configstrings[CS_MODELS+i]);
|
||||
name[37] = 0; // never go beyond one line
|
||||
if (name[0] != '*')
|
||||
Com_Printf ("%s\r", name);
|
||||
SCR_UpdateScreen ();
|
||||
Sys_SendKeyEvents (); // pump message loop
|
||||
if (name[0] == '#')
|
||||
{
|
||||
// special player weapon model
|
||||
if (num_cl_weaponmodels < MAX_CLIENTWEAPONMODELS)
|
||||
{
|
||||
strncpy(cl_weaponmodels[num_cl_weaponmodels], cl.configstrings[CS_MODELS+i]+1,
|
||||
sizeof(cl_weaponmodels[num_cl_weaponmodels]) - 1);
|
||||
num_cl_weaponmodels++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cl.model_draw[i] = re.RegisterModel (cl.configstrings[CS_MODELS+i]);
|
||||
if (name[0] == '*')
|
||||
cl.model_clip[i] = CM_InlineModel (cl.configstrings[CS_MODELS+i]);
|
||||
else
|
||||
cl.model_clip[i] = NULL;
|
||||
}
|
||||
if (name[0] != '*')
|
||||
Com_Printf (" \r");
|
||||
}
|
||||
|
||||
Com_Printf ("images\r", i);
|
||||
SCR_UpdateScreen ();
|
||||
for (i=1 ; i<MAX_IMAGES && cl.configstrings[CS_IMAGES+i][0] ; i++)
|
||||
{
|
||||
cl.image_precache[i] = re.RegisterPic (cl.configstrings[CS_IMAGES+i]);
|
||||
Sys_SendKeyEvents (); // pump message loop
|
||||
}
|
||||
|
||||
Com_Printf (" \r");
|
||||
for (i=0 ; i<MAX_CLIENTS ; i++)
|
||||
{
|
||||
if (!cl.configstrings[CS_PLAYERSKINS+i][0])
|
||||
continue;
|
||||
Com_Printf ("client %i\r", i);
|
||||
SCR_UpdateScreen ();
|
||||
Sys_SendKeyEvents (); // pump message loop
|
||||
CL_ParseClientinfo (i);
|
||||
Com_Printf (" \r");
|
||||
}
|
||||
|
||||
CL_LoadClientinfo (&cl.baseclientinfo, "unnamed\\male/grunt");
|
||||
|
||||
// set sky textures and speed
|
||||
Com_Printf ("sky\r", i);
|
||||
SCR_UpdateScreen ();
|
||||
rotate = atof (cl.configstrings[CS_SKYROTATE]);
|
||||
sscanf (cl.configstrings[CS_SKYAXIS], "%f %f %f",
|
||||
&axis[0], &axis[1], &axis[2]);
|
||||
re.SetSky (cl.configstrings[CS_SKY], rotate, axis);
|
||||
Com_Printf (" \r");
|
||||
|
||||
// the renderer can now free unneeded stuff
|
||||
re.EndRegistration ();
|
||||
|
||||
// clear any lines of console text
|
||||
Con_ClearNotify ();
|
||||
|
||||
SCR_UpdateScreen ();
|
||||
cl.refresh_prepped = true;
|
||||
cl.force_refdef = true; // make sure we have a valid refdef
|
||||
|
||||
// start the cd track
|
||||
CDAudio_Play (atoi(cl.configstrings[CS_CDTRACK]), true);
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
CalcFov
|
||||
====================
|
||||
*/
|
||||
float CalcFov (float fov_x, float width, float height)
|
||||
{
|
||||
float a;
|
||||
float x;
|
||||
|
||||
if (fov_x < 1 || fov_x > 179)
|
||||
Com_Error (ERR_DROP, "Bad fov: %f", fov_x);
|
||||
|
||||
x = width/tan(fov_x/360*M_PI);
|
||||
|
||||
a = atan (height/x);
|
||||
|
||||
a = a*360/M_PI;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
// gun frame debugging functions
|
||||
void V_Gun_Next_f (void)
|
||||
{
|
||||
gun_frame++;
|
||||
Com_Printf ("frame %i\n", gun_frame);
|
||||
}
|
||||
|
||||
void V_Gun_Prev_f (void)
|
||||
{
|
||||
gun_frame--;
|
||||
if (gun_frame < 0)
|
||||
gun_frame = 0;
|
||||
Com_Printf ("frame %i\n", gun_frame);
|
||||
}
|
||||
|
||||
void V_Gun_Model_f (void)
|
||||
{
|
||||
char name[MAX_QPATH];
|
||||
|
||||
if (Cmd_Argc() != 2)
|
||||
{
|
||||
gun_model = NULL;
|
||||
return;
|
||||
}
|
||||
Com_sprintf (name, sizeof(name), "models/%s/tris.md2", Cmd_Argv(1));
|
||||
gun_model = re.RegisterModel (name);
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
SCR_DrawCrosshair
|
||||
=================
|
||||
*/
|
||||
void SCR_DrawCrosshair (void)
|
||||
{
|
||||
if (!crosshair->value)
|
||||
return;
|
||||
|
||||
if (crosshair->modified)
|
||||
{
|
||||
crosshair->modified = false;
|
||||
SCR_TouchPics ();
|
||||
}
|
||||
|
||||
if (!crosshair_pic[0])
|
||||
return;
|
||||
|
||||
re.DrawPic (scr_vrect.x + ((scr_vrect.width - crosshair_width)>>1)
|
||||
, scr_vrect.y + ((scr_vrect.height - crosshair_height)>>1), crosshair_pic);
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
V_RenderView
|
||||
|
||||
==================
|
||||
*/
|
||||
void V_RenderView( float stereo_separation )
|
||||
{
|
||||
extern int entitycmpfnc( const entity_t *, const entity_t * );
|
||||
|
||||
if (cls.state != ca_active)
|
||||
return;
|
||||
|
||||
if (!cl.refresh_prepped)
|
||||
return; // still loading
|
||||
|
||||
if (cl_timedemo->value)
|
||||
{
|
||||
if (!cl.timedemo_start)
|
||||
cl.timedemo_start = Sys_Milliseconds ();
|
||||
cl.timedemo_frames++;
|
||||
}
|
||||
|
||||
// an invalid frame will just use the exact previous refdef
|
||||
// we can't use the old frame if the video mode has changed, though...
|
||||
if ( cl.frame.valid && (cl.force_refdef || !cl_paused->value) )
|
||||
{
|
||||
cl.force_refdef = false;
|
||||
|
||||
V_ClearScene ();
|
||||
|
||||
// build a refresh entity list and calc cl.sim*
|
||||
// this also calls CL_CalcViewValues which loads
|
||||
// v_forward, etc.
|
||||
CL_AddEntities ();
|
||||
|
||||
if (cl_testparticles->value)
|
||||
V_TestParticles ();
|
||||
if (cl_testentities->value)
|
||||
V_TestEntities ();
|
||||
if (cl_testlights->value)
|
||||
V_TestLights ();
|
||||
if (cl_testblend->value)
|
||||
{
|
||||
cl.refdef.blend[0] = 1;
|
||||
cl.refdef.blend[1] = 0.5;
|
||||
cl.refdef.blend[2] = 0.25;
|
||||
cl.refdef.blend[3] = 0.5;
|
||||
}
|
||||
|
||||
// offset vieworg appropriately if we're doing stereo separation
|
||||
if ( stereo_separation != 0 )
|
||||
{
|
||||
vec3_t tmp;
|
||||
|
||||
VectorScale( cl.v_right, stereo_separation, tmp );
|
||||
VectorAdd( cl.refdef.vieworg, tmp, cl.refdef.vieworg );
|
||||
}
|
||||
|
||||
// never let it sit exactly on a node line, because a water plane can
|
||||
// dissapear when viewed with the eye exactly on it.
|
||||
// the server protocol only specifies to 1/8 pixel, so add 1/16 in each axis
|
||||
cl.refdef.vieworg[0] += 1.0/16;
|
||||
cl.refdef.vieworg[1] += 1.0/16;
|
||||
cl.refdef.vieworg[2] += 1.0/16;
|
||||
|
||||
cl.refdef.x = scr_vrect.x;
|
||||
cl.refdef.y = scr_vrect.y;
|
||||
cl.refdef.width = scr_vrect.width;
|
||||
cl.refdef.height = scr_vrect.height;
|
||||
cl.refdef.fov_y = CalcFov (cl.refdef.fov_x, cl.refdef.width, cl.refdef.height);
|
||||
cl.refdef.time = cl.time*0.001;
|
||||
|
||||
cl.refdef.areabits = cl.frame.areabits;
|
||||
|
||||
if (!cl_add_entities->value)
|
||||
r_numentities = 0;
|
||||
if (!cl_add_particles->value)
|
||||
r_numparticles = 0;
|
||||
if (!cl_add_lights->value)
|
||||
r_numdlights = 0;
|
||||
if (!cl_add_blend->value)
|
||||
{
|
||||
VectorClear (cl.refdef.blend);
|
||||
}
|
||||
|
||||
cl.refdef.num_entities = r_numentities;
|
||||
cl.refdef.entities = r_entities;
|
||||
cl.refdef.num_particles = r_numparticles;
|
||||
cl.refdef.particles = r_particles;
|
||||
cl.refdef.num_dlights = r_numdlights;
|
||||
cl.refdef.dlights = r_dlights;
|
||||
cl.refdef.lightstyles = r_lightstyles;
|
||||
|
||||
cl.refdef.rdflags = cl.frame.playerstate.rdflags;
|
||||
|
||||
// sort entities for better cache locality
|
||||
qsort( cl.refdef.entities, cl.refdef.num_entities, sizeof( cl.refdef.entities[0] ), (int (*)(const void *, const void *))entitycmpfnc );
|
||||
}
|
||||
|
||||
re.RenderFrame (&cl.refdef);
|
||||
if (cl_stats->value)
|
||||
Com_Printf ("ent:%i lt:%i part:%i\n", r_numentities, r_numdlights, r_numparticles);
|
||||
if ( log_stats->value && ( log_stats_file != 0 ) )
|
||||
fprintf( log_stats_file, "%i,%i,%i,",r_numentities, r_numdlights, r_numparticles);
|
||||
|
||||
|
||||
SCR_AddDirtyPoint (scr_vrect.x, scr_vrect.y);
|
||||
SCR_AddDirtyPoint (scr_vrect.x+scr_vrect.width-1,
|
||||
scr_vrect.y+scr_vrect.height-1);
|
||||
|
||||
SCR_DrawCrosshair ();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
V_Viewpos_f
|
||||
=============
|
||||
*/
|
||||
void V_Viewpos_f (void)
|
||||
{
|
||||
Com_Printf ("(%i %i %i) : %i\n", (int)cl.refdef.vieworg[0],
|
||||
(int)cl.refdef.vieworg[1], (int)cl.refdef.vieworg[2],
|
||||
(int)cl.refdef.viewangles[YAW]);
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
V_Init
|
||||
=============
|
||||
*/
|
||||
void V_Init (void)
|
||||
{
|
||||
Cmd_AddCommand ("gun_next", V_Gun_Next_f);
|
||||
Cmd_AddCommand ("gun_prev", V_Gun_Prev_f);
|
||||
Cmd_AddCommand ("gun_model", V_Gun_Model_f);
|
||||
|
||||
Cmd_AddCommand ("viewpos", V_Viewpos_f);
|
||||
|
||||
crosshair = Cvar_Get ("crosshair", "0", CVAR_ARCHIVE);
|
||||
|
||||
cl_testblend = Cvar_Get ("cl_testblend", "0", 0);
|
||||
cl_testparticles = Cvar_Get ("cl_testparticles", "0", 0);
|
||||
cl_testentities = Cvar_Get ("cl_testentities", "0", 0);
|
||||
cl_testlights = Cvar_Get ("cl_testlights", "0", 0);
|
||||
|
||||
cl_stats = Cvar_Get ("cl_stats", "0", 0);
|
||||
}
|
584
client/client.h
Normal file
|
@ -0,0 +1,584 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// client.h -- primary header for client
|
||||
|
||||
//define PARANOID // speed sapping error checking
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "ref.h"
|
||||
|
||||
#include "vid.h"
|
||||
#include "screen.h"
|
||||
#include "sound.h"
|
||||
#include "input.h"
|
||||
#include "keys.h"
|
||||
#include "console.h"
|
||||
#include "cdaudio.h"
|
||||
|
||||
//=============================================================================
|
||||
|
||||
typedef struct
|
||||
{
|
||||
qboolean valid; // cleared if delta parsing was invalid
|
||||
int serverframe;
|
||||
int servertime; // server time the message is valid for (in msec)
|
||||
int deltaframe;
|
||||
byte areabits[MAX_MAP_AREAS/8]; // portalarea visibility bits
|
||||
player_state_t playerstate;
|
||||
int num_entities;
|
||||
int parse_entities; // non-masked index into cl_parse_entities array
|
||||
} frame_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
entity_state_t baseline; // delta from this if not from a previous frame
|
||||
entity_state_t current;
|
||||
entity_state_t prev; // will always be valid, but might just be a copy of current
|
||||
|
||||
int serverframe; // if not current, this ent isn't in the frame
|
||||
|
||||
int trailcount; // for diminishing grenade trails
|
||||
vec3_t lerp_origin; // for trails (variable hz)
|
||||
|
||||
int fly_stoptime;
|
||||
} centity_t;
|
||||
|
||||
#define MAX_CLIENTWEAPONMODELS 20 // PGM -- upped from 16 to fit the chainfist vwep
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char name[MAX_QPATH];
|
||||
char cinfo[MAX_QPATH];
|
||||
struct image_s *skin;
|
||||
struct image_s *icon;
|
||||
char iconname[MAX_QPATH];
|
||||
struct model_s *model;
|
||||
struct model_s *weaponmodel[MAX_CLIENTWEAPONMODELS];
|
||||
} clientinfo_t;
|
||||
|
||||
extern char cl_weaponmodels[MAX_CLIENTWEAPONMODELS][MAX_QPATH];
|
||||
extern int num_cl_weaponmodels;
|
||||
|
||||
#define CMD_BACKUP 64 // allow a lot of command backups for very fast systems
|
||||
|
||||
//
|
||||
// the client_state_t structure is wiped completely at every
|
||||
// server map change
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
int timeoutcount;
|
||||
|
||||
int timedemo_frames;
|
||||
int timedemo_start;
|
||||
|
||||
qboolean refresh_prepped; // false if on new level or new ref dll
|
||||
qboolean sound_prepped; // ambient sounds can start
|
||||
qboolean force_refdef; // vid has changed, so we can't use a paused refdef
|
||||
|
||||
int parse_entities; // index (not anded off) into cl_parse_entities[]
|
||||
|
||||
usercmd_t cmd;
|
||||
usercmd_t cmds[CMD_BACKUP]; // each mesage will send several old cmds
|
||||
int cmd_time[CMD_BACKUP]; // time sent, for calculating pings
|
||||
short predicted_origins[CMD_BACKUP][3]; // for debug comparing against server
|
||||
|
||||
float predicted_step; // for stair up smoothing
|
||||
unsigned predicted_step_time;
|
||||
|
||||
vec3_t predicted_origin; // generated by CL_PredictMovement
|
||||
vec3_t predicted_angles;
|
||||
vec3_t prediction_error;
|
||||
|
||||
frame_t frame; // received from server
|
||||
int surpressCount; // number of messages rate supressed
|
||||
frame_t frames[UPDATE_BACKUP];
|
||||
|
||||
// the client maintains its own idea of view angles, which are
|
||||
// sent to the server each frame. It is cleared to 0 upon entering each level.
|
||||
// the server sends a delta each frame which is added to the locally
|
||||
// tracked view angles to account for standing on rotating objects,
|
||||
// and teleport direction changes
|
||||
vec3_t viewangles;
|
||||
|
||||
int time; // this is the time value that the client
|
||||
// is rendering at. always <= cls.realtime
|
||||
float lerpfrac; // between oldframe and frame
|
||||
|
||||
refdef_t refdef;
|
||||
|
||||
vec3_t v_forward, v_right, v_up; // set when refdef.angles is set
|
||||
|
||||
//
|
||||
// transient data from server
|
||||
//
|
||||
char layout[1024]; // general 2D overlay
|
||||
int inventory[MAX_ITEMS];
|
||||
|
||||
//
|
||||
// non-gameserver infornamtion
|
||||
// FIXME: move this cinematic stuff into the cin_t structure
|
||||
FILE *cinematic_file;
|
||||
int cinematictime; // cls.realtime for first cinematic frame
|
||||
int cinematicframe;
|
||||
char cinematicpalette[768];
|
||||
qboolean cinematicpalette_active;
|
||||
|
||||
//
|
||||
// server state information
|
||||
//
|
||||
qboolean attractloop; // running the attract loop, any key will menu
|
||||
int servercount; // server identification for prespawns
|
||||
char gamedir[MAX_QPATH];
|
||||
int playernum;
|
||||
|
||||
char configstrings[MAX_CONFIGSTRINGS][MAX_QPATH];
|
||||
|
||||
//
|
||||
// locally derived information from server state
|
||||
//
|
||||
struct model_s *model_draw[MAX_MODELS];
|
||||
struct cmodel_s *model_clip[MAX_MODELS];
|
||||
|
||||
struct sfx_s *sound_precache[MAX_SOUNDS];
|
||||
struct image_s *image_precache[MAX_IMAGES];
|
||||
|
||||
clientinfo_t clientinfo[MAX_CLIENTS];
|
||||
clientinfo_t baseclientinfo;
|
||||
} client_state_t;
|
||||
|
||||
extern client_state_t cl;
|
||||
|
||||
/*
|
||||
==================================================================
|
||||
|
||||
the client_static_t structure is persistant through an arbitrary number
|
||||
of server connections
|
||||
|
||||
==================================================================
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
ca_uninitialized,
|
||||
ca_disconnected, // not talking to a server
|
||||
ca_connecting, // sending request packets to the server
|
||||
ca_connected, // netchan_t established, waiting for svc_serverdata
|
||||
ca_active // game views should be displayed
|
||||
} connstate_t;
|
||||
|
||||
typedef enum {
|
||||
dl_none,
|
||||
dl_model,
|
||||
dl_sound,
|
||||
dl_skin,
|
||||
dl_single
|
||||
} dltype_t; // download type
|
||||
|
||||
typedef enum {key_game, key_console, key_message, key_menu} keydest_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
connstate_t state;
|
||||
keydest_t key_dest;
|
||||
|
||||
int framecount;
|
||||
int realtime; // always increasing, no clamping, etc
|
||||
float frametime; // seconds since last frame
|
||||
|
||||
// screen rendering information
|
||||
float disable_screen; // showing loading plaque between levels
|
||||
// or changing rendering dlls
|
||||
// if time gets > 30 seconds ahead, break it
|
||||
int disable_servercount; // when we receive a frame and cl.servercount
|
||||
// > cls.disable_servercount, clear disable_screen
|
||||
|
||||
// connection information
|
||||
char servername[MAX_OSPATH]; // name of server from original connect
|
||||
float connect_time; // for connection retransmits
|
||||
|
||||
int quakePort; // a 16 bit value that allows quake servers
|
||||
// to work around address translating routers
|
||||
netchan_t netchan;
|
||||
int serverProtocol; // in case we are doing some kind of version hack
|
||||
|
||||
int challenge; // from the server to use for connecting
|
||||
|
||||
FILE *download; // file transfer from server
|
||||
char downloadtempname[MAX_OSPATH];
|
||||
char downloadname[MAX_OSPATH];
|
||||
int downloadnumber;
|
||||
dltype_t downloadtype;
|
||||
int downloadpercent;
|
||||
|
||||
// demo recording info must be here, so it isn't cleared on level change
|
||||
qboolean demorecording;
|
||||
qboolean demowaiting; // don't record until a non-delta message is received
|
||||
FILE *demofile;
|
||||
} client_static_t;
|
||||
|
||||
extern client_static_t cls;
|
||||
|
||||
//=============================================================================
|
||||
|
||||
//
|
||||
// cvars
|
||||
//
|
||||
extern cvar_t *cl_stereo_separation;
|
||||
extern cvar_t *cl_stereo;
|
||||
|
||||
extern cvar_t *cl_gun;
|
||||
extern cvar_t *cl_add_blend;
|
||||
extern cvar_t *cl_add_lights;
|
||||
extern cvar_t *cl_add_particles;
|
||||
extern cvar_t *cl_add_entities;
|
||||
extern cvar_t *cl_predict;
|
||||
extern cvar_t *cl_footsteps;
|
||||
extern cvar_t *cl_noskins;
|
||||
extern cvar_t *cl_autoskins;
|
||||
|
||||
extern cvar_t *cl_upspeed;
|
||||
extern cvar_t *cl_forwardspeed;
|
||||
extern cvar_t *cl_sidespeed;
|
||||
|
||||
extern cvar_t *cl_yawspeed;
|
||||
extern cvar_t *cl_pitchspeed;
|
||||
|
||||
extern cvar_t *cl_run;
|
||||
|
||||
extern cvar_t *cl_anglespeedkey;
|
||||
|
||||
extern cvar_t *cl_shownet;
|
||||
extern cvar_t *cl_showmiss;
|
||||
extern cvar_t *cl_showclamp;
|
||||
|
||||
extern cvar_t *lookspring;
|
||||
extern cvar_t *lookstrafe;
|
||||
extern cvar_t *sensitivity;
|
||||
|
||||
extern cvar_t *m_pitch;
|
||||
extern cvar_t *m_yaw;
|
||||
extern cvar_t *m_forward;
|
||||
extern cvar_t *m_side;
|
||||
|
||||
extern cvar_t *freelook;
|
||||
|
||||
extern cvar_t *cl_lightlevel; // FIXME HACK
|
||||
|
||||
extern cvar_t *cl_paused;
|
||||
extern cvar_t *cl_timedemo;
|
||||
|
||||
extern cvar_t *cl_vwep;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int key; // so entities can reuse same entry
|
||||
vec3_t color;
|
||||
vec3_t origin;
|
||||
float radius;
|
||||
float die; // stop lighting after this time
|
||||
float decay; // drop this each second
|
||||
float minlight; // don't add when contributing less
|
||||
} cdlight_t;
|
||||
|
||||
extern centity_t cl_entities[MAX_EDICTS];
|
||||
extern cdlight_t cl_dlights[MAX_DLIGHTS];
|
||||
|
||||
// the cl_parse_entities must be large enough to hold UPDATE_BACKUP frames of
|
||||
// entities, so that when a delta compressed message arives from the server
|
||||
// it can be un-deltad from the original
|
||||
#define MAX_PARSE_ENTITIES 1024
|
||||
extern entity_state_t cl_parse_entities[MAX_PARSE_ENTITIES];
|
||||
|
||||
//=============================================================================
|
||||
|
||||
extern netadr_t net_from;
|
||||
extern sizebuf_t net_message;
|
||||
|
||||
void DrawString (int x, int y, char *s);
|
||||
void DrawAltString (int x, int y, char *s); // toggle high bit
|
||||
qboolean CL_CheckOrDownloadFile (char *filename);
|
||||
|
||||
void CL_AddNetgraph (void);
|
||||
|
||||
//ROGUE
|
||||
typedef struct cl_sustain
|
||||
{
|
||||
int id;
|
||||
int type;
|
||||
int endtime;
|
||||
int nextthink;
|
||||
int thinkinterval;
|
||||
vec3_t org;
|
||||
vec3_t dir;
|
||||
int color;
|
||||
int count;
|
||||
int magnitude;
|
||||
void (*think)(struct cl_sustain *self);
|
||||
} cl_sustain_t;
|
||||
|
||||
#define MAX_SUSTAINS 32
|
||||
void CL_ParticleSteamEffect2(cl_sustain_t *self);
|
||||
|
||||
void CL_TeleporterParticles (entity_state_t *ent);
|
||||
void CL_ParticleEffect (vec3_t org, vec3_t dir, int color, int count);
|
||||
void CL_ParticleEffect2 (vec3_t org, vec3_t dir, int color, int count);
|
||||
|
||||
// RAFAEL
|
||||
void CL_ParticleEffect3 (vec3_t org, vec3_t dir, int color, int count);
|
||||
|
||||
|
||||
//=================================================
|
||||
|
||||
// ========
|
||||
// PGM
|
||||
typedef struct particle_s
|
||||
{
|
||||
struct particle_s *next;
|
||||
|
||||
float time;
|
||||
|
||||
vec3_t org;
|
||||
vec3_t vel;
|
||||
vec3_t accel;
|
||||
float color;
|
||||
float colorvel;
|
||||
float alpha;
|
||||
float alphavel;
|
||||
} cparticle_t;
|
||||
|
||||
|
||||
#define PARTICLE_GRAVITY 40
|
||||
#define BLASTER_PARTICLE_COLOR 0xe0
|
||||
// PMM
|
||||
#define INSTANT_PARTICLE -10000.0
|
||||
// PGM
|
||||
// ========
|
||||
|
||||
void CL_ClearEffects (void);
|
||||
void CL_ClearTEnts (void);
|
||||
void CL_BlasterTrail (vec3_t start, vec3_t end);
|
||||
void CL_QuadTrail (vec3_t start, vec3_t end);
|
||||
void CL_RailTrail (vec3_t start, vec3_t end);
|
||||
void CL_BubbleTrail (vec3_t start, vec3_t end);
|
||||
void CL_FlagTrail (vec3_t start, vec3_t end, float color);
|
||||
|
||||
// RAFAEL
|
||||
void CL_IonripperTrail (vec3_t start, vec3_t end);
|
||||
|
||||
// ========
|
||||
// PGM
|
||||
void CL_BlasterParticles2 (vec3_t org, vec3_t dir, unsigned int color);
|
||||
void CL_BlasterTrail2 (vec3_t start, vec3_t end);
|
||||
void CL_DebugTrail (vec3_t start, vec3_t end);
|
||||
void CL_SmokeTrail (vec3_t start, vec3_t end, int colorStart, int colorRun, int spacing);
|
||||
void CL_Flashlight (int ent, vec3_t pos);
|
||||
void CL_ForceWall (vec3_t start, vec3_t end, int color);
|
||||
void CL_FlameEffects (centity_t *ent, vec3_t origin);
|
||||
void CL_GenericParticleEffect (vec3_t org, vec3_t dir, int color, int count, int numcolors, int dirspread, float alphavel);
|
||||
void CL_BubbleTrail2 (vec3_t start, vec3_t end, int dist);
|
||||
void CL_Heatbeam (vec3_t start, vec3_t end);
|
||||
void CL_ParticleSteamEffect (vec3_t org, vec3_t dir, int color, int count, int magnitude);
|
||||
void CL_TrackerTrail (vec3_t start, vec3_t end, int particleColor);
|
||||
void CL_Tracker_Explode(vec3_t origin);
|
||||
void CL_TagTrail (vec3_t start, vec3_t end, float color);
|
||||
void CL_ColorFlash (vec3_t pos, int ent, int intensity, float r, float g, float b);
|
||||
void CL_Tracker_Shell(vec3_t origin);
|
||||
void CL_MonsterPlasma_Shell(vec3_t origin);
|
||||
void CL_ColorExplosionParticles (vec3_t org, int color, int run);
|
||||
void CL_ParticleSmokeEffect (vec3_t org, vec3_t dir, int color, int count, int magnitude);
|
||||
void CL_Widowbeamout (cl_sustain_t *self);
|
||||
void CL_Nukeblast (cl_sustain_t *self);
|
||||
void CL_WidowSplash (vec3_t org);
|
||||
// PGM
|
||||
// ========
|
||||
|
||||
int CL_ParseEntityBits (unsigned *bits);
|
||||
void CL_ParseDelta (entity_state_t *from, entity_state_t *to, int number, int bits);
|
||||
void CL_ParseFrame (void);
|
||||
|
||||
void CL_ParseTEnt (void);
|
||||
void CL_ParseConfigString (void);
|
||||
void CL_ParseMuzzleFlash (void);
|
||||
void CL_ParseMuzzleFlash2 (void);
|
||||
void SmokeAndFlash(vec3_t origin);
|
||||
|
||||
void CL_SetLightstyle (int i);
|
||||
|
||||
void CL_RunParticles (void);
|
||||
void CL_RunDLights (void);
|
||||
void CL_RunLightStyles (void);
|
||||
|
||||
void CL_AddEntities (void);
|
||||
void CL_AddDLights (void);
|
||||
void CL_AddTEnts (void);
|
||||
void CL_AddLightStyles (void);
|
||||
|
||||
//=================================================
|
||||
|
||||
void CL_PrepRefresh (void);
|
||||
void CL_RegisterSounds (void);
|
||||
|
||||
void CL_Quit_f (void);
|
||||
|
||||
void IN_Accumulate (void);
|
||||
|
||||
void CL_ParseLayout (void);
|
||||
|
||||
|
||||
//
|
||||
// cl_main
|
||||
//
|
||||
extern refexport_t re; // interface to refresh .dll
|
||||
|
||||
void CL_Init (void);
|
||||
|
||||
void CL_FixUpGender(void);
|
||||
void CL_Disconnect (void);
|
||||
void CL_Disconnect_f (void);
|
||||
void CL_GetChallengePacket (void);
|
||||
void CL_PingServers_f (void);
|
||||
void CL_Snd_Restart_f (void);
|
||||
void CL_RequestNextDownload (void);
|
||||
|
||||
//
|
||||
// cl_input
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
int down[2]; // key nums holding it down
|
||||
unsigned downtime; // msec timestamp
|
||||
unsigned msec; // msec down this frame
|
||||
int state;
|
||||
} kbutton_t;
|
||||
|
||||
extern kbutton_t in_mlook, in_klook;
|
||||
extern kbutton_t in_strafe;
|
||||
extern kbutton_t in_speed;
|
||||
|
||||
void CL_InitInput (void);
|
||||
void CL_SendCmd (void);
|
||||
void CL_SendMove (usercmd_t *cmd);
|
||||
|
||||
void CL_ClearState (void);
|
||||
|
||||
void CL_ReadPackets (void);
|
||||
|
||||
int CL_ReadFromServer (void);
|
||||
void CL_WriteToServer (usercmd_t *cmd);
|
||||
void CL_BaseMove (usercmd_t *cmd);
|
||||
|
||||
void IN_CenterView (void);
|
||||
|
||||
float CL_KeyState (kbutton_t *key);
|
||||
char *Key_KeynumToString (int keynum);
|
||||
|
||||
//
|
||||
// cl_demo.c
|
||||
//
|
||||
void CL_WriteDemoMessage (void);
|
||||
void CL_Stop_f (void);
|
||||
void CL_Record_f (void);
|
||||
|
||||
//
|
||||
// cl_parse.c
|
||||
//
|
||||
extern char *svc_strings[256];
|
||||
|
||||
void CL_ParseServerMessage (void);
|
||||
void CL_LoadClientinfo (clientinfo_t *ci, char *s);
|
||||
void SHOWNET(char *s);
|
||||
void CL_ParseClientinfo (int player);
|
||||
void CL_Download_f (void);
|
||||
|
||||
//
|
||||
// cl_view.c
|
||||
//
|
||||
extern int gun_frame;
|
||||
extern struct model_s *gun_model;
|
||||
|
||||
void V_Init (void);
|
||||
void V_RenderView( float stereo_separation );
|
||||
void V_AddEntity (entity_t *ent);
|
||||
void V_AddParticle (vec3_t org, int color, float alpha);
|
||||
void V_AddLight (vec3_t org, float intensity, float r, float g, float b);
|
||||
void V_AddLightStyle (int style, float r, float g, float b);
|
||||
|
||||
//
|
||||
// cl_tent.c
|
||||
//
|
||||
void CL_RegisterTEntSounds (void);
|
||||
void CL_RegisterTEntModels (void);
|
||||
void CL_SmokeAndFlash(vec3_t origin);
|
||||
|
||||
|
||||
//
|
||||
// cl_pred.c
|
||||
//
|
||||
void CL_InitPrediction (void);
|
||||
void CL_PredictMove (void);
|
||||
void CL_CheckPredictionError (void);
|
||||
|
||||
//
|
||||
// cl_fx.c
|
||||
//
|
||||
cdlight_t *CL_AllocDlight (int key);
|
||||
void CL_BigTeleportParticles (vec3_t org);
|
||||
void CL_RocketTrail (vec3_t start, vec3_t end, centity_t *old);
|
||||
void CL_DiminishingTrail (vec3_t start, vec3_t end, centity_t *old, int flags);
|
||||
void CL_FlyEffect (centity_t *ent, vec3_t origin);
|
||||
void CL_BfgParticles (entity_t *ent);
|
||||
void CL_AddParticles (void);
|
||||
void CL_EntityEvent (entity_state_t *ent);
|
||||
// RAFAEL
|
||||
void CL_TrapParticles (entity_t *ent);
|
||||
|
||||
//
|
||||
// menus
|
||||
//
|
||||
void M_Init (void);
|
||||
void M_Keydown (int key);
|
||||
void M_Draw (void);
|
||||
void M_Menu_Main_f (void);
|
||||
void M_ForceMenuOff (void);
|
||||
void M_AddToServerList (netadr_t adr, char *info);
|
||||
|
||||
//
|
||||
// cl_inv.c
|
||||
//
|
||||
void CL_ParseInventory (void);
|
||||
void CL_KeyInventory (int key);
|
||||
void CL_DrawInventory (void);
|
||||
|
||||
//
|
||||
// cl_pred.c
|
||||
//
|
||||
void CL_PredictMovement (void);
|
||||
|
||||
#if id386
|
||||
void x86_TimerStart( void );
|
||||
void x86_TimerStop( void );
|
||||
void x86_TimerInit( unsigned long smallest, unsigned longest );
|
||||
unsigned long *x86_TimerGetHistogram( void );
|
||||
#endif
|
682
client/console.c
Normal file
|
@ -0,0 +1,682 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// console.c
|
||||
|
||||
#include "client.h"
|
||||
|
||||
console_t con;
|
||||
|
||||
cvar_t *con_notifytime;
|
||||
|
||||
|
||||
#define MAXCMDLINE 256
|
||||
extern char key_lines[32][MAXCMDLINE];
|
||||
extern int edit_line;
|
||||
extern int key_linepos;
|
||||
|
||||
|
||||
void DrawString (int x, int y, char *s)
|
||||
{
|
||||
while (*s)
|
||||
{
|
||||
re.DrawChar (x, y, *s);
|
||||
x+=8;
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawAltString (int x, int y, char *s)
|
||||
{
|
||||
while (*s)
|
||||
{
|
||||
re.DrawChar (x, y, *s ^ 0x80);
|
||||
x+=8;
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Key_ClearTyping (void)
|
||||
{
|
||||
key_lines[edit_line][1] = 0; // clear any typing
|
||||
key_linepos = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
Con_ToggleConsole_f
|
||||
================
|
||||
*/
|
||||
void Con_ToggleConsole_f (void)
|
||||
{
|
||||
SCR_EndLoadingPlaque (); // get rid of loading plaque
|
||||
|
||||
if (cl.attractloop)
|
||||
{
|
||||
Cbuf_AddText ("killserver\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cls.state == ca_disconnected)
|
||||
{ // start the demo loop again
|
||||
Cbuf_AddText ("d1\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Key_ClearTyping ();
|
||||
Con_ClearNotify ();
|
||||
|
||||
if (cls.key_dest == key_console)
|
||||
{
|
||||
M_ForceMenuOff ();
|
||||
Cvar_Set ("paused", "0");
|
||||
}
|
||||
else
|
||||
{
|
||||
M_ForceMenuOff ();
|
||||
cls.key_dest = key_console;
|
||||
|
||||
if (Cvar_VariableValue ("maxclients") == 1
|
||||
&& Com_ServerState ())
|
||||
Cvar_Set ("paused", "1");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
Con_ToggleChat_f
|
||||
================
|
||||
*/
|
||||
void Con_ToggleChat_f (void)
|
||||
{
|
||||
Key_ClearTyping ();
|
||||
|
||||
if (cls.key_dest == key_console)
|
||||
{
|
||||
if (cls.state == ca_active)
|
||||
{
|
||||
M_ForceMenuOff ();
|
||||
cls.key_dest = key_game;
|
||||
}
|
||||
}
|
||||
else
|
||||
cls.key_dest = key_console;
|
||||
|
||||
Con_ClearNotify ();
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
Con_Clear_f
|
||||
================
|
||||
*/
|
||||
void Con_Clear_f (void)
|
||||
{
|
||||
memset (con.text, ' ', CON_TEXTSIZE);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
Con_Dump_f
|
||||
|
||||
Save the console contents out to a file
|
||||
================
|
||||
*/
|
||||
void Con_Dump_f (void)
|
||||
{
|
||||
int l, x;
|
||||
char *line;
|
||||
FILE *f;
|
||||
char buffer[1024];
|
||||
char name[MAX_OSPATH];
|
||||
|
||||
if (Cmd_Argc() != 2)
|
||||
{
|
||||
Com_Printf ("usage: condump <filename>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Com_sprintf (name, sizeof(name), "%s/%s.txt", FS_Gamedir(), Cmd_Argv(1));
|
||||
|
||||
Com_Printf ("Dumped console text to %s.\n", name);
|
||||
FS_CreatePath (name);
|
||||
f = fopen (name, "w");
|
||||
if (!f)
|
||||
{
|
||||
Com_Printf ("ERROR: couldn't open.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// skip empty lines
|
||||
for (l = con.current - con.totallines + 1 ; l <= con.current ; l++)
|
||||
{
|
||||
line = con.text + (l%con.totallines)*con.linewidth;
|
||||
for (x=0 ; x<con.linewidth ; x++)
|
||||
if (line[x] != ' ')
|
||||
break;
|
||||
if (x != con.linewidth)
|
||||
break;
|
||||
}
|
||||
|
||||
// write the remaining lines
|
||||
buffer[con.linewidth] = 0;
|
||||
for ( ; l <= con.current ; l++)
|
||||
{
|
||||
line = con.text + (l%con.totallines)*con.linewidth;
|
||||
strncpy (buffer, line, con.linewidth);
|
||||
for (x=con.linewidth-1 ; x>=0 ; x--)
|
||||
{
|
||||
if (buffer[x] == ' ')
|
||||
buffer[x] = 0;
|
||||
else
|
||||
break;
|
||||
}
|
||||
for (x=0; buffer[x]; x++)
|
||||
buffer[x] &= 0x7f;
|
||||
|
||||
fprintf (f, "%s\n", buffer);
|
||||
}
|
||||
|
||||
fclose (f);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
Con_ClearNotify
|
||||
================
|
||||
*/
|
||||
void Con_ClearNotify (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0 ; i<NUM_CON_TIMES ; i++)
|
||||
con.times[i] = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
Con_MessageMode_f
|
||||
================
|
||||
*/
|
||||
void Con_MessageMode_f (void)
|
||||
{
|
||||
chat_team = false;
|
||||
cls.key_dest = key_message;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
Con_MessageMode2_f
|
||||
================
|
||||
*/
|
||||
void Con_MessageMode2_f (void)
|
||||
{
|
||||
chat_team = true;
|
||||
cls.key_dest = key_message;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
Con_CheckResize
|
||||
|
||||
If the line width has changed, reformat the buffer.
|
||||
================
|
||||
*/
|
||||
void Con_CheckResize (void)
|
||||
{
|
||||
int i, j, width, oldwidth, oldtotallines, numlines, numchars;
|
||||
char tbuf[CON_TEXTSIZE];
|
||||
|
||||
width = (viddef.width >> 3) - 2;
|
||||
|
||||
if (width == con.linewidth)
|
||||
return;
|
||||
|
||||
if (width < 1) // video hasn't been initialized yet
|
||||
{
|
||||
width = 38;
|
||||
con.linewidth = width;
|
||||
con.totallines = CON_TEXTSIZE / con.linewidth;
|
||||
memset (con.text, ' ', CON_TEXTSIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
oldwidth = con.linewidth;
|
||||
con.linewidth = width;
|
||||
oldtotallines = con.totallines;
|
||||
con.totallines = CON_TEXTSIZE / con.linewidth;
|
||||
numlines = oldtotallines;
|
||||
|
||||
if (con.totallines < numlines)
|
||||
numlines = con.totallines;
|
||||
|
||||
numchars = oldwidth;
|
||||
|
||||
if (con.linewidth < numchars)
|
||||
numchars = con.linewidth;
|
||||
|
||||
memcpy (tbuf, con.text, CON_TEXTSIZE);
|
||||
memset (con.text, ' ', CON_TEXTSIZE);
|
||||
|
||||
for (i=0 ; i<numlines ; i++)
|
||||
{
|
||||
for (j=0 ; j<numchars ; j++)
|
||||
{
|
||||
con.text[(con.totallines - 1 - i) * con.linewidth + j] =
|
||||
tbuf[((con.current - i + oldtotallines) %
|
||||
oldtotallines) * oldwidth + j];
|
||||
}
|
||||
}
|
||||
|
||||
Con_ClearNotify ();
|
||||
}
|
||||
|
||||
con.current = con.totallines - 1;
|
||||
con.display = con.current;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
Con_Init
|
||||
================
|
||||
*/
|
||||
void Con_Init (void)
|
||||
{
|
||||
con.linewidth = -1;
|
||||
|
||||
Con_CheckResize ();
|
||||
|
||||
Com_Printf ("Console initialized.\n");
|
||||
|
||||
//
|
||||
// register our commands
|
||||
//
|
||||
con_notifytime = Cvar_Get ("con_notifytime", "3", 0);
|
||||
|
||||
Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
|
||||
Cmd_AddCommand ("togglechat", Con_ToggleChat_f);
|
||||
Cmd_AddCommand ("messagemode", Con_MessageMode_f);
|
||||
Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
|
||||
Cmd_AddCommand ("clear", Con_Clear_f);
|
||||
Cmd_AddCommand ("condump", Con_Dump_f);
|
||||
con.initialized = true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Con_Linefeed
|
||||
===============
|
||||
*/
|
||||
void Con_Linefeed (void)
|
||||
{
|
||||
con.x = 0;
|
||||
if (con.display == con.current)
|
||||
con.display++;
|
||||
con.current++;
|
||||
memset (&con.text[(con.current%con.totallines)*con.linewidth]
|
||||
, ' ', con.linewidth);
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
Con_Print
|
||||
|
||||
Handles cursor positioning, line wrapping, etc
|
||||
All console printing must go through this in order to be logged to disk
|
||||
If no console is visible, the text will appear at the top of the game window
|
||||
================
|
||||
*/
|
||||
void Con_Print (char *txt)
|
||||
{
|
||||
int y;
|
||||
int c, l;
|
||||
static int cr;
|
||||
int mask;
|
||||
|
||||
if (!con.initialized)
|
||||
return;
|
||||
|
||||
if (txt[0] == 1 || txt[0] == 2)
|
||||
{
|
||||
mask = 128; // go to colored text
|
||||
txt++;
|
||||
}
|
||||
else
|
||||
mask = 0;
|
||||
|
||||
|
||||
while ( (c = *txt) )
|
||||
{
|
||||
// count word length
|
||||
for (l=0 ; l< con.linewidth ; l++)
|
||||
if ( txt[l] <= ' ')
|
||||
break;
|
||||
|
||||
// word wrap
|
||||
if (l != con.linewidth && (con.x + l > con.linewidth) )
|
||||
con.x = 0;
|
||||
|
||||
txt++;
|
||||
|
||||
if (cr)
|
||||
{
|
||||
con.current--;
|
||||
cr = false;
|
||||
}
|
||||
|
||||
|
||||
if (!con.x)
|
||||
{
|
||||
Con_Linefeed ();
|
||||
// mark time for transparent overlay
|
||||
if (con.current >= 0)
|
||||
con.times[con.current % NUM_CON_TIMES] = cls.realtime;
|
||||
}
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '\n':
|
||||
con.x = 0;
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
con.x = 0;
|
||||
cr = 1;
|
||||
break;
|
||||
|
||||
default: // display character and advance
|
||||
y = con.current % con.totallines;
|
||||
con.text[y*con.linewidth+con.x] = c | mask | con.ormask;
|
||||
con.x++;
|
||||
if (con.x >= con.linewidth)
|
||||
con.x = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============
|
||||
Con_CenteredPrint
|
||||
==============
|
||||
*/
|
||||
void Con_CenteredPrint (char *text)
|
||||
{
|
||||
int l;
|
||||
char buffer[1024];
|
||||
|
||||
l = strlen(text);
|
||||
l = (con.linewidth-l)/2;
|
||||
if (l < 0)
|
||||
l = 0;
|
||||
memset (buffer, ' ', l);
|
||||
strcpy (buffer+l, text);
|
||||
strcat (buffer, "\n");
|
||||
Con_Print (buffer);
|
||||
}
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
DRAWING
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
Con_DrawInput
|
||||
|
||||
The input line scrolls horizontally if typing goes beyond the right edge
|
||||
================
|
||||
*/
|
||||
void Con_DrawInput (void)
|
||||
{
|
||||
int y;
|
||||
int i;
|
||||
char *text;
|
||||
|
||||
if (cls.key_dest == key_menu)
|
||||
return;
|
||||
if (cls.key_dest != key_console && cls.state == ca_active)
|
||||
return; // don't draw anything (always draw if not active)
|
||||
|
||||
text = key_lines[edit_line];
|
||||
|
||||
// add the cursor frame
|
||||
text[key_linepos] = 10+((int)(cls.realtime>>8)&1);
|
||||
|
||||
// fill out remainder with spaces
|
||||
for (i=key_linepos+1 ; i< con.linewidth ; i++)
|
||||
text[i] = ' ';
|
||||
|
||||
// prestep if horizontally scrolling
|
||||
if (key_linepos >= con.linewidth)
|
||||
text += 1 + key_linepos - con.linewidth;
|
||||
|
||||
// draw it
|
||||
y = con.vislines-16;
|
||||
|
||||
for (i=0 ; i<con.linewidth ; i++)
|
||||
re.DrawChar ( (i+1)<<3, con.vislines - 22, text[i]);
|
||||
|
||||
// remove cursor
|
||||
key_lines[edit_line][key_linepos] = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
Con_DrawNotify
|
||||
|
||||
Draws the last few lines of output transparently over the game top
|
||||
================
|
||||
*/
|
||||
void Con_DrawNotify (void)
|
||||
{
|
||||
int x, v;
|
||||
char *text;
|
||||
int i;
|
||||
int time;
|
||||
char *s;
|
||||
int skip;
|
||||
|
||||
v = 0;
|
||||
for (i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++)
|
||||
{
|
||||
if (i < 0)
|
||||
continue;
|
||||
time = con.times[i % NUM_CON_TIMES];
|
||||
if (time == 0)
|
||||
continue;
|
||||
time = cls.realtime - time;
|
||||
if (time > con_notifytime->value*1000)
|
||||
continue;
|
||||
text = con.text + (i % con.totallines)*con.linewidth;
|
||||
|
||||
for (x = 0 ; x < con.linewidth ; x++)
|
||||
re.DrawChar ( (x+1)<<3, v, text[x]);
|
||||
|
||||
v += 8;
|
||||
}
|
||||
|
||||
|
||||
if (cls.key_dest == key_message)
|
||||
{
|
||||
if (chat_team)
|
||||
{
|
||||
DrawString (8, v, "say_team:");
|
||||
skip = 11;
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawString (8, v, "say:");
|
||||
skip = 5;
|
||||
}
|
||||
|
||||
s = chat_buffer;
|
||||
if (chat_bufferlen > (viddef.width>>3)-(skip+1))
|
||||
s += chat_bufferlen - ((viddef.width>>3)-(skip+1));
|
||||
x = 0;
|
||||
while(s[x])
|
||||
{
|
||||
re.DrawChar ( (x+skip)<<3, v, s[x]);
|
||||
x++;
|
||||
}
|
||||
re.DrawChar ( (x+skip)<<3, v, 10+((cls.realtime>>8)&1));
|
||||
v += 8;
|
||||
}
|
||||
|
||||
if (v)
|
||||
{
|
||||
SCR_AddDirtyPoint (0,0);
|
||||
SCR_AddDirtyPoint (viddef.width-1, v);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
Con_DrawConsole
|
||||
|
||||
Draws the console with the solid background
|
||||
================
|
||||
*/
|
||||
void Con_DrawConsole (float frac)
|
||||
{
|
||||
int i, j, x, y, n;
|
||||
int rows;
|
||||
char *text;
|
||||
int row;
|
||||
int lines;
|
||||
char version[64];
|
||||
char dlbar[1024];
|
||||
|
||||
lines = viddef.height * frac;
|
||||
if (lines <= 0)
|
||||
return;
|
||||
|
||||
if (lines > viddef.height)
|
||||
lines = viddef.height;
|
||||
|
||||
// draw the background
|
||||
re.DrawStretchPic (0, -viddef.height+lines, viddef.width, viddef.height, "conback");
|
||||
SCR_AddDirtyPoint (0,0);
|
||||
SCR_AddDirtyPoint (viddef.width-1,lines-1);
|
||||
|
||||
Com_sprintf (version, sizeof(version), "v%4.2f", VERSION);
|
||||
for (x=0 ; x<5 ; x++)
|
||||
re.DrawChar (viddef.width-44+x*8, lines-12, 128 + version[x] );
|
||||
|
||||
// draw the text
|
||||
con.vislines = lines;
|
||||
|
||||
#if 0
|
||||
rows = (lines-8)>>3; // rows of text to draw
|
||||
|
||||
y = lines - 24;
|
||||
#else
|
||||
rows = (lines-22)>>3; // rows of text to draw
|
||||
|
||||
y = lines - 30;
|
||||
#endif
|
||||
|
||||
// draw from the bottom up
|
||||
if (con.display != con.current)
|
||||
{
|
||||
// draw arrows to show the buffer is backscrolled
|
||||
for (x=0 ; x<con.linewidth ; x+=4)
|
||||
re.DrawChar ( (x+1)<<3, y, '^');
|
||||
|
||||
y -= 8;
|
||||
rows--;
|
||||
}
|
||||
|
||||
row = con.display;
|
||||
for (i=0 ; i<rows ; i++, y-=8, row--)
|
||||
{
|
||||
if (row < 0)
|
||||
break;
|
||||
if (con.current - row >= con.totallines)
|
||||
break; // past scrollback wrap point
|
||||
|
||||
text = con.text + (row % con.totallines)*con.linewidth;
|
||||
|
||||
for (x=0 ; x<con.linewidth ; x++)
|
||||
re.DrawChar ( (x+1)<<3, y, text[x]);
|
||||
}
|
||||
|
||||
//ZOID
|
||||
// draw the download bar
|
||||
// figure out width
|
||||
if (cls.download) {
|
||||
if ((text = strrchr(cls.downloadname, '/')) != NULL)
|
||||
text++;
|
||||
else
|
||||
text = cls.downloadname;
|
||||
|
||||
x = con.linewidth - ((con.linewidth * 7) / 40);
|
||||
y = x - strlen(text) - 8;
|
||||
i = con.linewidth/3;
|
||||
if (strlen(text) > i) {
|
||||
y = x - i - 11;
|
||||
strncpy(dlbar, text, i);
|
||||
dlbar[i] = 0;
|
||||
strcat(dlbar, "...");
|
||||
} else
|
||||
strcpy(dlbar, text);
|
||||
strcat(dlbar, ": ");
|
||||
i = strlen(dlbar);
|
||||
dlbar[i++] = '\x80';
|
||||
// where's the dot go?
|
||||
if (cls.downloadpercent == 0)
|
||||
n = 0;
|
||||
else
|
||||
n = y * cls.downloadpercent / 100;
|
||||
|
||||
for (j = 0; j < y; j++)
|
||||
if (j == n)
|
||||
dlbar[i++] = '\x83';
|
||||
else
|
||||
dlbar[i++] = '\x81';
|
||||
dlbar[i++] = '\x82';
|
||||
dlbar[i] = 0;
|
||||
|
||||
sprintf(dlbar + strlen(dlbar), " %02d%%", cls.downloadpercent);
|
||||
|
||||
// draw it
|
||||
y = con.vislines-12;
|
||||
for (i = 0; i < strlen(dlbar); i++)
|
||||
re.DrawChar ( (i+1)<<3, y, dlbar[i]);
|
||||
}
|
||||
//ZOID
|
||||
|
||||
// draw the input prompt, user text, and cursor if desired
|
||||
Con_DrawInput ();
|
||||
}
|
||||
|
||||
|
62
client/console.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
//
|
||||
// console
|
||||
//
|
||||
|
||||
#define NUM_CON_TIMES 4
|
||||
|
||||
#define CON_TEXTSIZE 32768
|
||||
typedef struct
|
||||
{
|
||||
qboolean initialized;
|
||||
|
||||
char text[CON_TEXTSIZE];
|
||||
int current; // line where next message will be printed
|
||||
int x; // offset in current line for next print
|
||||
int display; // bottom of console displays this line
|
||||
|
||||
int ormask; // high bit mask for colored characters
|
||||
|
||||
int linewidth; // characters across screen
|
||||
int totallines; // total lines in console scrollback
|
||||
|
||||
float cursorspeed;
|
||||
|
||||
int vislines;
|
||||
|
||||
float times[NUM_CON_TIMES]; // cls.realtime time the line was generated
|
||||
// for transparent notify lines
|
||||
} console_t;
|
||||
|
||||
extern console_t con;
|
||||
|
||||
void Con_DrawCharacter (int cx, int line, int num);
|
||||
|
||||
void Con_CheckResize (void);
|
||||
void Con_Init (void);
|
||||
void Con_DrawConsole (float frac);
|
||||
void Con_Print (char *txt);
|
||||
void Con_CenteredPrint (char *text);
|
||||
void Con_Clear_f (void);
|
||||
void Con_DrawNotify (void);
|
||||
void Con_ClearNotify (void);
|
||||
void Con_ToggleConsole_f (void);
|
34
client/input.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// input.h -- external (non-keyboard) input devices
|
||||
|
||||
void IN_Init (void);
|
||||
|
||||
void IN_Shutdown (void);
|
||||
|
||||
void IN_Commands (void);
|
||||
// oportunity for devices to stick commands on the script buffer
|
||||
|
||||
void IN_Frame (void);
|
||||
|
||||
void IN_Move (usercmd_t *cmd);
|
||||
// add additional movement on top of the keyboard move cmd
|
||||
|
||||
void IN_Activate (qboolean active);
|
944
client/keys.c
Normal file
|
@ -0,0 +1,944 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#include "client.h"
|
||||
|
||||
/*
|
||||
|
||||
key up events are sent even if in console mode
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#define MAXCMDLINE 256
|
||||
char key_lines[32][MAXCMDLINE];
|
||||
int key_linepos;
|
||||
int shift_down=false;
|
||||
int anykeydown;
|
||||
|
||||
int edit_line=0;
|
||||
int history_line=0;
|
||||
|
||||
int key_waiting;
|
||||
char *keybindings[256];
|
||||
qboolean consolekeys[256]; // if true, can't be rebound while in console
|
||||
qboolean menubound[256]; // if true, can't be rebound while in menu
|
||||
int keyshift[256]; // key to map to if shift held down in console
|
||||
int key_repeats[256]; // if > 1, it is autorepeating
|
||||
qboolean keydown[256];
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *name;
|
||||
int keynum;
|
||||
} keyname_t;
|
||||
|
||||
keyname_t keynames[] =
|
||||
{
|
||||
{"TAB", K_TAB},
|
||||
{"ENTER", K_ENTER},
|
||||
{"ESCAPE", K_ESCAPE},
|
||||
{"SPACE", K_SPACE},
|
||||
{"BACKSPACE", K_BACKSPACE},
|
||||
{"UPARROW", K_UPARROW},
|
||||
{"DOWNARROW", K_DOWNARROW},
|
||||
{"LEFTARROW", K_LEFTARROW},
|
||||
{"RIGHTARROW", K_RIGHTARROW},
|
||||
|
||||
{"ALT", K_ALT},
|
||||
{"CTRL", K_CTRL},
|
||||
{"SHIFT", K_SHIFT},
|
||||
|
||||
{"F1", K_F1},
|
||||
{"F2", K_F2},
|
||||
{"F3", K_F3},
|
||||
{"F4", K_F4},
|
||||
{"F5", K_F5},
|
||||
{"F6", K_F6},
|
||||
{"F7", K_F7},
|
||||
{"F8", K_F8},
|
||||
{"F9", K_F9},
|
||||
{"F10", K_F10},
|
||||
{"F11", K_F11},
|
||||
{"F12", K_F12},
|
||||
|
||||
{"INS", K_INS},
|
||||
{"DEL", K_DEL},
|
||||
{"PGDN", K_PGDN},
|
||||
{"PGUP", K_PGUP},
|
||||
{"HOME", K_HOME},
|
||||
{"END", K_END},
|
||||
|
||||
{"MOUSE1", K_MOUSE1},
|
||||
{"MOUSE2", K_MOUSE2},
|
||||
{"MOUSE3", K_MOUSE3},
|
||||
|
||||
{"JOY1", K_JOY1},
|
||||
{"JOY2", K_JOY2},
|
||||
{"JOY3", K_JOY3},
|
||||
{"JOY4", K_JOY4},
|
||||
|
||||
{"AUX1", K_AUX1},
|
||||
{"AUX2", K_AUX2},
|
||||
{"AUX3", K_AUX3},
|
||||
{"AUX4", K_AUX4},
|
||||
{"AUX5", K_AUX5},
|
||||
{"AUX6", K_AUX6},
|
||||
{"AUX7", K_AUX7},
|
||||
{"AUX8", K_AUX8},
|
||||
{"AUX9", K_AUX9},
|
||||
{"AUX10", K_AUX10},
|
||||
{"AUX11", K_AUX11},
|
||||
{"AUX12", K_AUX12},
|
||||
{"AUX13", K_AUX13},
|
||||
{"AUX14", K_AUX14},
|
||||
{"AUX15", K_AUX15},
|
||||
{"AUX16", K_AUX16},
|
||||
{"AUX17", K_AUX17},
|
||||
{"AUX18", K_AUX18},
|
||||
{"AUX19", K_AUX19},
|
||||
{"AUX20", K_AUX20},
|
||||
{"AUX21", K_AUX21},
|
||||
{"AUX22", K_AUX22},
|
||||
{"AUX23", K_AUX23},
|
||||
{"AUX24", K_AUX24},
|
||||
{"AUX25", K_AUX25},
|
||||
{"AUX26", K_AUX26},
|
||||
{"AUX27", K_AUX27},
|
||||
{"AUX28", K_AUX28},
|
||||
{"AUX29", K_AUX29},
|
||||
{"AUX30", K_AUX30},
|
||||
{"AUX31", K_AUX31},
|
||||
{"AUX32", K_AUX32},
|
||||
|
||||
{"KP_HOME", K_KP_HOME },
|
||||
{"KP_UPARROW", K_KP_UPARROW },
|
||||
{"KP_PGUP", K_KP_PGUP },
|
||||
{"KP_LEFTARROW", K_KP_LEFTARROW },
|
||||
{"KP_5", K_KP_5 },
|
||||
{"KP_RIGHTARROW", K_KP_RIGHTARROW },
|
||||
{"KP_END", K_KP_END },
|
||||
{"KP_DOWNARROW", K_KP_DOWNARROW },
|
||||
{"KP_PGDN", K_KP_PGDN },
|
||||
{"KP_ENTER", K_KP_ENTER },
|
||||
{"KP_INS", K_KP_INS },
|
||||
{"KP_DEL", K_KP_DEL },
|
||||
{"KP_SLASH", K_KP_SLASH },
|
||||
{"KP_MINUS", K_KP_MINUS },
|
||||
{"KP_PLUS", K_KP_PLUS },
|
||||
|
||||
{"MWHEELUP", K_MWHEELUP },
|
||||
{"MWHEELDOWN", K_MWHEELDOWN },
|
||||
|
||||
{"PAUSE", K_PAUSE},
|
||||
|
||||
{"SEMICOLON", ';'}, // because a raw semicolon seperates commands
|
||||
|
||||
{NULL,0}
|
||||
};
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
LINE TYPING INTO THE CONSOLE
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
void CompleteCommand (void)
|
||||
{
|
||||
char *cmd, *s;
|
||||
|
||||
s = key_lines[edit_line]+1;
|
||||
if (*s == '\\' || *s == '/')
|
||||
s++;
|
||||
|
||||
cmd = Cmd_CompleteCommand (s);
|
||||
if (!cmd)
|
||||
cmd = Cvar_CompleteVariable (s);
|
||||
if (cmd)
|
||||
{
|
||||
key_lines[edit_line][1] = '/';
|
||||
strcpy (key_lines[edit_line]+2, cmd);
|
||||
key_linepos = strlen(cmd)+2;
|
||||
key_lines[edit_line][key_linepos] = ' ';
|
||||
key_linepos++;
|
||||
key_lines[edit_line][key_linepos] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
Key_Console
|
||||
|
||||
Interactive line editing and console scrollback
|
||||
====================
|
||||
*/
|
||||
void Key_Console (int key)
|
||||
{
|
||||
|
||||
switch ( key )
|
||||
{
|
||||
case K_KP_SLASH:
|
||||
key = '/';
|
||||
break;
|
||||
case K_KP_MINUS:
|
||||
key = '-';
|
||||
break;
|
||||
case K_KP_PLUS:
|
||||
key = '+';
|
||||
break;
|
||||
case K_KP_HOME:
|
||||
key = '7';
|
||||
break;
|
||||
case K_KP_UPARROW:
|
||||
key = '8';
|
||||
break;
|
||||
case K_KP_PGUP:
|
||||
key = '9';
|
||||
break;
|
||||
case K_KP_LEFTARROW:
|
||||
key = '4';
|
||||
break;
|
||||
case K_KP_5:
|
||||
key = '5';
|
||||
break;
|
||||
case K_KP_RIGHTARROW:
|
||||
key = '6';
|
||||
break;
|
||||
case K_KP_END:
|
||||
key = '1';
|
||||
break;
|
||||
case K_KP_DOWNARROW:
|
||||
key = '2';
|
||||
break;
|
||||
case K_KP_PGDN:
|
||||
key = '3';
|
||||
break;
|
||||
case K_KP_INS:
|
||||
key = '0';
|
||||
break;
|
||||
case K_KP_DEL:
|
||||
key = '.';
|
||||
break;
|
||||
}
|
||||
|
||||
if ( ( toupper( key ) == 'V' && keydown[K_CTRL] ) ||
|
||||
( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keydown[K_SHIFT] ) )
|
||||
{
|
||||
char *cbd;
|
||||
|
||||
if ( ( cbd = Sys_GetClipboardData() ) != 0 )
|
||||
{
|
||||
int i;
|
||||
|
||||
strtok( cbd, "\n\r\b" );
|
||||
|
||||
i = strlen( cbd );
|
||||
if ( i + key_linepos >= MAXCMDLINE)
|
||||
i= MAXCMDLINE - key_linepos;
|
||||
|
||||
if ( i > 0 )
|
||||
{
|
||||
cbd[i]=0;
|
||||
strcat( key_lines[edit_line], cbd );
|
||||
key_linepos += i;
|
||||
}
|
||||
free( cbd );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( key == 'l' )
|
||||
{
|
||||
if ( keydown[K_CTRL] )
|
||||
{
|
||||
Cbuf_AddText ("clear\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( key == K_ENTER || key == K_KP_ENTER )
|
||||
{ // backslash text are commands, else chat
|
||||
if (key_lines[edit_line][1] == '\\' || key_lines[edit_line][1] == '/')
|
||||
Cbuf_AddText (key_lines[edit_line]+2); // skip the >
|
||||
else
|
||||
Cbuf_AddText (key_lines[edit_line]+1); // valid command
|
||||
|
||||
Cbuf_AddText ("\n");
|
||||
Com_Printf ("%s\n",key_lines[edit_line]);
|
||||
edit_line = (edit_line + 1) & 31;
|
||||
history_line = edit_line;
|
||||
key_lines[edit_line][0] = ']';
|
||||
key_linepos = 1;
|
||||
if (cls.state == ca_disconnected)
|
||||
SCR_UpdateScreen (); // force an update, because the command
|
||||
// may take some time
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == K_TAB)
|
||||
{ // command completion
|
||||
CompleteCommand ();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ( key == K_BACKSPACE ) || ( key == K_LEFTARROW ) || ( key == K_KP_LEFTARROW ) || ( ( key == 'h' ) && ( keydown[K_CTRL] ) ) )
|
||||
{
|
||||
if (key_linepos > 1)
|
||||
key_linepos--;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) ||
|
||||
( ( key == 'p' ) && keydown[K_CTRL] ) )
|
||||
{
|
||||
do
|
||||
{
|
||||
history_line = (history_line - 1) & 31;
|
||||
} while (history_line != edit_line
|
||||
&& !key_lines[history_line][1]);
|
||||
if (history_line == edit_line)
|
||||
history_line = (edit_line+1)&31;
|
||||
strcpy(key_lines[edit_line], key_lines[history_line]);
|
||||
key_linepos = strlen(key_lines[edit_line]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) ||
|
||||
( ( key == 'n' ) && keydown[K_CTRL] ) )
|
||||
{
|
||||
if (history_line == edit_line) return;
|
||||
do
|
||||
{
|
||||
history_line = (history_line + 1) & 31;
|
||||
}
|
||||
while (history_line != edit_line
|
||||
&& !key_lines[history_line][1]);
|
||||
if (history_line == edit_line)
|
||||
{
|
||||
key_lines[edit_line][0] = ']';
|
||||
key_linepos = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(key_lines[edit_line], key_lines[history_line]);
|
||||
key_linepos = strlen(key_lines[edit_line]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == K_PGUP || key == K_KP_PGUP )
|
||||
{
|
||||
con.display -= 2;
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == K_PGDN || key == K_KP_PGDN )
|
||||
{
|
||||
con.display += 2;
|
||||
if (con.display > con.current)
|
||||
con.display = con.current;
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == K_HOME || key == K_KP_HOME )
|
||||
{
|
||||
con.display = con.current - con.totallines + 10;
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == K_END || key == K_KP_END )
|
||||
{
|
||||
con.display = con.current;
|
||||
return;
|
||||
}
|
||||
|
||||
if (key < 32 || key > 127)
|
||||
return; // non printable
|
||||
|
||||
if (key_linepos < MAXCMDLINE-1)
|
||||
{
|
||||
key_lines[edit_line][key_linepos] = key;
|
||||
key_linepos++;
|
||||
key_lines[edit_line][key_linepos] = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
qboolean chat_team;
|
||||
char chat_buffer[MAXCMDLINE];
|
||||
int chat_bufferlen = 0;
|
||||
|
||||
void Key_Message (int key)
|
||||
{
|
||||
|
||||
if ( key == K_ENTER || key == K_KP_ENTER )
|
||||
{
|
||||
if (chat_team)
|
||||
Cbuf_AddText ("say_team \"");
|
||||
else
|
||||
Cbuf_AddText ("say \"");
|
||||
Cbuf_AddText(chat_buffer);
|
||||
Cbuf_AddText("\"\n");
|
||||
|
||||
cls.key_dest = key_game;
|
||||
chat_bufferlen = 0;
|
||||
chat_buffer[0] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == K_ESCAPE)
|
||||
{
|
||||
cls.key_dest = key_game;
|
||||
chat_bufferlen = 0;
|
||||
chat_buffer[0] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (key < 32 || key > 127)
|
||||
return; // non printable
|
||||
|
||||
if (key == K_BACKSPACE)
|
||||
{
|
||||
if (chat_bufferlen)
|
||||
{
|
||||
chat_bufferlen--;
|
||||
chat_buffer[chat_bufferlen] = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (chat_bufferlen == sizeof(chat_buffer)-1)
|
||||
return; // all full
|
||||
|
||||
chat_buffer[chat_bufferlen++] = key;
|
||||
chat_buffer[chat_bufferlen] = 0;
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
|
||||
/*
|
||||
===================
|
||||
Key_StringToKeynum
|
||||
|
||||
Returns a key number to be used to index keybindings[] by looking at
|
||||
the given string. Single ascii characters return themselves, while
|
||||
the K_* names are matched up.
|
||||
===================
|
||||
*/
|
||||
int Key_StringToKeynum (char *str)
|
||||
{
|
||||
keyname_t *kn;
|
||||
|
||||
if (!str || !str[0])
|
||||
return -1;
|
||||
if (!str[1])
|
||||
return str[0];
|
||||
|
||||
for (kn=keynames ; kn->name ; kn++)
|
||||
{
|
||||
if (!Q_strcasecmp(str,kn->name))
|
||||
return kn->keynum;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
===================
|
||||
Key_KeynumToString
|
||||
|
||||
Returns a string (either a single ascii char, or a K_* name) for the
|
||||
given keynum.
|
||||
FIXME: handle quote special (general escape sequence?)
|
||||
===================
|
||||
*/
|
||||
char *Key_KeynumToString (int keynum)
|
||||
{
|
||||
keyname_t *kn;
|
||||
static char tinystr[2];
|
||||
|
||||
if (keynum == -1)
|
||||
return "<KEY NOT FOUND>";
|
||||
if (keynum > 32 && keynum < 127)
|
||||
{ // printable ascii
|
||||
tinystr[0] = keynum;
|
||||
tinystr[1] = 0;
|
||||
return tinystr;
|
||||
}
|
||||
|
||||
for (kn=keynames ; kn->name ; kn++)
|
||||
if (keynum == kn->keynum)
|
||||
return kn->name;
|
||||
|
||||
return "<UNKNOWN KEYNUM>";
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===================
|
||||
Key_SetBinding
|
||||
===================
|
||||
*/
|
||||
void Key_SetBinding (int keynum, char *binding)
|
||||
{
|
||||
char *new;
|
||||
int l;
|
||||
|
||||
if (keynum == -1)
|
||||
return;
|
||||
|
||||
// free old bindings
|
||||
if (keybindings[keynum])
|
||||
{
|
||||
Z_Free (keybindings[keynum]);
|
||||
keybindings[keynum] = NULL;
|
||||
}
|
||||
|
||||
// allocate memory for new binding
|
||||
l = strlen (binding);
|
||||
new = Z_Malloc (l+1);
|
||||
strcpy (new, binding);
|
||||
new[l] = 0;
|
||||
keybindings[keynum] = new;
|
||||
}
|
||||
|
||||
/*
|
||||
===================
|
||||
Key_Unbind_f
|
||||
===================
|
||||
*/
|
||||
void Key_Unbind_f (void)
|
||||
{
|
||||
int b;
|
||||
|
||||
if (Cmd_Argc() != 2)
|
||||
{
|
||||
Com_Printf ("unbind <key> : remove commands from a key\n");
|
||||
return;
|
||||
}
|
||||
|
||||
b = Key_StringToKeynum (Cmd_Argv(1));
|
||||
if (b==-1)
|
||||
{
|
||||
Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
|
||||
return;
|
||||
}
|
||||
|
||||
Key_SetBinding (b, "");
|
||||
}
|
||||
|
||||
void Key_Unbindall_f (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0 ; i<256 ; i++)
|
||||
if (keybindings[i])
|
||||
Key_SetBinding (i, "");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===================
|
||||
Key_Bind_f
|
||||
===================
|
||||
*/
|
||||
void Key_Bind_f (void)
|
||||
{
|
||||
int i, c, b;
|
||||
char cmd[1024];
|
||||
|
||||
c = Cmd_Argc();
|
||||
|
||||
if (c < 2)
|
||||
{
|
||||
Com_Printf ("bind <key> [command] : attach a command to a key\n");
|
||||
return;
|
||||
}
|
||||
b = Key_StringToKeynum (Cmd_Argv(1));
|
||||
if (b==-1)
|
||||
{
|
||||
Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == 2)
|
||||
{
|
||||
if (keybindings[b])
|
||||
Com_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keybindings[b] );
|
||||
else
|
||||
Com_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) );
|
||||
return;
|
||||
}
|
||||
|
||||
// copy the rest of the command line
|
||||
cmd[0] = 0; // start out with a null string
|
||||
for (i=2 ; i< c ; i++)
|
||||
{
|
||||
strcat (cmd, Cmd_Argv(i));
|
||||
if (i != (c-1))
|
||||
strcat (cmd, " ");
|
||||
}
|
||||
|
||||
Key_SetBinding (b, cmd);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Key_WriteBindings
|
||||
|
||||
Writes lines containing "bind key value"
|
||||
============
|
||||
*/
|
||||
void Key_WriteBindings (FILE *f)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0 ; i<256 ; i++)
|
||||
if (keybindings[i] && keybindings[i][0])
|
||||
fprintf (f, "bind %s \"%s\"\n", Key_KeynumToString(i), keybindings[i]);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Key_Bindlist_f
|
||||
|
||||
============
|
||||
*/
|
||||
void Key_Bindlist_f (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0 ; i<256 ; i++)
|
||||
if (keybindings[i] && keybindings[i][0])
|
||||
Com_Printf ("%s \"%s\"\n", Key_KeynumToString(i), keybindings[i]);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===================
|
||||
Key_Init
|
||||
===================
|
||||
*/
|
||||
void Key_Init (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0 ; i<32 ; i++)
|
||||
{
|
||||
key_lines[i][0] = ']';
|
||||
key_lines[i][1] = 0;
|
||||
}
|
||||
key_linepos = 1;
|
||||
|
||||
//
|
||||
// init ascii characters in console mode
|
||||
//
|
||||
for (i=32 ; i<128 ; i++)
|
||||
consolekeys[i] = true;
|
||||
consolekeys[K_ENTER] = true;
|
||||
consolekeys[K_KP_ENTER] = true;
|
||||
consolekeys[K_TAB] = true;
|
||||
consolekeys[K_LEFTARROW] = true;
|
||||
consolekeys[K_KP_LEFTARROW] = true;
|
||||
consolekeys[K_RIGHTARROW] = true;
|
||||
consolekeys[K_KP_RIGHTARROW] = true;
|
||||
consolekeys[K_UPARROW] = true;
|
||||
consolekeys[K_KP_UPARROW] = true;
|
||||
consolekeys[K_DOWNARROW] = true;
|
||||
consolekeys[K_KP_DOWNARROW] = true;
|
||||
consolekeys[K_BACKSPACE] = true;
|
||||
consolekeys[K_HOME] = true;
|
||||
consolekeys[K_KP_HOME] = true;
|
||||
consolekeys[K_END] = true;
|
||||
consolekeys[K_KP_END] = true;
|
||||
consolekeys[K_PGUP] = true;
|
||||
consolekeys[K_KP_PGUP] = true;
|
||||
consolekeys[K_PGDN] = true;
|
||||
consolekeys[K_KP_PGDN] = true;
|
||||
consolekeys[K_SHIFT] = true;
|
||||
consolekeys[K_INS] = true;
|
||||
consolekeys[K_KP_INS] = true;
|
||||
consolekeys[K_KP_DEL] = true;
|
||||
consolekeys[K_KP_SLASH] = true;
|
||||
consolekeys[K_KP_PLUS] = true;
|
||||
consolekeys[K_KP_MINUS] = true;
|
||||
consolekeys[K_KP_5] = true;
|
||||
|
||||
consolekeys['`'] = false;
|
||||
consolekeys['~'] = false;
|
||||
|
||||
for (i=0 ; i<256 ; i++)
|
||||
keyshift[i] = i;
|
||||
for (i='a' ; i<='z' ; i++)
|
||||
keyshift[i] = i - 'a' + 'A';
|
||||
keyshift['1'] = '!';
|
||||
keyshift['2'] = '@';
|
||||
keyshift['3'] = '#';
|
||||
keyshift['4'] = '$';
|
||||
keyshift['5'] = '%';
|
||||
keyshift['6'] = '^';
|
||||
keyshift['7'] = '&';
|
||||
keyshift['8'] = '*';
|
||||
keyshift['9'] = '(';
|
||||
keyshift['0'] = ')';
|
||||
keyshift['-'] = '_';
|
||||
keyshift['='] = '+';
|
||||
keyshift[','] = '<';
|
||||
keyshift['.'] = '>';
|
||||
keyshift['/'] = '?';
|
||||
keyshift[';'] = ':';
|
||||
keyshift['\''] = '"';
|
||||
keyshift['['] = '{';
|
||||
keyshift[']'] = '}';
|
||||
keyshift['`'] = '~';
|
||||
keyshift['\\'] = '|';
|
||||
|
||||
menubound[K_ESCAPE] = true;
|
||||
for (i=0 ; i<12 ; i++)
|
||||
menubound[K_F1+i] = true;
|
||||
|
||||
//
|
||||
// register our functions
|
||||
//
|
||||
Cmd_AddCommand ("bind",Key_Bind_f);
|
||||
Cmd_AddCommand ("unbind",Key_Unbind_f);
|
||||
Cmd_AddCommand ("unbindall",Key_Unbindall_f);
|
||||
Cmd_AddCommand ("bindlist",Key_Bindlist_f);
|
||||
}
|
||||
|
||||
/*
|
||||
===================
|
||||
Key_Event
|
||||
|
||||
Called by the system between frames for both key up and key down events
|
||||
Should NOT be called during an interrupt!
|
||||
===================
|
||||
*/
|
||||
void Key_Event (int key, qboolean down, unsigned time)
|
||||
{
|
||||
char *kb;
|
||||
char cmd[1024];
|
||||
|
||||
// hack for modal presses
|
||||
if (key_waiting == -1)
|
||||
{
|
||||
if (down)
|
||||
key_waiting = key;
|
||||
return;
|
||||
}
|
||||
|
||||
// update auto-repeat status
|
||||
if (down)
|
||||
{
|
||||
key_repeats[key]++;
|
||||
if (key != K_BACKSPACE
|
||||
&& key != K_PAUSE
|
||||
&& key != K_PGUP
|
||||
&& key != K_KP_PGUP
|
||||
&& key != K_PGDN
|
||||
&& key != K_KP_PGDN
|
||||
&& key_repeats[key] > 1)
|
||||
return; // ignore most autorepeats
|
||||
|
||||
if (key >= 200 && !keybindings[key])
|
||||
Com_Printf ("%s is unbound, hit F4 to set.\n", Key_KeynumToString (key) );
|
||||
}
|
||||
else
|
||||
{
|
||||
key_repeats[key] = 0;
|
||||
}
|
||||
|
||||
if (key == K_SHIFT)
|
||||
shift_down = down;
|
||||
|
||||
// console key is hardcoded, so the user can never unbind it
|
||||
if (key == '`' || key == '~')
|
||||
{
|
||||
if (!down)
|
||||
return;
|
||||
Con_ToggleConsole_f ();
|
||||
return;
|
||||
}
|
||||
|
||||
// any key during the attract mode will bring up the menu
|
||||
if (cl.attractloop && cls.key_dest != key_menu &&
|
||||
!(key >= K_F1 && key <= K_F12))
|
||||
key = K_ESCAPE;
|
||||
|
||||
// menu key is hardcoded, so the user can never unbind it
|
||||
if (key == K_ESCAPE)
|
||||
{
|
||||
if (!down)
|
||||
return;
|
||||
|
||||
if (cl.frame.playerstate.stats[STAT_LAYOUTS] && cls.key_dest == key_game)
|
||||
{ // put away help computer / inventory
|
||||
Cbuf_AddText ("cmd putaway\n");
|
||||
return;
|
||||
}
|
||||
switch (cls.key_dest)
|
||||
{
|
||||
case key_message:
|
||||
Key_Message (key);
|
||||
break;
|
||||
case key_menu:
|
||||
M_Keydown (key);
|
||||
break;
|
||||
case key_game:
|
||||
case key_console:
|
||||
M_Menu_Main_f ();
|
||||
break;
|
||||
default:
|
||||
Com_Error (ERR_FATAL, "Bad cls.key_dest");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// track if any key is down for BUTTON_ANY
|
||||
keydown[key] = down;
|
||||
if (down)
|
||||
{
|
||||
if (key_repeats[key] == 1)
|
||||
anykeydown++;
|
||||
}
|
||||
else
|
||||
{
|
||||
anykeydown--;
|
||||
if (anykeydown < 0)
|
||||
anykeydown = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// key up events only generate commands if the game key binding is
|
||||
// a button command (leading + sign). These will occur even in console mode,
|
||||
// to keep the character from continuing an action started before a console
|
||||
// switch. Button commands include the kenum as a parameter, so multiple
|
||||
// downs can be matched with ups
|
||||
//
|
||||
if (!down)
|
||||
{
|
||||
kb = keybindings[key];
|
||||
if (kb && kb[0] == '+')
|
||||
{
|
||||
Com_sprintf (cmd, sizeof(cmd), "-%s %i %i\n", kb+1, key, time);
|
||||
Cbuf_AddText (cmd);
|
||||
}
|
||||
if (keyshift[key] != key)
|
||||
{
|
||||
kb = keybindings[keyshift[key]];
|
||||
if (kb && kb[0] == '+')
|
||||
{
|
||||
Com_sprintf (cmd, sizeof(cmd), "-%s %i %i\n", kb+1, key, time);
|
||||
Cbuf_AddText (cmd);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// if not a consolekey, send to the interpreter no matter what mode is
|
||||
//
|
||||
if ( (cls.key_dest == key_menu && menubound[key])
|
||||
|| (cls.key_dest == key_console && !consolekeys[key])
|
||||
|| (cls.key_dest == key_game && ( cls.state == ca_active || !consolekeys[key] ) ) )
|
||||
{
|
||||
kb = keybindings[key];
|
||||
if (kb)
|
||||
{
|
||||
if (kb[0] == '+')
|
||||
{ // button commands add keynum and time as a parm
|
||||
Com_sprintf (cmd, sizeof(cmd), "%s %i %i\n", kb, key, time);
|
||||
Cbuf_AddText (cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
Cbuf_AddText (kb);
|
||||
Cbuf_AddText ("\n");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!down)
|
||||
return; // other systems only care about key down events
|
||||
|
||||
if (shift_down)
|
||||
key = keyshift[key];
|
||||
|
||||
switch (cls.key_dest)
|
||||
{
|
||||
case key_message:
|
||||
Key_Message (key);
|
||||
break;
|
||||
case key_menu:
|
||||
M_Keydown (key);
|
||||
break;
|
||||
|
||||
case key_game:
|
||||
case key_console:
|
||||
Key_Console (key);
|
||||
break;
|
||||
default:
|
||||
Com_Error (ERR_FATAL, "Bad cls.key_dest");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===================
|
||||
Key_ClearStates
|
||||
===================
|
||||
*/
|
||||
void Key_ClearStates (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
anykeydown = false;
|
||||
|
||||
for (i=0 ; i<256 ; i++)
|
||||
{
|
||||
if ( keydown[i] || key_repeats[i] )
|
||||
Key_Event( i, false, 0 );
|
||||
keydown[i] = 0;
|
||||
key_repeats[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===================
|
||||
Key_GetKey
|
||||
===================
|
||||
*/
|
||||
int Key_GetKey (void)
|
||||
{
|
||||
key_waiting = -1;
|
||||
|
||||
while (key_waiting == -1)
|
||||
Sys_SendKeyEvents ();
|
||||
|
||||
return key_waiting;
|
||||
}
|
||||
|
146
client/keys.h
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
//
|
||||
// these are the key numbers that should be passed to Key_Event
|
||||
//
|
||||
#define K_TAB 9
|
||||
#define K_ENTER 13
|
||||
#define K_ESCAPE 27
|
||||
#define K_SPACE 32
|
||||
|
||||
// normal keys should be passed as lowercased ascii
|
||||
|
||||
#define K_BACKSPACE 127
|
||||
#define K_UPARROW 128
|
||||
#define K_DOWNARROW 129
|
||||
#define K_LEFTARROW 130
|
||||
#define K_RIGHTARROW 131
|
||||
|
||||
#define K_ALT 132
|
||||
#define K_CTRL 133
|
||||
#define K_SHIFT 134
|
||||
#define K_F1 135
|
||||
#define K_F2 136
|
||||
#define K_F3 137
|
||||
#define K_F4 138
|
||||
#define K_F5 139
|
||||
#define K_F6 140
|
||||
#define K_F7 141
|
||||
#define K_F8 142
|
||||
#define K_F9 143
|
||||
#define K_F10 144
|
||||
#define K_F11 145
|
||||
#define K_F12 146
|
||||
#define K_INS 147
|
||||
#define K_DEL 148
|
||||
#define K_PGDN 149
|
||||
#define K_PGUP 150
|
||||
#define K_HOME 151
|
||||
#define K_END 152
|
||||
|
||||
#define K_KP_HOME 160
|
||||
#define K_KP_UPARROW 161
|
||||
#define K_KP_PGUP 162
|
||||
#define K_KP_LEFTARROW 163
|
||||
#define K_KP_5 164
|
||||
#define K_KP_RIGHTARROW 165
|
||||
#define K_KP_END 166
|
||||
#define K_KP_DOWNARROW 167
|
||||
#define K_KP_PGDN 168
|
||||
#define K_KP_ENTER 169
|
||||
#define K_KP_INS 170
|
||||
#define K_KP_DEL 171
|
||||
#define K_KP_SLASH 172
|
||||
#define K_KP_MINUS 173
|
||||
#define K_KP_PLUS 174
|
||||
|
||||
#define K_PAUSE 255
|
||||
|
||||
//
|
||||
// mouse buttons generate virtual keys
|
||||
//
|
||||
#define K_MOUSE1 200
|
||||
#define K_MOUSE2 201
|
||||
#define K_MOUSE3 202
|
||||
|
||||
//
|
||||
// joystick buttons
|
||||
//
|
||||
#define K_JOY1 203
|
||||
#define K_JOY2 204
|
||||
#define K_JOY3 205
|
||||
#define K_JOY4 206
|
||||
|
||||
//
|
||||
// aux keys are for multi-buttoned joysticks to generate so they can use
|
||||
// the normal binding process
|
||||
//
|
||||
#define K_AUX1 207
|
||||
#define K_AUX2 208
|
||||
#define K_AUX3 209
|
||||
#define K_AUX4 210
|
||||
#define K_AUX5 211
|
||||
#define K_AUX6 212
|
||||
#define K_AUX7 213
|
||||
#define K_AUX8 214
|
||||
#define K_AUX9 215
|
||||
#define K_AUX10 216
|
||||
#define K_AUX11 217
|
||||
#define K_AUX12 218
|
||||
#define K_AUX13 219
|
||||
#define K_AUX14 220
|
||||
#define K_AUX15 221
|
||||
#define K_AUX16 222
|
||||
#define K_AUX17 223
|
||||
#define K_AUX18 224
|
||||
#define K_AUX19 225
|
||||
#define K_AUX20 226
|
||||
#define K_AUX21 227
|
||||
#define K_AUX22 228
|
||||
#define K_AUX23 229
|
||||
#define K_AUX24 230
|
||||
#define K_AUX25 231
|
||||
#define K_AUX26 232
|
||||
#define K_AUX27 233
|
||||
#define K_AUX28 234
|
||||
#define K_AUX29 235
|
||||
#define K_AUX30 236
|
||||
#define K_AUX31 237
|
||||
#define K_AUX32 238
|
||||
|
||||
#define K_MWHEELDOWN 239
|
||||
#define K_MWHEELUP 240
|
||||
|
||||
extern char *keybindings[256];
|
||||
extern int key_repeats[256];
|
||||
|
||||
extern int anykeydown;
|
||||
extern char chat_buffer[];
|
||||
extern int chat_bufferlen;
|
||||
extern qboolean chat_team;
|
||||
|
||||
void Key_Event (int key, qboolean down, unsigned time);
|
||||
void Key_Init (void);
|
||||
void Key_WriteBindings (FILE *f);
|
||||
void Key_SetBinding (int keynum, char *binding);
|
||||
void Key_ClearStates (void);
|
||||
int Key_GetKey (void);
|
||||
|
4009
client/menu.c
Normal file
674
client/qmenu.c
Normal file
|
@ -0,0 +1,674 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "client.h"
|
||||
#include "qmenu.h"
|
||||
|
||||
static void Action_DoEnter( menuaction_s *a );
|
||||
static void Action_Draw( menuaction_s *a );
|
||||
static void Menu_DrawStatusBar( const char *string );
|
||||
static void Menulist_DoEnter( menulist_s *l );
|
||||
static void MenuList_Draw( menulist_s *l );
|
||||
static void Separator_Draw( menuseparator_s *s );
|
||||
static void Slider_DoSlide( menuslider_s *s, int dir );
|
||||
static void Slider_Draw( menuslider_s *s );
|
||||
static void SpinControl_DoEnter( menulist_s *s );
|
||||
static void SpinControl_Draw( menulist_s *s );
|
||||
static void SpinControl_DoSlide( menulist_s *s, int dir );
|
||||
|
||||
#define RCOLUMN_OFFSET 16
|
||||
#define LCOLUMN_OFFSET -16
|
||||
|
||||
extern refexport_t re;
|
||||
extern viddef_t viddef;
|
||||
|
||||
#define VID_WIDTH viddef.width
|
||||
#define VID_HEIGHT viddef.height
|
||||
|
||||
#define Draw_Char re.DrawChar
|
||||
#define Draw_Fill re.DrawFill
|
||||
|
||||
void Action_DoEnter( menuaction_s *a )
|
||||
{
|
||||
if ( a->generic.callback )
|
||||
a->generic.callback( a );
|
||||
}
|
||||
|
||||
void Action_Draw( menuaction_s *a )
|
||||
{
|
||||
if ( a->generic.flags & QMF_LEFT_JUSTIFY )
|
||||
{
|
||||
if ( a->generic.flags & QMF_GRAYED )
|
||||
Menu_DrawStringDark( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
|
||||
else
|
||||
Menu_DrawString( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( a->generic.flags & QMF_GRAYED )
|
||||
Menu_DrawStringR2LDark( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
|
||||
else
|
||||
Menu_DrawStringR2L( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
|
||||
}
|
||||
if ( a->generic.ownerdraw )
|
||||
a->generic.ownerdraw( a );
|
||||
}
|
||||
|
||||
qboolean Field_DoEnter( menufield_s *f )
|
||||
{
|
||||
if ( f->generic.callback )
|
||||
{
|
||||
f->generic.callback( f );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Field_Draw( menufield_s *f )
|
||||
{
|
||||
int i;
|
||||
char tempbuffer[128]="";
|
||||
|
||||
if ( f->generic.name )
|
||||
Menu_DrawStringR2LDark( f->generic.x + f->generic.parent->x + LCOLUMN_OFFSET, f->generic.y + f->generic.parent->y, f->generic.name );
|
||||
|
||||
strncpy( tempbuffer, f->buffer + f->visible_offset, f->visible_length );
|
||||
|
||||
Draw_Char( f->generic.x + f->generic.parent->x + 16, f->generic.y + f->generic.parent->y - 4, 18 );
|
||||
Draw_Char( f->generic.x + f->generic.parent->x + 16, f->generic.y + f->generic.parent->y + 4, 24 );
|
||||
|
||||
Draw_Char( f->generic.x + f->generic.parent->x + 24 + f->visible_length * 8, f->generic.y + f->generic.parent->y - 4, 20 );
|
||||
Draw_Char( f->generic.x + f->generic.parent->x + 24 + f->visible_length * 8, f->generic.y + f->generic.parent->y + 4, 26 );
|
||||
|
||||
for ( i = 0; i < f->visible_length; i++ )
|
||||
{
|
||||
Draw_Char( f->generic.x + f->generic.parent->x + 24 + i * 8, f->generic.y + f->generic.parent->y - 4, 19 );
|
||||
Draw_Char( f->generic.x + f->generic.parent->x + 24 + i * 8, f->generic.y + f->generic.parent->y + 4, 25 );
|
||||
}
|
||||
|
||||
Menu_DrawString( f->generic.x + f->generic.parent->x + 24, f->generic.y + f->generic.parent->y, tempbuffer );
|
||||
|
||||
if ( Menu_ItemAtCursor( f->generic.parent ) == f )
|
||||
{
|
||||
int offset;
|
||||
|
||||
if ( f->visible_offset )
|
||||
offset = f->visible_length;
|
||||
else
|
||||
offset = f->cursor;
|
||||
|
||||
if ( ( ( int ) ( Sys_Milliseconds() / 250 ) ) & 1 )
|
||||
{
|
||||
Draw_Char( f->generic.x + f->generic.parent->x + ( offset + 2 ) * 8 + 8,
|
||||
f->generic.y + f->generic.parent->y,
|
||||
11 );
|
||||
}
|
||||
else
|
||||
{
|
||||
Draw_Char( f->generic.x + f->generic.parent->x + ( offset + 2 ) * 8 + 8,
|
||||
f->generic.y + f->generic.parent->y,
|
||||
' ' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qboolean Field_Key( menufield_s *f, int key )
|
||||
{
|
||||
extern int keydown[];
|
||||
|
||||
switch ( key )
|
||||
{
|
||||
case K_KP_SLASH:
|
||||
key = '/';
|
||||
break;
|
||||
case K_KP_MINUS:
|
||||
key = '-';
|
||||
break;
|
||||
case K_KP_PLUS:
|
||||
key = '+';
|
||||
break;
|
||||
case K_KP_HOME:
|
||||
key = '7';
|
||||
break;
|
||||
case K_KP_UPARROW:
|
||||
key = '8';
|
||||
break;
|
||||
case K_KP_PGUP:
|
||||
key = '9';
|
||||
break;
|
||||
case K_KP_LEFTARROW:
|
||||
key = '4';
|
||||
break;
|
||||
case K_KP_5:
|
||||
key = '5';
|
||||
break;
|
||||
case K_KP_RIGHTARROW:
|
||||
key = '6';
|
||||
break;
|
||||
case K_KP_END:
|
||||
key = '1';
|
||||
break;
|
||||
case K_KP_DOWNARROW:
|
||||
key = '2';
|
||||
break;
|
||||
case K_KP_PGDN:
|
||||
key = '3';
|
||||
break;
|
||||
case K_KP_INS:
|
||||
key = '0';
|
||||
break;
|
||||
case K_KP_DEL:
|
||||
key = '.';
|
||||
break;
|
||||
}
|
||||
|
||||
if ( key > 127 )
|
||||
{
|
||||
switch ( key )
|
||||
{
|
||||
case K_DEL:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** support pasting from the clipboard
|
||||
*/
|
||||
if ( ( toupper( key ) == 'V' && keydown[K_CTRL] ) ||
|
||||
( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keydown[K_SHIFT] ) )
|
||||
{
|
||||
char *cbd;
|
||||
|
||||
if ( ( cbd = Sys_GetClipboardData() ) != 0 )
|
||||
{
|
||||
strtok( cbd, "\n\r\b" );
|
||||
|
||||
strncpy( f->buffer, cbd, f->length - 1 );
|
||||
f->cursor = strlen( f->buffer );
|
||||
f->visible_offset = f->cursor - f->visible_length;
|
||||
if ( f->visible_offset < 0 )
|
||||
f->visible_offset = 0;
|
||||
|
||||
free( cbd );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
switch ( key )
|
||||
{
|
||||
case K_KP_LEFTARROW:
|
||||
case K_LEFTARROW:
|
||||
case K_BACKSPACE:
|
||||
if ( f->cursor > 0 )
|
||||
{
|
||||
memmove( &f->buffer[f->cursor-1], &f->buffer[f->cursor], strlen( &f->buffer[f->cursor] ) + 1 );
|
||||
f->cursor--;
|
||||
|
||||
if ( f->visible_offset )
|
||||
{
|
||||
f->visible_offset--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case K_KP_DEL:
|
||||
case K_DEL:
|
||||
memmove( &f->buffer[f->cursor], &f->buffer[f->cursor+1], strlen( &f->buffer[f->cursor+1] ) + 1 );
|
||||
break;
|
||||
|
||||
case K_KP_ENTER:
|
||||
case K_ENTER:
|
||||
case K_ESCAPE:
|
||||
case K_TAB:
|
||||
return false;
|
||||
|
||||
case K_SPACE:
|
||||
default:
|
||||
if ( !isdigit( key ) && ( f->generic.flags & QMF_NUMBERSONLY ) )
|
||||
return false;
|
||||
|
||||
if ( f->cursor < f->length )
|
||||
{
|
||||
f->buffer[f->cursor++] = key;
|
||||
f->buffer[f->cursor] = 0;
|
||||
|
||||
if ( f->cursor > f->visible_length )
|
||||
{
|
||||
f->visible_offset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Menu_AddItem( menuframework_s *menu, void *item )
|
||||
{
|
||||
if ( menu->nitems == 0 )
|
||||
menu->nslots = 0;
|
||||
|
||||
if ( menu->nitems < MAXMENUITEMS )
|
||||
{
|
||||
menu->items[menu->nitems] = item;
|
||||
( ( menucommon_s * ) menu->items[menu->nitems] )->parent = menu;
|
||||
menu->nitems++;
|
||||
}
|
||||
|
||||
menu->nslots = Menu_TallySlots( menu );
|
||||
}
|
||||
|
||||
/*
|
||||
** Menu_AdjustCursor
|
||||
**
|
||||
** This function takes the given menu, the direction, and attempts
|
||||
** to adjust the menu's cursor so that it's at the next available
|
||||
** slot.
|
||||
*/
|
||||
void Menu_AdjustCursor( menuframework_s *m, int dir )
|
||||
{
|
||||
menucommon_s *citem;
|
||||
|
||||
/*
|
||||
** see if it's in a valid spot
|
||||
*/
|
||||
if ( m->cursor >= 0 && m->cursor < m->nitems )
|
||||
{
|
||||
if ( ( citem = Menu_ItemAtCursor( m ) ) != 0 )
|
||||
{
|
||||
if ( citem->type != MTYPE_SEPARATOR )
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** it's not in a valid spot, so crawl in the direction indicated until we
|
||||
** find a valid spot
|
||||
*/
|
||||
if ( dir == 1 )
|
||||
{
|
||||
while ( 1 )
|
||||
{
|
||||
citem = Menu_ItemAtCursor( m );
|
||||
if ( citem )
|
||||
if ( citem->type != MTYPE_SEPARATOR )
|
||||
break;
|
||||
m->cursor += dir;
|
||||
if ( m->cursor >= m->nitems )
|
||||
m->cursor = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while ( 1 )
|
||||
{
|
||||
citem = Menu_ItemAtCursor( m );
|
||||
if ( citem )
|
||||
if ( citem->type != MTYPE_SEPARATOR )
|
||||
break;
|
||||
m->cursor += dir;
|
||||
if ( m->cursor < 0 )
|
||||
m->cursor = m->nitems - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu_Center( menuframework_s *menu )
|
||||
{
|
||||
int height;
|
||||
|
||||
height = ( ( menucommon_s * ) menu->items[menu->nitems-1])->y;
|
||||
height += 10;
|
||||
|
||||
menu->y = ( VID_HEIGHT - height ) / 2;
|
||||
}
|
||||
|
||||
void Menu_Draw( menuframework_s *menu )
|
||||
{
|
||||
int i;
|
||||
menucommon_s *item;
|
||||
|
||||
/*
|
||||
** draw contents
|
||||
*/
|
||||
for ( i = 0; i < menu->nitems; i++ )
|
||||
{
|
||||
switch ( ( ( menucommon_s * ) menu->items[i] )->type )
|
||||
{
|
||||
case MTYPE_FIELD:
|
||||
Field_Draw( ( menufield_s * ) menu->items[i] );
|
||||
break;
|
||||
case MTYPE_SLIDER:
|
||||
Slider_Draw( ( menuslider_s * ) menu->items[i] );
|
||||
break;
|
||||
case MTYPE_LIST:
|
||||
MenuList_Draw( ( menulist_s * ) menu->items[i] );
|
||||
break;
|
||||
case MTYPE_SPINCONTROL:
|
||||
SpinControl_Draw( ( menulist_s * ) menu->items[i] );
|
||||
break;
|
||||
case MTYPE_ACTION:
|
||||
Action_Draw( ( menuaction_s * ) menu->items[i] );
|
||||
break;
|
||||
case MTYPE_SEPARATOR:
|
||||
Separator_Draw( ( menuseparator_s * ) menu->items[i] );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
item = Menu_ItemAtCursor( menu );
|
||||
|
||||
if ( item && item->cursordraw )
|
||||
{
|
||||
item->cursordraw( item );
|
||||
}
|
||||
else if ( menu->cursordraw )
|
||||
{
|
||||
menu->cursordraw( menu );
|
||||
}
|
||||
else if ( item && item->type != MTYPE_FIELD )
|
||||
{
|
||||
if ( item->flags & QMF_LEFT_JUSTIFY )
|
||||
{
|
||||
Draw_Char( menu->x + item->x - 24 + item->cursor_offset, menu->y + item->y, 12 + ( ( int ) ( Sys_Milliseconds()/250 ) & 1 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
Draw_Char( menu->x + item->cursor_offset, menu->y + item->y, 12 + ( ( int ) ( Sys_Milliseconds()/250 ) & 1 ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( item )
|
||||
{
|
||||
if ( item->statusbarfunc )
|
||||
item->statusbarfunc( ( void * ) item );
|
||||
else if ( item->statusbar )
|
||||
Menu_DrawStatusBar( item->statusbar );
|
||||
else
|
||||
Menu_DrawStatusBar( menu->statusbar );
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Menu_DrawStatusBar( menu->statusbar );
|
||||
}
|
||||
}
|
||||
|
||||
void Menu_DrawStatusBar( const char *string )
|
||||
{
|
||||
if ( string )
|
||||
{
|
||||
int l = strlen( string );
|
||||
int maxrow = VID_HEIGHT / 8;
|
||||
int maxcol = VID_WIDTH / 8;
|
||||
int col = maxcol / 2 - l / 2;
|
||||
|
||||
Draw_Fill( 0, VID_HEIGHT-8, VID_WIDTH, 8, 4 );
|
||||
Menu_DrawString( col*8, VID_HEIGHT - 8, string );
|
||||
}
|
||||
else
|
||||
{
|
||||
Draw_Fill( 0, VID_HEIGHT-8, VID_WIDTH, 8, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
void Menu_DrawString( int x, int y, const char *string )
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for ( i = 0; i < strlen( string ); i++ )
|
||||
{
|
||||
Draw_Char( ( x + i*8 ), y, string[i] );
|
||||
}
|
||||
}
|
||||
|
||||
void Menu_DrawStringDark( int x, int y, const char *string )
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for ( i = 0; i < strlen( string ); i++ )
|
||||
{
|
||||
Draw_Char( ( x + i*8 ), y, string[i] + 128 );
|
||||
}
|
||||
}
|
||||
|
||||
void Menu_DrawStringR2L( int x, int y, const char *string )
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for ( i = 0; i < strlen( string ); i++ )
|
||||
{
|
||||
Draw_Char( ( x - i*8 ), y, string[strlen(string)-i-1] );
|
||||
}
|
||||
}
|
||||
|
||||
void Menu_DrawStringR2LDark( int x, int y, const char *string )
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for ( i = 0; i < strlen( string ); i++ )
|
||||
{
|
||||
Draw_Char( ( x - i*8 ), y, string[strlen(string)-i-1]+128 );
|
||||
}
|
||||
}
|
||||
|
||||
void *Menu_ItemAtCursor( menuframework_s *m )
|
||||
{
|
||||
if ( m->cursor < 0 || m->cursor >= m->nitems )
|
||||
return 0;
|
||||
|
||||
return m->items[m->cursor];
|
||||
}
|
||||
|
||||
qboolean Menu_SelectItem( menuframework_s *s )
|
||||
{
|
||||
menucommon_s *item = ( menucommon_s * ) Menu_ItemAtCursor( s );
|
||||
|
||||
if ( item )
|
||||
{
|
||||
switch ( item->type )
|
||||
{
|
||||
case MTYPE_FIELD:
|
||||
return Field_DoEnter( ( menufield_s * ) item ) ;
|
||||
case MTYPE_ACTION:
|
||||
Action_DoEnter( ( menuaction_s * ) item );
|
||||
return true;
|
||||
case MTYPE_LIST:
|
||||
// Menulist_DoEnter( ( menulist_s * ) item );
|
||||
return false;
|
||||
case MTYPE_SPINCONTROL:
|
||||
// SpinControl_DoEnter( ( menulist_s * ) item );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Menu_SetStatusBar( menuframework_s *m, const char *string )
|
||||
{
|
||||
m->statusbar = string;
|
||||
}
|
||||
|
||||
void Menu_SlideItem( menuframework_s *s, int dir )
|
||||
{
|
||||
menucommon_s *item = ( menucommon_s * ) Menu_ItemAtCursor( s );
|
||||
|
||||
if ( item )
|
||||
{
|
||||
switch ( item->type )
|
||||
{
|
||||
case MTYPE_SLIDER:
|
||||
Slider_DoSlide( ( menuslider_s * ) item, dir );
|
||||
break;
|
||||
case MTYPE_SPINCONTROL:
|
||||
SpinControl_DoSlide( ( menulist_s * ) item, dir );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Menu_TallySlots( menuframework_s *menu )
|
||||
{
|
||||
int i;
|
||||
int total = 0;
|
||||
|
||||
for ( i = 0; i < menu->nitems; i++ )
|
||||
{
|
||||
if ( ( ( menucommon_s * ) menu->items[i] )->type == MTYPE_LIST )
|
||||
{
|
||||
int nitems = 0;
|
||||
const char **n = ( ( menulist_s * ) menu->items[i] )->itemnames;
|
||||
|
||||
while (*n)
|
||||
nitems++, n++;
|
||||
|
||||
total += nitems;
|
||||
}
|
||||
else
|
||||
{
|
||||
total++;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
void Menulist_DoEnter( menulist_s *l )
|
||||
{
|
||||
int start;
|
||||
|
||||
start = l->generic.y / 10 + 1;
|
||||
|
||||
l->curvalue = l->generic.parent->cursor - start;
|
||||
|
||||
if ( l->generic.callback )
|
||||
l->generic.callback( l );
|
||||
}
|
||||
|
||||
void MenuList_Draw( menulist_s *l )
|
||||
{
|
||||
const char **n;
|
||||
int y = 0;
|
||||
|
||||
Menu_DrawStringR2LDark( l->generic.x + l->generic.parent->x + LCOLUMN_OFFSET, l->generic.y + l->generic.parent->y, l->generic.name );
|
||||
|
||||
n = l->itemnames;
|
||||
|
||||
Draw_Fill( l->generic.x - 112 + l->generic.parent->x, l->generic.parent->y + l->generic.y + l->curvalue*10 + 10, 128, 10, 16 );
|
||||
while ( *n )
|
||||
{
|
||||
Menu_DrawStringR2LDark( l->generic.x + l->generic.parent->x + LCOLUMN_OFFSET, l->generic.y + l->generic.parent->y + y + 10, *n );
|
||||
|
||||
n++;
|
||||
y += 10;
|
||||
}
|
||||
}
|
||||
|
||||
void Separator_Draw( menuseparator_s *s )
|
||||
{
|
||||
if ( s->generic.name )
|
||||
Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, s->generic.name );
|
||||
}
|
||||
|
||||
void Slider_DoSlide( menuslider_s *s, int dir )
|
||||
{
|
||||
s->curvalue += dir;
|
||||
|
||||
if ( s->curvalue > s->maxvalue )
|
||||
s->curvalue = s->maxvalue;
|
||||
else if ( s->curvalue < s->minvalue )
|
||||
s->curvalue = s->minvalue;
|
||||
|
||||
if ( s->generic.callback )
|
||||
s->generic.callback( s );
|
||||
}
|
||||
|
||||
#define SLIDER_RANGE 10
|
||||
|
||||
void Slider_Draw( menuslider_s *s )
|
||||
{
|
||||
int i;
|
||||
|
||||
Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x + LCOLUMN_OFFSET,
|
||||
s->generic.y + s->generic.parent->y,
|
||||
s->generic.name );
|
||||
|
||||
s->range = ( s->curvalue - s->minvalue ) / ( float ) ( s->maxvalue - s->minvalue );
|
||||
|
||||
if ( s->range < 0)
|
||||
s->range = 0;
|
||||
if ( s->range > 1)
|
||||
s->range = 1;
|
||||
Draw_Char( s->generic.x + s->generic.parent->x + RCOLUMN_OFFSET, s->generic.y + s->generic.parent->y, 128);
|
||||
for ( i = 0; i < SLIDER_RANGE; i++ )
|
||||
Draw_Char( RCOLUMN_OFFSET + s->generic.x + i*8 + s->generic.parent->x + 8, s->generic.y + s->generic.parent->y, 129);
|
||||
Draw_Char( RCOLUMN_OFFSET + s->generic.x + i*8 + s->generic.parent->x + 8, s->generic.y + s->generic.parent->y, 130);
|
||||
Draw_Char( ( int ) ( 8 + RCOLUMN_OFFSET + s->generic.parent->x + s->generic.x + (SLIDER_RANGE-1)*8 * s->range ), s->generic.y + s->generic.parent->y, 131);
|
||||
}
|
||||
|
||||
void SpinControl_DoEnter( menulist_s *s )
|
||||
{
|
||||
s->curvalue++;
|
||||
if ( s->itemnames[s->curvalue] == 0 )
|
||||
s->curvalue = 0;
|
||||
|
||||
if ( s->generic.callback )
|
||||
s->generic.callback( s );
|
||||
}
|
||||
|
||||
void SpinControl_DoSlide( menulist_s *s, int dir )
|
||||
{
|
||||
s->curvalue += dir;
|
||||
|
||||
if ( s->curvalue < 0 )
|
||||
s->curvalue = 0;
|
||||
else if ( s->itemnames[s->curvalue] == 0 )
|
||||
s->curvalue--;
|
||||
|
||||
if ( s->generic.callback )
|
||||
s->generic.callback( s );
|
||||
}
|
||||
|
||||
void SpinControl_Draw( menulist_s *s )
|
||||
{
|
||||
char buffer[100];
|
||||
|
||||
if ( s->generic.name )
|
||||
{
|
||||
Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x + LCOLUMN_OFFSET,
|
||||
s->generic.y + s->generic.parent->y,
|
||||
s->generic.name );
|
||||
}
|
||||
if ( !strchr( s->itemnames[s->curvalue], '\n' ) )
|
||||
{
|
||||
Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, s->itemnames[s->curvalue] );
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy( buffer, s->itemnames[s->curvalue] );
|
||||
*strchr( buffer, '\n' ) = 0;
|
||||
Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, buffer );
|
||||
strcpy( buffer, strchr( s->itemnames[s->curvalue], '\n' ) + 1 );
|
||||
Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y + 10, buffer );
|
||||
}
|
||||
}
|
||||
|
140
client/qmenu.h
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#ifndef __QMENU_H__
|
||||
#define __QMENU_H__
|
||||
|
||||
#define MAXMENUITEMS 64
|
||||
|
||||
#define MTYPE_SLIDER 0
|
||||
#define MTYPE_LIST 1
|
||||
#define MTYPE_ACTION 2
|
||||
#define MTYPE_SPINCONTROL 3
|
||||
#define MTYPE_SEPARATOR 4
|
||||
#define MTYPE_FIELD 5
|
||||
|
||||
#define K_TAB 9
|
||||
#define K_ENTER 13
|
||||
#define K_ESCAPE 27
|
||||
#define K_SPACE 32
|
||||
|
||||
// normal keys should be passed as lowercased ascii
|
||||
|
||||
#define K_BACKSPACE 127
|
||||
#define K_UPARROW 128
|
||||
#define K_DOWNARROW 129
|
||||
#define K_LEFTARROW 130
|
||||
#define K_RIGHTARROW 131
|
||||
|
||||
#define QMF_LEFT_JUSTIFY 0x00000001
|
||||
#define QMF_GRAYED 0x00000002
|
||||
#define QMF_NUMBERSONLY 0x00000004
|
||||
|
||||
typedef struct _tag_menuframework
|
||||
{
|
||||
int x, y;
|
||||
int cursor;
|
||||
|
||||
int nitems;
|
||||
int nslots;
|
||||
void *items[64];
|
||||
|
||||
const char *statusbar;
|
||||
|
||||
void (*cursordraw)( struct _tag_menuframework *m );
|
||||
|
||||
} menuframework_s;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int type;
|
||||
const char *name;
|
||||
int x, y;
|
||||
menuframework_s *parent;
|
||||
int cursor_offset;
|
||||
int localdata[4];
|
||||
unsigned flags;
|
||||
|
||||
const char *statusbar;
|
||||
|
||||
void (*callback)( void *self );
|
||||
void (*statusbarfunc)( void *self );
|
||||
void (*ownerdraw)( void *self );
|
||||
void (*cursordraw)( void *self );
|
||||
} menucommon_s;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
menucommon_s generic;
|
||||
|
||||
char buffer[80];
|
||||
int cursor;
|
||||
int length;
|
||||
int visible_length;
|
||||
int visible_offset;
|
||||
} menufield_s;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
menucommon_s generic;
|
||||
|
||||
float minvalue;
|
||||
float maxvalue;
|
||||
float curvalue;
|
||||
|
||||
float range;
|
||||
} menuslider_s;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
menucommon_s generic;
|
||||
|
||||
int curvalue;
|
||||
|
||||
const char **itemnames;
|
||||
} menulist_s;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
menucommon_s generic;
|
||||
} menuaction_s;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
menucommon_s generic;
|
||||
} menuseparator_s;
|
||||
|
||||
qboolean Field_Key( menufield_s *field, int key );
|
||||
|
||||
void Menu_AddItem( menuframework_s *menu, void *item );
|
||||
void Menu_AdjustCursor( menuframework_s *menu, int dir );
|
||||
void Menu_Center( menuframework_s *menu );
|
||||
void Menu_Draw( menuframework_s *menu );
|
||||
void *Menu_ItemAtCursor( menuframework_s *m );
|
||||
qboolean Menu_SelectItem( menuframework_s *s );
|
||||
void Menu_SetStatusBar( menuframework_s *s, const char *string );
|
||||
void Menu_SlideItem( menuframework_s *s, int dir );
|
||||
int Menu_TallySlots( menuframework_s *menu );
|
||||
|
||||
void Menu_DrawString( int, int, const char * );
|
||||
void Menu_DrawStringDark( int, int, const char * );
|
||||
void Menu_DrawStringR2L( int, int, const char * );
|
||||
void Menu_DrawStringR2LDark( int, int, const char * );
|
||||
|
||||
#endif
|
228
client/ref.h
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#ifndef __REF_H
|
||||
#define __REF_H
|
||||
|
||||
#include "../qcommon/qcommon.h"
|
||||
|
||||
#define MAX_DLIGHTS 32
|
||||
#define MAX_ENTITIES 128
|
||||
#define MAX_PARTICLES 4096
|
||||
#define MAX_LIGHTSTYLES 256
|
||||
|
||||
#define POWERSUIT_SCALE 4.0F
|
||||
|
||||
#define SHELL_RED_COLOR 0xF2
|
||||
#define SHELL_GREEN_COLOR 0xD0
|
||||
#define SHELL_BLUE_COLOR 0xF3
|
||||
|
||||
#define SHELL_RG_COLOR 0xDC
|
||||
//#define SHELL_RB_COLOR 0x86
|
||||
#define SHELL_RB_COLOR 0x68
|
||||
#define SHELL_BG_COLOR 0x78
|
||||
|
||||
//ROGUE
|
||||
#define SHELL_DOUBLE_COLOR 0xDF // 223
|
||||
#define SHELL_HALF_DAM_COLOR 0x90
|
||||
#define SHELL_CYAN_COLOR 0x72
|
||||
//ROGUE
|
||||
|
||||
#define SHELL_WHITE_COLOR 0xD7
|
||||
|
||||
typedef struct entity_s
|
||||
{
|
||||
struct model_s *model; // opaque type outside refresh
|
||||
float angles[3];
|
||||
|
||||
/*
|
||||
** most recent data
|
||||
*/
|
||||
float origin[3]; // also used as RF_BEAM's "from"
|
||||
int frame; // also used as RF_BEAM's diameter
|
||||
|
||||
/*
|
||||
** previous data for lerping
|
||||
*/
|
||||
float oldorigin[3]; // also used as RF_BEAM's "to"
|
||||
int oldframe;
|
||||
|
||||
/*
|
||||
** misc
|
||||
*/
|
||||
float backlerp; // 0.0 = current, 1.0 = old
|
||||
int skinnum; // also used as RF_BEAM's palette index
|
||||
|
||||
int lightstyle; // for flashing entities
|
||||
float alpha; // ignore if RF_TRANSLUCENT isn't set
|
||||
|
||||
struct image_s *skin; // NULL for inline skin
|
||||
int flags;
|
||||
|
||||
} entity_t;
|
||||
|
||||
#define ENTITY_FLAGS 68
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vec3_t origin;
|
||||
vec3_t color;
|
||||
float intensity;
|
||||
} dlight_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vec3_t origin;
|
||||
int color;
|
||||
float alpha;
|
||||
} particle_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float rgb[3]; // 0.0 - 2.0
|
||||
float white; // highest of rgb
|
||||
} lightstyle_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int x, y, width, height;// in virtual screen coordinates
|
||||
float fov_x, fov_y;
|
||||
float vieworg[3];
|
||||
float viewangles[3];
|
||||
float blend[4]; // rgba 0-1 full screen blend
|
||||
float time; // time is uesed to auto animate
|
||||
int rdflags; // RDF_UNDERWATER, etc
|
||||
|
||||
byte *areabits; // if not NULL, only areas with set bits will be drawn
|
||||
|
||||
lightstyle_t *lightstyles; // [MAX_LIGHTSTYLES]
|
||||
|
||||
int num_entities;
|
||||
entity_t *entities;
|
||||
|
||||
int num_dlights;
|
||||
dlight_t *dlights;
|
||||
|
||||
int num_particles;
|
||||
particle_t *particles;
|
||||
} refdef_t;
|
||||
|
||||
|
||||
|
||||
#define API_VERSION 3
|
||||
|
||||
//
|
||||
// these are the functions exported by the refresh module
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
// if api_version is different, the dll cannot be used
|
||||
int api_version;
|
||||
|
||||
// called when the library is loaded
|
||||
qboolean (*Init) ( void *hinstance, void *wndproc );
|
||||
|
||||
// called before the library is unloaded
|
||||
void (*Shutdown) (void);
|
||||
|
||||
// All data that will be used in a level should be
|
||||
// registered before rendering any frames to prevent disk hits,
|
||||
// but they can still be registered at a later time
|
||||
// if necessary.
|
||||
//
|
||||
// EndRegistration will free any remaining data that wasn't registered.
|
||||
// Any model_s or skin_s pointers from before the BeginRegistration
|
||||
// are no longer valid after EndRegistration.
|
||||
//
|
||||
// Skins and images need to be differentiated, because skins
|
||||
// are flood filled to eliminate mip map edge errors, and pics have
|
||||
// an implicit "pics/" prepended to the name. (a pic name that starts with a
|
||||
// slash will not use the "pics/" prefix or the ".pcx" postfix)
|
||||
void (*BeginRegistration) (char *map);
|
||||
struct model_s *(*RegisterModel) (char *name);
|
||||
struct image_s *(*RegisterSkin) (char *name);
|
||||
struct image_s *(*RegisterPic) (char *name);
|
||||
void (*SetSky) (char *name, float rotate, vec3_t axis);
|
||||
void (*EndRegistration) (void);
|
||||
|
||||
void (*RenderFrame) (refdef_t *fd);
|
||||
|
||||
void (*DrawGetPicSize) (int *w, int *h, char *name); // will return 0 0 if not found
|
||||
void (*DrawPic) (int x, int y, char *name);
|
||||
void (*DrawStretchPic) (int x, int y, int w, int h, char *name);
|
||||
void (*DrawChar) (int x, int y, int c);
|
||||
void (*DrawTileClear) (int x, int y, int w, int h, char *name);
|
||||
void (*DrawFill) (int x, int y, int w, int h, int c);
|
||||
void (*DrawFadeScreen) (void);
|
||||
|
||||
// Draw images for cinematic rendering (which can have a different palette). Note that calls
|
||||
void (*DrawStretchRaw) (int x, int y, int w, int h, int cols, int rows, byte *data);
|
||||
|
||||
/*
|
||||
** video mode and refresh state management entry points
|
||||
*/
|
||||
void (*CinematicSetPalette)( const unsigned char *palette); // NULL = game palette
|
||||
void (*BeginFrame)( float camera_separation );
|
||||
void (*EndFrame) (void);
|
||||
|
||||
void (*AppActivate)( qboolean activate );
|
||||
|
||||
} refexport_t;
|
||||
|
||||
//
|
||||
// these are the functions imported by the refresh module
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
void (*Sys_Error) (int err_level, char *str, ...);
|
||||
|
||||
void (*Cmd_AddCommand) (char *name, void(*cmd)(void));
|
||||
void (*Cmd_RemoveCommand) (char *name);
|
||||
int (*Cmd_Argc) (void);
|
||||
char *(*Cmd_Argv) (int i);
|
||||
void (*Cmd_ExecuteText) (int exec_when, char *text);
|
||||
|
||||
void (*Con_Printf) (int print_level, char *str, ...);
|
||||
|
||||
// files will be memory mapped read only
|
||||
// the returned buffer may be part of a larger pak file,
|
||||
// or a discrete file from anywhere in the quake search path
|
||||
// a -1 return means the file does not exist
|
||||
// NULL can be passed for buf to just determine existance
|
||||
int (*FS_LoadFile) (char *name, void **buf);
|
||||
void (*FS_FreeFile) (void *buf);
|
||||
|
||||
// gamedir will be the current directory that generated
|
||||
// files should be stored to, ie: "f:\quake\id1"
|
||||
char *(*FS_Gamedir) (void);
|
||||
|
||||
cvar_t *(*Cvar_Get) (char *name, char *value, int flags);
|
||||
cvar_t *(*Cvar_Set)( char *name, char *value );
|
||||
void (*Cvar_SetValue)( char *name, float value );
|
||||
|
||||
qboolean (*Vid_GetModeInfo)( int *width, int *height, int mode );
|
||||
void (*Vid_MenuInit)( void );
|
||||
void (*Vid_NewWindow)( int width, int height );
|
||||
} refimport_t;
|
||||
|
||||
|
||||
// this is the only function actually exported at the linker level
|
||||
typedef refexport_t (*GetRefAPI_t) (refimport_t);
|
||||
|
||||
#endif // __REF_H
|
62
client/screen.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// screen.h
|
||||
|
||||
void SCR_Init (void);
|
||||
|
||||
void SCR_UpdateScreen (void);
|
||||
|
||||
void SCR_SizeUp (void);
|
||||
void SCR_SizeDown (void);
|
||||
void SCR_CenterPrint (char *str);
|
||||
void SCR_BeginLoadingPlaque (void);
|
||||
void SCR_EndLoadingPlaque (void);
|
||||
|
||||
void SCR_DebugGraph (float value, int color);
|
||||
|
||||
void SCR_TouchPics (void);
|
||||
|
||||
void SCR_RunConsole (void);
|
||||
|
||||
extern float scr_con_current;
|
||||
extern float scr_conlines; // lines of console to display
|
||||
|
||||
extern int sb_lines;
|
||||
|
||||
extern cvar_t *scr_viewsize;
|
||||
extern cvar_t *crosshair;
|
||||
|
||||
extern vrect_t scr_vrect; // position of render window
|
||||
|
||||
extern char crosshair_pic[MAX_QPATH];
|
||||
extern int crosshair_width, crosshair_height;
|
||||
|
||||
void SCR_AddDirtyPoint (int x, int y);
|
||||
void SCR_DirtyScreen (void);
|
||||
|
||||
//
|
||||
// scr_cin.c
|
||||
//
|
||||
void SCR_PlayCinematic (char *name);
|
||||
qboolean SCR_DrawCinematic (void);
|
||||
void SCR_RunCinematic (void);
|
||||
void SCR_StopCinematic (void);
|
||||
void SCR_FinishCinematic (void);
|
||||
|
1214
client/snd_dma.c
Normal file
164
client/snd_loc.h
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// snd_loc.h -- private sound functions
|
||||
|
||||
// !!! if this is changed, the asm code must change !!!
|
||||
typedef struct
|
||||
{
|
||||
int left;
|
||||
int right;
|
||||
} portable_samplepair_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int length;
|
||||
int loopstart;
|
||||
int speed; // not needed, because converted on load?
|
||||
int width;
|
||||
int stereo;
|
||||
byte data[1]; // variable sized
|
||||
} sfxcache_t;
|
||||
|
||||
typedef struct sfx_s
|
||||
{
|
||||
char name[MAX_QPATH];
|
||||
int registration_sequence;
|
||||
sfxcache_t *cache;
|
||||
char *truename;
|
||||
} sfx_t;
|
||||
|
||||
// a playsound_t will be generated by each call to S_StartSound,
|
||||
// when the mixer reaches playsound->begin, the playsound will
|
||||
// be assigned to a channel
|
||||
typedef struct playsound_s
|
||||
{
|
||||
struct playsound_s *prev, *next;
|
||||
sfx_t *sfx;
|
||||
float volume;
|
||||
float attenuation;
|
||||
int entnum;
|
||||
int entchannel;
|
||||
qboolean fixed_origin; // use origin field instead of entnum's origin
|
||||
vec3_t origin;
|
||||
unsigned begin; // begin on this sample
|
||||
} playsound_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int channels;
|
||||
int samples; // mono samples in buffer
|
||||
int submission_chunk; // don't mix less than this #
|
||||
int samplepos; // in mono samples
|
||||
int samplebits;
|
||||
int speed;
|
||||
byte *buffer;
|
||||
} dma_t;
|
||||
|
||||
// !!! if this is changed, the asm code must change !!!
|
||||
typedef struct
|
||||
{
|
||||
sfx_t *sfx; // sfx number
|
||||
int leftvol; // 0-255 volume
|
||||
int rightvol; // 0-255 volume
|
||||
int end; // end time in global paintsamples
|
||||
int pos; // sample position in sfx
|
||||
int looping; // where to loop, -1 = no looping OBSOLETE?
|
||||
int entnum; // to allow overriding a specific sound
|
||||
int entchannel; //
|
||||
vec3_t origin; // only use if fixed_origin is set
|
||||
vec_t dist_mult; // distance multiplier (attenuation/clipK)
|
||||
int master_vol; // 0-255 master volume
|
||||
qboolean fixed_origin; // use origin instead of fetching entnum's origin
|
||||
qboolean autosound; // from an entity->sound, cleared each frame
|
||||
} channel_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int rate;
|
||||
int width;
|
||||
int channels;
|
||||
int loopstart;
|
||||
int samples;
|
||||
int dataofs; // chunk starts this many bytes from file start
|
||||
} wavinfo_t;
|
||||
|
||||
|
||||
/*
|
||||
====================================================================
|
||||
|
||||
SYSTEM SPECIFIC FUNCTIONS
|
||||
|
||||
====================================================================
|
||||
*/
|
||||
|
||||
// initializes cycling through a DMA buffer and returns information on it
|
||||
qboolean SNDDMA_Init(void);
|
||||
|
||||
// gets the current DMA position
|
||||
int SNDDMA_GetDMAPos(void);
|
||||
|
||||
// shutdown the DMA xfer.
|
||||
void SNDDMA_Shutdown(void);
|
||||
|
||||
void SNDDMA_BeginPainting (void);
|
||||
|
||||
void SNDDMA_Submit(void);
|
||||
|
||||
//====================================================================
|
||||
|
||||
#define MAX_CHANNELS 32
|
||||
extern channel_t channels[MAX_CHANNELS];
|
||||
|
||||
extern int paintedtime;
|
||||
extern int s_rawend;
|
||||
extern vec3_t listener_origin;
|
||||
extern vec3_t listener_forward;
|
||||
extern vec3_t listener_right;
|
||||
extern vec3_t listener_up;
|
||||
extern dma_t dma;
|
||||
extern playsound_t s_pendingplays;
|
||||
|
||||
#define MAX_RAW_SAMPLES 8192
|
||||
extern portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES];
|
||||
|
||||
extern cvar_t *s_volume;
|
||||
extern cvar_t *s_nosound;
|
||||
extern cvar_t *s_loadas8bit;
|
||||
extern cvar_t *s_khz;
|
||||
extern cvar_t *s_show;
|
||||
extern cvar_t *s_mixahead;
|
||||
extern cvar_t *s_testsound;
|
||||
extern cvar_t *s_primary;
|
||||
|
||||
wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength);
|
||||
|
||||
void S_InitScaletable (void);
|
||||
|
||||
sfxcache_t *S_LoadSound (sfx_t *s);
|
||||
|
||||
void S_IssuePlaysound (playsound_t *ps);
|
||||
|
||||
void S_PaintChannels(int endtime);
|
||||
|
||||
// picks a channel based on priorities, empty slots, number of channels
|
||||
channel_t *S_PickChannel(int entnum, int entchannel);
|
||||
|
||||
// spatializes a channel
|
||||
void S_Spatialize(channel_t *ch);
|
359
client/snd_mem.c
Normal file
|
@ -0,0 +1,359 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// snd_mem.c: sound caching
|
||||
|
||||
#include "client.h"
|
||||
#include "snd_loc.h"
|
||||
|
||||
int cache_full_cycle;
|
||||
|
||||
byte *S_Alloc (int size);
|
||||
|
||||
/*
|
||||
================
|
||||
ResampleSfx
|
||||
================
|
||||
*/
|
||||
void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data)
|
||||
{
|
||||
int outcount;
|
||||
int srcsample;
|
||||
float stepscale;
|
||||
int i;
|
||||
int sample, samplefrac, fracstep;
|
||||
sfxcache_t *sc;
|
||||
|
||||
sc = sfx->cache;
|
||||
if (!sc)
|
||||
return;
|
||||
|
||||
stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2
|
||||
|
||||
outcount = sc->length / stepscale;
|
||||
sc->length = outcount;
|
||||
if (sc->loopstart != -1)
|
||||
sc->loopstart = sc->loopstart / stepscale;
|
||||
|
||||
sc->speed = dma.speed;
|
||||
if (s_loadas8bit->value)
|
||||
sc->width = 1;
|
||||
else
|
||||
sc->width = inwidth;
|
||||
sc->stereo = 0;
|
||||
|
||||
// resample / decimate to the current source rate
|
||||
|
||||
if (stepscale == 1 && inwidth == 1 && sc->width == 1)
|
||||
{
|
||||
// fast special case
|
||||
for (i=0 ; i<outcount ; i++)
|
||||
((signed char *)sc->data)[i]
|
||||
= (int)( (unsigned char)(data[i]) - 128);
|
||||
}
|
||||
else
|
||||
{
|
||||
// general case
|
||||
samplefrac = 0;
|
||||
fracstep = stepscale*256;
|
||||
for (i=0 ; i<outcount ; i++)
|
||||
{
|
||||
srcsample = samplefrac >> 8;
|
||||
samplefrac += fracstep;
|
||||
if (inwidth == 2)
|
||||
sample = LittleShort ( ((short *)data)[srcsample] );
|
||||
else
|
||||
sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8;
|
||||
if (sc->width == 2)
|
||||
((short *)sc->data)[i] = sample;
|
||||
else
|
||||
((signed char *)sc->data)[i] = sample >> 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
|
||||
/*
|
||||
==============
|
||||
S_LoadSound
|
||||
==============
|
||||
*/
|
||||
sfxcache_t *S_LoadSound (sfx_t *s)
|
||||
{
|
||||
char namebuffer[MAX_QPATH];
|
||||
byte *data;
|
||||
wavinfo_t info;
|
||||
int len;
|
||||
float stepscale;
|
||||
sfxcache_t *sc;
|
||||
int size;
|
||||
char *name;
|
||||
|
||||
if (s->name[0] == '*')
|
||||
return NULL;
|
||||
|
||||
// see if still in memory
|
||||
sc = s->cache;
|
||||
if (sc)
|
||||
return sc;
|
||||
|
||||
//Com_Printf ("S_LoadSound: %x\n", (int)stackbuf);
|
||||
// load it in
|
||||
if (s->truename)
|
||||
name = s->truename;
|
||||
else
|
||||
name = s->name;
|
||||
|
||||
if (name[0] == '#')
|
||||
strcpy(namebuffer, &name[1]);
|
||||
else
|
||||
Com_sprintf (namebuffer, sizeof(namebuffer), "sound/%s", name);
|
||||
|
||||
// Com_Printf ("loading %s\n",namebuffer);
|
||||
|
||||
size = FS_LoadFile (namebuffer, (void **)&data);
|
||||
|
||||
if (!data)
|
||||
{
|
||||
Com_DPrintf ("Couldn't load %s\n", namebuffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
info = GetWavinfo (s->name, data, size);
|
||||
if (info.channels != 1)
|
||||
{
|
||||
Com_Printf ("%s is a stereo sample\n",s->name);
|
||||
FS_FreeFile (data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
stepscale = (float)info.rate / dma.speed;
|
||||
len = info.samples / stepscale;
|
||||
|
||||
len = len * info.width * info.channels;
|
||||
|
||||
sc = s->cache = Z_Malloc (len + sizeof(sfxcache_t));
|
||||
if (!sc)
|
||||
{
|
||||
FS_FreeFile (data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sc->length = info.samples;
|
||||
sc->loopstart = info.loopstart;
|
||||
sc->speed = info.rate;
|
||||
sc->width = info.width;
|
||||
sc->stereo = info.channels;
|
||||
|
||||
ResampleSfx (s, sc->speed, sc->width, data + info.dataofs);
|
||||
|
||||
FS_FreeFile (data);
|
||||
|
||||
return sc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
WAV loading
|
||||
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
|
||||
byte *data_p;
|
||||
byte *iff_end;
|
||||
byte *last_chunk;
|
||||
byte *iff_data;
|
||||
int iff_chunk_len;
|
||||
|
||||
|
||||
short GetLittleShort(void)
|
||||
{
|
||||
short val = 0;
|
||||
val = *data_p;
|
||||
val = val + (*(data_p+1)<<8);
|
||||
data_p += 2;
|
||||
return val;
|
||||
}
|
||||
|
||||
int GetLittleLong(void)
|
||||
{
|
||||
int val = 0;
|
||||
val = *data_p;
|
||||
val = val + (*(data_p+1)<<8);
|
||||
val = val + (*(data_p+2)<<16);
|
||||
val = val + (*(data_p+3)<<24);
|
||||
data_p += 4;
|
||||
return val;
|
||||
}
|
||||
|
||||
void FindNextChunk(char *name)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
data_p=last_chunk;
|
||||
|
||||
if (data_p >= iff_end)
|
||||
{ // didn't find the chunk
|
||||
data_p = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
data_p += 4;
|
||||
iff_chunk_len = GetLittleLong();
|
||||
if (iff_chunk_len < 0)
|
||||
{
|
||||
data_p = NULL;
|
||||
return;
|
||||
}
|
||||
// if (iff_chunk_len > 1024*1024)
|
||||
// Sys_Error ("FindNextChunk: %i length is past the 1 meg sanity limit", iff_chunk_len);
|
||||
data_p -= 8;
|
||||
last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 );
|
||||
if (!strncmp(data_p, name, 4))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void FindChunk(char *name)
|
||||
{
|
||||
last_chunk = iff_data;
|
||||
FindNextChunk (name);
|
||||
}
|
||||
|
||||
|
||||
void DumpChunks(void)
|
||||
{
|
||||
char str[5];
|
||||
|
||||
str[4] = 0;
|
||||
data_p=iff_data;
|
||||
do
|
||||
{
|
||||
memcpy (str, data_p, 4);
|
||||
data_p += 4;
|
||||
iff_chunk_len = GetLittleLong();
|
||||
Com_Printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len);
|
||||
data_p += (iff_chunk_len + 1) & ~1;
|
||||
} while (data_p < iff_end);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
GetWavinfo
|
||||
============
|
||||
*/
|
||||
wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength)
|
||||
{
|
||||
wavinfo_t info;
|
||||
int i;
|
||||
int format;
|
||||
int samples;
|
||||
|
||||
memset (&info, 0, sizeof(info));
|
||||
|
||||
if (!wav)
|
||||
return info;
|
||||
|
||||
iff_data = wav;
|
||||
iff_end = wav + wavlength;
|
||||
|
||||
// find "RIFF" chunk
|
||||
FindChunk("RIFF");
|
||||
if (!(data_p && !strncmp(data_p+8, "WAVE", 4)))
|
||||
{
|
||||
Com_Printf("Missing RIFF/WAVE chunks\n");
|
||||
return info;
|
||||
}
|
||||
|
||||
// get "fmt " chunk
|
||||
iff_data = data_p + 12;
|
||||
// DumpChunks ();
|
||||
|
||||
FindChunk("fmt ");
|
||||
if (!data_p)
|
||||
{
|
||||
Com_Printf("Missing fmt chunk\n");
|
||||
return info;
|
||||
}
|
||||
data_p += 8;
|
||||
format = GetLittleShort();
|
||||
if (format != 1)
|
||||
{
|
||||
Com_Printf("Microsoft PCM format only\n");
|
||||
return info;
|
||||
}
|
||||
|
||||
info.channels = GetLittleShort();
|
||||
info.rate = GetLittleLong();
|
||||
data_p += 4+2;
|
||||
info.width = GetLittleShort() / 8;
|
||||
|
||||
// get cue chunk
|
||||
FindChunk("cue ");
|
||||
if (data_p)
|
||||
{
|
||||
data_p += 32;
|
||||
info.loopstart = GetLittleLong();
|
||||
// Com_Printf("loopstart=%d\n", sfx->loopstart);
|
||||
|
||||
// if the next chunk is a LIST chunk, look for a cue length marker
|
||||
FindNextChunk ("LIST");
|
||||
if (data_p)
|
||||
{
|
||||
if (!strncmp (data_p + 28, "mark", 4))
|
||||
{ // this is not a proper parse, but it works with cooledit...
|
||||
data_p += 24;
|
||||
i = GetLittleLong (); // samples in loop
|
||||
info.samples = info.loopstart + i;
|
||||
// Com_Printf("looped length: %i\n", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
info.loopstart = -1;
|
||||
|
||||
// find data chunk
|
||||
FindChunk("data");
|
||||
if (!data_p)
|
||||
{
|
||||
Com_Printf("Missing data chunk\n");
|
||||
return info;
|
||||
}
|
||||
|
||||
data_p += 4;
|
||||
samples = GetLittleLong () / info.width;
|
||||
|
||||
if (info.samples)
|
||||
{
|
||||
if (samples < info.samples)
|
||||
Com_Error (ERR_DROP, "Sound %s has a bad loop length", name);
|
||||
}
|
||||
else
|
||||
info.samples = samples;
|
||||
|
||||
info.dataofs = data_p - wav;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
499
client/snd_mix.c
Normal file
|
@ -0,0 +1,499 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// snd_mix.c -- portable code to mix sounds for snd_dma.c
|
||||
|
||||
#include "client.h"
|
||||
#include "snd_loc.h"
|
||||
|
||||
#define PAINTBUFFER_SIZE 2048
|
||||
portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE];
|
||||
int snd_scaletable[32][256];
|
||||
int *snd_p, snd_linear_count, snd_vol;
|
||||
short *snd_out;
|
||||
|
||||
void S_WriteLinearBlastStereo16 (void);
|
||||
|
||||
#if !(defined __linux__ && defined __i386__)
|
||||
#if !id386
|
||||
|
||||
void S_WriteLinearBlastStereo16 (void)
|
||||
{
|
||||
int i;
|
||||
int val;
|
||||
|
||||
for (i=0 ; i<snd_linear_count ; i+=2)
|
||||
{
|
||||
val = snd_p[i]>>8;
|
||||
if (val > 0x7fff)
|
||||
snd_out[i] = 0x7fff;
|
||||
else if (val < (short)0x8000)
|
||||
snd_out[i] = (short)0x8000;
|
||||
else
|
||||
snd_out[i] = val;
|
||||
|
||||
val = snd_p[i+1]>>8;
|
||||
if (val > 0x7fff)
|
||||
snd_out[i+1] = 0x7fff;
|
||||
else if (val < (short)0x8000)
|
||||
snd_out[i+1] = (short)0x8000;
|
||||
else
|
||||
snd_out[i+1] = val;
|
||||
}
|
||||
}
|
||||
#else
|
||||
__declspec( naked ) void S_WriteLinearBlastStereo16 (void)
|
||||
{
|
||||
__asm {
|
||||
|
||||
push edi
|
||||
push ebx
|
||||
mov ecx,ds:dword ptr[snd_linear_count]
|
||||
mov ebx,ds:dword ptr[snd_p]
|
||||
mov edi,ds:dword ptr[snd_out]
|
||||
LWLBLoopTop:
|
||||
mov eax,ds:dword ptr[-8+ebx+ecx*4]
|
||||
sar eax,8
|
||||
cmp eax,07FFFh
|
||||
jg LClampHigh
|
||||
cmp eax,0FFFF8000h
|
||||
jnl LClampDone
|
||||
mov eax,0FFFF8000h
|
||||
jmp LClampDone
|
||||
LClampHigh:
|
||||
mov eax,07FFFh
|
||||
LClampDone:
|
||||
mov edx,ds:dword ptr[-4+ebx+ecx*4]
|
||||
sar edx,8
|
||||
cmp edx,07FFFh
|
||||
jg LClampHigh2
|
||||
cmp edx,0FFFF8000h
|
||||
jnl LClampDone2
|
||||
mov edx,0FFFF8000h
|
||||
jmp LClampDone2
|
||||
LClampHigh2:
|
||||
mov edx,07FFFh
|
||||
LClampDone2:
|
||||
shl edx,16
|
||||
and eax,0FFFFh
|
||||
or edx,eax
|
||||
mov ds:dword ptr[-4+edi+ecx*2],edx
|
||||
sub ecx,2
|
||||
jnz LWLBLoopTop
|
||||
pop ebx
|
||||
pop edi
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void S_TransferStereo16 (unsigned long *pbuf, int endtime)
|
||||
{
|
||||
int lpos;
|
||||
int lpaintedtime;
|
||||
|
||||
snd_p = (int *) paintbuffer;
|
||||
lpaintedtime = paintedtime;
|
||||
|
||||
while (lpaintedtime < endtime)
|
||||
{
|
||||
// handle recirculating buffer issues
|
||||
lpos = lpaintedtime & ((dma.samples>>1)-1);
|
||||
|
||||
snd_out = (short *) pbuf + (lpos<<1);
|
||||
|
||||
snd_linear_count = (dma.samples>>1) - lpos;
|
||||
if (lpaintedtime + snd_linear_count > endtime)
|
||||
snd_linear_count = endtime - lpaintedtime;
|
||||
|
||||
snd_linear_count <<= 1;
|
||||
|
||||
// write a linear blast of samples
|
||||
S_WriteLinearBlastStereo16 ();
|
||||
|
||||
snd_p += snd_linear_count;
|
||||
lpaintedtime += (snd_linear_count>>1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===================
|
||||
S_TransferPaintBuffer
|
||||
|
||||
===================
|
||||
*/
|
||||
void S_TransferPaintBuffer(int endtime)
|
||||
{
|
||||
int out_idx;
|
||||
int count;
|
||||
int out_mask;
|
||||
int *p;
|
||||
int step;
|
||||
int val;
|
||||
unsigned long *pbuf;
|
||||
|
||||
pbuf = (unsigned long *)dma.buffer;
|
||||
|
||||
if (s_testsound->value)
|
||||
{
|
||||
int i;
|
||||
int count;
|
||||
|
||||
// write a fixed sine wave
|
||||
count = (endtime - paintedtime);
|
||||
for (i=0 ; i<count ; i++)
|
||||
paintbuffer[i].left = paintbuffer[i].right = sin((paintedtime+i)*0.1)*20000*256;
|
||||
}
|
||||
|
||||
|
||||
if (dma.samplebits == 16 && dma.channels == 2)
|
||||
{ // optimized case
|
||||
S_TransferStereo16 (pbuf, endtime);
|
||||
}
|
||||
else
|
||||
{ // general case
|
||||
p = (int *) paintbuffer;
|
||||
count = (endtime - paintedtime) * dma.channels;
|
||||
out_mask = dma.samples - 1;
|
||||
out_idx = paintedtime * dma.channels & out_mask;
|
||||
step = 3 - dma.channels;
|
||||
|
||||
if (dma.samplebits == 16)
|
||||
{
|
||||
short *out = (short *) pbuf;
|
||||
while (count--)
|
||||
{
|
||||
val = *p >> 8;
|
||||
p+= step;
|
||||
if (val > 0x7fff)
|
||||
val = 0x7fff;
|
||||
else if (val < (short)0x8000)
|
||||
val = (short)0x8000;
|
||||
out[out_idx] = val;
|
||||
out_idx = (out_idx + 1) & out_mask;
|
||||
}
|
||||
}
|
||||
else if (dma.samplebits == 8)
|
||||
{
|
||||
unsigned char *out = (unsigned char *) pbuf;
|
||||
while (count--)
|
||||
{
|
||||
val = *p >> 8;
|
||||
p+= step;
|
||||
if (val > 0x7fff)
|
||||
val = 0x7fff;
|
||||
else if (val < (short)0x8000)
|
||||
val = (short)0x8000;
|
||||
out[out_idx] = (val>>8) + 128;
|
||||
out_idx = (out_idx + 1) & out_mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
CHANNEL MIXING
|
||||
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
void S_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int endtime, int offset);
|
||||
void S_PaintChannelFrom16 (channel_t *ch, sfxcache_t *sc, int endtime, int offset);
|
||||
|
||||
void S_PaintChannels(int endtime)
|
||||
{
|
||||
int i;
|
||||
int end;
|
||||
channel_t *ch;
|
||||
sfxcache_t *sc;
|
||||
int ltime, count;
|
||||
playsound_t *ps;
|
||||
|
||||
snd_vol = s_volume->value*256;
|
||||
|
||||
//Com_Printf ("%i to %i\n", paintedtime, endtime);
|
||||
while (paintedtime < endtime)
|
||||
{
|
||||
// if paintbuffer is smaller than DMA buffer
|
||||
end = endtime;
|
||||
if (endtime - paintedtime > PAINTBUFFER_SIZE)
|
||||
end = paintedtime + PAINTBUFFER_SIZE;
|
||||
|
||||
// start any playsounds
|
||||
while (1)
|
||||
{
|
||||
ps = s_pendingplays.next;
|
||||
if (ps == &s_pendingplays)
|
||||
break; // no more pending sounds
|
||||
if (ps->begin <= paintedtime)
|
||||
{
|
||||
S_IssuePlaysound (ps);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ps->begin < end)
|
||||
end = ps->begin; // stop here
|
||||
break;
|
||||
}
|
||||
|
||||
// clear the paint buffer
|
||||
if (s_rawend < paintedtime)
|
||||
{
|
||||
// Com_Printf ("clear\n");
|
||||
memset(paintbuffer, 0, (end - paintedtime) * sizeof(portable_samplepair_t));
|
||||
}
|
||||
else
|
||||
{ // copy from the streaming sound source
|
||||
int s;
|
||||
int stop;
|
||||
|
||||
stop = (end < s_rawend) ? end : s_rawend;
|
||||
|
||||
for (i=paintedtime ; i<stop ; i++)
|
||||
{
|
||||
s = i&(MAX_RAW_SAMPLES-1);
|
||||
paintbuffer[i-paintedtime] = s_rawsamples[s];
|
||||
}
|
||||
// if (i != end)
|
||||
// Com_Printf ("partial stream\n");
|
||||
// else
|
||||
// Com_Printf ("full stream\n");
|
||||
for ( ; i<end ; i++)
|
||||
{
|
||||
paintbuffer[i-paintedtime].left =
|
||||
paintbuffer[i-paintedtime].right = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// paint in the channels.
|
||||
ch = channels;
|
||||
for (i=0; i<MAX_CHANNELS ; i++, ch++)
|
||||
{
|
||||
ltime = paintedtime;
|
||||
|
||||
while (ltime < end)
|
||||
{
|
||||
if (!ch->sfx || (!ch->leftvol && !ch->rightvol) )
|
||||
break;
|
||||
|
||||
// max painting is to the end of the buffer
|
||||
count = end - ltime;
|
||||
|
||||
// might be stopped by running out of data
|
||||
if (ch->end - ltime < count)
|
||||
count = ch->end - ltime;
|
||||
|
||||
sc = S_LoadSound (ch->sfx);
|
||||
if (!sc)
|
||||
break;
|
||||
|
||||
if (count > 0 && ch->sfx)
|
||||
{
|
||||
if (sc->width == 1)// FIXME; 8 bit asm is wrong now
|
||||
S_PaintChannelFrom8(ch, sc, count, ltime - paintedtime);
|
||||
else
|
||||
S_PaintChannelFrom16(ch, sc, count, ltime - paintedtime);
|
||||
|
||||
ltime += count;
|
||||
}
|
||||
|
||||
// if at end of loop, restart
|
||||
if (ltime >= ch->end)
|
||||
{
|
||||
if (ch->autosound)
|
||||
{ // autolooping sounds always go back to start
|
||||
ch->pos = 0;
|
||||
ch->end = ltime + sc->length;
|
||||
}
|
||||
else if (sc->loopstart >= 0)
|
||||
{
|
||||
ch->pos = sc->loopstart;
|
||||
ch->end = ltime + sc->length - ch->pos;
|
||||
}
|
||||
else
|
||||
{ // channel just stopped
|
||||
ch->sfx = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// transfer out according to DMA format
|
||||
S_TransferPaintBuffer(end);
|
||||
paintedtime = end;
|
||||
}
|
||||
}
|
||||
|
||||
void S_InitScaletable (void)
|
||||
{
|
||||
int i, j;
|
||||
int scale;
|
||||
|
||||
s_volume->modified = false;
|
||||
for (i=0 ; i<32 ; i++)
|
||||
{
|
||||
scale = i * 8 * 256 * s_volume->value;
|
||||
for (j=0 ; j<256 ; j++)
|
||||
snd_scaletable[i][j] = ((signed char)j) * scale;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if !(defined __linux__ && defined __i386__)
|
||||
#if !id386
|
||||
|
||||
void S_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int count, int offset)
|
||||
{
|
||||
int data;
|
||||
int *lscale, *rscale;
|
||||
unsigned char *sfx;
|
||||
int i;
|
||||
portable_samplepair_t *samp;
|
||||
|
||||
if (ch->leftvol > 255)
|
||||
ch->leftvol = 255;
|
||||
if (ch->rightvol > 255)
|
||||
ch->rightvol = 255;
|
||||
|
||||
//ZOID-- >>11 has been changed to >>3, >>11 didn't make much sense
|
||||
//as it would always be zero.
|
||||
lscale = snd_scaletable[ ch->leftvol >> 3];
|
||||
rscale = snd_scaletable[ ch->rightvol >> 3];
|
||||
sfx = (signed char *)sc->data + ch->pos;
|
||||
|
||||
samp = &paintbuffer[offset];
|
||||
|
||||
for (i=0 ; i<count ; i++, samp++)
|
||||
{
|
||||
data = sfx[i];
|
||||
samp->left += lscale[data];
|
||||
samp->right += rscale[data];
|
||||
}
|
||||
|
||||
ch->pos += count;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
__declspec( naked ) void S_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int count, int offset)
|
||||
{
|
||||
__asm {
|
||||
push esi
|
||||
push edi
|
||||
push ebx
|
||||
push ebp
|
||||
mov ebx,ds:dword ptr[4+16+esp]
|
||||
mov esi,ds:dword ptr[8+16+esp]
|
||||
mov eax,ds:dword ptr[4+ebx]
|
||||
mov edx,ds:dword ptr[8+ebx]
|
||||
cmp eax,255
|
||||
jna LLeftSet
|
||||
mov eax,255
|
||||
LLeftSet:
|
||||
cmp edx,255
|
||||
jna LRightSet
|
||||
mov edx,255
|
||||
LRightSet:
|
||||
and eax,0F8h
|
||||
add esi,20
|
||||
and edx,0F8h
|
||||
mov edi,ds:dword ptr[16+ebx]
|
||||
mov ecx,ds:dword ptr[12+16+esp]
|
||||
add esi,edi
|
||||
shl eax,7
|
||||
add edi,ecx
|
||||
shl edx,7
|
||||
mov ds:dword ptr[16+ebx],edi
|
||||
add eax,offset snd_scaletable
|
||||
add edx,offset snd_scaletable
|
||||
sub ebx,ebx
|
||||
mov bl,ds:byte ptr[-1+esi+ecx*1]
|
||||
test ecx,1
|
||||
jz LMix8Loop
|
||||
mov edi,ds:dword ptr[eax+ebx*4]
|
||||
mov ebp,ds:dword ptr[edx+ebx*4]
|
||||
add edi,ds:dword ptr[paintbuffer+0-8+ecx*8]
|
||||
add ebp,ds:dword ptr[paintbuffer+4-8+ecx*8]
|
||||
mov ds:dword ptr[paintbuffer+0-8+ecx*8],edi
|
||||
mov ds:dword ptr[paintbuffer+4-8+ecx*8],ebp
|
||||
mov bl,ds:byte ptr[-2+esi+ecx*1]
|
||||
dec ecx
|
||||
jz LDone
|
||||
LMix8Loop:
|
||||
mov edi,ds:dword ptr[eax+ebx*4]
|
||||
mov ebp,ds:dword ptr[edx+ebx*4]
|
||||
add edi,ds:dword ptr[paintbuffer+0-8+ecx*8]
|
||||
add ebp,ds:dword ptr[paintbuffer+4-8+ecx*8]
|
||||
mov bl,ds:byte ptr[-2+esi+ecx*1]
|
||||
mov ds:dword ptr[paintbuffer+0-8+ecx*8],edi
|
||||
mov ds:dword ptr[paintbuffer+4-8+ecx*8],ebp
|
||||
mov edi,ds:dword ptr[eax+ebx*4]
|
||||
mov ebp,ds:dword ptr[edx+ebx*4]
|
||||
mov bl,ds:byte ptr[-3+esi+ecx*1]
|
||||
add edi,ds:dword ptr[paintbuffer+0-8*2+ecx*8]
|
||||
add ebp,ds:dword ptr[paintbuffer+4-8*2+ecx*8]
|
||||
mov ds:dword ptr[paintbuffer+0-8*2+ecx*8],edi
|
||||
mov ds:dword ptr[paintbuffer+4-8*2+ecx*8],ebp
|
||||
sub ecx,2
|
||||
jnz LMix8Loop
|
||||
LDone:
|
||||
pop ebp
|
||||
pop ebx
|
||||
pop edi
|
||||
pop esi
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void S_PaintChannelFrom16 (channel_t *ch, sfxcache_t *sc, int count, int offset)
|
||||
{
|
||||
int data;
|
||||
int left, right;
|
||||
int leftvol, rightvol;
|
||||
signed short *sfx;
|
||||
int i;
|
||||
portable_samplepair_t *samp;
|
||||
|
||||
leftvol = ch->leftvol*snd_vol;
|
||||
rightvol = ch->rightvol*snd_vol;
|
||||
sfx = (signed short *)sc->data + ch->pos;
|
||||
|
||||
samp = &paintbuffer[offset];
|
||||
for (i=0 ; i<count ; i++, samp++)
|
||||
{
|
||||
data = sfx[i];
|
||||
left = (data * leftvol)>>8;
|
||||
right = (data * rightvol)>>8;
|
||||
samp->left += left;
|
||||
samp->right += right;
|
||||
}
|
||||
|
||||
ch->pos += count;
|
||||
}
|
||||
|
45
client/sound.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
struct sfx_s;
|
||||
|
||||
void S_Init (void);
|
||||
void S_Shutdown (void);
|
||||
|
||||
// if origin is NULL, the sound will be dynamically sourced from the entity
|
||||
void S_StartSound (vec3_t origin, int entnum, int entchannel, struct sfx_s *sfx, float fvol, float attenuation, float timeofs);
|
||||
void S_StartLocalSound (char *s);
|
||||
|
||||
void S_RawSamples (int samples, int rate, int width, int channels, byte *data);
|
||||
|
||||
void S_StopAllSounds(void);
|
||||
void S_Update (vec3_t origin, vec3_t v_forward, vec3_t v_right, vec3_t v_up);
|
||||
|
||||
void S_Activate (qboolean active);
|
||||
|
||||
void S_BeginRegistration (void);
|
||||
struct sfx_s *S_RegisterSound (char *sample);
|
||||
void S_EndRegistration (void);
|
||||
|
||||
struct sfx_s *S_FindName (char *name, qboolean create);
|
||||
|
||||
// the sound code makes callbacks to the client for entitiy position
|
||||
// information, so entities can be dynamically re-spatialized
|
||||
void CL_GetEntitySoundOrigin (int ent, vec3_t org);
|
41
client/vid.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// vid.h -- video driver defs
|
||||
|
||||
typedef struct vrect_s
|
||||
{
|
||||
int x,y,width,height;
|
||||
} vrect_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned width, height; // coordinates from main game
|
||||
} viddef_t;
|
||||
|
||||
extern viddef_t viddef; // global video state
|
||||
|
||||
// Video module initialisation etc
|
||||
void VID_Init (void);
|
||||
void VID_Shutdown (void);
|
||||
void VID_CheckChanges (void);
|
||||
|
||||
void VID_MenuInit( void );
|
||||
void VID_MenuDraw( void );
|
||||
const char *VID_MenuKey( int );
|
95
client/x86.c
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include "client.h"
|
||||
|
||||
#if id386
|
||||
|
||||
static unsigned long bias;
|
||||
static unsigned long *histogram;
|
||||
static unsigned long start, range;
|
||||
static unsigned long bias;
|
||||
|
||||
__declspec( naked ) void x86_TimerStart( void )
|
||||
{
|
||||
__asm _emit 0fh
|
||||
__asm _emit 31h
|
||||
__asm mov start, eax
|
||||
__asm ret
|
||||
}
|
||||
|
||||
__declspec( naked ) void x86_TimerStop( void )
|
||||
{
|
||||
__asm push edi
|
||||
__asm mov edi, histogram
|
||||
__asm _emit 0fh
|
||||
__asm _emit 31h
|
||||
__asm sub eax, start
|
||||
__asm sub eax, bias
|
||||
__asm js discard
|
||||
__asm cmp eax, range
|
||||
__asm jge discard
|
||||
__asm lea edi, [edi + eax*4]
|
||||
__asm inc dword ptr [edi]
|
||||
discard:
|
||||
__asm pop edi
|
||||
__asm ret
|
||||
}
|
||||
|
||||
#pragma warning( disable: 4035 )
|
||||
static __declspec( naked ) unsigned long x86_TimerStopBias( void )
|
||||
{
|
||||
__asm push edi
|
||||
__asm mov edi, histogram
|
||||
__asm _emit 0fh
|
||||
__asm _emit 31h
|
||||
__asm sub eax, start
|
||||
__asm pop edi
|
||||
__asm ret
|
||||
}
|
||||
#pragma warning( default:4035 )
|
||||
|
||||
void x86_TimerInit( unsigned long smallest, unsigned length )
|
||||
{
|
||||
int i;
|
||||
unsigned long biastable[100];
|
||||
|
||||
range = length;
|
||||
bias = 10000;
|
||||
|
||||
for ( i = 0; i < 100; i++ )
|
||||
{
|
||||
x86_TimerStart();
|
||||
biastable[i] = x86_TimerStopBias();
|
||||
|
||||
if ( bias > biastable[i] )
|
||||
bias = biastable[i];
|
||||
}
|
||||
|
||||
bias += smallest;
|
||||
histogram = Z_Malloc( range * sizeof( unsigned long ) );
|
||||
}
|
||||
|
||||
unsigned long *x86_TimerGetHistogram( void )
|
||||
{
|
||||
return histogram;
|
||||
}
|
||||
|
||||
#endif
|
2
ctf/ctf.def
Normal file
|
@ -0,0 +1,2 @@
|
|||
EXPORTS
|
||||
GetGameAPI
|
1007
ctf/ctf.dsp
Normal file
BIN
ctf/docs/admin.gif
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
ctf/docs/adminset.gif
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
ctf/docs/automac.gif
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
ctf/docs/ghost.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
ctf/docs/grapple.jpg
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
ctf/docs/layout.jpg
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
ctf/docs/mainctf_back.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
ctf/docs/menu.gif
Normal file
After Width: | Height: | Size: 12 KiB |
1243
ctf/docs/q2ctf.html
Normal file
BIN
ctf/docs/say_team.gif
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
ctf/docs/stats.jpg
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
ctf/docs/tech1.gif
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
ctf/docs/tech2.gif
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
ctf/docs/tech3.gif
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
ctf/docs/tech4.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
1117
ctf/g_ai.c
Normal file
157
ctf/g_chase.c
Normal file
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#include "g_local.h"
|
||||
|
||||
|
||||
void UpdateChaseCam(edict_t *ent)
|
||||
{
|
||||
vec3_t o, ownerv, goal;
|
||||
edict_t *targ;
|
||||
vec3_t forward, right;
|
||||
trace_t trace;
|
||||
int i;
|
||||
vec3_t oldgoal;
|
||||
vec3_t angles;
|
||||
|
||||
// is our chase target gone?
|
||||
if (!ent->client->chase_target->inuse) {
|
||||
ent->client->chase_target = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
targ = ent->client->chase_target;
|
||||
|
||||
VectorCopy(targ->s.origin, ownerv);
|
||||
VectorCopy(ent->s.origin, oldgoal);
|
||||
|
||||
ownerv[2] += targ->viewheight;
|
||||
|
||||
VectorCopy(targ->client->v_angle, angles);
|
||||
if (angles[PITCH] > 56)
|
||||
angles[PITCH] = 56;
|
||||
AngleVectors (angles, forward, right, NULL);
|
||||
VectorNormalize(forward);
|
||||
VectorMA(ownerv, -30, forward, o);
|
||||
|
||||
if (o[2] < targ->s.origin[2] + 20)
|
||||
o[2] = targ->s.origin[2] + 20;
|
||||
|
||||
// jump animation lifts
|
||||
if (!targ->groundentity)
|
||||
o[2] += 16;
|
||||
|
||||
trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
|
||||
|
||||
VectorCopy(trace.endpos, goal);
|
||||
|
||||
VectorMA(goal, 2, forward, goal);
|
||||
|
||||
// pad for floors and ceilings
|
||||
VectorCopy(goal, o);
|
||||
o[2] += 6;
|
||||
trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
|
||||
if (trace.fraction < 1) {
|
||||
VectorCopy(trace.endpos, goal);
|
||||
goal[2] -= 6;
|
||||
}
|
||||
|
||||
VectorCopy(goal, o);
|
||||
o[2] -= 6;
|
||||
trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
|
||||
if (trace.fraction < 1) {
|
||||
VectorCopy(trace.endpos, goal);
|
||||
goal[2] += 6;
|
||||
}
|
||||
|
||||
ent->client->ps.pmove.pm_type = PM_FREEZE;
|
||||
|
||||
VectorCopy(goal, ent->s.origin);
|
||||
for (i=0 ; i<3 ; i++)
|
||||
ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]);
|
||||
|
||||
VectorCopy(targ->client->v_angle, ent->client->ps.viewangles);
|
||||
VectorCopy(targ->client->v_angle, ent->client->v_angle);
|
||||
|
||||
ent->viewheight = 0;
|
||||
ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
|
||||
gi.linkentity(ent);
|
||||
|
||||
if ((!ent->client->showscores && !ent->client->menu &&
|
||||
!ent->client->showinventory && !ent->client->showhelp &&
|
||||
!(level.framenum & 31)) || ent->client->update_chase) {
|
||||
char s[1024];
|
||||
|
||||
ent->client->update_chase = false;
|
||||
sprintf(s, "xv 0 yb -68 string2 \"Chasing %s\"",
|
||||
targ->client->pers.netname);
|
||||
gi.WriteByte (svc_layout);
|
||||
gi.WriteString (s);
|
||||
gi.unicast(ent, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ChaseNext(edict_t *ent)
|
||||
{
|
||||
int i;
|
||||
edict_t *e;
|
||||
|
||||
if (!ent->client->chase_target)
|
||||
return;
|
||||
|
||||
i = ent->client->chase_target - g_edicts;
|
||||
do {
|
||||
i++;
|
||||
if (i > maxclients->value)
|
||||
i = 1;
|
||||
e = g_edicts + i;
|
||||
if (!e->inuse)
|
||||
continue;
|
||||
if (e->solid != SOLID_NOT)
|
||||
break;
|
||||
} while (e != ent->client->chase_target);
|
||||
|
||||
ent->client->chase_target = e;
|
||||
ent->client->update_chase = true;
|
||||
}
|
||||
|
||||
void ChasePrev(edict_t *ent)
|
||||
{
|
||||
int i;
|
||||
edict_t *e;
|
||||
|
||||
if (!ent->client->chase_target)
|
||||
return;
|
||||
|
||||
i = ent->client->chase_target - g_edicts;
|
||||
do {
|
||||
i--;
|
||||
if (i < 1)
|
||||
i = maxclients->value;
|
||||
e = g_edicts + i;
|
||||
if (!e->inuse)
|
||||
continue;
|
||||
if (e->solid != SOLID_NOT)
|
||||
break;
|
||||
} while (e != ent->client->chase_target);
|
||||
|
||||
ent->client->chase_target = e;
|
||||
ent->client->update_chase = true;
|
||||
}
|
1066
ctf/g_cmds.c
Normal file
596
ctf/g_combat.c
Normal file
|
@ -0,0 +1,596 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// 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 = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
if (trace.ent == targ)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID);
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
|
||||
VectorCopy (targ->s.origin, dest);
|
||||
dest[0] += 15.0;
|
||||
dest[1] += 15.0;
|
||||
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
|
||||
VectorCopy (targ->s.origin, dest);
|
||||
dest[0] += 15.0;
|
||||
dest[1] -= 15.0;
|
||||
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
|
||||
VectorCopy (targ->s.origin, dest);
|
||||
dest[0] -= 15.0;
|
||||
dest[1] += 15.0;
|
||||
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
|
||||
VectorCopy (targ->s.origin, dest);
|
||||
dest[0] -= 15.0;
|
||||
dest[1] -= 15.0;
|
||||
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Killed
|
||||
============
|
||||
*/
|
||||
void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
||||
{
|
||||
if (targ->health < -999)
|
||||
targ->health = -999;
|
||||
|
||||
targ->enemy = attacker;
|
||||
|
||||
if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
|
||||
{
|
||||
// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type
|
||||
if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY))
|
||||
{
|
||||
level.killed_monsters++;
|
||||
if (coop->value && attacker->client)
|
||||
attacker->client->resp.score++;
|
||||
// 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 = 1; // power armor is weaker in CTF
|
||||
pa_te_type = TE_SHIELD_SPARKS;
|
||||
damage = (2 * damage) / 3;
|
||||
}
|
||||
|
||||
save = power * damagePerCell;
|
||||
if (!save)
|
||||
return 0;
|
||||
if (save > damage)
|
||||
save = damage;
|
||||
|
||||
SpawnDamage (pa_te_type, point, normal, save);
|
||||
ent->powerarmor_time = level.time + 0.2;
|
||||
|
||||
power_used = save / damagePerCell;
|
||||
|
||||
if (client)
|
||||
client->pers.inventory[index] -= power_used;
|
||||
else
|
||||
ent->monsterinfo.power_armor_power -= power_used;
|
||||
return save;
|
||||
}
|
||||
|
||||
static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags)
|
||||
{
|
||||
gclient_t *client;
|
||||
int save;
|
||||
int index;
|
||||
gitem_t *armor;
|
||||
|
||||
if (!damage)
|
||||
return 0;
|
||||
|
||||
client = ent->client;
|
||||
|
||||
if (!client)
|
||||
return 0;
|
||||
|
||||
if (dflags & DAMAGE_NO_ARMOR)
|
||||
return 0;
|
||||
|
||||
index = ArmorIndex (ent);
|
||||
if (!index)
|
||||
return 0;
|
||||
|
||||
armor = GetItemByIndex (index);
|
||||
|
||||
if (dflags & DAMAGE_ENERGY)
|
||||
save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage);
|
||||
else
|
||||
save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage);
|
||||
if (save >= client->pers.inventory[index])
|
||||
save = client->pers.inventory[index];
|
||||
|
||||
if (!save)
|
||||
return 0;
|
||||
|
||||
client->pers.inventory[index] -= save;
|
||||
SpawnDamage (te_sparks, point, normal, save);
|
||||
|
||||
return save;
|
||||
}
|
||||
|
||||
void M_ReactToDamage (edict_t *targ, edict_t *attacker)
|
||||
{
|
||||
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;
|
||||
FoundTarget (targ);
|
||||
}
|
||||
}
|
||||
|
||||
qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker)
|
||||
{
|
||||
//ZOID
|
||||
if (ctf->value && targ->client && attacker->client)
|
||||
if (targ->client->resp.ctf_team == attacker->client->resp.ctf_team &&
|
||||
targ != attacker)
|
||||
return true;
|
||||
//ZOID
|
||||
|
||||
//FIXME make the next line real and uncomment this block
|
||||
// if ((ability to damage a teammate == OFF) && (targ's team == attacker's team))
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!targ->takedamage)
|
||||
return;
|
||||
|
||||
// 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;
|
||||
|
||||
// easy mode takes half damage
|
||||
if (skill->value == 0 && deathmatch->value == 0 && targ->client)
|
||||
{
|
||||
damage *= 0.5;
|
||||
if (!damage)
|
||||
damage = 1;
|
||||
}
|
||||
|
||||
client = targ->client;
|
||||
|
||||
if (dflags & DAMAGE_BULLET)
|
||||
te_sparks = TE_BULLET_SPARKS;
|
||||
else
|
||||
te_sparks = TE_SPARKS;
|
||||
|
||||
VectorNormalize(dir);
|
||||
|
||||
// bonus damage for suprising a monster
|
||||
if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
|
||||
damage *= 2;
|
||||
|
||||
//ZOID
|
||||
//strength tech
|
||||
damage = CTFApplyStrength(attacker, damage);
|
||||
//ZOID
|
||||
|
||||
if (targ->flags & FL_NO_KNOCKBACK)
|
||||
knockback = 0;
|
||||
|
||||
// figure momentum add
|
||||
if (!(dflags & DAMAGE_NO_KNOCKBACK))
|
||||
{
|
||||
if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
|
||||
{
|
||||
vec3_t kvel;
|
||||
float mass;
|
||||
|
||||
if (targ->mass < 50)
|
||||
mass = 50;
|
||||
else
|
||||
mass = targ->mass;
|
||||
|
||||
if (targ->client && attacker == targ)
|
||||
VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack...
|
||||
else
|
||||
VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
|
||||
|
||||
VectorAdd (targ->velocity, kvel, targ->velocity);
|
||||
}
|
||||
}
|
||||
|
||||
take = damage;
|
||||
save = 0;
|
||||
|
||||
// check for godmode
|
||||
if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) )
|
||||
{
|
||||
take = 0;
|
||||
save = damage;
|
||||
SpawnDamage (te_sparks, point, normal, save);
|
||||
}
|
||||
|
||||
// check for invincibility
|
||||
if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
|
||||
{
|
||||
if (targ->pain_debounce_time < level.time)
|
||||
{
|
||||
gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
|
||||
targ->pain_debounce_time = level.time + 2;
|
||||
}
|
||||
take = 0;
|
||||
save = damage;
|
||||
}
|
||||
|
||||
//ZOID
|
||||
//team armor protect
|
||||
if (ctf->value && targ->client && attacker->client &&
|
||||
targ->client->resp.ctf_team == attacker->client->resp.ctf_team &&
|
||||
targ != attacker && ((int)dmflags->value & DF_ARMOR_PROTECT)) {
|
||||
psave = asave = 0;
|
||||
} else {
|
||||
//ZOID
|
||||
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;
|
||||
|
||||
//ZOID
|
||||
//resistance tech
|
||||
take = CTFApplyResistance(targ, take);
|
||||
//ZOID
|
||||
|
||||
// team damage avoidance
|
||||
if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
|
||||
return;
|
||||
|
||||
//ZOID
|
||||
CTFCheckHurtCarrier(targ, attacker);
|
||||
//ZOID
|
||||
|
||||
// do the damage
|
||||
if (take)
|
||||
{
|
||||
if ((targ->svflags & SVF_MONSTER) || (client))
|
||||
SpawnDamage (TE_BLOOD, point, normal, take);
|
||||
else
|
||||
SpawnDamage (te_sparks, point, normal, take);
|
||||
|
||||
if (!CTFMatchSetup())
|
||||
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) && !CTFMatchSetup())
|
||||
targ->pain (targ, attacker, knockback, take);
|
||||
}
|
||||
else if (take)
|
||||
{
|
||||
if (targ->pain)
|
||||
targ->pain (targ, attacker, knockback, take);
|
||||
}
|
||||
|
||||
// add to the damage inflicted on a player this frame
|
||||
// the total will be turned into screen blends and view angle kicks
|
||||
// at the end of the frame
|
||||
if (client)
|
||||
{
|
||||
client->damage_parmor += psave;
|
||||
client->damage_armor += asave;
|
||||
client->damage_blood += take;
|
||||
client->damage_knockback += knockback;
|
||||
VectorCopy (point, client->damage_from);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
T_RadiusDamage
|
||||
============
|
||||
*/
|
||||
void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod)
|
||||
{
|
||||
float points;
|
||||
edict_t *ent = NULL;
|
||||
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);
|
||||
if (ent == attacker)
|
||||
points = points * 0.5;
|
||||
if (points > 0)
|
||||
{
|
||||
if (CanDamage (ent, inflictor))
|
||||
{
|
||||
VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
|
||||
T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4197
ctf/g_ctf.c
Normal file
192
ctf/g_ctf.h
Normal file
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
#define CTF_VERSION 1.52
|
||||
#define CTF_VSTRING2(x) #x
|
||||
#define CTF_VSTRING(x) CTF_VSTRING2(x)
|
||||
#define CTF_STRING_VERSION CTF_VSTRING(CTF_VERSION)
|
||||
|
||||
#define STAT_CTF_TEAM1_PIC 17
|
||||
#define STAT_CTF_TEAM1_CAPS 18
|
||||
#define STAT_CTF_TEAM2_PIC 19
|
||||
#define STAT_CTF_TEAM2_CAPS 20
|
||||
#define STAT_CTF_FLAG_PIC 21
|
||||
#define STAT_CTF_JOINED_TEAM1_PIC 22
|
||||
#define STAT_CTF_JOINED_TEAM2_PIC 23
|
||||
#define STAT_CTF_TEAM1_HEADER 24
|
||||
#define STAT_CTF_TEAM2_HEADER 25
|
||||
#define STAT_CTF_TECH 26
|
||||
#define STAT_CTF_ID_VIEW 27
|
||||
#define STAT_CTF_MATCH 28
|
||||
#define STAT_CTF_ID_VIEW_COLOR 29
|
||||
#define STAT_CTF_TEAMINFO 30
|
||||
|
||||
#define CONFIG_CTF_MATCH (CS_AIRACCEL-1)
|
||||
#define CONFIG_CTF_TEAMINFO (CS_AIRACCEL-2)
|
||||
|
||||
typedef enum {
|
||||
CTF_NOTEAM,
|
||||
CTF_TEAM1,
|
||||
CTF_TEAM2
|
||||
} ctfteam_t;
|
||||
|
||||
typedef enum {
|
||||
CTF_GRAPPLE_STATE_FLY,
|
||||
CTF_GRAPPLE_STATE_PULL,
|
||||
CTF_GRAPPLE_STATE_HANG
|
||||
} ctfgrapplestate_t;
|
||||
|
||||
typedef struct ghost_s {
|
||||
char netname[16];
|
||||
int number;
|
||||
|
||||
// stats
|
||||
int deaths;
|
||||
int kills;
|
||||
int caps;
|
||||
int basedef;
|
||||
int carrierdef;
|
||||
|
||||
int code; // ghost code
|
||||
int team; // team
|
||||
int score; // frags at time of disconnect
|
||||
edict_t *ent;
|
||||
} ghost_t;
|
||||
|
||||
extern cvar_t *ctf;
|
||||
|
||||
#define CTF_TEAM1_SKIN "ctf_r"
|
||||
#define CTF_TEAM2_SKIN "ctf_b"
|
||||
|
||||
#define DF_CTF_FORCEJOIN 131072
|
||||
#define DF_ARMOR_PROTECT 262144
|
||||
#define DF_CTF_NO_TECH 524288
|
||||
|
||||
#define CTF_CAPTURE_BONUS 15 // what you get for capture
|
||||
#define CTF_TEAM_BONUS 10 // what your team gets for capture
|
||||
#define CTF_RECOVERY_BONUS 1 // what you get for recovery
|
||||
#define CTF_FLAG_BONUS 0 // what you get for picking up enemy flag
|
||||
#define CTF_FRAG_CARRIER_BONUS 2 // what you get for fragging enemy flag carrier
|
||||
#define CTF_FLAG_RETURN_TIME 40 // seconds until auto return
|
||||
|
||||
#define CTF_CARRIER_DANGER_PROTECT_BONUS 2 // bonus for fraggin someone who has recently hurt your flag carrier
|
||||
#define CTF_CARRIER_PROTECT_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag carrier
|
||||
#define CTF_FLAG_DEFENSE_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag
|
||||
#define CTF_RETURN_FLAG_ASSIST_BONUS 1 // awarded for returning a flag that causes a capture to happen almost immediately
|
||||
#define CTF_FRAG_CARRIER_ASSIST_BONUS 2 // award for fragging a flag carrier if a capture happens almost immediately
|
||||
|
||||
#define CTF_TARGET_PROTECT_RADIUS 400 // the radius around an object being defended where a target will be worth extra frags
|
||||
#define CTF_ATTACKER_PROTECT_RADIUS 400 // the radius around an object being defended where an attacker will get extra frags when making kills
|
||||
|
||||
#define CTF_CARRIER_DANGER_PROTECT_TIMEOUT 8
|
||||
#define CTF_FRAG_CARRIER_ASSIST_TIMEOUT 10
|
||||
#define CTF_RETURN_FLAG_ASSIST_TIMEOUT 10
|
||||
|
||||
#define CTF_AUTO_FLAG_RETURN_TIMEOUT 30 // number of seconds before dropped flag auto-returns
|
||||
|
||||
#define CTF_TECH_TIMEOUT 60 // seconds before techs spawn again
|
||||
|
||||
#define CTF_GRAPPLE_SPEED 650 // speed of grapple in flight
|
||||
#define CTF_GRAPPLE_PULL_SPEED 650 // speed player is pulled at
|
||||
|
||||
void CTFInit(void);
|
||||
void CTFSpawn(void);
|
||||
void CTFPrecache(void);
|
||||
|
||||
void SP_info_player_team1(edict_t *self);
|
||||
void SP_info_player_team2(edict_t *self);
|
||||
|
||||
char *CTFTeamName(int team);
|
||||
char *CTFOtherTeamName(int team);
|
||||
void CTFAssignSkin(edict_t *ent, char *s);
|
||||
void CTFAssignTeam(gclient_t *who);
|
||||
edict_t *SelectCTFSpawnPoint (edict_t *ent);
|
||||
qboolean CTFPickup_Flag(edict_t *ent, edict_t *other);
|
||||
qboolean CTFDrop_Flag(edict_t *ent, gitem_t *item);
|
||||
void CTFEffects(edict_t *player);
|
||||
void CTFCalcScores(void);
|
||||
void SetCTFStats(edict_t *ent);
|
||||
void CTFDeadDropFlag(edict_t *self);
|
||||
void CTFScoreboardMessage (edict_t *ent, edict_t *killer);
|
||||
void CTFTeam_f (edict_t *ent);
|
||||
void CTFID_f (edict_t *ent);
|
||||
void CTFSay_Team(edict_t *who, char *msg);
|
||||
void CTFFlagSetup (edict_t *ent);
|
||||
void CTFResetFlag(int ctf_team);
|
||||
void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker);
|
||||
void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker);
|
||||
|
||||
// GRAPPLE
|
||||
void CTFWeapon_Grapple (edict_t *ent);
|
||||
void CTFPlayerResetGrapple(edict_t *ent);
|
||||
void CTFGrapplePull(edict_t *self);
|
||||
void CTFResetGrapple(edict_t *self);
|
||||
|
||||
//TECH
|
||||
gitem_t *CTFWhat_Tech(edict_t *ent);
|
||||
qboolean CTFPickup_Tech (edict_t *ent, edict_t *other);
|
||||
void CTFDrop_Tech(edict_t *ent, gitem_t *item);
|
||||
void CTFDeadDropTech(edict_t *ent);
|
||||
void CTFSetupTechSpawn(void);
|
||||
int CTFApplyResistance(edict_t *ent, int dmg);
|
||||
int CTFApplyStrength(edict_t *ent, int dmg);
|
||||
qboolean CTFApplyStrengthSound(edict_t *ent);
|
||||
qboolean CTFApplyHaste(edict_t *ent);
|
||||
void CTFApplyHasteSound(edict_t *ent);
|
||||
void CTFApplyRegeneration(edict_t *ent);
|
||||
qboolean CTFHasRegeneration(edict_t *ent);
|
||||
void CTFRespawnTech(edict_t *ent);
|
||||
void CTFResetTech(void);
|
||||
|
||||
void CTFOpenJoinMenu(edict_t *ent);
|
||||
qboolean CTFStartClient(edict_t *ent);
|
||||
void CTFVoteYes(edict_t *ent);
|
||||
void CTFVoteNo(edict_t *ent);
|
||||
void CTFReady(edict_t *ent);
|
||||
void CTFNotReady(edict_t *ent);
|
||||
qboolean CTFNextMap(void);
|
||||
qboolean CTFMatchSetup(void);
|
||||
qboolean CTFMatchOn(void);
|
||||
void CTFGhost(edict_t *ent);
|
||||
void CTFAdmin(edict_t *ent);
|
||||
qboolean CTFInMatch(void);
|
||||
void CTFStats(edict_t *ent);
|
||||
void CTFWarp(edict_t *ent);
|
||||
void CTFBoot(edict_t *ent);
|
||||
void CTFPlayerList(edict_t *ent);
|
||||
|
||||
qboolean CTFCheckRules(void);
|
||||
|
||||
void SP_misc_ctf_banner (edict_t *ent);
|
||||
void SP_misc_ctf_small_banner (edict_t *ent);
|
||||
|
||||
extern char *ctf_statusbar;
|
||||
|
||||
void UpdateChaseCam(edict_t *ent);
|
||||
void ChaseNext(edict_t *ent);
|
||||
void ChasePrev(edict_t *ent);
|
||||
|
||||
void CTFObserver(edict_t *ent);
|
||||
|
||||
void SP_trigger_teleport (edict_t *ent);
|
||||
void SP_info_teleport_destination (edict_t *ent);
|
||||
|
||||
void CTFSetPowerUpEffect(edict_t *ent, int def);
|
||||
|
2047
ctf/g_func.c
Normal file
2446
ctf/g_items.c
Normal file
1153
ctf/g_local.h
Normal file
429
ctf/g_main.c
Normal file
|
@ -0,0 +1,429 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
#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;
|
||||
|
||||
edict_t *g_edicts;
|
||||
|
||||
cvar_t *deathmatch;
|
||||
cvar_t *coop;
|
||||
cvar_t *dmflags;
|
||||
cvar_t *skill;
|
||||
cvar_t *fraglimit;
|
||||
cvar_t *timelimit;
|
||||
//ZOID
|
||||
cvar_t *capturelimit;
|
||||
cvar_t *instantweap;
|
||||
//ZOID
|
||||
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;
|
||||
|
||||
cvar_t *sv_maplist;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CreateTargetChangeLevel
|
||||
|
||||
Returns the created target changelevel
|
||||
=================
|
||||
*/
|
||||
edict_t *CreateTargetChangeLevel(char *map)
|
||||
{
|
||||
edict_t *ent;
|
||||
|
||||
ent = G_Spawn ();
|
||||
ent->classname = "target_changelevel";
|
||||
Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map);
|
||||
ent->map = level.nextmap;
|
||||
return ent;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
EndDMLevel
|
||||
|
||||
The timelimit or fraglimit has been exceeded
|
||||
=================
|
||||
*/
|
||||
void EndDMLevel (void)
|
||||
{
|
||||
edict_t *ent;
|
||||
char *s, *t, *f;
|
||||
static const char *seps = " ,\n\r";
|
||||
|
||||
// stay on same level flag
|
||||
if ((int)dmflags->value & DF_SAME_LEVEL)
|
||||
{
|
||||
BeginIntermission (CreateTargetChangeLevel (level.mapname) );
|
||||
return;
|
||||
}
|
||||
|
||||
if (*level.forcemap) {
|
||||
BeginIntermission (CreateTargetChangeLevel (level.forcemap) );
|
||||
return;
|
||||
}
|
||||
|
||||
// see if it's in the map list
|
||||
if (*sv_maplist->string) {
|
||||
s = strdup(sv_maplist->string);
|
||||
f = NULL;
|
||||
t = strtok(s, seps);
|
||||
while (t != NULL) {
|
||||
if (Q_stricmp(t, level.mapname) == 0) {
|
||||
// it's in the list, go to the next one
|
||||
t = strtok(NULL, seps);
|
||||
if (t == NULL) { // end of list, go to first one
|
||||
if (f == NULL) // there isn't a first one, same level
|
||||
BeginIntermission (CreateTargetChangeLevel (level.mapname) );
|
||||
else
|
||||
BeginIntermission (CreateTargetChangeLevel (f) );
|
||||
} else
|
||||
BeginIntermission (CreateTargetChangeLevel (t) );
|
||||
free(s);
|
||||
return;
|
||||
}
|
||||
if (!f)
|
||||
f = t;
|
||||
t = strtok(NULL, seps);
|
||||
}
|
||||
free(s);
|
||||
}
|
||||
|
||||
if (level.nextmap[0]) // go to a specific map
|
||||
BeginIntermission (CreateTargetChangeLevel (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
|
||||
BeginIntermission (CreateTargetChangeLevel (level.mapname) );
|
||||
return;
|
||||
}
|
||||
BeginIntermission (ent);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CheckDMRules
|
||||
=================
|
||||
*/
|
||||
void CheckDMRules (void)
|
||||
{
|
||||
int i;
|
||||
gclient_t *cl;
|
||||
|
||||
if (level.intermissiontime)
|
||||
return;
|
||||
|
||||
if (!deathmatch->value)
|
||||
return;
|
||||
|
||||
//ZOID
|
||||
if (ctf->value && CTFCheckRules()) {
|
||||
EndDMLevel ();
|
||||
return;
|
||||
}
|
||||
if (CTFInMatch())
|
||||
return; // no checking in match mode
|
||||
//ZOID
|
||||
|
||||
if (timelimit->value)
|
||||
{
|
||||
if (level.time >= timelimit->value*60)
|
||||
{
|
||||
gi.bprintf (PRINT_HIGH, "Timelimit hit.\n");
|
||||
EndDMLevel ();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
level.exitintermission = 0;
|
||||
level.intermissiontime = 0;
|
||||
|
||||
if (CTFNextMap())
|
||||
return;
|
||||
|
||||
Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap);
|
||||
gi.AddCommandString (command);
|
||||
ClientEndServerFrames ();
|
||||
|
||||
level.changemap = NULL;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
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 ();
|
||||
}
|
||||
|
1909
ctf/g_misc.c
Normal file
740
ctf/g_monster.c
Normal file
|
@ -0,0 +1,740 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#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);
|
||||
}
|
959
ctf/g_phys.c
Normal file
|
@ -0,0 +1,959 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// g_phys.c
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
/*
|
||||
|
||||
|
||||
pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move.
|
||||
|
||||
onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects
|
||||
|
||||
doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH
|
||||
bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS
|
||||
corpses are SOLID_NOT and MOVETYPE_TOSS
|
||||
crates are SOLID_BBOX and MOVETYPE_TOSS
|
||||
walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP
|
||||
flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY
|
||||
|
||||
solid_edge items only clip against bsp models.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
SV_TestEntityPosition
|
||||
|
||||
============
|
||||
*/
|
||||
edict_t *SV_TestEntityPosition (edict_t *ent)
|
||||
{
|
||||
trace_t trace;
|
||||
int mask;
|
||||
|
||||
|
||||
if (ent->clipmask)
|
||||
mask = ent->clipmask;
|
||||
else
|
||||
mask = MASK_SOLID;
|
||||
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask);
|
||||
|
||||
if (trace.startsolid)
|
||||
return g_edicts;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
SV_CheckVelocity
|
||||
================
|
||||
*/
|
||||
void SV_CheckVelocity (edict_t *ent)
|
||||
{
|
||||
int i;
|
||||
|
||||
//
|
||||
// bound velocity
|
||||
//
|
||||
for (i=0 ; i<3 ; i++)
|
||||
{
|
||||
if (ent->velocity[i] > sv_maxvelocity->value)
|
||||
ent->velocity[i] = sv_maxvelocity->value;
|
||||
else if (ent->velocity[i] < -sv_maxvelocity->value)
|
||||
ent->velocity[i] = -sv_maxvelocity->value;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
SV_RunThink
|
||||
|
||||
Runs thinking code for this frame if necessary
|
||||
=============
|
||||
*/
|
||||
qboolean SV_RunThink (edict_t *ent)
|
||||
{
|
||||
float thinktime;
|
||||
|
||||
thinktime = ent->nextthink;
|
||||
if (thinktime <= 0)
|
||||
return true;
|
||||
if (thinktime > level.time+0.001)
|
||||
return true;
|
||||
|
||||
ent->nextthink = 0;
|
||||
if (!ent->think)
|
||||
gi.error ("NULL ent->think");
|
||||
ent->think (ent);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_Impact
|
||||
|
||||
Two entities have touched, so run their touch functions
|
||||
==================
|
||||
*/
|
||||
void SV_Impact (edict_t *e1, trace_t *trace)
|
||||
{
|
||||
edict_t *e2;
|
||||
// cplane_t backplane;
|
||||
|
||||
e2 = trace->ent;
|
||||
|
||||
if (e1->touch && e1->solid != SOLID_NOT)
|
||||
e1->touch (e1, e2, &trace->plane, trace->surface);
|
||||
|
||||
if (e2->touch && e2->solid != SOLID_NOT)
|
||||
e2->touch (e2, e1, NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
ClipVelocity
|
||||
|
||||
Slide off of the impacting object
|
||||
returns the blocked flags (1 = floor, 2 = step / wall)
|
||||
==================
|
||||
*/
|
||||
#define STOP_EPSILON 0.1
|
||||
|
||||
int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce)
|
||||
{
|
||||
float backoff;
|
||||
float change;
|
||||
int i, blocked;
|
||||
|
||||
blocked = 0;
|
||||
if (normal[2] > 0)
|
||||
blocked |= 1; // floor
|
||||
if (!normal[2])
|
||||
blocked |= 2; // step
|
||||
|
||||
backoff = DotProduct (in, normal) * overbounce;
|
||||
|
||||
for (i=0 ; i<3 ; i++)
|
||||
{
|
||||
change = normal[i]*backoff;
|
||||
out[i] = in[i] - change;
|
||||
if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
|
||||
out[i] = 0;
|
||||
}
|
||||
|
||||
return blocked;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
SV_FlyMove
|
||||
|
||||
The basic solid body movement clip that slides along multiple planes
|
||||
Returns the clipflags if the velocity was modified (hit something solid)
|
||||
1 = floor
|
||||
2 = wall / step
|
||||
4 = dead stop
|
||||
============
|
||||
*/
|
||||
#define MAX_CLIP_PLANES 5
|
||||
int SV_FlyMove (edict_t *ent, float time, int mask)
|
||||
{
|
||||
edict_t *hit;
|
||||
int bumpcount, numbumps;
|
||||
vec3_t dir;
|
||||
float d;
|
||||
int numplanes;
|
||||
vec3_t planes[MAX_CLIP_PLANES];
|
||||
vec3_t primal_velocity, original_velocity, new_velocity;
|
||||
int i, j;
|
||||
trace_t trace;
|
||||
vec3_t end;
|
||||
float time_left;
|
||||
int blocked;
|
||||
|
||||
numbumps = 4;
|
||||
|
||||
blocked = 0;
|
||||
VectorCopy (ent->velocity, original_velocity);
|
||||
VectorCopy (ent->velocity, primal_velocity);
|
||||
numplanes = 0;
|
||||
|
||||
time_left = time;
|
||||
|
||||
ent->groundentity = NULL;
|
||||
for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
|
||||
{
|
||||
for (i=0 ; i<3 ; i++)
|
||||
end[i] = ent->s.origin[i] + time_left * ent->velocity[i];
|
||||
|
||||
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask);
|
||||
|
||||
if (trace.allsolid)
|
||||
{ // entity is trapped in another solid
|
||||
VectorCopy (vec3_origin, ent->velocity);
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (trace.fraction > 0)
|
||||
{ // actually covered some distance
|
||||
VectorCopy (trace.endpos, ent->s.origin);
|
||||
VectorCopy (ent->velocity, original_velocity);
|
||||
numplanes = 0;
|
||||
}
|
||||
|
||||
if (trace.fraction == 1)
|
||||
break; // moved the entire distance
|
||||
|
||||
hit = trace.ent;
|
||||
|
||||
if (trace.plane.normal[2] > 0.7)
|
||||
{
|
||||
blocked |= 1; // floor
|
||||
if ( hit->solid == SOLID_BSP)
|
||||
{
|
||||
ent->groundentity = hit;
|
||||
ent->groundentity_linkcount = hit->linkcount;
|
||||
}
|
||||
}
|
||||
if (!trace.plane.normal[2])
|
||||
{
|
||||
blocked |= 2; // step
|
||||
}
|
||||
|
||||
//
|
||||
// run the impact function
|
||||
//
|
||||
SV_Impact (ent, &trace);
|
||||
if (!ent->inuse)
|
||||
break; // removed by the impact function
|
||||
|
||||
|
||||
time_left -= time_left * trace.fraction;
|
||||
|
||||
// cliped to another plane
|
||||
if (numplanes >= MAX_CLIP_PLANES)
|
||||
{ // this shouldn't really happen
|
||||
VectorCopy (vec3_origin, ent->velocity);
|
||||
return 3;
|
||||
}
|
||||
|
||||
VectorCopy (trace.plane.normal, planes[numplanes]);
|
||||
numplanes++;
|
||||
|
||||
//
|
||||
// modify original_velocity so it parallels all of the clip planes
|
||||
//
|
||||
for (i=0 ; i<numplanes ; i++)
|
||||
{
|
||||
ClipVelocity (original_velocity, planes[i], new_velocity, 1);
|
||||
for (j=0 ; j<numplanes ; j++)
|
||||
if (j != i)
|
||||
{
|
||||
if (DotProduct (new_velocity, planes[j]) < 0)
|
||||
break; // not ok
|
||||
}
|
||||
if (j == numplanes)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i != numplanes)
|
||||
{ // go along this plane
|
||||
VectorCopy (new_velocity, ent->velocity);
|
||||
}
|
||||
else
|
||||
{ // go along the crease
|
||||
if (numplanes != 2)
|
||||
{
|
||||
// gi.dprintf ("clip velocity, numplanes == %i\n",numplanes);
|
||||
VectorCopy (vec3_origin, ent->velocity);
|
||||
return 7;
|
||||
}
|
||||
CrossProduct (planes[0], planes[1], dir);
|
||||
d = DotProduct (dir, ent->velocity);
|
||||
VectorScale (dir, d, ent->velocity);
|
||||
}
|
||||
|
||||
//
|
||||
// if original velocity is against the original velocity, stop dead
|
||||
// to avoid tiny occilations in sloping corners
|
||||
//
|
||||
if (DotProduct (ent->velocity, primal_velocity) <= 0)
|
||||
{
|
||||
VectorCopy (vec3_origin, ent->velocity);
|
||||
return blocked;
|
||||
}
|
||||
}
|
||||
|
||||
return blocked;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
SV_AddGravity
|
||||
|
||||
============
|
||||
*/
|
||||
void SV_AddGravity (edict_t *ent)
|
||||
{
|
||||
ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME;
|
||||
}
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
PUSHMOVE
|
||||
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
============
|
||||
SV_PushEntity
|
||||
|
||||
Does not change the entities velocity at all
|
||||
============
|
||||
*/
|
||||
trace_t SV_PushEntity (edict_t *ent, vec3_t push)
|
||||
{
|
||||
trace_t trace;
|
||||
vec3_t start;
|
||||
vec3_t end;
|
||||
int mask;
|
||||
|
||||
VectorCopy (ent->s.origin, start);
|
||||
VectorAdd (start, push, end);
|
||||
|
||||
retry:
|
||||
if (ent->clipmask)
|
||||
mask = ent->clipmask;
|
||||
else
|
||||
mask = MASK_SOLID;
|
||||
|
||||
trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask);
|
||||
|
||||
VectorCopy (trace.endpos, ent->s.origin);
|
||||
gi.linkentity (ent);
|
||||
|
||||
if (trace.fraction != 1.0)
|
||||
{
|
||||
SV_Impact (ent, &trace);
|
||||
|
||||
// if the pushed entity went away and the pusher is still there
|
||||
if (!trace.ent->inuse && ent->inuse)
|
||||
{
|
||||
// move the pusher back and try again
|
||||
VectorCopy (start, ent->s.origin);
|
||||
gi.linkentity (ent);
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
|
||||
if (ent->inuse)
|
||||
G_TouchTriggers (ent);
|
||||
|
||||
return trace;
|
||||
}
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
edict_t *ent;
|
||||
vec3_t origin;
|
||||
vec3_t angles;
|
||||
float deltayaw;
|
||||
} pushed_t;
|
||||
pushed_t pushed[MAX_EDICTS], *pushed_p;
|
||||
|
||||
edict_t *obstacle;
|
||||
|
||||
/*
|
||||
============
|
||||
SV_Push
|
||||
|
||||
Objects need to be moved back on a failed push,
|
||||
otherwise riders would continue to slide.
|
||||
============
|
||||
*/
|
||||
qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove)
|
||||
{
|
||||
int i, e;
|
||||
edict_t *check, *block;
|
||||
vec3_t mins, maxs;
|
||||
pushed_t *p;
|
||||
vec3_t org, org2, move2, forward, right, up;
|
||||
|
||||
// clamp the move to 1/8 units, so the position will
|
||||
// be accurate for client side prediction
|
||||
for (i=0 ; i<3 ; i++)
|
||||
{
|
||||
float temp;
|
||||
temp = move[i]*8.0;
|
||||
if (temp > 0.0)
|
||||
temp += 0.5;
|
||||
else
|
||||
temp -= 0.5;
|
||||
move[i] = 0.125 * (int)temp;
|
||||
}
|
||||
|
||||
// find the bounding box
|
||||
for (i=0 ; i<3 ; i++)
|
||||
{
|
||||
mins[i] = pusher->absmin[i] + move[i];
|
||||
maxs[i] = pusher->absmax[i] + move[i];
|
||||
}
|
||||
|
||||
// we need this for pushing things later
|
||||
VectorSubtract (vec3_origin, amove, org);
|
||||
AngleVectors (org, forward, right, up);
|
||||
|
||||
// save the pusher's original position
|
||||
pushed_p->ent = pusher;
|
||||
VectorCopy (pusher->s.origin, pushed_p->origin);
|
||||
VectorCopy (pusher->s.angles, pushed_p->angles);
|
||||
if (pusher->client)
|
||||
pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW];
|
||||
pushed_p++;
|
||||
|
||||
// move the pusher to it's final position
|
||||
VectorAdd (pusher->s.origin, move, pusher->s.origin);
|
||||
VectorAdd (pusher->s.angles, amove, pusher->s.angles);
|
||||
gi.linkentity (pusher);
|
||||
|
||||
// see if any solid entities are inside the final position
|
||||
check = g_edicts+1;
|
||||
for (e = 1; e < globals.num_edicts; e++, check++)
|
||||
{
|
||||
if (!check->inuse)
|
||||
continue;
|
||||
if (check->movetype == MOVETYPE_PUSH
|
||||
|| check->movetype == MOVETYPE_STOP
|
||||
|| check->movetype == MOVETYPE_NONE
|
||||
|| check->movetype == MOVETYPE_NOCLIP)
|
||||
continue;
|
||||
|
||||
if (!check->area.prev)
|
||||
continue; // not linked in anywhere
|
||||
|
||||
// if the entity is standing on the pusher, it will definitely be moved
|
||||
if (check->groundentity != pusher)
|
||||
{
|
||||
// see if the ent needs to be tested
|
||||
if ( check->absmin[0] >= maxs[0]
|
||||
|| check->absmin[1] >= maxs[1]
|
||||
|| check->absmin[2] >= maxs[2]
|
||||
|| check->absmax[0] <= mins[0]
|
||||
|| check->absmax[1] <= mins[1]
|
||||
|| check->absmax[2] <= mins[2] )
|
||||
continue;
|
||||
|
||||
// see if the ent's bbox is inside the pusher's final position
|
||||
if (!SV_TestEntityPosition (check))
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher))
|
||||
{
|
||||
// move this entity
|
||||
pushed_p->ent = check;
|
||||
VectorCopy (check->s.origin, pushed_p->origin);
|
||||
VectorCopy (check->s.angles, pushed_p->angles);
|
||||
pushed_p++;
|
||||
|
||||
// try moving the contacted entity
|
||||
VectorAdd (check->s.origin, move, check->s.origin);
|
||||
if (check->client)
|
||||
{ // FIXME: doesn't rotate monsters?
|
||||
check->client->ps.pmove.delta_angles[YAW] += amove[YAW];
|
||||
}
|
||||
|
||||
// figure movement due to the pusher's amove
|
||||
VectorSubtract (check->s.origin, pusher->s.origin, org);
|
||||
org2[0] = DotProduct (org, forward);
|
||||
org2[1] = -DotProduct (org, right);
|
||||
org2[2] = DotProduct (org, up);
|
||||
VectorSubtract (org2, org, move2);
|
||||
VectorAdd (check->s.origin, move2, check->s.origin);
|
||||
|
||||
// may have pushed them off an edge
|
||||
if (check->groundentity != pusher)
|
||||
check->groundentity = NULL;
|
||||
|
||||
block = SV_TestEntityPosition (check);
|
||||
if (!block)
|
||||
{ // pushed ok
|
||||
gi.linkentity (check);
|
||||
// impact?
|
||||
continue;
|
||||
}
|
||||
|
||||
// if it is ok to leave in the old position, do it
|
||||
// this is only relevent for riding entities, not pushed
|
||||
// FIXME: this doesn't acount for rotation
|
||||
VectorSubtract (check->s.origin, move, check->s.origin);
|
||||
block = SV_TestEntityPosition (check);
|
||||
if (!block)
|
||||
{
|
||||
pushed_p--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// save off the obstacle so we can call the block function
|
||||
obstacle = check;
|
||||
|
||||
// move back any entities we already moved
|
||||
// go backwards, so if the same entity was pushed
|
||||
// twice, it goes back to the original position
|
||||
for (p=pushed_p-1 ; p>=pushed ; p--)
|
||||
{
|
||||
VectorCopy (p->origin, p->ent->s.origin);
|
||||
VectorCopy (p->angles, p->ent->s.angles);
|
||||
if (p->ent->client)
|
||||
{
|
||||
p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw;
|
||||
}
|
||||
gi.linkentity (p->ent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//FIXME: is there a better way to handle this?
|
||||
// see if anything we moved has touched a trigger
|
||||
for (p=pushed_p-1 ; p>=pushed ; p--)
|
||||
G_TouchTriggers (p->ent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_Physics_Pusher
|
||||
|
||||
Bmodel objects don't interact with each other, but
|
||||
push all box objects
|
||||
================
|
||||
*/
|
||||
void SV_Physics_Pusher (edict_t *ent)
|
||||
{
|
||||
vec3_t move, amove;
|
||||
edict_t *part, *mv;
|
||||
|
||||
// if not a team captain, so movement will be handled elsewhere
|
||||
if ( ent->flags & FL_TEAMSLAVE)
|
||||
return;
|
||||
|
||||
// make sure all team slaves can move before commiting
|
||||
// any moves or calling any think functions
|
||||
// if the move is blocked, all moved objects will be backed out
|
||||
//retry:
|
||||
pushed_p = pushed;
|
||||
for (part = ent ; part ; part=part->teamchain)
|
||||
{
|
||||
if (part->velocity[0] || part->velocity[1] || part->velocity[2] ||
|
||||
part->avelocity[0] || part->avelocity[1] || part->avelocity[2]
|
||||
)
|
||||
{ // object is moving
|
||||
VectorScale (part->velocity, FRAMETIME, move);
|
||||
VectorScale (part->avelocity, FRAMETIME, amove);
|
||||
|
||||
if (!SV_Push (part, move, amove))
|
||||
break; // move was blocked
|
||||
}
|
||||
}
|
||||
if (pushed_p > &pushed[MAX_EDICTS])
|
||||
gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted");
|
||||
|
||||
if (part)
|
||||
{
|
||||
// the move failed, bump all nextthink times and back out moves
|
||||
for (mv = ent ; mv ; mv=mv->teamchain)
|
||||
{
|
||||
if (mv->nextthink > 0)
|
||||
mv->nextthink += FRAMETIME;
|
||||
}
|
||||
|
||||
// if the pusher has a "blocked" function, call it
|
||||
// otherwise, just stay in place until the obstacle is gone
|
||||
if (part->blocked)
|
||||
part->blocked (part, obstacle);
|
||||
#if 0
|
||||
// if the pushed entity went away and the pusher is still there
|
||||
if (!obstacle->inuse && part->inuse)
|
||||
goto retry;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// the move succeeded, so call all think functions
|
||||
for (part = ent ; part ; part=part->teamchain)
|
||||
{
|
||||
SV_RunThink (part);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================
|
||||
|
||||
/*
|
||||
=============
|
||||
SV_Physics_None
|
||||
|
||||
Non moving objects can only think
|
||||
=============
|
||||
*/
|
||||
void SV_Physics_None (edict_t *ent)
|
||||
{
|
||||
// regular thinking
|
||||
SV_RunThink (ent);
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
SV_Physics_Noclip
|
||||
|
||||
A moving object that doesn't obey physics
|
||||
=============
|
||||
*/
|
||||
void SV_Physics_Noclip (edict_t *ent)
|
||||
{
|
||||
// regular thinking
|
||||
if (!SV_RunThink (ent))
|
||||
return;
|
||||
|
||||
VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
|
||||
VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin);
|
||||
|
||||
gi.linkentity (ent);
|
||||
}
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
TOSS / BOUNCE
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
=============
|
||||
SV_Physics_Toss
|
||||
|
||||
Toss, bounce, and fly movement. When onground, do nothing.
|
||||
=============
|
||||
*/
|
||||
void SV_Physics_Toss (edict_t *ent)
|
||||
{
|
||||
trace_t trace;
|
||||
vec3_t move;
|
||||
float backoff;
|
||||
edict_t *slave;
|
||||
qboolean wasinwater;
|
||||
qboolean isinwater;
|
||||
vec3_t old_origin;
|
||||
|
||||
// regular thinking
|
||||
SV_RunThink (ent);
|
||||
|
||||
// if not a team captain, so movement will be handled elsewhere
|
||||
if ( ent->flags & FL_TEAMSLAVE)
|
||||
return;
|
||||
|
||||
if (ent->velocity[2] > 0)
|
||||
ent->groundentity = NULL;
|
||||
|
||||
// check for the groundentity going away
|
||||
if (ent->groundentity)
|
||||
if (!ent->groundentity->inuse)
|
||||
ent->groundentity = NULL;
|
||||
|
||||
// if onground, return without moving
|
||||
if ( ent->groundentity )
|
||||
return;
|
||||
|
||||
VectorCopy (ent->s.origin, old_origin);
|
||||
|
||||
SV_CheckVelocity (ent);
|
||||
|
||||
// add gravity
|
||||
if (ent->movetype != MOVETYPE_FLY
|
||||
&& ent->movetype != MOVETYPE_FLYMISSILE)
|
||||
SV_AddGravity (ent);
|
||||
|
||||
// move angles
|
||||
VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
|
||||
|
||||
// move origin
|
||||
VectorScale (ent->velocity, FRAMETIME, move);
|
||||
trace = SV_PushEntity (ent, move);
|
||||
if (!ent->inuse)
|
||||
return;
|
||||
|
||||
if (trace.fraction < 1)
|
||||
{
|
||||
if (ent->movetype == MOVETYPE_BOUNCE)
|
||||
backoff = 1.5;
|
||||
else
|
||||
backoff = 1;
|
||||
|
||||
ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff);
|
||||
|
||||
// stop if on ground
|
||||
if (trace.plane.normal[2] > 0.7)
|
||||
{
|
||||
if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE )
|
||||
{
|
||||
ent->groundentity = trace.ent;
|
||||
ent->groundentity_linkcount = trace.ent->linkcount;
|
||||
VectorCopy (vec3_origin, ent->velocity);
|
||||
VectorCopy (vec3_origin, ent->avelocity);
|
||||
}
|
||||
}
|
||||
|
||||
// if (ent->touch)
|
||||
// ent->touch (ent, trace.ent, &trace.plane, trace.surface);
|
||||
}
|
||||
|
||||
// check for water transition
|
||||
wasinwater = (ent->watertype & MASK_WATER);
|
||||
ent->watertype = gi.pointcontents (ent->s.origin);
|
||||
isinwater = ent->watertype & MASK_WATER;
|
||||
|
||||
if (isinwater)
|
||||
ent->waterlevel = 1;
|
||||
else
|
||||
ent->waterlevel = 0;
|
||||
|
||||
if (!wasinwater && isinwater)
|
||||
gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
|
||||
else if (wasinwater && !isinwater)
|
||||
gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
|
||||
|
||||
// move teamslaves
|
||||
for (slave = ent->teamchain; slave; slave = slave->teamchain)
|
||||
{
|
||||
VectorCopy (ent->s.origin, slave->s.origin);
|
||||
gi.linkentity (slave);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
STEPPING MOVEMENT
|
||||
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
=============
|
||||
SV_Physics_Step
|
||||
|
||||
Monsters freefall when they don't have a ground entity, otherwise
|
||||
all movement is done with discrete steps.
|
||||
|
||||
This is also used for objects that have become still on the ground, but
|
||||
will fall if the floor is pulled out from under them.
|
||||
FIXME: is this true?
|
||||
=============
|
||||
*/
|
||||
|
||||
//FIXME: hacked in for E3 demo
|
||||
#define sv_stopspeed 100
|
||||
#define sv_friction 6
|
||||
#define sv_waterfriction 1
|
||||
|
||||
void SV_AddRotationalFriction (edict_t *ent)
|
||||
{
|
||||
int n;
|
||||
float adjustment;
|
||||
|
||||
VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
|
||||
adjustment = FRAMETIME * sv_stopspeed * sv_friction;
|
||||
for (n = 0; n < 3; n++)
|
||||
{
|
||||
if (ent->avelocity[n] > 0)
|
||||
{
|
||||
ent->avelocity[n] -= adjustment;
|
||||
if (ent->avelocity[n] < 0)
|
||||
ent->avelocity[n] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->avelocity[n] += adjustment;
|
||||
if (ent->avelocity[n] > 0)
|
||||
ent->avelocity[n] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SV_Physics_Step (edict_t *ent)
|
||||
{
|
||||
qboolean wasonground;
|
||||
qboolean hitsound = false;
|
||||
float *vel;
|
||||
float speed, newspeed, control;
|
||||
float friction;
|
||||
edict_t *groundentity;
|
||||
int mask;
|
||||
|
||||
// airborn monsters should always check for ground
|
||||
if (!ent->groundentity)
|
||||
M_CheckGround (ent);
|
||||
|
||||
groundentity = ent->groundentity;
|
||||
|
||||
SV_CheckVelocity (ent);
|
||||
|
||||
if (groundentity)
|
||||
wasonground = true;
|
||||
else
|
||||
wasonground = false;
|
||||
|
||||
if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
|
||||
SV_AddRotationalFriction (ent);
|
||||
|
||||
// add gravity except:
|
||||
// flying monsters
|
||||
// swimming monsters who are in the water
|
||||
if (! wasonground)
|
||||
if (!(ent->flags & FL_FLY))
|
||||
if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2)))
|
||||
{
|
||||
if (ent->velocity[2] < sv_gravity->value*-0.1)
|
||||
hitsound = true;
|
||||
if (ent->waterlevel == 0)
|
||||
SV_AddGravity (ent);
|
||||
}
|
||||
|
||||
// friction for flying monsters that have been given vertical velocity
|
||||
if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0))
|
||||
{
|
||||
speed = fabs(ent->velocity[2]);
|
||||
control = speed < sv_stopspeed ? sv_stopspeed : speed;
|
||||
friction = sv_friction/3;
|
||||
newspeed = speed - (FRAMETIME * control * friction);
|
||||
if (newspeed < 0)
|
||||
newspeed = 0;
|
||||
newspeed /= speed;
|
||||
ent->velocity[2] *= newspeed;
|
||||
}
|
||||
|
||||
// friction for flying monsters that have been given vertical velocity
|
||||
if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0))
|
||||
{
|
||||
speed = fabs(ent->velocity[2]);
|
||||
control = speed < sv_stopspeed ? sv_stopspeed : speed;
|
||||
newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel);
|
||||
if (newspeed < 0)
|
||||
newspeed = 0;
|
||||
newspeed /= speed;
|
||||
ent->velocity[2] *= newspeed;
|
||||
}
|
||||
|
||||
if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0])
|
||||
{
|
||||
// apply friction
|
||||
// let dead monsters who aren't completely onground slide
|
||||
if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY)))
|
||||
if (!(ent->health <= 0.0 && !M_CheckBottom(ent)))
|
||||
{
|
||||
vel = ent->velocity;
|
||||
speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]);
|
||||
if (speed)
|
||||
{
|
||||
friction = sv_friction;
|
||||
|
||||
control = speed < sv_stopspeed ? sv_stopspeed : speed;
|
||||
newspeed = speed - FRAMETIME*control*friction;
|
||||
|
||||
if (newspeed < 0)
|
||||
newspeed = 0;
|
||||
newspeed /= speed;
|
||||
|
||||
vel[0] *= newspeed;
|
||||
vel[1] *= newspeed;
|
||||
}
|
||||
}
|
||||
|
||||
if (ent->svflags & SVF_MONSTER)
|
||||
mask = MASK_MONSTERSOLID;
|
||||
else
|
||||
mask = MASK_SOLID;
|
||||
SV_FlyMove (ent, FRAMETIME, mask);
|
||||
|
||||
gi.linkentity (ent);
|
||||
G_TouchTriggers (ent);
|
||||
|
||||
if (ent->groundentity)
|
||||
if (!wasonground)
|
||||
if (hitsound)
|
||||
gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0);
|
||||
}
|
||||
|
||||
// regular thinking
|
||||
SV_RunThink (ent);
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
/*
|
||||
================
|
||||
G_RunEntity
|
||||
|
||||
================
|
||||
*/
|
||||
void G_RunEntity (edict_t *ent)
|
||||
{
|
||||
if (ent->prethink)
|
||||
ent->prethink (ent);
|
||||
|
||||
switch ( (int)ent->movetype)
|
||||
{
|
||||
case MOVETYPE_PUSH:
|
||||
case MOVETYPE_STOP:
|
||||
SV_Physics_Pusher (ent);
|
||||
break;
|
||||
case MOVETYPE_NONE:
|
||||
SV_Physics_None (ent);
|
||||
break;
|
||||
case MOVETYPE_NOCLIP:
|
||||
SV_Physics_Noclip (ent);
|
||||
break;
|
||||
case MOVETYPE_STEP:
|
||||
SV_Physics_Step (ent);
|
||||
break;
|
||||
case MOVETYPE_TOSS:
|
||||
case MOVETYPE_BOUNCE:
|
||||
case MOVETYPE_FLY:
|
||||
case MOVETYPE_FLYMISSILE:
|
||||
SV_Physics_Toss (ent);
|
||||
break;
|
||||
default:
|
||||
gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype);
|
||||
}
|
||||
}
|
744
ctf/g_save.c
Normal file
|
@ -0,0 +1,744 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
#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");
|
||||
|
||||
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", "4", CVAR_SERVERINFO | CVAR_LATCH);
|
||||
deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH);
|
||||
coop = gi.cvar ("coop", "0", CVAR_LATCH);
|
||||
skill = gi.cvar ("skill", "1", CVAR_LATCH);
|
||||
maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH);
|
||||
|
||||
//ZOID
|
||||
//This game.dll only supports deathmatch
|
||||
if (!deathmatch->value) {
|
||||
gi.dprintf("Forcing deathmatch.");
|
||||
gi.cvar_set("deathmatch", "1");
|
||||
}
|
||||
//force coop off
|
||||
if (coop->value)
|
||||
gi.cvar_set("coop", "0");
|
||||
//ZOID
|
||||
|
||||
|
||||
// change anytime vars
|
||||
dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO);
|
||||
fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO);
|
||||
timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO);
|
||||
//ZOID
|
||||
capturelimit = gi.cvar ("capturelimit", "0", CVAR_SERVERINFO);
|
||||
instantweap = gi.cvar ("instantweap", "0", CVAR_SERVERINFO);
|
||||
//ZOID
|
||||
password = gi.cvar ("password", "", CVAR_USERINFO);
|
||||
filterban = gi.cvar ("filterban", "1", 0);
|
||||
|
||||
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);
|
||||
|
||||
// dm map list
|
||||
sv_maplist = gi.cvar ("sv_maplist", "", 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;
|
||||
|
||||
//ZOID
|
||||
CTFInit();
|
||||
//ZOID
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
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 allready have been called on the
|
||||
level the same way it was when the level was saved.
|
||||
|
||||
That is necessary to get the baselines
|
||||
set up identically.
|
||||
|
||||
The server will have cleared all of the world links before
|
||||
calling ReadLevel.
|
||||
|
||||
No clients are connected yet.
|
||||
=================
|
||||
*/
|
||||
void ReadLevel (char *filename)
|
||||
{
|
||||
int entnum;
|
||||
FILE *f;
|
||||
int i;
|
||||
void *base;
|
||||
edict_t *ent;
|
||||
|
||||
f = fopen (filename, "rb");
|
||||
if (!f)
|
||||
gi.error ("Couldn't open %s", filename);
|
||||
|
||||
// free any dynamic memory allocated by loading the level
|
||||
// base state
|
||||
gi.FreeTags (TAG_LEVEL);
|
||||
|
||||
// wipe all the entities
|
||||
memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0]));
|
||||
globals.num_edicts = maxclients->value+1;
|
||||
|
||||
// check edict size
|
||||
fread (&i, sizeof(i), 1, f);
|
||||
if (i != sizeof(edict_t))
|
||||
{
|
||||
fclose (f);
|
||||
gi.error ("ReadLevel: mismatched edict size");
|
||||
}
|
||||
|
||||
// check function pointer base address
|
||||
fread (&base, sizeof(base), 1, f);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
989
ctf/g_spawn.c
Normal file
|
@ -0,0 +1,989 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
#include "g_local.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *name;
|
||||
void (*spawn)(edict_t *ent);
|
||||
} spawn_t;
|
||||
|
||||
|
||||
void SP_item_health (edict_t *self);
|
||||
void SP_item_health_small (edict_t *self);
|
||||
void SP_item_health_large (edict_t *self);
|
||||
void SP_item_health_mega (edict_t *self);
|
||||
|
||||
void SP_info_player_start (edict_t *ent);
|
||||
void SP_info_player_deathmatch (edict_t *ent);
|
||||
void SP_info_player_coop (edict_t *ent);
|
||||
void SP_info_player_intermission (edict_t *ent);
|
||||
|
||||
void SP_func_plat (edict_t *ent);
|
||||
void SP_func_rotating (edict_t *ent);
|
||||
void SP_func_button (edict_t *ent);
|
||||
void SP_func_door (edict_t *ent);
|
||||
void SP_func_door_secret (edict_t *ent);
|
||||
void SP_func_door_rotating (edict_t *ent);
|
||||
void SP_func_water (edict_t *ent);
|
||||
void SP_func_train (edict_t *ent);
|
||||
void SP_func_conveyor (edict_t *self);
|
||||
void SP_func_wall (edict_t *self);
|
||||
void SP_func_object (edict_t *self);
|
||||
void SP_func_explosive (edict_t *self);
|
||||
void SP_func_timer (edict_t *self);
|
||||
void SP_func_areaportal (edict_t *ent);
|
||||
void SP_func_clock (edict_t *ent);
|
||||
void SP_func_killbox (edict_t *ent);
|
||||
|
||||
void SP_trigger_always (edict_t *ent);
|
||||
void SP_trigger_once (edict_t *ent);
|
||||
void SP_trigger_multiple (edict_t *ent);
|
||||
void SP_trigger_relay (edict_t *ent);
|
||||
void SP_trigger_push (edict_t *ent);
|
||||
void SP_trigger_hurt (edict_t *ent);
|
||||
void SP_trigger_key (edict_t *ent);
|
||||
void SP_trigger_counter (edict_t *ent);
|
||||
void SP_trigger_elevator (edict_t *ent);
|
||||
void SP_trigger_gravity (edict_t *ent);
|
||||
void SP_trigger_monsterjump (edict_t *ent);
|
||||
|
||||
void SP_target_temp_entity (edict_t *ent);
|
||||
void SP_target_speaker (edict_t *ent);
|
||||
void SP_target_explosion (edict_t *ent);
|
||||
void SP_target_changelevel (edict_t *ent);
|
||||
void SP_target_secret (edict_t *ent);
|
||||
void SP_target_goal (edict_t *ent);
|
||||
void SP_target_splash (edict_t *ent);
|
||||
void SP_target_spawner (edict_t *ent);
|
||||
void SP_target_blaster (edict_t *ent);
|
||||
void SP_target_crosslevel_trigger (edict_t *ent);
|
||||
void SP_target_crosslevel_target (edict_t *ent);
|
||||
void SP_target_laser (edict_t *self);
|
||||
void SP_target_help (edict_t *ent);
|
||||
void SP_target_actor (edict_t *ent);
|
||||
void SP_target_lightramp (edict_t *self);
|
||||
void SP_target_earthquake (edict_t *ent);
|
||||
void SP_target_character (edict_t *ent);
|
||||
void SP_target_string (edict_t *ent);
|
||||
|
||||
void SP_worldspawn (edict_t *ent);
|
||||
void SP_viewthing (edict_t *ent);
|
||||
|
||||
void SP_light (edict_t *self);
|
||||
void SP_light_mine1 (edict_t *ent);
|
||||
void SP_light_mine2 (edict_t *ent);
|
||||
void SP_info_null (edict_t *self);
|
||||
void SP_info_notnull (edict_t *self);
|
||||
void SP_path_corner (edict_t *self);
|
||||
void SP_point_combat (edict_t *self);
|
||||
|
||||
void SP_misc_explobox (edict_t *self);
|
||||
void SP_misc_banner (edict_t *self);
|
||||
void SP_misc_satellite_dish (edict_t *self);
|
||||
void SP_misc_actor (edict_t *self);
|
||||
void SP_misc_gib_arm (edict_t *self);
|
||||
void SP_misc_gib_leg (edict_t *self);
|
||||
void SP_misc_gib_head (edict_t *self);
|
||||
void SP_misc_insane (edict_t *self);
|
||||
void SP_misc_deadsoldier (edict_t *self);
|
||||
void SP_misc_viper (edict_t *self);
|
||||
void SP_misc_viper_bomb (edict_t *self);
|
||||
void SP_misc_bigviper (edict_t *self);
|
||||
void SP_misc_strogg_ship (edict_t *self);
|
||||
void SP_misc_teleporter (edict_t *self);
|
||||
void SP_misc_teleporter_dest (edict_t *self);
|
||||
void SP_misc_blackhole (edict_t *self);
|
||||
void SP_misc_eastertank (edict_t *self);
|
||||
void SP_misc_easterchick (edict_t *self);
|
||||
void SP_misc_easterchick2 (edict_t *self);
|
||||
|
||||
void SP_monster_berserk (edict_t *self);
|
||||
void SP_monster_gladiator (edict_t *self);
|
||||
void SP_monster_gunner (edict_t *self);
|
||||
void SP_monster_infantry (edict_t *self);
|
||||
void SP_monster_soldier_light (edict_t *self);
|
||||
void SP_monster_soldier (edict_t *self);
|
||||
void SP_monster_soldier_ss (edict_t *self);
|
||||
void SP_monster_tank (edict_t *self);
|
||||
void SP_monster_medic (edict_t *self);
|
||||
void SP_monster_flipper (edict_t *self);
|
||||
void SP_monster_chick (edict_t *self);
|
||||
void SP_monster_parasite (edict_t *self);
|
||||
void SP_monster_flyer (edict_t *self);
|
||||
void SP_monster_brain (edict_t *self);
|
||||
void SP_monster_floater (edict_t *self);
|
||||
void SP_monster_hover (edict_t *self);
|
||||
void SP_monster_mutant (edict_t *self);
|
||||
void SP_monster_supertank (edict_t *self);
|
||||
void SP_monster_boss2 (edict_t *self);
|
||||
void SP_monster_jorg (edict_t *self);
|
||||
void SP_monster_boss3_stand (edict_t *self);
|
||||
|
||||
void SP_monster_commander_body (edict_t *self);
|
||||
|
||||
void SP_turret_breach (edict_t *self);
|
||||
void SP_turret_base (edict_t *self);
|
||||
void SP_turret_driver (edict_t *self);
|
||||
|
||||
|
||||
spawn_t spawns[] = {
|
||||
{"item_health", SP_item_health},
|
||||
{"item_health_small", SP_item_health_small},
|
||||
{"item_health_large", SP_item_health_large},
|
||||
{"item_health_mega", SP_item_health_mega},
|
||||
|
||||
{"info_player_start", SP_info_player_start},
|
||||
{"info_player_deathmatch", SP_info_player_deathmatch},
|
||||
{"info_player_coop", SP_info_player_coop},
|
||||
{"info_player_intermission", SP_info_player_intermission},
|
||||
//ZOID
|
||||
{"info_player_team1", SP_info_player_team1},
|
||||
{"info_player_team2", SP_info_player_team2},
|
||||
//ZOID
|
||||
|
||||
{"func_plat", SP_func_plat},
|
||||
{"func_button", SP_func_button},
|
||||
{"func_door", SP_func_door},
|
||||
{"func_door_secret", SP_func_door_secret},
|
||||
{"func_door_rotating", SP_func_door_rotating},
|
||||
{"func_rotating", SP_func_rotating},
|
||||
{"func_train", SP_func_train},
|
||||
{"func_water", SP_func_water},
|
||||
{"func_conveyor", SP_func_conveyor},
|
||||
{"func_areaportal", SP_func_areaportal},
|
||||
{"func_clock", SP_func_clock},
|
||||
{"func_wall", SP_func_wall},
|
||||
{"func_object", SP_func_object},
|
||||
{"func_timer", SP_func_timer},
|
||||
{"func_explosive", SP_func_explosive},
|
||||
{"func_killbox", SP_func_killbox},
|
||||
|
||||
{"trigger_always", SP_trigger_always},
|
||||
{"trigger_once", SP_trigger_once},
|
||||
{"trigger_multiple", SP_trigger_multiple},
|
||||
{"trigger_relay", SP_trigger_relay},
|
||||
{"trigger_push", SP_trigger_push},
|
||||
{"trigger_hurt", SP_trigger_hurt},
|
||||
{"trigger_key", SP_trigger_key},
|
||||
{"trigger_counter", SP_trigger_counter},
|
||||
{"trigger_elevator", SP_trigger_elevator},
|
||||
{"trigger_gravity", SP_trigger_gravity},
|
||||
{"trigger_monsterjump", SP_trigger_monsterjump},
|
||||
|
||||
{"target_temp_entity", SP_target_temp_entity},
|
||||
{"target_speaker", SP_target_speaker},
|
||||
{"target_explosion", SP_target_explosion},
|
||||
{"target_changelevel", SP_target_changelevel},
|
||||
{"target_secret", SP_target_secret},
|
||||
{"target_goal", SP_target_goal},
|
||||
{"target_splash", SP_target_splash},
|
||||
{"target_spawner", SP_target_spawner},
|
||||
{"target_blaster", SP_target_blaster},
|
||||
{"target_crosslevel_trigger", SP_target_crosslevel_trigger},
|
||||
{"target_crosslevel_target", SP_target_crosslevel_target},
|
||||
{"target_laser", SP_target_laser},
|
||||
{"target_help", SP_target_help},
|
||||
#if 0 // remove monster code
|
||||
{"target_actor", SP_target_actor},
|
||||
#endif
|
||||
{"target_lightramp", SP_target_lightramp},
|
||||
{"target_earthquake", SP_target_earthquake},
|
||||
{"target_character", SP_target_character},
|
||||
{"target_string", SP_target_string},
|
||||
|
||||
{"worldspawn", SP_worldspawn},
|
||||
{"viewthing", SP_viewthing},
|
||||
|
||||
{"light", SP_light},
|
||||
{"light_mine1", SP_light_mine1},
|
||||
{"light_mine2", SP_light_mine2},
|
||||
{"info_null", SP_info_null},
|
||||
{"func_group", SP_info_null},
|
||||
{"info_notnull", SP_info_notnull},
|
||||
{"path_corner", SP_path_corner},
|
||||
{"point_combat", SP_point_combat},
|
||||
|
||||
{"misc_explobox", SP_misc_explobox},
|
||||
{"misc_banner", SP_misc_banner},
|
||||
//ZOID
|
||||
{"misc_ctf_banner", SP_misc_ctf_banner},
|
||||
{"misc_ctf_small_banner", SP_misc_ctf_small_banner},
|
||||
//ZOID
|
||||
{"misc_satellite_dish", SP_misc_satellite_dish},
|
||||
#if 0 // remove monster code
|
||||
{"misc_actor", SP_misc_actor},
|
||||
#endif
|
||||
{"misc_gib_arm", SP_misc_gib_arm},
|
||||
{"misc_gib_leg", SP_misc_gib_leg},
|
||||
{"misc_gib_head", SP_misc_gib_head},
|
||||
#if 0 // remove monster code
|
||||
{"misc_insane", SP_misc_insane},
|
||||
#endif
|
||||
{"misc_deadsoldier", SP_misc_deadsoldier},
|
||||
{"misc_viper", SP_misc_viper},
|
||||
{"misc_viper_bomb", SP_misc_viper_bomb},
|
||||
{"misc_bigviper", SP_misc_bigviper},
|
||||
{"misc_strogg_ship", SP_misc_strogg_ship},
|
||||
{"misc_teleporter", SP_misc_teleporter},
|
||||
{"misc_teleporter_dest", SP_misc_teleporter_dest},
|
||||
//ZOID
|
||||
{"trigger_teleport", SP_trigger_teleport},
|
||||
{"info_teleport_destination", SP_info_teleport_destination},
|
||||
//ZOID
|
||||
{"misc_blackhole", SP_misc_blackhole},
|
||||
{"misc_eastertank", SP_misc_eastertank},
|
||||
{"misc_easterchick", SP_misc_easterchick},
|
||||
{"misc_easterchick2", SP_misc_easterchick2},
|
||||
|
||||
#if 0 // remove monster code
|
||||
{"monster_berserk", SP_monster_berserk},
|
||||
{"monster_gladiator", SP_monster_gladiator},
|
||||
{"monster_gunner", SP_monster_gunner},
|
||||
{"monster_infantry", SP_monster_infantry},
|
||||
{"monster_soldier_light", SP_monster_soldier_light},
|
||||
{"monster_soldier", SP_monster_soldier},
|
||||
{"monster_soldier_ss", SP_monster_soldier_ss},
|
||||
{"monster_tank", SP_monster_tank},
|
||||
{"monster_tank_commander", SP_monster_tank},
|
||||
{"monster_medic", SP_monster_medic},
|
||||
{"monster_flipper", SP_monster_flipper},
|
||||
{"monster_chick", SP_monster_chick},
|
||||
{"monster_parasite", SP_monster_parasite},
|
||||
{"monster_flyer", SP_monster_flyer},
|
||||
{"monster_brain", SP_monster_brain},
|
||||
{"monster_floater", SP_monster_floater},
|
||||
{"monster_hover", SP_monster_hover},
|
||||
{"monster_mutant", SP_monster_mutant},
|
||||
{"monster_supertank", SP_monster_supertank},
|
||||
{"monster_boss2", SP_monster_boss2},
|
||||
{"monster_boss3_stand", SP_monster_boss3_stand},
|
||||
{"monster_jorg", SP_monster_jorg},
|
||||
|
||||
{"monster_commander_body", SP_monster_commander_body},
|
||||
|
||||
{"turret_breach", SP_turret_breach},
|
||||
{"turret_base", SP_turret_base},
|
||||
{"turret_driver", SP_turret_driver},
|
||||
#endif
|
||||
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
/*
|
||||
===============
|
||||
ED_CallSpawn
|
||||
|
||||
Finds the spawn function for the entity and calls it
|
||||
===============
|
||||
*/
|
||||
void ED_CallSpawn (edict_t *ent)
|
||||
{
|
||||
spawn_t *s;
|
||||
gitem_t *item;
|
||||
int i;
|
||||
|
||||
if (!ent->classname)
|
||||
{
|
||||
gi.dprintf ("ED_CallSpawn: NULL classname\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// check item spawn functions
|
||||
for (i=0,item=itemlist ; i<game.num_items ; i++,item++)
|
||||
{
|
||||
if (!item->classname)
|
||||
continue;
|
||||
if (!strcmp(item->classname, ent->classname))
|
||||
{ // found it
|
||||
SpawnItem (ent, item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check normal spawn functions
|
||||
for (s=spawns ; s->name ; s++)
|
||||
{
|
||||
if (!strcmp(s->name, ent->classname))
|
||||
{ // found it
|
||||
s->spawn (ent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
gi.dprintf ("%s doesn't have a spawn function\n", ent->classname);
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
ED_NewString
|
||||
=============
|
||||
*/
|
||||
char *ED_NewString (char *string)
|
||||
{
|
||||
char *newb, *new_p;
|
||||
int i,l;
|
||||
|
||||
l = strlen(string) + 1;
|
||||
|
||||
newb = gi.TagMalloc (l, TAG_LEVEL);
|
||||
|
||||
new_p = newb;
|
||||
|
||||
for (i=0 ; i< l ; i++)
|
||||
{
|
||||
if (string[i] == '\\' && i < l-1)
|
||||
{
|
||||
i++;
|
||||
if (string[i] == 'n')
|
||||
*new_p++ = '\n';
|
||||
else
|
||||
*new_p++ = '\\';
|
||||
}
|
||||
else
|
||||
*new_p++ = string[i];
|
||||
}
|
||||
|
||||
return newb;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
ED_ParseField
|
||||
|
||||
Takes a key/value pair and sets the binary values
|
||||
in an edict
|
||||
===============
|
||||
*/
|
||||
void ED_ParseField (char *key, char *value, edict_t *ent)
|
||||
{
|
||||
field_t *f;
|
||||
byte *b;
|
||||
float v;
|
||||
vec3_t vec;
|
||||
|
||||
for (f=fields ; f->name ; f++)
|
||||
{
|
||||
if (!Q_stricmp(f->name, key))
|
||||
{ // found it
|
||||
if (f->flags & FFL_SPAWNTEMP)
|
||||
b = (byte *)&st;
|
||||
else
|
||||
b = (byte *)ent;
|
||||
|
||||
switch (f->type)
|
||||
{
|
||||
case F_LSTRING:
|
||||
*(char **)(b+f->ofs) = ED_NewString (value);
|
||||
break;
|
||||
case F_VECTOR:
|
||||
sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]);
|
||||
((float *)(b+f->ofs))[0] = vec[0];
|
||||
((float *)(b+f->ofs))[1] = vec[1];
|
||||
((float *)(b+f->ofs))[2] = vec[2];
|
||||
break;
|
||||
case F_INT:
|
||||
*(int *)(b+f->ofs) = atoi(value);
|
||||
break;
|
||||
case F_FLOAT:
|
||||
*(float *)(b+f->ofs) = atof(value);
|
||||
break;
|
||||
case F_ANGLEHACK:
|
||||
v = atof(value);
|
||||
((float *)(b+f->ofs))[0] = 0;
|
||||
((float *)(b+f->ofs))[1] = v;
|
||||
((float *)(b+f->ofs))[2] = 0;
|
||||
break;
|
||||
case F_IGNORE:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
gi.dprintf ("%s is not a field\n", key);
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
ED_ParseEdict
|
||||
|
||||
Parses an edict out of the given string, returning the new position
|
||||
ed should be a properly initialized empty edict.
|
||||
====================
|
||||
*/
|
||||
char *ED_ParseEdict (char *data, edict_t *ent)
|
||||
{
|
||||
qboolean init;
|
||||
char keyname[256];
|
||||
char *com_token;
|
||||
|
||||
init = false;
|
||||
memset (&st, 0, sizeof(st));
|
||||
|
||||
// go through all the dictionary pairs
|
||||
while (1)
|
||||
{
|
||||
// parse key
|
||||
com_token = COM_Parse (&data);
|
||||
if (com_token[0] == '}')
|
||||
break;
|
||||
if (!data)
|
||||
gi.error ("ED_ParseEntity: EOF without closing brace");
|
||||
|
||||
strncpy (keyname, com_token, sizeof(keyname)-1);
|
||||
|
||||
// parse value
|
||||
com_token = COM_Parse (&data);
|
||||
if (!data)
|
||||
gi.error ("ED_ParseEntity: EOF without closing brace");
|
||||
|
||||
if (com_token[0] == '}')
|
||||
gi.error ("ED_ParseEntity: closing brace without data");
|
||||
|
||||
init = true;
|
||||
|
||||
// keynames with a leading underscore are used for utility comments,
|
||||
// and are immediately discarded by quake
|
||||
if (keyname[0] == '_')
|
||||
continue;
|
||||
|
||||
ED_ParseField (keyname, com_token, ent);
|
||||
}
|
||||
|
||||
if (!init)
|
||||
memset (ent, 0, sizeof(*ent));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
G_FindTeams
|
||||
|
||||
Chain together all entities with a matching team field.
|
||||
|
||||
All but the first will have the FL_TEAMSLAVE flag set.
|
||||
All but the last will have the teamchain field set to the next one
|
||||
================
|
||||
*/
|
||||
void G_FindTeams (void)
|
||||
{
|
||||
edict_t *e, *e2, *chain;
|
||||
int i, j;
|
||||
int c, c2;
|
||||
|
||||
c = 0;
|
||||
c2 = 0;
|
||||
for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++)
|
||||
{
|
||||
if (!e->inuse)
|
||||
continue;
|
||||
if (!e->team)
|
||||
continue;
|
||||
if (e->flags & FL_TEAMSLAVE)
|
||||
continue;
|
||||
chain = e;
|
||||
e->teammaster = e;
|
||||
c++;
|
||||
c2++;
|
||||
for (j=i+1, e2=e+1 ; j < globals.num_edicts ; j++,e2++)
|
||||
{
|
||||
if (!e2->inuse)
|
||||
continue;
|
||||
if (!e2->team)
|
||||
continue;
|
||||
if (e2->flags & FL_TEAMSLAVE)
|
||||
continue;
|
||||
if (!strcmp(e->team, e2->team))
|
||||
{
|
||||
c2++;
|
||||
chain->teamchain = e2;
|
||||
e2->teammaster = e;
|
||||
chain = e2;
|
||||
e2->flags |= FL_TEAMSLAVE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gi.dprintf ("%i teams with %i entities\n", c, c2);
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
SpawnEntities
|
||||
|
||||
Creates a server's entity / program execution context by
|
||||
parsing textual entity definitions out of an ent file.
|
||||
==============
|
||||
*/
|
||||
void SpawnEntities (char *mapname, char *entities, char *spawnpoint)
|
||||
{
|
||||
edict_t *ent;
|
||||
int inhibit;
|
||||
char *com_token;
|
||||
int i;
|
||||
float skill_level;
|
||||
|
||||
skill_level = floor (skill->value);
|
||||
if (skill_level < 0)
|
||||
skill_level = 0;
|
||||
if (skill_level > 3)
|
||||
skill_level = 3;
|
||||
if (skill->value != skill_level)
|
||||
gi.cvar_forceset("skill", va("%f", skill_level));
|
||||
|
||||
SaveClientData ();
|
||||
|
||||
gi.FreeTags (TAG_LEVEL);
|
||||
|
||||
memset (&level, 0, sizeof(level));
|
||||
memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0]));
|
||||
|
||||
strncpy (level.mapname, mapname, sizeof(level.mapname)-1);
|
||||
strncpy (game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)-1);
|
||||
|
||||
// set client fields on player ents
|
||||
for (i=0 ; i<game.maxclients ; i++)
|
||||
g_edicts[i+1].client = game.clients + i;
|
||||
|
||||
ent = NULL;
|
||||
inhibit = 0;
|
||||
|
||||
// parse ents
|
||||
while (1)
|
||||
{
|
||||
// parse the opening brace
|
||||
com_token = COM_Parse (&entities);
|
||||
if (!entities)
|
||||
break;
|
||||
if (com_token[0] != '{')
|
||||
gi.error ("ED_LoadFromFile: found %s when expecting {",com_token);
|
||||
|
||||
if (!ent)
|
||||
ent = g_edicts;
|
||||
else
|
||||
ent = G_Spawn ();
|
||||
entities = ED_ParseEdict (entities, ent);
|
||||
|
||||
// yet another map hack
|
||||
if (!stricmp(level.mapname, "command") && !stricmp(ent->classname, "trigger_once") && !stricmp(ent->model, "*27"))
|
||||
ent->spawnflags &= ~SPAWNFLAG_NOT_HARD;
|
||||
|
||||
// remove things (except the world) from different skill levels or deathmatch
|
||||
if (ent != g_edicts)
|
||||
{
|
||||
if (deathmatch->value)
|
||||
{
|
||||
if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH )
|
||||
{
|
||||
G_FreeEdict (ent);
|
||||
inhibit++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */
|
||||
((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) ||
|
||||
((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) ||
|
||||
(((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD))
|
||||
)
|
||||
{
|
||||
G_FreeEdict (ent);
|
||||
inhibit++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH);
|
||||
}
|
||||
|
||||
ED_CallSpawn (ent);
|
||||
}
|
||||
|
||||
gi.dprintf ("%i entities inhibited\n", inhibit);
|
||||
|
||||
G_FindTeams ();
|
||||
|
||||
PlayerTrail_Init ();
|
||||
|
||||
//ZOID
|
||||
CTFSpawn();
|
||||
//ZOID
|
||||
}
|
||||
|
||||
|
||||
//===================================================================
|
||||
|
||||
#if 0
|
||||
// cursor positioning
|
||||
xl <value>
|
||||
xr <value>
|
||||
yb <value>
|
||||
yt <value>
|
||||
xv <value>
|
||||
yv <value>
|
||||
|
||||
// drawing
|
||||
statpic <name>
|
||||
pic <stat>
|
||||
num <fieldwidth> <stat>
|
||||
string <stat>
|
||||
|
||||
// control
|
||||
if <stat>
|
||||
ifeq <stat> <value>
|
||||
ifbit <stat> <value>
|
||||
endif
|
||||
|
||||
#endif
|
||||
|
||||
char *single_statusbar =
|
||||
"yb -24 "
|
||||
|
||||
// health
|
||||
"xv 0 "
|
||||
"hnum "
|
||||
"xv 50 "
|
||||
"pic 0 "
|
||||
|
||||
// ammo
|
||||
"if 2 "
|
||||
" xv 100 "
|
||||
" anum "
|
||||
" xv 150 "
|
||||
" pic 2 "
|
||||
"endif "
|
||||
|
||||
// armor
|
||||
"if 4 "
|
||||
" xv 200 "
|
||||
" rnum "
|
||||
" xv 250 "
|
||||
" pic 4 "
|
||||
"endif "
|
||||
|
||||
// selected item
|
||||
"if 6 "
|
||||
" xv 296 "
|
||||
" pic 6 "
|
||||
"endif "
|
||||
|
||||
"yb -50 "
|
||||
|
||||
// picked up item
|
||||
"if 7 "
|
||||
" xv 0 "
|
||||
" pic 7 "
|
||||
" xv 26 "
|
||||
" yb -42 "
|
||||
" stat_string 8 "
|
||||
" yb -50 "
|
||||
"endif "
|
||||
|
||||
// timer
|
||||
"if 9 "
|
||||
" xv 262 "
|
||||
" num 2 10 "
|
||||
" xv 296 "
|
||||
" pic 9 "
|
||||
"endif "
|
||||
|
||||
// help / weapon icon
|
||||
"if 11 "
|
||||
" xv 148 "
|
||||
" pic 11 "
|
||||
"endif "
|
||||
;
|
||||
|
||||
char *dm_statusbar =
|
||||
"yb -24 "
|
||||
|
||||
// health
|
||||
"xv 0 "
|
||||
"hnum "
|
||||
"xv 50 "
|
||||
"pic 0 "
|
||||
|
||||
// ammo
|
||||
"if 2 "
|
||||
" xv 100 "
|
||||
" anum "
|
||||
" xv 150 "
|
||||
" pic 2 "
|
||||
"endif "
|
||||
|
||||
// armor
|
||||
"if 4 "
|
||||
" xv 200 "
|
||||
" rnum "
|
||||
" xv 250 "
|
||||
" pic 4 "
|
||||
"endif "
|
||||
|
||||
// selected item
|
||||
"if 6 "
|
||||
" xv 296 "
|
||||
" pic 6 "
|
||||
"endif "
|
||||
|
||||
"yb -50 "
|
||||
|
||||
// picked up item
|
||||
"if 7 "
|
||||
" xv 0 "
|
||||
" pic 7 "
|
||||
" xv 26 "
|
||||
" yb -42 "
|
||||
" stat_string 8 "
|
||||
" yb -50 "
|
||||
"endif "
|
||||
|
||||
// timer
|
||||
"if 9 "
|
||||
" xv 246 "
|
||||
" num 2 10 "
|
||||
" xv 296 "
|
||||
" pic 9 "
|
||||
"endif "
|
||||
|
||||
// help / weapon icon
|
||||
"if 11 "
|
||||
" xv 148 "
|
||||
" pic 11 "
|
||||
"endif "
|
||||
|
||||
// frags
|
||||
"xr -50 "
|
||||
"yt 2 "
|
||||
"num 3 14"
|
||||
;
|
||||
|
||||
|
||||
/*QUAKED worldspawn (0 0 0) ?
|
||||
|
||||
Only used for the world.
|
||||
"sky" environment map name
|
||||
"skyaxis" vector axis for rotating sky
|
||||
"skyrotate" speed of rotation in degrees/second
|
||||
"sounds" music cd track number
|
||||
"gravity" 800 is default gravity
|
||||
"message" text to print at user logon
|
||||
*/
|
||||
void SP_worldspawn (edict_t *ent)
|
||||
{
|
||||
ent->movetype = MOVETYPE_PUSH;
|
||||
ent->solid = SOLID_BSP;
|
||||
ent->inuse = true; // since the world doesn't use G_Spawn()
|
||||
ent->s.modelindex = 1; // world model is always index 1
|
||||
|
||||
//---------------
|
||||
|
||||
// reserve some spots for dead player bodies for coop / deathmatch
|
||||
InitBodyQue ();
|
||||
|
||||
// set configstrings for items
|
||||
SetItemNames ();
|
||||
|
||||
if (st.nextmap)
|
||||
strcpy (level.nextmap, st.nextmap);
|
||||
|
||||
// make some data visible to the server
|
||||
|
||||
if (ent->message && ent->message[0])
|
||||
{
|
||||
gi.configstring (CS_NAME, ent->message);
|
||||
strncpy (level.level_name, ent->message, sizeof(level.level_name));
|
||||
}
|
||||
else
|
||||
strncpy (level.level_name, level.mapname, sizeof(level.level_name));
|
||||
|
||||
if (st.sky && st.sky[0])
|
||||
gi.configstring (CS_SKY, st.sky);
|
||||
else
|
||||
gi.configstring (CS_SKY, "unit1_");
|
||||
|
||||
gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) );
|
||||
|
||||
gi.configstring (CS_SKYAXIS, va("%f %f %f",
|
||||
st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) );
|
||||
|
||||
gi.configstring (CS_CDTRACK, va("%i", ent->sounds) );
|
||||
|
||||
gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) );
|
||||
|
||||
// status bar program
|
||||
if (deathmatch->value)
|
||||
//ZOID
|
||||
if (ctf->value) {
|
||||
gi.configstring (CS_STATUSBAR, ctf_statusbar);
|
||||
CTFPrecache();
|
||||
} else
|
||||
//ZOID
|
||||
gi.configstring (CS_STATUSBAR, dm_statusbar);
|
||||
else
|
||||
gi.configstring (CS_STATUSBAR, single_statusbar);
|
||||
|
||||
//---------------
|
||||
|
||||
|
||||
// help icon for statusbar
|
||||
gi.imageindex ("i_help");
|
||||
level.pic_health = gi.imageindex ("i_health");
|
||||
gi.imageindex ("help");
|
||||
gi.imageindex ("field_3");
|
||||
|
||||
if (!st.gravity)
|
||||
gi.cvar_set("sv_gravity", "800");
|
||||
else
|
||||
gi.cvar_set("sv_gravity", st.gravity);
|
||||
|
||||
snd_fry = gi.soundindex ("player/fry.wav"); // standing in lava / slime
|
||||
|
||||
PrecacheItem (FindItem ("Blaster"));
|
||||
|
||||
gi.soundindex ("player/lava1.wav");
|
||||
gi.soundindex ("player/lava2.wav");
|
||||
|
||||
gi.soundindex ("misc/pc_up.wav");
|
||||
gi.soundindex ("misc/talk1.wav");
|
||||
|
||||
gi.soundindex ("misc/udeath.wav");
|
||||
|
||||
// gibs
|
||||
gi.soundindex ("items/respawn1.wav");
|
||||
|
||||
// sexed sounds
|
||||
gi.soundindex ("*death1.wav");
|
||||
gi.soundindex ("*death2.wav");
|
||||
gi.soundindex ("*death3.wav");
|
||||
gi.soundindex ("*death4.wav");
|
||||
gi.soundindex ("*fall1.wav");
|
||||
gi.soundindex ("*fall2.wav");
|
||||
gi.soundindex ("*gurp1.wav"); // drowning damage
|
||||
gi.soundindex ("*gurp2.wav");
|
||||
gi.soundindex ("*jump1.wav"); // player jump
|
||||
gi.soundindex ("*pain25_1.wav");
|
||||
gi.soundindex ("*pain25_2.wav");
|
||||
gi.soundindex ("*pain50_1.wav");
|
||||
gi.soundindex ("*pain50_2.wav");
|
||||
gi.soundindex ("*pain75_1.wav");
|
||||
gi.soundindex ("*pain75_2.wav");
|
||||
gi.soundindex ("*pain100_1.wav");
|
||||
gi.soundindex ("*pain100_2.wav");
|
||||
|
||||
// sexed models
|
||||
// THIS ORDER MUST MATCH THE DEFINES IN g_local.h
|
||||
// you can add more, max 15
|
||||
gi.modelindex ("#w_blaster.md2");
|
||||
gi.modelindex ("#w_shotgun.md2");
|
||||
gi.modelindex ("#w_sshotgun.md2");
|
||||
gi.modelindex ("#w_machinegun.md2");
|
||||
gi.modelindex ("#w_chaingun.md2");
|
||||
gi.modelindex ("#a_grenades.md2");
|
||||
gi.modelindex ("#w_glauncher.md2");
|
||||
gi.modelindex ("#w_rlauncher.md2");
|
||||
gi.modelindex ("#w_hyperblaster.md2");
|
||||
gi.modelindex ("#w_railgun.md2");
|
||||
gi.modelindex ("#w_bfg.md2");
|
||||
gi.modelindex ("#w_grapple.md2");
|
||||
|
||||
//-------------------
|
||||
|
||||
gi.soundindex ("player/gasp1.wav"); // gasping for air
|
||||
gi.soundindex ("player/gasp2.wav"); // head breaking surface, not gasping
|
||||
|
||||
gi.soundindex ("player/watr_in.wav"); // feet hitting water
|
||||
gi.soundindex ("player/watr_out.wav"); // feet leaving water
|
||||
|
||||
gi.soundindex ("player/watr_un.wav"); // head going underwater
|
||||
|
||||
gi.soundindex ("player/u_breath1.wav");
|
||||
gi.soundindex ("player/u_breath2.wav");
|
||||
|
||||
gi.soundindex ("items/pkup.wav"); // bonus item pickup
|
||||
gi.soundindex ("world/land.wav"); // landing thud
|
||||
gi.soundindex ("misc/h2ohit1.wav"); // landing splash
|
||||
|
||||
gi.soundindex ("items/damage.wav");
|
||||
gi.soundindex ("items/protect.wav");
|
||||
gi.soundindex ("items/protect4.wav");
|
||||
gi.soundindex ("weapons/noammo.wav");
|
||||
|
||||
gi.soundindex ("infantry/inflies1.wav");
|
||||
|
||||
sm_meat_index = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2");
|
||||
gi.modelindex ("models/objects/gibs/arm/tris.md2");
|
||||
gi.modelindex ("models/objects/gibs/bone/tris.md2");
|
||||
gi.modelindex ("models/objects/gibs/bone2/tris.md2");
|
||||
gi.modelindex ("models/objects/gibs/chest/tris.md2");
|
||||
gi.modelindex ("models/objects/gibs/skull/tris.md2");
|
||||
gi.modelindex ("models/objects/gibs/head2/tris.md2");
|
||||
|
||||
//
|
||||
// Setup light animation tables. 'a' is total darkness, 'z' is doublebright.
|
||||
//
|
||||
|
||||
// 0 normal
|
||||
gi.configstring(CS_LIGHTS+0, "m");
|
||||
|
||||
// 1 FLICKER (first variety)
|
||||
gi.configstring(CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo");
|
||||
|
||||
// 2 SLOW STRONG PULSE
|
||||
gi.configstring(CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
|
||||
|
||||
// 3 CANDLE (first variety)
|
||||
gi.configstring(CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
|
||||
|
||||
// 4 FAST STROBE
|
||||
gi.configstring(CS_LIGHTS+4, "mamamamamama");
|
||||
|
||||
// 5 GENTLE PULSE 1
|
||||
gi.configstring(CS_LIGHTS+5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj");
|
||||
|
||||
// 6 FLICKER (second variety)
|
||||
gi.configstring(CS_LIGHTS+6, "nmonqnmomnmomomno");
|
||||
|
||||
// 7 CANDLE (second variety)
|
||||
gi.configstring(CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm");
|
||||
|
||||
// 8 CANDLE (third variety)
|
||||
gi.configstring(CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
|
||||
|
||||
// 9 SLOW STROBE (fourth variety)
|
||||
gi.configstring(CS_LIGHTS+9, "aaaaaaaazzzzzzzz");
|
||||
|
||||
// 10 FLUORESCENT FLICKER
|
||||
gi.configstring(CS_LIGHTS+10, "mmamammmmammamamaaamammma");
|
||||
|
||||
// 11 SLOW PULSE NOT FADE TO BLACK
|
||||
gi.configstring(CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba");
|
||||
|
||||
// styles 32-62 are assigned by the light program for switchable lights
|
||||
|
||||
// 63 testing
|
||||
gi.configstring(CS_LIGHTS+63, "a");
|
||||
}
|
||||
|
300
ctf/g_svcmds.c
Normal file
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
#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);
|
||||
}
|
||||
|
809
ctf/g_target.c
Normal file
|
@ -0,0 +1,809 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#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 allways atten 3 / vol 1, and the use function toggles it on/off.
|
||||
Multiple identical looping sounds will just increase volume without any speed cost.
|
||||
*/
|
||||
void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator)
|
||||
{
|
||||
int chan;
|
||||
|
||||
if (ent->spawnflags & 3)
|
||||
{ // looping sound toggles
|
||||
if (ent->s.sound)
|
||||
ent->s.sound = 0; // turn it off
|
||||
else
|
||||
ent->s.sound = ent->noise_index; // start it
|
||||
}
|
||||
else
|
||||
{ // normal sound
|
||||
if (ent->spawnflags & 4)
|
||||
chan = CHAN_VOICE|CHAN_RELIABLE;
|
||||
else
|
||||
chan = CHAN_VOICE;
|
||||
// use a positioned_sound, because this entity won't normally be
|
||||
// sent to any clients because it is invisible
|
||||
gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void SP_target_speaker (edict_t *ent)
|
||||
{
|
||||
char buffer[MAX_QPATH];
|
||||
|
||||
if(!st.noise)
|
||||
{
|
||||
gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin));
|
||||
return;
|
||||
}
|
||||
if (!strstr (st.noise, ".wav"))
|
||||
Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
|
||||
else
|
||||
strncpy (buffer, st.noise, sizeof(buffer));
|
||||
ent->noise_index = gi.soundindex (buffer);
|
||||
|
||||
if (!ent->volume)
|
||||
ent->volume = 1.0;
|
||||
|
||||
if (!ent->attenuation)
|
||||
ent->attenuation = 1.0;
|
||||
else if (ent->attenuation == -1) // use -1 so 0 defaults to 1
|
||||
ent->attenuation = 0;
|
||||
|
||||
// check for prestarted looping sound
|
||||
if (ent->spawnflags & 1)
|
||||
ent->s.sound = ent->noise_index;
|
||||
|
||||
ent->use = Use_Target_Speaker;
|
||||
|
||||
// must link the entity so we get areas and clusters so
|
||||
// the server can determine who to send updates to
|
||||
gi.linkentity (ent);
|
||||
}
|
||||
|
||||
|
||||
//==========================================================
|
||||
|
||||
void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator)
|
||||
{
|
||||
if (ent->spawnflags & 1)
|
||||
strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1);
|
||||
else
|
||||
strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1);
|
||||
|
||||
game.helpchanged++;
|
||||
}
|
||||
|
||||
/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
|
||||
When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars.
|
||||
*/
|
||||
void SP_target_help(edict_t *ent)
|
||||
{
|
||||
if (deathmatch->value)
|
||||
{ // auto-remove for deathmatch
|
||||
G_FreeEdict (ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ent->message)
|
||||
{
|
||||
gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin));
|
||||
G_FreeEdict (ent);
|
||||
return;
|
||||
}
|
||||
ent->use = Use_Target_Help;
|
||||
}
|
||||
|
||||
//==========================================================
|
||||
|
||||
/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
|
||||
Counts a secret found.
|
||||
These are single use targets.
|
||||
*/
|
||||
void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator)
|
||||
{
|
||||
gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
|
||||
|
||||
level.found_secrets++;
|
||||
|
||||
G_UseTargets (ent, activator);
|
||||
G_FreeEdict (ent);
|
||||
}
|
||||
|
||||
void SP_target_secret (edict_t *ent)
|
||||
{
|
||||
if (deathmatch->value)
|
||||
{ // auto-remove for deathmatch
|
||||
G_FreeEdict (ent);
|
||||
return;
|
||||
}
|
||||
|
||||
ent->use = use_target_secret;
|
||||
if (!st.noise)
|
||||
st.noise = "misc/secret.wav";
|
||||
ent->noise_index = gi.soundindex (st.noise);
|
||||
ent->svflags = SVF_NOCLIENT;
|
||||
level.total_secrets++;
|
||||
// map bug hack
|
||||
if (!stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624)
|
||||
ent->message = "You have found a secret area.";
|
||||
}
|
||||
|
||||
//==========================================================
|
||||
|
||||
/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
|
||||
Counts a goal completed.
|
||||
These are single use targets.
|
||||
*/
|
||||
void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator)
|
||||
{
|
||||
gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
|
||||
|
||||
level.found_goals++;
|
||||
|
||||
if (level.found_goals == level.total_goals)
|
||||
gi.configstring (CS_CDTRACK, "0");
|
||||
|
||||
G_UseTargets (ent, activator);
|
||||
G_FreeEdict (ent);
|
||||
}
|
||||
|
||||
void SP_target_goal (edict_t *ent)
|
||||
{
|
||||
if (deathmatch->value)
|
||||
{ // auto-remove for deathmatch
|
||||
G_FreeEdict (ent);
|
||||
return;
|
||||
}
|
||||
|
||||
ent->use = use_target_goal;
|
||||
if (!st.noise)
|
||||
st.noise = "misc/secret.wav";
|
||||
ent->noise_index = gi.soundindex (st.noise);
|
||||
ent->svflags = SVF_NOCLIENT;
|
||||
level.total_goals++;
|
||||
}
|
||||
|
||||
//==========================================================
|
||||
|
||||
|
||||
/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
|
||||
Spawns an explosion temporary entity when used.
|
||||
|
||||
"delay" wait this long before going off
|
||||
"dmg" how much radius damage should be done, defaults to 0
|
||||
*/
|
||||
void target_explosion_explode (edict_t *self)
|
||||
{
|
||||
float save;
|
||||
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_EXPLOSION1);
|
||||
gi.WritePosition (self->s.origin);
|
||||
gi.multicast (self->s.origin, MULTICAST_PHS);
|
||||
|
||||
T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);
|
||||
|
||||
save = self->delay;
|
||||
self->delay = 0;
|
||||
G_UseTargets (self, self->activator);
|
||||
self->delay = save;
|
||||
}
|
||||
|
||||
void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
self->activator = activator;
|
||||
|
||||
if (!self->delay)
|
||||
{
|
||||
target_explosion_explode (self);
|
||||
return;
|
||||
}
|
||||
|
||||
self->think = target_explosion_explode;
|
||||
self->nextthink = level.time + self->delay;
|
||||
}
|
||||
|
||||
void SP_target_explosion (edict_t *ent)
|
||||
{
|
||||
ent->use = use_target_explosion;
|
||||
ent->svflags = SVF_NOCLIENT;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================
|
||||
|
||||
/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
|
||||
Changes level to "map" when fired
|
||||
*/
|
||||
void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator)
|
||||
{
|
||||
if (level.intermissiontime)
|
||||
return; // allready activated
|
||||
|
||||
if (!deathmatch->value && !coop->value)
|
||||
{
|
||||
if (g_edicts[1].health <= 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// if noexit, do a ton of damage to other
|
||||
if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world)
|
||||
{
|
||||
T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT);
|
||||
return;
|
||||
}
|
||||
|
||||
// if multiplayer, let everyone know who hit the exit
|
||||
if (deathmatch->value)
|
||||
{
|
||||
if (activator && activator->client)
|
||||
gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname);
|
||||
}
|
||||
|
||||
// if going to a new unit, clear cross triggers
|
||||
if (strstr(self->map, "*"))
|
||||
game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);
|
||||
|
||||
BeginIntermission (self);
|
||||
}
|
||||
|
||||
void SP_target_changelevel (edict_t *ent)
|
||||
{
|
||||
if (!ent->map)
|
||||
{
|
||||
gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
|
||||
G_FreeEdict (ent);
|
||||
return;
|
||||
}
|
||||
|
||||
// ugly hack because *SOMEBODY* screwed up their map
|
||||
if((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 = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
|
||||
|
||||
if (!tr.ent)
|
||||
break;
|
||||
|
||||
// hurt it if we can
|
||||
if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
|
||||
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");
|
||||
}
|
598
ctf/g_trigger.c
Normal file
|
@ -0,0 +1,598 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#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;
|
||||
}
|
||||
|
570
ctf/g_utils.c
Normal file
|
@ -0,0 +1,570 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gi.dprintf("TARGET: activating %s\n", ent->target);
|
||||
|
||||
//
|
||||
// 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;
|
||||
|
||||
if (/* vec[YAW] == 0 && */ vec[PITCH] == 0)
|
||||
{
|
||||
yaw = 0;
|
||||
if (vec[YAW] > 0)
|
||||
yaw = 90;
|
||||
else if (vec[YAW] < 0)
|
||||
yaw = -90;
|
||||
}
|
||||
else
|
||||
{
|
||||
yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
|
||||
if (yaw < 0)
|
||||
yaw += 360;
|
||||
}
|
||||
|
||||
return yaw;
|
||||
}
|
||||
|
||||
|
||||
void vectoangles (vec3_t value1, vec3_t angles)
|
||||
{
|
||||
float forward;
|
||||
float yaw, pitch;
|
||||
|
||||
if (value1[1] == 0 && value1[0] == 0)
|
||||
{
|
||||
yaw = 0;
|
||||
if (value1[2] > 0)
|
||||
pitch = 90;
|
||||
else
|
||||
pitch = 270;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value1[0])
|
||||
yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI);
|
||||
else if (value1[1] > 0)
|
||||
yaw = 90;
|
||||
else
|
||||
yaw = -90;
|
||||
if (yaw < 0)
|
||||
yaw += 360;
|
||||
|
||||
forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]);
|
||||
pitch = (int) (atan2(value1[2], forward) * 180 / M_PI);
|
||||
if (pitch < 0)
|
||||
pitch += 360;
|
||||
}
|
||||
|
||||
angles[PITCH] = -pitch;
|
||||
angles[YAW] = yaw;
|
||||
angles[ROLL] = 0;
|
||||
}
|
||||
|
||||
char *G_CopyString (char *in)
|
||||
{
|
||||
char *out;
|
||||
|
||||
out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL);
|
||||
strcpy (out, in);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
void G_InitEdict (edict_t *e)
|
||||
{
|
||||
e->inuse = true;
|
||||
e->classname = "noclass";
|
||||
e->gravity = 1.0;
|
||||
e->s.number = e - g_edicts;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
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
|
||||
}
|
921
ctf/g_weapon.c
Normal file
|
@ -0,0 +1,921 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#include "g_local.h"
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
check_dodge
|
||||
|
||||
This is a support routine used when a client is firing
|
||||
a non-instant attack weapon. It checks to see if a
|
||||
monster's dodge function should be called.
|
||||
=================
|
||||
*/
|
||||
static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed)
|
||||
{
|
||||
vec3_t end;
|
||||
vec3_t v;
|
||||
trace_t tr;
|
||||
float eta;
|
||||
|
||||
// easy mode only ducks one quarter the time
|
||||
if (skill->value == 0)
|
||||
{
|
||||
if (random() > 0.25)
|
||||
return;
|
||||
}
|
||||
VectorMA (start, 8192, dir, end);
|
||||
tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
|
||||
if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self))
|
||||
{
|
||||
VectorSubtract (tr.endpos, start, v);
|
||||
eta = (VectorLength(v) - tr.ent->maxs[0]) / speed;
|
||||
tr.ent->monsterinfo.dodge (tr.ent, self, eta);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
fire_hit
|
||||
|
||||
Used for all impact (hit/punch/slash) attacks
|
||||
=================
|
||||
*/
|
||||
qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick)
|
||||
{
|
||||
trace_t tr;
|
||||
vec3_t forward, right, up;
|
||||
vec3_t v;
|
||||
vec3_t point;
|
||||
float range;
|
||||
vec3_t dir;
|
||||
|
||||
//see if enemy is in range
|
||||
VectorSubtract (self->enemy->s.origin, self->s.origin, dir);
|
||||
range = VectorLength(dir);
|
||||
if (range > aim[0])
|
||||
return false;
|
||||
|
||||
if (aim[1] > self->mins[0] && aim[1] < self->maxs[0])
|
||||
{
|
||||
// the hit is straight on so back the range up to the edge of their bbox
|
||||
range -= self->enemy->maxs[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is a side hit so adjust the "right" value out to the edge of their bbox
|
||||
if (aim[1] < 0)
|
||||
aim[1] = self->enemy->mins[0];
|
||||
else
|
||||
aim[1] = self->enemy->maxs[0];
|
||||
}
|
||||
|
||||
VectorMA (self->s.origin, range, dir, point);
|
||||
|
||||
tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT);
|
||||
if (tr.fraction < 1)
|
||||
{
|
||||
if (!tr.ent->takedamage)
|
||||
return false;
|
||||
// if it will hit any client/monster then hit the one we wanted to hit
|
||||
if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
|
||||
tr.ent = self->enemy;
|
||||
}
|
||||
|
||||
AngleVectors(self->s.angles, forward, right, up);
|
||||
VectorMA (self->s.origin, range, forward, point);
|
||||
VectorMA (point, aim[1], right, point);
|
||||
VectorMA (point, aim[2], up, point);
|
||||
VectorSubtract (point, self->enemy->s.origin, dir);
|
||||
|
||||
// do the damage
|
||||
T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT);
|
||||
|
||||
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
|
||||
return false;
|
||||
|
||||
// do our special form of knockback here
|
||||
VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v);
|
||||
VectorSubtract (v, point, v);
|
||||
VectorNormalize (v);
|
||||
VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity);
|
||||
if (self->enemy->velocity[2] > 0)
|
||||
self->enemy->groundentity = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
fire_lead
|
||||
|
||||
This is an internal support routine used for bullet/pellet based weapons.
|
||||
=================
|
||||
*/
|
||||
static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod)
|
||||
{
|
||||
trace_t tr;
|
||||
vec3_t dir;
|
||||
vec3_t forward, right, up;
|
||||
vec3_t end;
|
||||
float r;
|
||||
float u;
|
||||
vec3_t water_start;
|
||||
qboolean water = false;
|
||||
int content_mask = MASK_SHOT | MASK_WATER;
|
||||
|
||||
tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);
|
||||
if (!(tr.fraction < 1.0))
|
||||
{
|
||||
vectoangles (aimdir, dir);
|
||||
AngleVectors (dir, forward, right, up);
|
||||
|
||||
r = crandom()*hspread;
|
||||
u = crandom()*vspread;
|
||||
VectorMA (start, 8192, forward, end);
|
||||
VectorMA (end, r, right, end);
|
||||
VectorMA (end, u, up, end);
|
||||
|
||||
if (gi.pointcontents (start) & MASK_WATER)
|
||||
{
|
||||
water = true;
|
||||
VectorCopy (start, water_start);
|
||||
content_mask &= ~MASK_WATER;
|
||||
}
|
||||
|
||||
tr = gi.trace (start, NULL, NULL, end, self, content_mask);
|
||||
|
||||
// see if we hit water
|
||||
if (tr.contents & MASK_WATER)
|
||||
{
|
||||
int color;
|
||||
|
||||
water = true;
|
||||
VectorCopy (tr.endpos, water_start);
|
||||
|
||||
if (!VectorCompare (start, tr.endpos))
|
||||
{
|
||||
if (tr.contents & CONTENTS_WATER)
|
||||
{
|
||||
if (strcmp(tr.surface->name, "*brwater") == 0)
|
||||
color = SPLASH_BROWN_WATER;
|
||||
else
|
||||
color = SPLASH_BLUE_WATER;
|
||||
}
|
||||
else if (tr.contents & CONTENTS_SLIME)
|
||||
color = SPLASH_SLIME;
|
||||
else if (tr.contents & CONTENTS_LAVA)
|
||||
color = SPLASH_LAVA;
|
||||
else
|
||||
color = SPLASH_UNKNOWN;
|
||||
|
||||
if (color != SPLASH_UNKNOWN)
|
||||
{
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_SPLASH);
|
||||
gi.WriteByte (8);
|
||||
gi.WritePosition (tr.endpos);
|
||||
gi.WriteDir (tr.plane.normal);
|
||||
gi.WriteByte (color);
|
||||
gi.multicast (tr.endpos, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
// change bullet's course when it enters water
|
||||
VectorSubtract (end, start, dir);
|
||||
vectoangles (dir, dir);
|
||||
AngleVectors (dir, forward, right, up);
|
||||
r = crandom()*hspread*2;
|
||||
u = crandom()*vspread*2;
|
||||
VectorMA (water_start, 8192, forward, end);
|
||||
VectorMA (end, r, right, end);
|
||||
VectorMA (end, u, up, end);
|
||||
}
|
||||
|
||||
// re-trace ignoring water this time
|
||||
tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT);
|
||||
}
|
||||
}
|
||||
|
||||
// send gun puff / flash
|
||||
if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
|
||||
{
|
||||
if (tr.fraction < 1.0)
|
||||
{
|
||||
if (tr.ent->takedamage)
|
||||
{
|
||||
T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strncmp (tr.surface->name, "sky", 3) != 0)
|
||||
{
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (te_impact);
|
||||
gi.WritePosition (tr.endpos);
|
||||
gi.WriteDir (tr.plane.normal);
|
||||
gi.multicast (tr.endpos, MULTICAST_PVS);
|
||||
|
||||
if (self->client)
|
||||
PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if went through water, determine where the end and make a bubble trail
|
||||
if (water)
|
||||
{
|
||||
vec3_t pos;
|
||||
|
||||
VectorSubtract (tr.endpos, water_start, dir);
|
||||
VectorNormalize (dir);
|
||||
VectorMA (tr.endpos, -2, dir, pos);
|
||||
if (gi.pointcontents (pos) & MASK_WATER)
|
||||
VectorCopy (pos, tr.endpos);
|
||||
else
|
||||
tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
|
||||
|
||||
VectorAdd (water_start, tr.endpos, pos);
|
||||
VectorScale (pos, 0.5, pos);
|
||||
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_BUBBLETRAIL);
|
||||
gi.WritePosition (water_start);
|
||||
gi.WritePosition (tr.endpos);
|
||||
gi.multicast (pos, MULTICAST_PVS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
fire_bullet
|
||||
|
||||
Fires a single round. Used for machinegun and chaingun. Would be fine for
|
||||
pistols, rifles, etc....
|
||||
=================
|
||||
*/
|
||||
void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod)
|
||||
{
|
||||
fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
fire_shotgun
|
||||
|
||||
Shoots shotgun pellets. Used by shotgun and super shotgun.
|
||||
=================
|
||||
*/
|
||||
void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
fire_blaster
|
||||
|
||||
Fires a single blaster bolt. Used by the blaster and hyper blaster.
|
||||
=================
|
||||
*/
|
||||
void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||
{
|
||||
int mod;
|
||||
|
||||
if (other == self->owner)
|
||||
return;
|
||||
|
||||
if (surf && (surf->flags & SURF_SKY))
|
||||
{
|
||||
G_FreeEdict (self);
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->owner->client)
|
||||
PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
|
||||
|
||||
if (other->takedamage)
|
||||
{
|
||||
if (self->spawnflags & 1)
|
||||
mod = MOD_HYPERBLASTER;
|
||||
else
|
||||
mod = MOD_BLASTER;
|
||||
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod);
|
||||
}
|
||||
else
|
||||
{
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_BLASTER);
|
||||
gi.WritePosition (self->s.origin);
|
||||
if (!plane)
|
||||
gi.WriteDir (vec3_origin);
|
||||
else
|
||||
gi.WriteDir (plane->normal);
|
||||
gi.multicast (self->s.origin, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
G_FreeEdict (self);
|
||||
}
|
||||
|
||||
void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper)
|
||||
{
|
||||
edict_t *bolt;
|
||||
trace_t tr;
|
||||
|
||||
VectorNormalize (dir);
|
||||
|
||||
bolt = G_Spawn();
|
||||
bolt->svflags = SVF_PROJECTILE; // special net code is used for projectiles
|
||||
VectorCopy (start, bolt->s.origin);
|
||||
VectorCopy (start, bolt->s.old_origin);
|
||||
vectoangles (dir, bolt->s.angles);
|
||||
VectorScale (dir, speed, bolt->velocity);
|
||||
bolt->movetype = MOVETYPE_FLYMISSILE;
|
||||
bolt->clipmask = MASK_SHOT;
|
||||
bolt->solid = SOLID_BBOX;
|
||||
bolt->s.effects |= effect;
|
||||
VectorClear (bolt->mins);
|
||||
VectorClear (bolt->maxs);
|
||||
bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2");
|
||||
bolt->s.sound = gi.soundindex ("misc/lasfly.wav");
|
||||
bolt->owner = self;
|
||||
bolt->touch = blaster_touch;
|
||||
bolt->nextthink = level.time + 2;
|
||||
bolt->think = G_FreeEdict;
|
||||
bolt->dmg = damage;
|
||||
bolt->classname = "bolt";
|
||||
if (hyper)
|
||||
bolt->spawnflags = 1;
|
||||
gi.linkentity (bolt);
|
||||
|
||||
if (self->client)
|
||||
check_dodge (self, bolt->s.origin, dir, speed);
|
||||
|
||||
tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
|
||||
if (tr.fraction < 1.0)
|
||||
{
|
||||
VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
|
||||
bolt->touch (bolt, tr.ent, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
fire_grenade
|
||||
=================
|
||||
*/
|
||||
static void Grenade_Explode (edict_t *ent)
|
||||
{
|
||||
vec3_t origin;
|
||||
int mod;
|
||||
|
||||
if (ent->owner->client)
|
||||
PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
|
||||
|
||||
//FIXME: if we are onground then raise our Z just a bit since we are a point?
|
||||
if (ent->enemy)
|
||||
{
|
||||
float points;
|
||||
vec3_t v;
|
||||
vec3_t dir;
|
||||
|
||||
VectorAdd (ent->enemy->mins, ent->enemy->maxs, v);
|
||||
VectorMA (ent->enemy->s.origin, 0.5, v, v);
|
||||
VectorSubtract (ent->s.origin, v, v);
|
||||
points = ent->dmg - 0.5 * VectorLength (v);
|
||||
VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir);
|
||||
if (ent->spawnflags & 1)
|
||||
mod = MOD_HANDGRENADE;
|
||||
else
|
||||
mod = MOD_GRENADE;
|
||||
T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
|
||||
}
|
||||
|
||||
if (ent->spawnflags & 2)
|
||||
mod = MOD_HELD_GRENADE;
|
||||
else if (ent->spawnflags & 1)
|
||||
mod = MOD_HG_SPLASH;
|
||||
else
|
||||
mod = MOD_G_SPLASH;
|
||||
T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod);
|
||||
|
||||
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
if (ent->waterlevel)
|
||||
{
|
||||
if (ent->groundentity)
|
||||
gi.WriteByte (TE_GRENADE_EXPLOSION_WATER);
|
||||
else
|
||||
gi.WriteByte (TE_ROCKET_EXPLOSION_WATER);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ent->groundentity)
|
||||
gi.WriteByte (TE_GRENADE_EXPLOSION);
|
||||
else
|
||||
gi.WriteByte (TE_ROCKET_EXPLOSION);
|
||||
}
|
||||
gi.WritePosition (origin);
|
||||
gi.multicast (ent->s.origin, MULTICAST_PHS);
|
||||
|
||||
G_FreeEdict (ent);
|
||||
}
|
||||
|
||||
static void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||
{
|
||||
if (other == ent->owner)
|
||||
return;
|
||||
|
||||
if (surf && (surf->flags & SURF_SKY))
|
||||
{
|
||||
G_FreeEdict (ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!other->takedamage)
|
||||
{
|
||||
if (ent->spawnflags & 1)
|
||||
{
|
||||
if (random() > 0.5)
|
||||
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
|
||||
else
|
||||
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ent->enemy = other;
|
||||
Grenade_Explode (ent);
|
||||
}
|
||||
|
||||
void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius)
|
||||
{
|
||||
edict_t *grenade;
|
||||
vec3_t dir;
|
||||
vec3_t forward, right, up;
|
||||
|
||||
vectoangles (aimdir, dir);
|
||||
AngleVectors (dir, forward, right, up);
|
||||
|
||||
grenade = G_Spawn();
|
||||
VectorCopy (start, grenade->s.origin);
|
||||
VectorScale (aimdir, speed, grenade->velocity);
|
||||
VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
|
||||
VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
|
||||
VectorSet (grenade->avelocity, 300, 300, 300);
|
||||
grenade->movetype = MOVETYPE_BOUNCE;
|
||||
grenade->clipmask = MASK_SHOT;
|
||||
grenade->solid = SOLID_BBOX;
|
||||
grenade->s.effects |= EF_GRENADE;
|
||||
VectorClear (grenade->mins);
|
||||
VectorClear (grenade->maxs);
|
||||
grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2");
|
||||
grenade->owner = self;
|
||||
grenade->touch = Grenade_Touch;
|
||||
grenade->nextthink = level.time + timer;
|
||||
grenade->think = Grenade_Explode;
|
||||
grenade->dmg = damage;
|
||||
grenade->dmg_radius = damage_radius;
|
||||
grenade->classname = "grenade";
|
||||
|
||||
gi.linkentity (grenade);
|
||||
}
|
||||
|
||||
void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held)
|
||||
{
|
||||
edict_t *grenade;
|
||||
vec3_t dir;
|
||||
vec3_t forward, right, up;
|
||||
|
||||
vectoangles (aimdir, dir);
|
||||
AngleVectors (dir, forward, right, up);
|
||||
|
||||
grenade = G_Spawn();
|
||||
VectorCopy (start, grenade->s.origin);
|
||||
VectorScale (aimdir, speed, grenade->velocity);
|
||||
VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
|
||||
VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
|
||||
VectorSet (grenade->avelocity, 300, 300, 300);
|
||||
grenade->movetype = MOVETYPE_BOUNCE;
|
||||
grenade->clipmask = MASK_SHOT;
|
||||
grenade->solid = SOLID_BBOX;
|
||||
grenade->s.effects |= EF_GRENADE;
|
||||
VectorClear (grenade->mins);
|
||||
VectorClear (grenade->maxs);
|
||||
grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2");
|
||||
grenade->owner = self;
|
||||
grenade->touch = Grenade_Touch;
|
||||
grenade->nextthink = level.time + timer;
|
||||
grenade->think = Grenade_Explode;
|
||||
grenade->dmg = damage;
|
||||
grenade->dmg_radius = damage_radius;
|
||||
grenade->classname = "hgrenade";
|
||||
if (held)
|
||||
grenade->spawnflags = 3;
|
||||
else
|
||||
grenade->spawnflags = 1;
|
||||
grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav");
|
||||
|
||||
if (timer <= 0.0)
|
||||
Grenade_Explode (grenade);
|
||||
else
|
||||
{
|
||||
gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0);
|
||||
gi.linkentity (grenade);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
fire_rocket
|
||||
=================
|
||||
*/
|
||||
void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||
{
|
||||
vec3_t origin;
|
||||
int n;
|
||||
|
||||
if (other == ent->owner)
|
||||
return;
|
||||
|
||||
if (surf && (surf->flags & SURF_SKY))
|
||||
{
|
||||
G_FreeEdict (ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ent->owner->client)
|
||||
PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
|
||||
|
||||
// calculate position for the explosion entity
|
||||
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
||||
|
||||
if (other->takedamage)
|
||||
{
|
||||
T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET);
|
||||
}
|
||||
else
|
||||
{
|
||||
// don't throw any debris in net games
|
||||
if (!deathmatch->value && !coop->value)
|
||||
{
|
||||
if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING)))
|
||||
{
|
||||
n = rand() % 5;
|
||||
while(n--)
|
||||
ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH);
|
||||
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
if (ent->waterlevel)
|
||||
gi.WriteByte (TE_ROCKET_EXPLOSION_WATER);
|
||||
else
|
||||
gi.WriteByte (TE_ROCKET_EXPLOSION);
|
||||
gi.WritePosition (origin);
|
||||
gi.multicast (ent->s.origin, MULTICAST_PHS);
|
||||
|
||||
G_FreeEdict (ent);
|
||||
}
|
||||
|
||||
void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
|
||||
{
|
||||
edict_t *rocket;
|
||||
|
||||
rocket = G_Spawn();
|
||||
VectorCopy (start, rocket->s.origin);
|
||||
VectorCopy (dir, rocket->movedir);
|
||||
vectoangles (dir, rocket->s.angles);
|
||||
VectorScale (dir, speed, rocket->velocity);
|
||||
rocket->movetype = MOVETYPE_FLYMISSILE;
|
||||
rocket->clipmask = MASK_SHOT;
|
||||
rocket->solid = SOLID_BBOX;
|
||||
rocket->s.effects |= EF_ROCKET;
|
||||
VectorClear (rocket->mins);
|
||||
VectorClear (rocket->maxs);
|
||||
rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
|
||||
rocket->owner = self;
|
||||
rocket->touch = rocket_touch;
|
||||
rocket->nextthink = level.time + 8000/speed;
|
||||
rocket->think = G_FreeEdict;
|
||||
rocket->dmg = damage;
|
||||
rocket->radius_dmg = radius_damage;
|
||||
rocket->dmg_radius = damage_radius;
|
||||
rocket->s.sound = gi.soundindex ("weapons/rockfly.wav");
|
||||
rocket->classname = "rocket";
|
||||
|
||||
if (self->client)
|
||||
check_dodge (self, rocket->s.origin, dir, speed);
|
||||
|
||||
gi.linkentity (rocket);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
fire_rail
|
||||
=================
|
||||
*/
|
||||
void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick)
|
||||
{
|
||||
vec3_t from;
|
||||
vec3_t end;
|
||||
trace_t tr;
|
||||
edict_t *ignore;
|
||||
int mask;
|
||||
qboolean water;
|
||||
|
||||
VectorMA (start, 8192, aimdir, end);
|
||||
VectorCopy (start, from);
|
||||
ignore = self;
|
||||
water = false;
|
||||
mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA;
|
||||
while (ignore)
|
||||
{
|
||||
tr = gi.trace (from, NULL, NULL, end, ignore, mask);
|
||||
|
||||
if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA))
|
||||
{
|
||||
mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA);
|
||||
water = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//ZOID--added so rail goes through SOLID_BBOX entities (gibs, etc)
|
||||
if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) ||
|
||||
(tr.ent->solid == SOLID_BBOX))
|
||||
ignore = tr.ent;
|
||||
else
|
||||
ignore = NULL;
|
||||
|
||||
if ((tr.ent != self) && (tr.ent->takedamage))
|
||||
T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN);
|
||||
}
|
||||
|
||||
VectorCopy (tr.endpos, from);
|
||||
}
|
||||
|
||||
// send gun puff / flash
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_RAILTRAIL);
|
||||
gi.WritePosition (start);
|
||||
gi.WritePosition (tr.endpos);
|
||||
gi.multicast (self->s.origin, MULTICAST_PHS);
|
||||
// gi.multicast (start, MULTICAST_PHS);
|
||||
if (water)
|
||||
{
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_RAILTRAIL);
|
||||
gi.WritePosition (start);
|
||||
gi.WritePosition (tr.endpos);
|
||||
gi.multicast (tr.endpos, MULTICAST_PHS);
|
||||
}
|
||||
|
||||
if (self->client)
|
||||
PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
fire_bfg
|
||||
=================
|
||||
*/
|
||||
void bfg_explode (edict_t *self)
|
||||
{
|
||||
edict_t *ent;
|
||||
float points;
|
||||
vec3_t v;
|
||||
float dist;
|
||||
|
||||
if (self->s.frame == 0)
|
||||
{
|
||||
// the BFG effect
|
||||
ent = NULL;
|
||||
while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL)
|
||||
{
|
||||
if (!ent->takedamage)
|
||||
continue;
|
||||
if (ent == self->owner)
|
||||
continue;
|
||||
if (!CanDamage (ent, self))
|
||||
continue;
|
||||
if (!CanDamage (ent, self->owner))
|
||||
continue;
|
||||
|
||||
VectorAdd (ent->mins, ent->maxs, v);
|
||||
VectorMA (ent->s.origin, 0.5, v, v);
|
||||
VectorSubtract (self->s.origin, v, v);
|
||||
dist = VectorLength(v);
|
||||
points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius));
|
||||
if (ent == self->owner)
|
||||
points = points * 0.5;
|
||||
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_BFG_EXPLOSION);
|
||||
gi.WritePosition (ent->s.origin);
|
||||
gi.multicast (ent->s.origin, MULTICAST_PHS);
|
||||
T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT);
|
||||
}
|
||||
}
|
||||
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
self->s.frame++;
|
||||
if (self->s.frame == 5)
|
||||
self->think = G_FreeEdict;
|
||||
}
|
||||
|
||||
void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||||
{
|
||||
if (other == self->owner)
|
||||
return;
|
||||
|
||||
if (surf && (surf->flags & SURF_SKY))
|
||||
{
|
||||
G_FreeEdict (self);
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->owner->client)
|
||||
PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
|
||||
|
||||
// core explosion - prevents firing it into the wall/floor
|
||||
if (other->takedamage)
|
||||
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST);
|
||||
T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST);
|
||||
|
||||
gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0);
|
||||
self->solid = SOLID_NOT;
|
||||
self->touch = NULL;
|
||||
VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin);
|
||||
VectorClear (self->velocity);
|
||||
self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2");
|
||||
self->s.frame = 0;
|
||||
self->s.sound = 0;
|
||||
self->s.effects &= ~EF_ANIM_ALLFAST;
|
||||
self->think = bfg_explode;
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
self->enemy = other;
|
||||
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_BFG_BIGEXPLOSION);
|
||||
gi.WritePosition (self->s.origin);
|
||||
gi.multicast (self->s.origin, MULTICAST_PVS);
|
||||
}
|
||||
|
||||
|
||||
void bfg_think (edict_t *self)
|
||||
{
|
||||
edict_t *ent;
|
||||
edict_t *ignore;
|
||||
vec3_t point;
|
||||
vec3_t dir;
|
||||
vec3_t start;
|
||||
vec3_t end;
|
||||
int dmg;
|
||||
trace_t tr;
|
||||
|
||||
if (deathmatch->value)
|
||||
dmg = 5;
|
||||
else
|
||||
dmg = 10;
|
||||
|
||||
ent = NULL;
|
||||
while ((ent = findradius(ent, self->s.origin, 256)) != NULL)
|
||||
{
|
||||
if (ent == self)
|
||||
continue;
|
||||
|
||||
if (ent == self->owner)
|
||||
continue;
|
||||
|
||||
if (!ent->takedamage)
|
||||
continue;
|
||||
|
||||
if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0))
|
||||
continue;
|
||||
|
||||
//ZOID
|
||||
//don't target players in CTF
|
||||
if (ctf->value && ent->client &&
|
||||
self->owner->client &&
|
||||
ent->client->resp.ctf_team == self->owner->client->resp.ctf_team)
|
||||
continue;
|
||||
//ZOID
|
||||
|
||||
VectorMA (ent->absmin, 0.5, ent->size, point);
|
||||
|
||||
VectorSubtract (point, self->s.origin, dir);
|
||||
VectorNormalize (dir);
|
||||
|
||||
ignore = self;
|
||||
VectorCopy (self->s.origin, start);
|
||||
VectorMA (start, 2048, dir, end);
|
||||
while(1)
|
||||
{
|
||||
tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
|
||||
|
||||
if (!tr.ent)
|
||||
break;
|
||||
|
||||
// hurt it if we can
|
||||
if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner))
|
||||
T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER);
|
||||
|
||||
// if we hit something that's not a monster or player we're done
|
||||
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
|
||||
{
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_LASER_SPARKS);
|
||||
gi.WriteByte (4);
|
||||
gi.WritePosition (tr.endpos);
|
||||
gi.WriteDir (tr.plane.normal);
|
||||
gi.WriteByte (self->s.skinnum);
|
||||
gi.multicast (tr.endpos, MULTICAST_PVS);
|
||||
break;
|
||||
}
|
||||
|
||||
ignore = tr.ent;
|
||||
VectorCopy (tr.endpos, start);
|
||||
}
|
||||
|
||||
gi.WriteByte (svc_temp_entity);
|
||||
gi.WriteByte (TE_BFG_LASER);
|
||||
gi.WritePosition (self->s.origin);
|
||||
gi.WritePosition (tr.endpos);
|
||||
gi.multicast (self->s.origin, MULTICAST_PHS);
|
||||
}
|
||||
|
||||
self->nextthink = level.time + FRAMETIME;
|
||||
}
|
||||
|
||||
|
||||
void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius)
|
||||
{
|
||||
edict_t *bfg;
|
||||
|
||||
bfg = G_Spawn();
|
||||
VectorCopy (start, bfg->s.origin);
|
||||
VectorCopy (dir, bfg->movedir);
|
||||
vectoangles (dir, bfg->s.angles);
|
||||
VectorScale (dir, speed, bfg->velocity);
|
||||
bfg->movetype = MOVETYPE_FLYMISSILE;
|
||||
bfg->clipmask = MASK_SHOT;
|
||||
bfg->solid = SOLID_BBOX;
|
||||
bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST;
|
||||
VectorClear (bfg->mins);
|
||||
VectorClear (bfg->maxs);
|
||||
bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2");
|
||||
bfg->owner = self;
|
||||
bfg->touch = bfg_touch;
|
||||
bfg->nextthink = level.time + 8000/speed;
|
||||
bfg->think = G_FreeEdict;
|
||||
bfg->radius_dmg = damage;
|
||||
bfg->dmg_radius = damage_radius;
|
||||
bfg->classname = "bfg blast";
|
||||
bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav");
|
||||
|
||||
bfg->think = bfg_think;
|
||||
bfg->nextthink = level.time + FRAMETIME;
|
||||
bfg->teammaster = bfg;
|
||||
bfg->teamchain = NULL;
|
||||
|
||||
if (self->client)
|
||||
check_dodge (self, bfg->s.origin, dir, speed);
|
||||
|
||||
gi.linkentity (bfg);
|
||||
}
|
242
ctf/game.h
Normal file
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
// 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
|
||||
//ZOID
|
||||
#define SVF_PROJECTILE 0x00000008 // entity is simple projectile, used for network optimization
|
||||
// if an entity is projectile, the model index/x/y/z/pitch/yaw are sent, encoded into
|
||||
// seven (or eight) bytes. This is to speed up projectiles. Currently, only the
|
||||
// hyperblaster makes use of this. use for items that are moving with a constant
|
||||
// velocity that don't change direction or model
|
||||
//ZOID
|
||||
|
||||
// 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);
|
556
ctf/m_move.c
Normal file
|
@ -0,0 +1,556 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// 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);
|
||||
}
|
225
ctf/m_player.h
Normal file
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
1741
ctf/p_client.c
Normal file
544
ctf/p_hud.c
Normal file
|
@ -0,0 +1,544 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#include "g_local.h"
|
||||
|
||||
|
||||
|
||||
/*
|
||||
======================================================================
|
||||
|
||||
INTERMISSION
|
||||
|
||||
======================================================================
|
||||
*/
|
||||
|
||||
void MoveClientToIntermission (edict_t *ent)
|
||||
{
|
||||
if (deathmatch->value || coop->value)
|
||||
ent->client->showscores = true;
|
||||
VectorCopy (level.intermission_origin, ent->s.origin);
|
||||
ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8;
|
||||
ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8;
|
||||
ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8;
|
||||
VectorCopy (level.intermission_angle, ent->client->ps.viewangles);
|
||||
ent->client->ps.pmove.pm_type = PM_FREEZE;
|
||||
ent->client->ps.gunindex = 0;
|
||||
ent->client->ps.blend[3] = 0;
|
||||
ent->client->ps.rdflags &= ~RDF_UNDERWATER;
|
||||
|
||||
// clean up powerup info
|
||||
ent->client->quad_framenum = 0;
|
||||
ent->client->invincible_framenum = 0;
|
||||
ent->client->breather_framenum = 0;
|
||||
ent->client->enviro_framenum = 0;
|
||||
ent->client->grenade_blew_up = false;
|
||||
ent->client->grenade_time = 0;
|
||||
|
||||
ent->viewheight = 0;
|
||||
ent->s.modelindex = 0;
|
||||
ent->s.modelindex2 = 0;
|
||||
ent->s.modelindex3 = 0;
|
||||
ent->s.modelindex = 0;
|
||||
ent->s.effects = 0;
|
||||
ent->s.sound = 0;
|
||||
ent->solid = SOLID_NOT;
|
||||
|
||||
// add the layout
|
||||
|
||||
if (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; // allready activated
|
||||
|
||||
//ZOID
|
||||
if (deathmatch->value && ctf->value)
|
||||
CTFCalcScores();
|
||||
//ZOID
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
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;
|
||||
|
||||
//ZOID
|
||||
if (ctf->value) {
|
||||
CTFScoreboardMessage (ent, killer);
|
||||
return;
|
||||
}
|
||||
//ZOID
|
||||
|
||||
// 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;
|
||||
//ZOID
|
||||
if (ent->client->menu)
|
||||
PMenu_Close(ent);
|
||||
//ZOID
|
||||
|
||||
if (!deathmatch->value && !coop->value)
|
||||
return;
|
||||
|
||||
if (ent->client->showscores)
|
||||
{
|
||||
ent->client->showscores = false;
|
||||
ent->client->update_chase = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ent->client->showscores = true;
|
||||
|
||||
DeathmatchScoreboard (ent);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
HelpComputer
|
||||
|
||||
Draw help computer.
|
||||
==================
|
||||
*/
|
||||
void HelpComputer (edict_t *ent)
|
||||
{
|
||||
char string[1024];
|
||||
char *sk;
|
||||
|
||||
if (skill->value == 0)
|
||||
sk = "easy";
|
||||
else if (skill->value == 1)
|
||||
sk = "medium";
|
||||
else if (skill->value == 2)
|
||||
sk = "hard";
|
||||
else
|
||||
sk = "hard+";
|
||||
|
||||
// send the layout
|
||||
Com_sprintf (string, sizeof(string),
|
||||
"xv 32 yv 8 picn help " // background
|
||||
"xv 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
|
||||
===============
|
||||
*/
|
||||
void G_SetStats (edict_t *ent)
|
||||
{
|
||||
gitem_t *item;
|
||||
int index, cells;
|
||||
int power_armor_type;
|
||||
|
||||
//
|
||||
// health
|
||||
//
|
||||
ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
|
||||
ent->client->ps.stats[STAT_HEALTH] = ent->health;
|
||||
|
||||
//
|
||||
// ammo
|
||||
//
|
||||
if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */)
|
||||
{
|
||||
ent->client->ps.stats[STAT_AMMO_ICON] = 0;
|
||||
ent->client->ps.stats[STAT_AMMO] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
item = &itemlist[ent->client->ammo_index];
|
||||
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon);
|
||||
ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index];
|
||||
}
|
||||
|
||||
//
|
||||
// armor
|
||||
//
|
||||
power_armor_type = PowerArmorType (ent);
|
||||
if (power_armor_type)
|
||||
{
|
||||
cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
|
||||
if (cells == 0)
|
||||
{ // ran out of cells for power armor
|
||||
ent->flags &= ~FL_POWER_ARMOR;
|
||||
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
|
||||
power_armor_type = 0;;
|
||||
}
|
||||
}
|
||||
|
||||
index = ArmorIndex (ent);
|
||||
if (power_armor_type && (!index || (level.framenum & 8) ) )
|
||||
{ // flash between power armor and other armor icon
|
||||
ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield");
|
||||
ent->client->ps.stats[STAT_ARMOR] = cells;
|
||||
}
|
||||
else if (index)
|
||||
{
|
||||
item = GetItemByIndex (index);
|
||||
ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon);
|
||||
ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
|
||||
ent->client->ps.stats[STAT_ARMOR] = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// pickup message
|
||||
//
|
||||
if (level.time > ent->client->pickup_msg_time)
|
||||
{
|
||||
ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
|
||||
ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// timers
|
||||
//
|
||||
if (ent->client->quad_framenum > level.framenum)
|
||||
{
|
||||
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad");
|
||||
ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10;
|
||||
}
|
||||
else if (ent->client->invincible_framenum > level.framenum)
|
||||
{
|
||||
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability");
|
||||
ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10;
|
||||
}
|
||||
else if (ent->client->enviro_framenum > level.framenum)
|
||||
{
|
||||
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit");
|
||||
ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10;
|
||||
}
|
||||
else if (ent->client->breather_framenum > level.framenum)
|
||||
{
|
||||
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather");
|
||||
ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->client->ps.stats[STAT_TIMER_ICON] = 0;
|
||||
ent->client->ps.stats[STAT_TIMER] = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// selected item
|
||||
//
|
||||
if (ent->client->pers.selected_item == -1)
|
||||
ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
|
||||
else
|
||||
ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon);
|
||||
|
||||
ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
|
||||
|
||||
//
|
||||
// layouts
|
||||
//
|
||||
ent->client->ps.stats[STAT_LAYOUTS] = 0;
|
||||
|
||||
if (deathmatch->value)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
//
|
||||
// frags
|
||||
//
|
||||
ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
|
||||
|
||||
//
|
||||
// help icon / current weapon if not shown
|
||||
//
|
||||
if (ent->client->resp.helpchanged && (level.framenum&8) )
|
||||
ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help");
|
||||
else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91)
|
||||
&& ent->client->pers.weapon)
|
||||
ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon);
|
||||
else
|
||||
ent->client->ps.stats[STAT_HELPICON] = 0;
|
||||
|
||||
//ZOID
|
||||
SetCTFStats(ent);
|
||||
//ZOID
|
||||
}
|
||||
|
256
ctf/p_menu.c
Normal file
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#include "g_local.h"
|
||||
|
||||
// Note that the pmenu entries are duplicated
|
||||
// this is so that a static set of pmenu entries can be used
|
||||
// for multiple clients and changed without interference
|
||||
// note that arg will be freed when the menu is closed, it must be allocated memory
|
||||
pmenuhnd_t *PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num, void *arg)
|
||||
{
|
||||
pmenuhnd_t *hnd;
|
||||
pmenu_t *p;
|
||||
int i;
|
||||
|
||||
if (!ent->client)
|
||||
return NULL;
|
||||
|
||||
if (ent->client->menu) {
|
||||
gi.dprintf("warning, ent already has a menu\n");
|
||||
PMenu_Close(ent);
|
||||
}
|
||||
|
||||
hnd = malloc(sizeof(*hnd));
|
||||
|
||||
hnd->arg = arg;
|
||||
hnd->entries = malloc(sizeof(pmenu_t) * num);
|
||||
memcpy(hnd->entries, entries, sizeof(pmenu_t) * num);
|
||||
// duplicate the strings since they may be from static memory
|
||||
for (i = 0; i < num; i++)
|
||||
if (entries[i].text)
|
||||
hnd->entries[i].text = strdup(entries[i].text);
|
||||
|
||||
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_Do_Update(ent);
|
||||
gi.unicast (ent, true);
|
||||
|
||||
return hnd;
|
||||
}
|
||||
|
||||
void PMenu_Close(edict_t *ent)
|
||||
{
|
||||
int i;
|
||||
pmenuhnd_t *hnd;
|
||||
|
||||
if (!ent->client->menu)
|
||||
return;
|
||||
|
||||
hnd = ent->client->menu;
|
||||
for (i = 0; i < hnd->num; i++)
|
||||
if (hnd->entries[i].text)
|
||||
free(hnd->entries[i].text);
|
||||
free(hnd->entries);
|
||||
if (hnd->arg)
|
||||
free(hnd->arg);
|
||||
free(hnd);
|
||||
ent->client->menu = NULL;
|
||||
ent->client->showscores = false;
|
||||
}
|
||||
|
||||
// only use on pmenu's that have been called with PMenu_Open
|
||||
void PMenu_UpdateEntry(pmenu_t *entry, const char *text, int align, SelectFunc_t SelectFunc)
|
||||
{
|
||||
if (entry->text)
|
||||
free(entry->text);
|
||||
entry->text = strdup(text);
|
||||
entry->align = align;
|
||||
entry->SelectFunc = SelectFunc;
|
||||
}
|
||||
|
||||
void PMenu_Do_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 picn inventory ");
|
||||
|
||||
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_Update(edict_t *ent)
|
||||
{
|
||||
if (!ent->client->menu) {
|
||||
gi.dprintf("warning: ent has no menu\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (level.time - ent->client->menutime >= 1.0) {
|
||||
// been a second or more since last update, update now
|
||||
PMenu_Do_Update(ent);
|
||||
gi.unicast (ent, true);
|
||||
ent->client->menutime = level.time;
|
||||
ent->client->menudirty = false;
|
||||
}
|
||||
ent->client->menutime = level.time + 0.2;
|
||||
ent->client->menudirty = true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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, hnd);
|
||||
}
|
49
ctf/p_menu.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
enum {
|
||||
PMENU_ALIGN_LEFT,
|
||||
PMENU_ALIGN_CENTER,
|
||||
PMENU_ALIGN_RIGHT
|
||||
};
|
||||
|
||||
typedef struct pmenuhnd_s {
|
||||
struct pmenu_s *entries;
|
||||
int cur;
|
||||
int num;
|
||||
void *arg;
|
||||
} pmenuhnd_t;
|
||||
|
||||
typedef void (*SelectFunc_t)(edict_t *ent, pmenuhnd_t *hnd);
|
||||
|
||||
typedef struct pmenu_s {
|
||||
char *text;
|
||||
int align;
|
||||
SelectFunc_t SelectFunc;
|
||||
} pmenu_t;
|
||||
|
||||
pmenuhnd_t *PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num, void *arg);
|
||||
void PMenu_Close(edict_t *ent);
|
||||
void PMenu_UpdateEntry(pmenu_t *entry, const char *text, int align, SelectFunc_t SelectFunc);
|
||||
void PMenu_Do_Update(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);
|
146
ctf/p_trail.c
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#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)];
|
||||
}
|
1129
ctf/p_view.c
Normal file
1465
ctf/p_weapon.c
Normal file
1419
ctf/q_shared.c
Normal file
1200
ctf/q_shared.h
Normal file
1117
game/g_ai.c
Normal file
175
game/g_chase.c
Normal file
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#include "g_local.h"
|
||||
|
||||
void UpdateChaseCam(edict_t *ent)
|
||||
{
|
||||
vec3_t o, ownerv, goal;
|
||||
edict_t *targ;
|
||||
vec3_t forward, right;
|
||||
trace_t trace;
|
||||
int i;
|
||||
vec3_t oldgoal;
|
||||
vec3_t angles;
|
||||
|
||||
// is our chase target gone?
|
||||
if (!ent->client->chase_target->inuse
|
||||
|| ent->client->chase_target->client->resp.spectator) {
|
||||
edict_t *old = ent->client->chase_target;
|
||||
ChaseNext(ent);
|
||||
if (ent->client->chase_target == old) {
|
||||
ent->client->chase_target = NULL;
|
||||
ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
targ = ent->client->chase_target;
|
||||
|
||||
VectorCopy(targ->s.origin, ownerv);
|
||||
VectorCopy(ent->s.origin, oldgoal);
|
||||
|
||||
ownerv[2] += targ->viewheight;
|
||||
|
||||
VectorCopy(targ->client->v_angle, angles);
|
||||
if (angles[PITCH] > 56)
|
||||
angles[PITCH] = 56;
|
||||
AngleVectors (angles, forward, right, NULL);
|
||||
VectorNormalize(forward);
|
||||
VectorMA(ownerv, -30, forward, o);
|
||||
|
||||
if (o[2] < targ->s.origin[2] + 20)
|
||||
o[2] = targ->s.origin[2] + 20;
|
||||
|
||||
// jump animation lifts
|
||||
if (!targ->groundentity)
|
||||
o[2] += 16;
|
||||
|
||||
trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
|
||||
|
||||
VectorCopy(trace.endpos, goal);
|
||||
|
||||
VectorMA(goal, 2, forward, goal);
|
||||
|
||||
// pad for floors and ceilings
|
||||
VectorCopy(goal, o);
|
||||
o[2] += 6;
|
||||
trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
|
||||
if (trace.fraction < 1) {
|
||||
VectorCopy(trace.endpos, goal);
|
||||
goal[2] -= 6;
|
||||
}
|
||||
|
||||
VectorCopy(goal, o);
|
||||
o[2] -= 6;
|
||||
trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
|
||||
if (trace.fraction < 1) {
|
||||
VectorCopy(trace.endpos, goal);
|
||||
goal[2] += 6;
|
||||
}
|
||||
|
||||
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(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->client->resp.spectator)
|
||||
break;
|
||||
} while (e != ent->client->chase_target);
|
||||
|
||||
ent->client->chase_target = e;
|
||||
ent->client->update_chase = true;
|
||||
}
|
||||
|
||||
void ChasePrev(edict_t *ent)
|
||||
{
|
||||
int i;
|
||||
edict_t *e;
|
||||
|
||||
if (!ent->client->chase_target)
|
||||
return;
|
||||
|
||||
i = ent->client->chase_target - g_edicts;
|
||||
do {
|
||||
i--;
|
||||
if (i < 1)
|
||||
i = maxclients->value;
|
||||
e = g_edicts + i;
|
||||
if (!e->inuse)
|
||||
continue;
|
||||
if (!e->client->resp.spectator)
|
||||
break;
|
||||
} while (e != ent->client->chase_target);
|
||||
|
||||
ent->client->chase_target = e;
|
||||
ent->client->update_chase = true;
|
||||
}
|
||||
|
||||
void GetChaseTarget(edict_t *ent)
|
||||
{
|
||||
int i;
|
||||
edict_t *other;
|
||||
|
||||
for (i = 1; i <= maxclients->value; i++) {
|
||||
other = g_edicts + i;
|
||||
if (other->inuse && !other->client->resp.spectator) {
|
||||
ent->client->chase_target = other;
|
||||
ent->client->update_chase = true;
|
||||
UpdateChaseCam(ent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
gi.centerprintf(ent, "No other players to chase.");
|
||||
}
|
||||
|
992
game/g_cmds.c
Normal file
|
@ -0,0 +1,992 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#include "g_local.h"
|
||||
#include "m_player.h"
|
||||
|
||||
|
||||
char *ClientTeam (edict_t *ent)
|
||||
{
|
||||
char *p;
|
||||
static char value[512];
|
||||
|
||||
value[0] = 0;
|
||||
|
||||
if (!ent->client)
|
||||
return value;
|
||||
|
||||
strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin"));
|
||||
p = strchr(value, '/');
|
||||
if (!p)
|
||||
return value;
|
||||
|
||||
if ((int)(dmflags->value) & DF_MODELTEAMS)
|
||||
{
|
||||
*p = 0;
|
||||
return value;
|
||||
}
|
||||
|
||||
// if ((int)(dmflags->value) & DF_SKINTEAMS)
|
||||
return ++p;
|
||||
}
|
||||
|
||||
qboolean OnSameTeam (edict_t *ent1, edict_t *ent2)
|
||||
{
|
||||
char ent1Team [512];
|
||||
char ent2Team [512];
|
||||
|
||||
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
|
||||
return false;
|
||||
|
||||
strcpy (ent1Team, ClientTeam (ent1));
|
||||
strcpy (ent2Team, ClientTeam (ent2));
|
||||
|
||||
if (strcmp(ent1Team, ent2Team) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void SelectNextItem (edict_t *ent, int itflags)
|
||||
{
|
||||
gclient_t *cl;
|
||||
int i, index;
|
||||
gitem_t *it;
|
||||
|
||||
cl = ent->client;
|
||||
|
||||
if (cl->chase_target) {
|
||||
ChaseNext(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
// scan for the next valid one
|
||||
for (i=1 ; i<=MAX_ITEMS ; i++)
|
||||
{
|
||||
index = (cl->pers.selected_item + i)%MAX_ITEMS;
|
||||
if (!cl->pers.inventory[index])
|
||||
continue;
|
||||
it = &itemlist[index];
|
||||
if (!it->use)
|
||||
continue;
|
||||
if (!(it->flags & itflags))
|
||||
continue;
|
||||
|
||||
cl->pers.selected_item = index;
|
||||
return;
|
||||
}
|
||||
|
||||
cl->pers.selected_item = -1;
|
||||
}
|
||||
|
||||
void SelectPrevItem (edict_t *ent, int itflags)
|
||||
{
|
||||
gclient_t *cl;
|
||||
int i, index;
|
||||
gitem_t *it;
|
||||
|
||||
cl = ent->client;
|
||||
|
||||
if (cl->chase_target) {
|
||||
ChasePrev(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
// scan for the next valid one
|
||||
for (i=1 ; i<=MAX_ITEMS ; i++)
|
||||
{
|
||||
index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS;
|
||||
if (!cl->pers.inventory[index])
|
||||
continue;
|
||||
it = &itemlist[index];
|
||||
if (!it->use)
|
||||
continue;
|
||||
if (!(it->flags & itflags))
|
||||
continue;
|
||||
|
||||
cl->pers.selected_item = index;
|
||||
return;
|
||||
}
|
||||
|
||||
cl->pers.selected_item = -1;
|
||||
}
|
||||
|
||||
void ValidateSelectedItem (edict_t *ent)
|
||||
{
|
||||
gclient_t *cl;
|
||||
|
||||
cl = ent->client;
|
||||
|
||||
if (cl->pers.inventory[cl->pers.selected_item])
|
||||
return; // valid
|
||||
|
||||
SelectNextItem (ent, -1);
|
||||
}
|
||||
|
||||
|
||||
//=================================================================================
|
||||
|
||||
/*
|
||||
==================
|
||||
Cmd_Give_f
|
||||
|
||||
Give items to a client
|
||||
==================
|
||||
*/
|
||||
void Cmd_Give_f (edict_t *ent)
|
||||
{
|
||||
char *name;
|
||||
gitem_t *it;
|
||||
int index;
|
||||
int i;
|
||||
qboolean give_all;
|
||||
edict_t *it_ent;
|
||||
|
||||
if (deathmatch->value && !sv_cheats->value)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
name = gi.args();
|
||||
|
||||
if (Q_stricmp(name, "all") == 0)
|
||||
give_all = true;
|
||||
else
|
||||
give_all = false;
|
||||
|
||||
if (give_all || Q_stricmp(gi.argv(1), "health") == 0)
|
||||
{
|
||||
if (gi.argc() == 3)
|
||||
ent->health = atoi(gi.argv(2));
|
||||
else
|
||||
ent->health = ent->max_health;
|
||||
if (!give_all)
|
||||
return;
|
||||
}
|
||||
|
||||
if (give_all || Q_stricmp(name, "weapons") == 0)
|
||||
{
|
||||
for (i=0 ; i<game.num_items ; i++)
|
||||
{
|
||||
it = itemlist + i;
|
||||
if (!it->pickup)
|
||||
continue;
|
||||
if (!(it->flags & IT_WEAPON))
|
||||
continue;
|
||||
ent->client->pers.inventory[i] += 1;
|
||||
}
|
||||
if (!give_all)
|
||||
return;
|
||||
}
|
||||
|
||||
if (give_all || Q_stricmp(name, "ammo") == 0)
|
||||
{
|
||||
for (i=0 ; i<game.num_items ; i++)
|
||||
{
|
||||
it = itemlist + i;
|
||||
if (!it->pickup)
|
||||
continue;
|
||||
if (!(it->flags & IT_AMMO))
|
||||
continue;
|
||||
Add_Ammo (ent, it, 1000);
|
||||
}
|
||||
if (!give_all)
|
||||
return;
|
||||
}
|
||||
|
||||
if (give_all || Q_stricmp(name, "armor") == 0)
|
||||
{
|
||||
gitem_armor_t *info;
|
||||
|
||||
it = FindItem("Jacket Armor");
|
||||
ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
|
||||
|
||||
it = FindItem("Combat Armor");
|
||||
ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
|
||||
|
||||
it = FindItem("Body Armor");
|
||||
info = (gitem_armor_t *)it->info;
|
||||
ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count;
|
||||
|
||||
if (!give_all)
|
||||
return;
|
||||
}
|
||||
|
||||
if (give_all || Q_stricmp(name, "Power Shield") == 0)
|
||||
{
|
||||
it = FindItem("Power Shield");
|
||||
it_ent = G_Spawn();
|
||||
it_ent->classname = it->classname;
|
||||
SpawnItem (it_ent, it);
|
||||
Touch_Item (it_ent, ent, NULL, NULL);
|
||||
if (it_ent->inuse)
|
||||
G_FreeEdict(it_ent);
|
||||
|
||||
if (!give_all)
|
||||
return;
|
||||
}
|
||||
|
||||
if (give_all)
|
||||
{
|
||||
for (i=0 ; i<game.num_items ; i++)
|
||||
{
|
||||
it = itemlist + i;
|
||||
if (!it->pickup)
|
||||
continue;
|
||||
if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO))
|
||||
continue;
|
||||
ent->client->pers.inventory[i] = 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
it = FindItem (name);
|
||||
if (!it)
|
||||
{
|
||||
name = gi.argv(1);
|
||||
it = FindItem (name);
|
||||
if (!it)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "unknown item\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!it->pickup)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "non-pickup item\n");
|
||||
return;
|
||||
}
|
||||
|
||||
index = ITEM_INDEX(it);
|
||||
|
||||
if (it->flags & IT_AMMO)
|
||||
{
|
||||
if (gi.argc() == 3)
|
||||
ent->client->pers.inventory[index] = atoi(gi.argv(2));
|
||||
else
|
||||
ent->client->pers.inventory[index] += it->quantity;
|
||||
}
|
||||
else
|
||||
{
|
||||
it_ent = G_Spawn();
|
||||
it_ent->classname = it->classname;
|
||||
SpawnItem (it_ent, it);
|
||||
Touch_Item (it_ent, ent, NULL, NULL);
|
||||
if (it_ent->inuse)
|
||||
G_FreeEdict(it_ent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
Cmd_God_f
|
||||
|
||||
Sets client to godmode
|
||||
|
||||
argv(0) god
|
||||
==================
|
||||
*/
|
||||
void Cmd_God_f (edict_t *ent)
|
||||
{
|
||||
char *msg;
|
||||
|
||||
if (deathmatch->value && !sv_cheats->value)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ent->flags ^= FL_GODMODE;
|
||||
if (!(ent->flags & FL_GODMODE) )
|
||||
msg = "godmode OFF\n";
|
||||
else
|
||||
msg = "godmode ON\n";
|
||||
|
||||
gi.cprintf (ent, PRINT_HIGH, msg);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
Cmd_Notarget_f
|
||||
|
||||
Sets client to notarget
|
||||
|
||||
argv(0) notarget
|
||||
==================
|
||||
*/
|
||||
void Cmd_Notarget_f (edict_t *ent)
|
||||
{
|
||||
char *msg;
|
||||
|
||||
if (deathmatch->value && !sv_cheats->value)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ent->flags ^= FL_NOTARGET;
|
||||
if (!(ent->flags & FL_NOTARGET) )
|
||||
msg = "notarget OFF\n";
|
||||
else
|
||||
msg = "notarget ON\n";
|
||||
|
||||
gi.cprintf (ent, PRINT_HIGH, msg);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
Cmd_Noclip_f
|
||||
|
||||
argv(0) noclip
|
||||
==================
|
||||
*/
|
||||
void Cmd_Noclip_f (edict_t *ent)
|
||||
{
|
||||
char *msg;
|
||||
|
||||
if (deathmatch->value && !sv_cheats->value)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ent->movetype == MOVETYPE_NOCLIP)
|
||||
{
|
||||
ent->movetype = MOVETYPE_WALK;
|
||||
msg = "noclip OFF\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->movetype = MOVETYPE_NOCLIP;
|
||||
msg = "noclip ON\n";
|
||||
}
|
||||
|
||||
gi.cprintf (ent, PRINT_HIGH, msg);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
Cmd_Use_f
|
||||
|
||||
Use an inventory item
|
||||
==================
|
||||
*/
|
||||
void Cmd_Use_f (edict_t *ent)
|
||||
{
|
||||
int index;
|
||||
gitem_t *it;
|
||||
char *s;
|
||||
|
||||
s = gi.args();
|
||||
it = FindItem (s);
|
||||
if (!it)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s);
|
||||
return;
|
||||
}
|
||||
if (!it->use)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n");
|
||||
return;
|
||||
}
|
||||
index = ITEM_INDEX(it);
|
||||
if (!ent->client->pers.inventory[index])
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
|
||||
return;
|
||||
}
|
||||
|
||||
it->use (ent, it);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
Cmd_Drop_f
|
||||
|
||||
Drop an inventory item
|
||||
==================
|
||||
*/
|
||||
void Cmd_Drop_f (edict_t *ent)
|
||||
{
|
||||
int index;
|
||||
gitem_t *it;
|
||||
char *s;
|
||||
|
||||
s = gi.args();
|
||||
it = FindItem (s);
|
||||
if (!it)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s);
|
||||
return;
|
||||
}
|
||||
if (!it->drop)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n");
|
||||
return;
|
||||
}
|
||||
index = ITEM_INDEX(it);
|
||||
if (!ent->client->pers.inventory[index])
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
|
||||
return;
|
||||
}
|
||||
|
||||
it->drop (ent, it);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
Cmd_Inven_f
|
||||
=================
|
||||
*/
|
||||
void Cmd_Inven_f (edict_t *ent)
|
||||
{
|
||||
int i;
|
||||
gclient_t *cl;
|
||||
|
||||
cl = ent->client;
|
||||
|
||||
cl->showscores = false;
|
||||
cl->showhelp = false;
|
||||
|
||||
if (cl->showinventory)
|
||||
{
|
||||
cl->showinventory = false;
|
||||
return;
|
||||
}
|
||||
|
||||
cl->showinventory = true;
|
||||
|
||||
gi.WriteByte (svc_inventory);
|
||||
for (i=0 ; i<MAX_ITEMS ; i++)
|
||||
{
|
||||
gi.WriteShort (cl->pers.inventory[i]);
|
||||
}
|
||||
gi.unicast (ent, true);
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Cmd_InvUse_f
|
||||
=================
|
||||
*/
|
||||
void Cmd_InvUse_f (edict_t *ent)
|
||||
{
|
||||
gitem_t *it;
|
||||
|
||||
ValidateSelectedItem (ent);
|
||||
|
||||
if (ent->client->pers.selected_item == -1)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "No item to use.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
it = &itemlist[ent->client->pers.selected_item];
|
||||
if (!it->use)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n");
|
||||
return;
|
||||
}
|
||||
it->use (ent, it);
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Cmd_WeapPrev_f
|
||||
=================
|
||||
*/
|
||||
void Cmd_WeapPrev_f (edict_t *ent)
|
||||
{
|
||||
gclient_t *cl;
|
||||
int i, index;
|
||||
gitem_t *it;
|
||||
int selected_weapon;
|
||||
|
||||
cl = ent->client;
|
||||
|
||||
if (!cl->pers.weapon)
|
||||
return;
|
||||
|
||||
selected_weapon = ITEM_INDEX(cl->pers.weapon);
|
||||
|
||||
// scan for the next valid one
|
||||
for (i=1 ; i<=MAX_ITEMS ; i++)
|
||||
{
|
||||
index = (selected_weapon + i)%MAX_ITEMS;
|
||||
if (!cl->pers.inventory[index])
|
||||
continue;
|
||||
it = &itemlist[index];
|
||||
if (!it->use)
|
||||
continue;
|
||||
if (! (it->flags & IT_WEAPON) )
|
||||
continue;
|
||||
it->use (ent, it);
|
||||
if (cl->pers.weapon == it)
|
||||
return; // successful
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Cmd_WeapNext_f
|
||||
=================
|
||||
*/
|
||||
void Cmd_WeapNext_f (edict_t *ent)
|
||||
{
|
||||
gclient_t *cl;
|
||||
int i, index;
|
||||
gitem_t *it;
|
||||
int selected_weapon;
|
||||
|
||||
cl = ent->client;
|
||||
|
||||
if (!cl->pers.weapon)
|
||||
return;
|
||||
|
||||
selected_weapon = ITEM_INDEX(cl->pers.weapon);
|
||||
|
||||
// scan for the next valid one
|
||||
for (i=1 ; i<=MAX_ITEMS ; i++)
|
||||
{
|
||||
index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS;
|
||||
if (!cl->pers.inventory[index])
|
||||
continue;
|
||||
it = &itemlist[index];
|
||||
if (!it->use)
|
||||
continue;
|
||||
if (! (it->flags & IT_WEAPON) )
|
||||
continue;
|
||||
it->use (ent, it);
|
||||
if (cl->pers.weapon == it)
|
||||
return; // successful
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Cmd_WeapLast_f
|
||||
=================
|
||||
*/
|
||||
void Cmd_WeapLast_f (edict_t *ent)
|
||||
{
|
||||
gclient_t *cl;
|
||||
int index;
|
||||
gitem_t *it;
|
||||
|
||||
cl = ent->client;
|
||||
|
||||
if (!cl->pers.weapon || !cl->pers.lastweapon)
|
||||
return;
|
||||
|
||||
index = ITEM_INDEX(cl->pers.lastweapon);
|
||||
if (!cl->pers.inventory[index])
|
||||
return;
|
||||
it = &itemlist[index];
|
||||
if (!it->use)
|
||||
return;
|
||||
if (! (it->flags & IT_WEAPON) )
|
||||
return;
|
||||
it->use (ent, it);
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Cmd_InvDrop_f
|
||||
=================
|
||||
*/
|
||||
void Cmd_InvDrop_f (edict_t *ent)
|
||||
{
|
||||
gitem_t *it;
|
||||
|
||||
ValidateSelectedItem (ent);
|
||||
|
||||
if (ent->client->pers.selected_item == -1)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
it = &itemlist[ent->client->pers.selected_item];
|
||||
if (!it->drop)
|
||||
{
|
||||
gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n");
|
||||
return;
|
||||
}
|
||||
it->drop (ent, it);
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Cmd_Kill_f
|
||||
=================
|
||||
*/
|
||||
void Cmd_Kill_f (edict_t *ent)
|
||||
{
|
||||
if((level.time - ent->client->respawn_time) < 5)
|
||||
return;
|
||||
ent->flags &= ~FL_GODMODE;
|
||||
ent->health = 0;
|
||||
meansOfDeath = MOD_SUICIDE;
|
||||
player_die (ent, ent, ent, 100000, vec3_origin);
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Cmd_PutAway_f
|
||||
=================
|
||||
*/
|
||||
void Cmd_PutAway_f (edict_t *ent)
|
||||
{
|
||||
ent->client->showscores = false;
|
||||
ent->client->showhelp = false;
|
||||
ent->client->showinventory = false;
|
||||
}
|
||||
|
||||
|
||||
int PlayerSort (void const *a, void const *b)
|
||||
{
|
||||
int anum, bnum;
|
||||
|
||||
anum = *(int *)a;
|
||||
bnum = *(int *)b;
|
||||
|
||||
anum = game.clients[anum].ps.stats[STAT_FRAGS];
|
||||
bnum = game.clients[bnum].ps.stats[STAT_FRAGS];
|
||||
|
||||
if (anum < bnum)
|
||||
return -1;
|
||||
if (anum > bnum)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Cmd_Players_f
|
||||
=================
|
||||
*/
|
||||
void Cmd_Players_f (edict_t *ent)
|
||||
{
|
||||
int i;
|
||||
int count;
|
||||
char small[64];
|
||||
char large[1280];
|
||||
int index[256];
|
||||
|
||||
count = 0;
|
||||
for (i = 0 ; i < maxclients->value ; i++)
|
||||
if (game.clients[i].pers.connected)
|
||||
{
|
||||
index[count] = i;
|
||||
count++;
|
||||
}
|
||||
|
||||
// sort by frags
|
||||
qsort (index, count, sizeof(index[0]), PlayerSort);
|
||||
|
||||
// print information
|
||||
large[0] = 0;
|
||||
|
||||
for (i = 0 ; i < count ; i++)
|
||||
{
|
||||
Com_sprintf (small, sizeof(small), "%3i %s\n",
|
||||
game.clients[index[i]].ps.stats[STAT_FRAGS],
|
||||
game.clients[index[i]].pers.netname);
|
||||
if (strlen (small) + strlen(large) > sizeof(large) - 100 )
|
||||
{ // can't print all of them in one packet
|
||||
strcat (large, "...\n");
|
||||
break;
|
||||
}
|
||||
strcat (large, small);
|
||||
}
|
||||
|
||||
gi.cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count);
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Cmd_Wave_f
|
||||
=================
|
||||
*/
|
||||
void Cmd_Wave_f (edict_t *ent)
|
||||
{
|
||||
int i;
|
||||
|
||||
i = atoi (gi.argv(1));
|
||||
|
||||
// can't wave when ducked
|
||||
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
|
||||
return;
|
||||
|
||||
if (ent->client->anim_priority > ANIM_WAVE)
|
||||
return;
|
||||
|
||||
ent->client->anim_priority = ANIM_WAVE;
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
gi.cprintf (ent, PRINT_HIGH, "flipoff\n");
|
||||
ent->s.frame = FRAME_flip01-1;
|
||||
ent->client->anim_end = FRAME_flip12;
|
||||
break;
|
||||
case 1:
|
||||
gi.cprintf (ent, PRINT_HIGH, "salute\n");
|
||||
ent->s.frame = FRAME_salute01-1;
|
||||
ent->client->anim_end = FRAME_salute11;
|
||||
break;
|
||||
case 2:
|
||||
gi.cprintf (ent, PRINT_HIGH, "taunt\n");
|
||||
ent->s.frame = FRAME_taunt01-1;
|
||||
ent->client->anim_end = FRAME_taunt17;
|
||||
break;
|
||||
case 3:
|
||||
gi.cprintf (ent, PRINT_HIGH, "wave\n");
|
||||
ent->s.frame = FRAME_wave01-1;
|
||||
ent->client->anim_end = FRAME_wave11;
|
||||
break;
|
||||
case 4:
|
||||
default:
|
||||
gi.cprintf (ent, PRINT_HIGH, "point\n");
|
||||
ent->s.frame = FRAME_point01-1;
|
||||
ent->client->anim_end = FRAME_point12;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
Cmd_Say_f
|
||||
==================
|
||||
*/
|
||||
void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0)
|
||||
{
|
||||
int i, j;
|
||||
edict_t *other;
|
||||
char *p;
|
||||
char text[2048];
|
||||
gclient_t *cl;
|
||||
|
||||
if (gi.argc () < 2 && !arg0)
|
||||
return;
|
||||
|
||||
if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
|
||||
team = false;
|
||||
|
||||
if (team)
|
||||
Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname);
|
||||
else
|
||||
Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname);
|
||||
|
||||
if (arg0)
|
||||
{
|
||||
strcat (text, gi.argv(0));
|
||||
strcat (text, " ");
|
||||
strcat (text, gi.args());
|
||||
}
|
||||
else
|
||||
{
|
||||
p = gi.args();
|
||||
|
||||
if (*p == '"')
|
||||
{
|
||||
p++;
|
||||
p[strlen(p)-1] = 0;
|
||||
}
|
||||
strcat(text, p);
|
||||
}
|
||||
|
||||
// don't let text be too long for malicious reasons
|
||||
if (strlen(text) > 150)
|
||||
text[150] = 0;
|
||||
|
||||
strcat(text, "\n");
|
||||
|
||||
if (flood_msgs->value) {
|
||||
cl = ent->client;
|
||||
|
||||
if (level.time < cl->flood_locktill) {
|
||||
gi.cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds\n",
|
||||
(int)(cl->flood_locktill - level.time));
|
||||
return;
|
||||
}
|
||||
i = cl->flood_whenhead - flood_msgs->value + 1;
|
||||
if (i < 0)
|
||||
i = (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])) + i;
|
||||
if (cl->flood_when[i] &&
|
||||
level.time - cl->flood_when[i] < flood_persecond->value) {
|
||||
cl->flood_locktill = level.time + flood_waitdelay->value;
|
||||
gi.cprintf(ent, PRINT_CHAT, "Flood protection: You can't talk for %d seconds.\n",
|
||||
(int)flood_waitdelay->value);
|
||||
return;
|
||||
}
|
||||
cl->flood_whenhead = (cl->flood_whenhead + 1) %
|
||||
(sizeof(cl->flood_when)/sizeof(cl->flood_when[0]));
|
||||
cl->flood_when[cl->flood_whenhead] = level.time;
|
||||
}
|
||||
|
||||
if (dedicated->value)
|
||||
gi.cprintf(NULL, PRINT_CHAT, "%s", text);
|
||||
|
||||
for (j = 1; j <= game.maxclients; j++)
|
||||
{
|
||||
other = &g_edicts[j];
|
||||
if (!other->inuse)
|
||||
continue;
|
||||
if (!other->client)
|
||||
continue;
|
||||
if (team)
|
||||
{
|
||||
if (!OnSameTeam(ent, other))
|
||||
continue;
|
||||
}
|
||||
gi.cprintf(other, PRINT_CHAT, "%s", text);
|
||||
}
|
||||
}
|
||||
|
||||
void Cmd_PlayerList_f(edict_t *ent)
|
||||
{
|
||||
int i;
|
||||
char st[80];
|
||||
char text[1400];
|
||||
edict_t *e2;
|
||||
|
||||
// connect time, ping, score, name
|
||||
*text = 0;
|
||||
for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++) {
|
||||
if (!e2->inuse)
|
||||
continue;
|
||||
|
||||
Com_sprintf(st, sizeof(st), "%02d:%02d %4d %3d %s%s\n",
|
||||
(level.framenum - e2->client->resp.enterframe) / 600,
|
||||
((level.framenum - e2->client->resp.enterframe) % 600)/10,
|
||||
e2->client->ping,
|
||||
e2->client->resp.score,
|
||||
e2->client->pers.netname,
|
||||
e2->client->resp.spectator ? " (spectator)" : "");
|
||||
if (strlen(text) + strlen(st) > sizeof(text) - 50) {
|
||||
sprintf(text+strlen(text), "And more...\n");
|
||||
gi.cprintf(ent, PRINT_HIGH, "%s", text);
|
||||
return;
|
||||
}
|
||||
strcat(text, st);
|
||||
}
|
||||
gi.cprintf(ent, PRINT_HIGH, "%s", text);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
ClientCommand
|
||||
=================
|
||||
*/
|
||||
void ClientCommand (edict_t *ent)
|
||||
{
|
||||
char *cmd;
|
||||
|
||||
if (!ent->client)
|
||||
return; // not fully in game yet
|
||||
|
||||
cmd = gi.argv(0);
|
||||
|
||||
if (Q_stricmp (cmd, "players") == 0)
|
||||
{
|
||||
Cmd_Players_f (ent);
|
||||
return;
|
||||
}
|
||||
if (Q_stricmp (cmd, "say") == 0)
|
||||
{
|
||||
Cmd_Say_f (ent, false, false);
|
||||
return;
|
||||
}
|
||||
if (Q_stricmp (cmd, "say_team") == 0)
|
||||
{
|
||||
Cmd_Say_f (ent, true, false);
|
||||
return;
|
||||
}
|
||||
if (Q_stricmp (cmd, "score") == 0)
|
||||
{
|
||||
Cmd_Score_f (ent);
|
||||
return;
|
||||
}
|
||||
if (Q_stricmp (cmd, "help") == 0)
|
||||
{
|
||||
Cmd_Help_f (ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (level.intermissiontime)
|
||||
return;
|
||||
|
||||
if (Q_stricmp (cmd, "use") == 0)
|
||||
Cmd_Use_f (ent);
|
||||
else if (Q_stricmp (cmd, "drop") == 0)
|
||||
Cmd_Drop_f (ent);
|
||||
else if (Q_stricmp (cmd, "give") == 0)
|
||||
Cmd_Give_f (ent);
|
||||
else if (Q_stricmp (cmd, "god") == 0)
|
||||
Cmd_God_f (ent);
|
||||
else if (Q_stricmp (cmd, "notarget") == 0)
|
||||
Cmd_Notarget_f (ent);
|
||||
else if (Q_stricmp (cmd, "noclip") == 0)
|
||||
Cmd_Noclip_f (ent);
|
||||
else if (Q_stricmp (cmd, "inven") == 0)
|
||||
Cmd_Inven_f (ent);
|
||||
else if (Q_stricmp (cmd, "invnext") == 0)
|
||||
SelectNextItem (ent, -1);
|
||||
else if (Q_stricmp (cmd, "invprev") == 0)
|
||||
SelectPrevItem (ent, -1);
|
||||
else if (Q_stricmp (cmd, "invnextw") == 0)
|
||||
SelectNextItem (ent, IT_WEAPON);
|
||||
else if (Q_stricmp (cmd, "invprevw") == 0)
|
||||
SelectPrevItem (ent, IT_WEAPON);
|
||||
else if (Q_stricmp (cmd, "invnextp") == 0)
|
||||
SelectNextItem (ent, IT_POWERUP);
|
||||
else if (Q_stricmp (cmd, "invprevp") == 0)
|
||||
SelectPrevItem (ent, IT_POWERUP);
|
||||
else if (Q_stricmp (cmd, "invuse") == 0)
|
||||
Cmd_InvUse_f (ent);
|
||||
else if (Q_stricmp (cmd, "invdrop") == 0)
|
||||
Cmd_InvDrop_f (ent);
|
||||
else if (Q_stricmp (cmd, "weapprev") == 0)
|
||||
Cmd_WeapPrev_f (ent);
|
||||
else if (Q_stricmp (cmd, "weapnext") == 0)
|
||||
Cmd_WeapNext_f (ent);
|
||||
else if (Q_stricmp (cmd, "weaplast") == 0)
|
||||
Cmd_WeapLast_f (ent);
|
||||
else if (Q_stricmp (cmd, "kill") == 0)
|
||||
Cmd_Kill_f (ent);
|
||||
else if (Q_stricmp (cmd, "putaway") == 0)
|
||||
Cmd_PutAway_f (ent);
|
||||
else if (Q_stricmp (cmd, "wave") == 0)
|
||||
Cmd_Wave_f (ent);
|
||||
else if (Q_stricmp(cmd, "playerlist") == 0)
|
||||
Cmd_PlayerList_f(ent);
|
||||
else // anything that doesn't match a command will be a chat
|
||||
Cmd_Say_f (ent, false, true);
|
||||
}
|
576
game/g_combat.c
Normal file
|
@ -0,0 +1,576 @@
|
|||
/*
|
||||
Copyright (C) 1997-2001 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
// 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 = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
if (trace.ent == targ)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID);
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
|
||||
VectorCopy (targ->s.origin, dest);
|
||||
dest[0] += 15.0;
|
||||
dest[1] += 15.0;
|
||||
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
|
||||
VectorCopy (targ->s.origin, dest);
|
||||
dest[0] += 15.0;
|
||||
dest[1] -= 15.0;
|
||||
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
|
||||
VectorCopy (targ->s.origin, dest);
|
||||
dest[0] -= 15.0;
|
||||
dest[1] += 15.0;
|
||||
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
|
||||
VectorCopy (targ->s.origin, dest);
|
||||
dest[0] -= 15.0;
|
||||
dest[1] -= 15.0;
|
||||
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
||||
if (trace.fraction == 1.0)
|
||||
return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Killed
|
||||
============
|
||||
*/
|
||||
void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
||||
{
|
||||
if (targ->health < -999)
|
||||
targ->health = -999;
|
||||
|
||||
targ->enemy = attacker;
|
||||
|
||||
if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
|
||||
{
|
||||
// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type
|
||||
if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY))
|
||||
{
|
||||
level.killed_monsters++;
|
||||
if (coop->value && attacker->client)
|
||||
attacker->client->resp.score++;
|
||||
// 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)
|
||||
{
|
||||
targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
|
||||
|
||||
// this can only happen in coop (both new and old enemies are clients)
|
||||
// only switch if can't see the current enemy
|
||||
if (targ->enemy && targ->enemy->client)
|
||||
{
|
||||
if (visible(targ, targ->enemy))
|
||||
{
|
||||
targ->oldenemy = attacker;
|
||||
return;
|
||||
}
|
||||
targ->oldenemy = targ->enemy;
|
||||
}
|
||||
targ->enemy = attacker;
|
||||
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
|
||||
FoundTarget (targ);
|
||||
return;
|
||||
}
|
||||
|
||||
// it's the same base (walk/swim/fly) type and a different classname and it's not a tank
|
||||
// (they spray too much), get mad at them
|
||||
if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) &&
|
||||
(strcmp (targ->classname, attacker->classname) != 0) &&
|
||||
(strcmp(attacker->classname, "monster_tank") != 0) &&
|
||||
(strcmp(attacker->classname, "monster_supertank") != 0) &&
|
||||
(strcmp(attacker->classname, "monster_makron") != 0) &&
|
||||
(strcmp(attacker->classname, "monster_jorg") != 0) )
|
||||
{
|
||||
if (targ->enemy && targ->enemy->client)
|
||||
targ->oldenemy = targ->enemy;
|
||||
targ->enemy = attacker;
|
||||
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
|
||||
FoundTarget (targ);
|
||||
}
|
||||
// if they *meant* to shoot us, then shoot back
|
||||
else if (attacker->enemy == targ)
|
||||
{
|
||||
if (targ->enemy && targ->enemy->client)
|
||||
targ->oldenemy = targ->enemy;
|
||||
targ->enemy = attacker;
|
||||
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
|
||||
FoundTarget (targ);
|
||||
}
|
||||
// otherwise get mad at whoever they are mad at (help our buddy) unless it is us!
|
||||
else if (attacker->enemy && attacker->enemy != targ)
|
||||
{
|
||||
if (targ->enemy && targ->enemy->client)
|
||||
targ->oldenemy = targ->enemy;
|
||||
targ->enemy = attacker->enemy;
|
||||
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
|
||||
FoundTarget (targ);
|
||||
}
|
||||
}
|
||||
|
||||
qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker)
|
||||
{
|
||||
//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 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;
|
||||
|
||||
if (!targ->takedamage)
|
||||
return;
|
||||
|
||||
// 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;
|
||||
|
||||
// easy mode takes half damage
|
||||
if (skill->value == 0 && deathmatch->value == 0 && targ->client)
|
||||
{
|
||||
damage *= 0.5;
|
||||
if (!damage)
|
||||
damage = 1;
|
||||
}
|
||||
|
||||
client = targ->client;
|
||||
|
||||
if (dflags & DAMAGE_BULLET)
|
||||
te_sparks = TE_BULLET_SPARKS;
|
||||
else
|
||||
te_sparks = TE_SPARKS;
|
||||
|
||||
VectorNormalize(dir);
|
||||
|
||||
// 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;
|
||||
float mass;
|
||||
|
||||
if (targ->mass < 50)
|
||||
mass = 50;
|
||||
else
|
||||
mass = targ->mass;
|
||||
|
||||
if (targ->client && attacker == targ)
|
||||
VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack...
|
||||
else
|
||||
VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
|
||||
|
||||
VectorAdd (targ->velocity, kvel, targ->velocity);
|
||||
}
|
||||
}
|
||||
|
||||
take = damage;
|
||||
save = 0;
|
||||
|
||||
// check for godmode
|
||||
if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) )
|
||||
{
|
||||
take = 0;
|
||||
save = damage;
|
||||
SpawnDamage (te_sparks, point, normal, save);
|
||||
}
|
||||
|
||||
// check for invincibility
|
||||
if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
|
||||
{
|
||||
if (targ->pain_debounce_time < level.time)
|
||||
{
|
||||
gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
|
||||
targ->pain_debounce_time = level.time + 2;
|
||||
}
|
||||
take = 0;
|
||||
save = damage;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// do the damage
|
||||
if (take)
|
||||
{
|
||||
if ((targ->svflags & SVF_MONSTER) || (client))
|
||||
SpawnDamage (TE_BLOOD, point, normal, take);
|
||||
else
|
||||
SpawnDamage (te_sparks, point, normal, take);
|
||||
|
||||
|
||||
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;
|
||||
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);
|
||||
if (ent == attacker)
|
||||
points = points * 0.5;
|
||||
if (points > 0)
|
||||
{
|
||||
if (CanDamage (ent, inflictor))
|
||||
{
|
||||
VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
|
||||
T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|