Compare commits
11 Commits
winampgit-
...
18800b55df
Author | SHA1 | Date | |
---|---|---|---|
18800b55df | |||
a84b085a2d | |||
dbb73578f8 | |||
ac242b35b8 | |||
d8904caf59 | |||
38f8b7c882 | |||
25bfe82bec | |||
40035fa235 | |||
0bf99969fd | |||
e8bd66b89c | |||
b585e51a81 |
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.
|
7
Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "random"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8"
|
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
79
LICENSE.md
@ -1,79 +0,0 @@
|
||||
# Winamp Collaborative License (WCL) Version 1.0.1
|
||||
|
||||
This License governs the use, modification, and distribution of the Winamp software.
|
||||
By using, Modifying, or distributing this software, you agree to the following terms:
|
||||
|
||||
## Preamble
|
||||
The Winamp Collaborative License is a free, copyleft license for software and other kinds of works. It is designed to ensure that you have the freedom to use, Modify, and study the software, but with certain restrictions on the distribution of modifications to maintain the integrity and collaboration of the project.
|
||||
|
||||
## TERMS AND CONDITIONS
|
||||
|
||||
### 1. Definitions
|
||||
- "This License" refers to version 1.0 of the Winamp Collaborative License.
|
||||
- "The Program" refers to any copyrightable work Licensed under this License.
|
||||
- "You" refers to each Licensee, whether an individual or organization.
|
||||
- "Modify" means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy.
|
||||
- "Covered Work" means either the unmodified Program or a work based on the Program.
|
||||
- "Convey" means any kind of propagation that enables other parties to make or receive copies.
|
||||
|
||||
### 2. Basic Permissions
|
||||
All rights granted under this License are granted for the term of copyright on the Program and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a Covered work is covered by this License only if its contents constitute a Covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
### 3. Grant of License
|
||||
You are granted the right to view, access, and study the source code of the software.
|
||||
You are granted the right to Modify the software for private use only. You may make, run, and propagate Covered works that you do not Convey, without conditions, so long as your License otherwise remains in force.
|
||||
|
||||
### 4. Contributions
|
||||
- Contribution to Project: You are encouraged to contribute improvements, enhancements, and bug fixes back to the project. Contributions must be submitted to the official repository and will be reviewed and incorporated at the discretion of the maintainers.
|
||||
- Assignment of Rights: By submitting contributions, you agree that all intellectual property rights, including copyright, in your contributions are assigned to Winamp. You hereby grant Winamp a perpetual, worldwide, non-exclusive, royalty-free license to use, copy, modify, and distribute your contributions as part of the software, without any compensation to you.
|
||||
- Waiver of Rights: You waive any rights to claim authorship of the contributions or to object to any distortion, mutilation, or other modifications of the contributions.
|
||||
|
||||
### 5. Restrictions
|
||||
- No Distribution of Modified Versions: You may not distribute modified versions of the software, whether in source or binary form.
|
||||
- Official Distribution: Only the maintainers of the official repository are allowed to distribute the software and its modifications.
|
||||
|
||||
### 6. No Sublicensing
|
||||
Sublicensing is not allowed; section 5 makes it unnecessary.
|
||||
|
||||
### 7. Protecting Users' Legal Rights From Anti-Circumvention Law
|
||||
|
||||
No Covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
|
||||
|
||||
### 8. Disclaimer of Warranty
|
||||
The software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software.
|
||||
|
||||
### 9. Limitation of Liability
|
||||
In no event will the authors or copyright holders be liable for any special, incidental, indirect, or consequential damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or any other pecuniary loss) arising out of the use of or inability to use the software, even if the authors or copyright holders have been advised of the possibility of such damages.
|
||||
|
||||
### 10. Termination
|
||||
This License and the rights granted hereunder will terminate automatically if you fail to comply with the terms and conditions herein. Upon termination, you must cease all use of the software and destroy all copies, full or partial, of the software.
|
||||
|
||||
### 11. Protection of Copyright
|
||||
The original authors or copyright holders of the software retain all rights, title, and interest in the software. This license does not transfer any ownership rights.
|
||||
You must retain all copyright, patent, trademark, and attribution notices in the source code and documentation of the software.
|
||||
By submitting contributions, you agree that the contributions are your original work and you grant the project a perpetual, worldwide, non-exclusive, royalty-free license to use, copy, modify, and distribute your contributions as part of the software.
|
||||
Any unauthorized use, reproduction, or distribution of the software, or any portion thereof, may result in civil and criminal penalties.
|
||||
|
||||
### 12. Patent Rights
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims to make, use, sell, offer for sale, import, and otherwise run, modify and propagate the contents of its contributor version.
|
||||
If you initiate litigation (including a cross-claim or counterclaim) against any party alleging that the Program or a contribution incorporated within the Program constitutes direct or contributory patent infringement, then any patent licenses granted to you under this License for that Program shall terminate as of the date such litigation is filed.
|
||||
|
||||
### 13. Trademark Usage
|
||||
This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for describing the origin of the Program and reproducing the content of the copyright notice.
|
||||
|
||||
### 14. Privacy and Data Protection
|
||||
You must comply with all applicable privacy and data protection laws in connection with your use of the Program.
|
||||
If the Program collects user data, you must provide clear notice and obtain any necessary consents for such collection.
|
||||
|
||||
### 15. Support and Updates
|
||||
The Licensor has no obligation to provide support, updates, or maintenance for the Program. Any such support, updates, or maintenance will be provided at the sole discretion of the Licensor.
|
||||
|
||||
### 16. Compliance
|
||||
You must comply with all applicable laws and regulations in connection with your use of the Program.
|
||||
|
||||
### 17. Miscellaneous
|
||||
- Governing Law and Jurisdiction: This License shall be governed by and construed in accordance with the laws of Belgium. Any disputes arising out of or in connection with this License shall be subject to the exclusive jurisdiction of the courts located in Brussels, Belgium.
|
||||
- Severability: If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
|
||||
By using, Modifying, or contributing to the software, you acknowledge that you have read, understood, and agree to be bound by these terms and conditions.
|
||||
|
||||
This custom License aims to maintain the collaborative nature of the project while restricting the distribution of modified versions.
|
150
baseq2/config.cfg
Normal file
@ -0,0 +1,150 @@
|
||||
// generated by quake, do not modify
|
||||
bind TAB "inven"
|
||||
bind ENTER "invuse"
|
||||
bind ESCAPE "togglemenu"
|
||||
bind SPACE "+moveup"
|
||||
bind ' "inven_drop"
|
||||
bind + "sizeup"
|
||||
bind , "+moveleft"
|
||||
bind - "sizedown"
|
||||
bind . "+moveright"
|
||||
bind / "weapnext"
|
||||
bind 0 "use BFG10K"
|
||||
bind 1 "use Blaster"
|
||||
bind 2 "use Shotgun"
|
||||
bind 3 "use Super Shotgun"
|
||||
bind 4 "use Machinegun"
|
||||
bind 5 "use Chaingun"
|
||||
bind 6 "use Grenade Launcher"
|
||||
bind 7 "use Rocket Launcher"
|
||||
bind 8 "use HyperBlaster"
|
||||
bind 9 "use Railgun"
|
||||
bind = "sizeup"
|
||||
bind [ "invprev"
|
||||
bind \ "+mlook"
|
||||
bind ] "invnext"
|
||||
bind ` "toggleconsole"
|
||||
bind a "+moveleft"
|
||||
bind b "use rebreather"
|
||||
bind c "+movedown"
|
||||
bind d "+moveright"
|
||||
bind e "weapnext"
|
||||
bind g "use grenades"
|
||||
bind h "wave 0"
|
||||
bind i "use invulnerability"
|
||||
bind j "wave 1"
|
||||
bind k "wave 2"
|
||||
bind l "wave 3"
|
||||
bind p "use shield"
|
||||
bind q "invprev"
|
||||
bind r "invuse"
|
||||
bind s "+back"
|
||||
bind t "messagemode"
|
||||
bind u "wave 4"
|
||||
bind w "+forward"
|
||||
bind x "centerview"
|
||||
bind z "+movedown"
|
||||
bind ~ "toggleconsole"
|
||||
bind BACKSPACE "invdrop"
|
||||
bind UPARROW "+forward"
|
||||
bind DOWNARROW "+back"
|
||||
bind LEFTARROW "+left"
|
||||
bind RIGHTARROW "+right"
|
||||
bind ALT "+strafe"
|
||||
bind CTRL "+attack"
|
||||
bind SHIFT "+speed"
|
||||
bind F1 "cmd help"
|
||||
bind F2 "menu_savegame"
|
||||
bind F3 "menu_loadgame"
|
||||
bind F4 "give ammo"
|
||||
bind F5 "give weapons"
|
||||
bind F6 "r_speeds 0"
|
||||
bind F7 "r_speeds 1"
|
||||
bind F8 "notarget"
|
||||
bind F9 "noclip"
|
||||
bind F10 "god"
|
||||
bind F11 "screenshot"
|
||||
bind F12 "quit"
|
||||
bind INS "+klook"
|
||||
bind DEL "+lookdown"
|
||||
bind PGDN "+lookup"
|
||||
bind PGUP "+lookup"
|
||||
bind END "centerview"
|
||||
bind MOUSE1 "+attack"
|
||||
bind MOUSE2 "+strafe"
|
||||
bind MOUSE3 "+mlook"
|
||||
bind PAUSE "pause"
|
||||
set gl_3dlabs_broken "1"
|
||||
set gl_swapinterval "1"
|
||||
set gl_ext_compiled_vertex_array "1"
|
||||
set gl_ext_pointparameters "1"
|
||||
set gl_ext_multitexture "1"
|
||||
set gl_ext_palettedtexture "1"
|
||||
set gl_ext_swapinterval "1"
|
||||
set gl_vertex_arrays "0"
|
||||
set gl_texturesolidmode "default"
|
||||
set gl_texturealphamode "default"
|
||||
set gl_texturemode "GL_LINEAR_MIPMAP_NEAREST"
|
||||
set gl_driver "opengl32"
|
||||
set gl_finish "0"
|
||||
set gl_shadows "0"
|
||||
set gl_mode "3"
|
||||
set gl_modulate "1"
|
||||
set gl_particle_att_c "0.01"
|
||||
set gl_particle_att_b "0.0"
|
||||
set gl_particle_att_a "0.01"
|
||||
set gl_particle_size "40"
|
||||
set gl_particle_max_size "40"
|
||||
set gl_particle_min_size "2"
|
||||
set g_select_empty "0"
|
||||
set in_joystick "0"
|
||||
set in_mouse "1"
|
||||
set cl_vwep "1"
|
||||
set gender_auto "1"
|
||||
set gender "male"
|
||||
set fov "90"
|
||||
set msg "1"
|
||||
set rate "25000"
|
||||
set freelook "0"
|
||||
set cl_stereo_separation "0.4"
|
||||
set adr8 ""
|
||||
set adr7 ""
|
||||
set adr6 ""
|
||||
set adr5 ""
|
||||
set adr4 ""
|
||||
set adr3 ""
|
||||
set adr2 ""
|
||||
set adr1 ""
|
||||
set adr0 ""
|
||||
set cd_nocd "0"
|
||||
set s_primary "0"
|
||||
set s_mixahead "0.2"
|
||||
set s_loadas8bit "1"
|
||||
set s_khz "11"
|
||||
set s_volume "0.7"
|
||||
set sw_mode "0"
|
||||
set sw_stipplealpha "0"
|
||||
set sw_allow_modex "1"
|
||||
set vid_gamma "1"
|
||||
set vid_ypos "32"
|
||||
set vid_xpos "115"
|
||||
set vid_ref "gl"
|
||||
set sv_reconnect_limit "3"
|
||||
set allow_download_maps "1"
|
||||
set allow_download_sounds "1"
|
||||
set allow_download_models "1"
|
||||
set allow_download_players "0"
|
||||
set allow_download "0"
|
||||
set hostname "noname"
|
||||
set skin "male/grunt"
|
||||
set name "hook"
|
||||
set lookstrafe "0"
|
||||
set lookspring "1"
|
||||
set m_pitch "-0.022000"
|
||||
set hand "2"
|
||||
set cl_run "0"
|
||||
set crosshair "1"
|
||||
set sensitivity "9.000000"
|
||||
set win_noalttab "0"
|
||||
set vid_fullscreen "0"
|
||||
set viewsize "100"
|
BIN
baseq2/save/save0/game.ssv
Normal file
BIN
baseq2/save/save0/server.ssv
Normal file
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 ();
|
||||
}
|
1500
client/cl_ents.c
Normal file
2298
client/cl_fx.c
Normal file
542
client/cl_input.c
Normal file
@ -0,0 +1,542 @@
|
||||
/*
|
||||
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 > 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
806
client/cl_parse.c
Normal file
@ -0,0 +1,806 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
i = MSG_ReadShort (&net_message);
|
||||
if (i < 0 || i >= MAX_CONFIGSTRINGS)
|
||||
Com_Error (ERR_DROP, "configstring > MAX_CONFIGSTRINGS");
|
||||
s = MSG_ReadString(&net_message);
|
||||
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)
|
||||
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);
|
943
client/keys.c
Normal file
@ -0,0 +1,943 @@
|
||||
/*
|
||||
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_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);
|
||||
|
4016
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
|
224
client/ref.h
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
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 "../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);
|
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;
|
||||
}
|
||||
|
497
client/snd_mix.c
Normal file
@ -0,0 +1,497 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
lscale = snd_scaletable[ ch->leftvol >> 11];
|
||||
rscale = snd_scaletable[ ch->rightvol >> 11];
|
||||
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);
|
42
client/vid.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
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
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
} 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
|
16
ctf/2do.txt
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
switching teams during setup should clear ready
|
||||
center stuff should be left aligned on admin
|
||||
match command for ingame match request
|
||||
admin kick option (menu)
|
||||
can't join team when match PREGAME (menu was still open)
|
||||
server pause in 3.15?
|
||||
|
||||
*time changes are broken
|
||||
*ghosting in makes you invisible (svf_noclient?)
|
||||
*ghost doesn't restore frags
|
||||
*resetall needs to clear grapple (and match start)
|
||||
*reset all techs at match start
|
||||
*'and admin'
|
||||
*timelimit/fraglimit disabled in match mode
|
||||
*telefrags at start of match [needs tested]
|
159
ctf/Makefile.Linux.i386
Normal file
@ -0,0 +1,159 @@
|
||||
#
|
||||
# Quake2 gamei386.so Makefile for Linux 2.0
|
||||
#
|
||||
# Jan '98 by Zoid <zoid@idsoftware.com>
|
||||
#
|
||||
# ELF only
|
||||
#
|
||||
# Probably requires GNU make
|
||||
#
|
||||
# This builds the gamei386.so for Linux based on the q2source_12_11.zip
|
||||
# release.
|
||||
# Put his Makefile in the game subdirectory you get when you unzip
|
||||
# q2source_12_11.zip.
|
||||
#
|
||||
# There are two compiler errors you'll get, the following fixes
|
||||
# are necessary:
|
||||
#
|
||||
# In g_local.h (around line 828), you must change the
|
||||
# typedef struct g_client_s { ... } gclient_t;
|
||||
# to just:
|
||||
# struct g_client_s { ... };
|
||||
# The typedef is already defined elsewhere (seems to compile fine under
|
||||
# MSCV++ for Win32 for some reason).
|
||||
#
|
||||
# m_player.h has a Ctrl-Z at the end (damn DOS editors). Remove it or
|
||||
# gcc complains.
|
||||
#
|
||||
# Note that the source in q2source_12_11.zip is for version 3.05. To
|
||||
# get it to run with Linux 3.10, change the following in game.h:
|
||||
# #define GAME_API_VERSION 1
|
||||
# change it to:
|
||||
# #define GAME_API_VERSION 2
|
||||
|
||||
ARCH=i386
|
||||
CC=gcc
|
||||
BASE_CFLAGS=-Dstricmp=strcasecmp
|
||||
|
||||
#use these cflags to optimize it
|
||||
CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \
|
||||
-fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \
|
||||
-malign-jumps=2 -malign-functions=2
|
||||
#use these when debugging
|
||||
#CFLAGS=$(BASE_CFLAGS) -g
|
||||
|
||||
OBJDIR=linux
|
||||
|
||||
LDFLAGS=-ldl -lm
|
||||
SHLIBEXT=so
|
||||
SHLIBCFLAGS=-fPIC
|
||||
SHLIBLDFLAGS=-shared
|
||||
|
||||
DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $<
|
||||
|
||||
#############################################################################
|
||||
# SETUP AND BUILD
|
||||
# GAME
|
||||
#############################################################################
|
||||
|
||||
GAME_OBJS = \
|
||||
$(OBJDIR)/g_ai.o $(OBJDIR)/p_client.o $(OBJDIR)/g_svcmds.o $(OBJDIR)/g_cmds.o \
|
||||
$(OBJDIR)/g_combat.o $(OBJDIR)/g_func.o $(OBJDIR)/g_items.o \
|
||||
$(OBJDIR)/g_main.o $(OBJDIR)/g_misc.o $(OBJDIR)/g_monster.o $(OBJDIR)/g_phys.o \
|
||||
$(OBJDIR)/g_save.o $(OBJDIR)/g_spawn.o \
|
||||
$(OBJDIR)/g_target.o $(OBJDIR)/g_trigger.o $(OBJDIR)/g_utils.o $(OBJDIR)/g_weapon.o \
|
||||
$(OBJDIR)/m_move.o \
|
||||
$(OBJDIR)/p_hud.o $(OBJDIR)/p_trail.o $(OBJDIR)/p_view.o $(OBJDIR)/p_weapon.o \
|
||||
$(OBJDIR)/q_shared.o $(OBJDIR)/g_ctf.o $(OBJDIR)/p_menu.o $(OBJDIR)/g_chase.o
|
||||
|
||||
game$(ARCH).$(SHLIBEXT) : $(GAME_OBJS)
|
||||
$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(GAME_OBJS)
|
||||
|
||||
$(OBJDIR)/g_ai.o : g_ai.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/p_client.o : p_client.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_svcmds.o : g_svcmds.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_cmds.o : g_cmds.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_combat.o : g_combat.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_func.o : g_func.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_items.o : g_items.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_main.o : g_main.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_misc.o : g_misc.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_monster.o : g_monster.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_phys.o : g_phys.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_save.o : g_save.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_spawn.o : g_spawn.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_target.o : g_target.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_trigger.o : g_trigger.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_utils.o : g_utils.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_weapon.o : g_weapon.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/m_move.o : m_move.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/p_hud.o : p_hud.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/p_trail.o : p_trail.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/p_view.o : p_view.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/p_weapon.o : p_weapon.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/q_shared.o : q_shared.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_ctf.o : g_ctf.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/p_menu.o : p_menu.c
|
||||
$(DO_CC)
|
||||
|
||||
$(OBJDIR)/g_chase.o : g_chase.c
|
||||
$(DO_CC)
|
||||
|
||||
#############################################################################
|
||||
# MISC
|
||||
#############################################################################
|
||||
|
||||
clean:
|
||||
-rm -f $(GAME_OBJS)
|
||||
|
||||
depend:
|
||||
gcc -MM $(GAME_OBJS:.o=.c)
|
||||
|
1009
ctf/ctf.001
Normal file
2
ctf/ctf.def
Normal file
@ -0,0 +1,2 @@
|
||||
EXPORTS
|
||||
GetGameAPI
|
1007
ctf/ctf.dsp
Normal file
17
ctf/ctf.plg
Normal file
@ -0,0 +1,17 @@
|
||||
--------------------Configuration: ctf - Win32 Debug Alpha--------------------
|
||||
Begining build with project "G:\quake2\code\ctf\ctf.dsp", at root.
|
||||
Active configuration is Win32 (ALPHA) Dynamic-Link Library (based on Win32 (ALPHA) Dynamic-Link Library)
|
||||
|
||||
Project's tools are:
|
||||
"OLE Type Library Maker" with flags "/nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 "
|
||||
"C/C++ Compiler for Alpha" with flags "/nologo /MTd /Gt0 /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR".\DebugAXP/" /Fp".\DebugAXP/ctf.pch" /YX /Fo".\DebugAXP/" /Fd".\DebugAXP/" /FD /c "
|
||||
"Win32 Resource Compiler" with flags "/l 0x409 /d "_DEBUG" "
|
||||
"Browser Database Maker" with flags "/nologo /o"..\DebugAXP/ctf.bsc" "
|
||||
"COFF Linker for Alpha" with flags "winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"..\DebugAXP/gameaxp.pdb" /map:".\DebugAXP/gameaxp.map" /debug /machine:ALPHA /def:".\ctf.def" /out:".\debugAXP\gameaxp.dll" /implib:"..\DebugAXP/gameaxp.lib" /pdbtype:sept "
|
||||
"Custom Build" with flags ""
|
||||
"<Component 0xa>" with flags ""
|
||||
|
||||
|
||||
|
||||
|
||||
gameaxp.dll - 0 error(s), 0 warning(s)
|
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4016
ctf/g_ctf.c
Normal file
185
ctf/g_ctf.h
Normal file
@ -0,0 +1,185 @@
|
||||
/*
|
||||
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.09b
|
||||
#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 CONFIG_CTF_MATCH (CS_MAXCLIENTS-1)
|
||||
|
||||
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 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);
|
2047
ctf/g_func.c
Normal file
2446
ctf/g_items.c
Normal file
1145
ctf/g_local.h
Normal file
427
ctf/g_main.c
Normal file
@ -0,0 +1,427 @@
|
||||
/*
|
||||
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 *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);
|
||||
}
|
||||
}
|
743
ctf/g_save.c
Normal file
@ -0,0 +1,743 @@
|
||||
/*
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
998
ctf/g_spawn.c
Normal file
@ -0,0 +1,998 @@
|
||||
/*
|
||||
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);
|
||||
//precaches
|
||||
gi.imageindex("i_ctf1");
|
||||
gi.imageindex("i_ctf2");
|
||||
gi.imageindex("i_ctf1d");
|
||||
gi.imageindex("i_ctf2d");
|
||||
gi.imageindex("i_ctf1t");
|
||||
gi.imageindex("i_ctf2t");
|
||||
gi.imageindex("i_ctfj");
|
||||
} else
|
||||
//ZOID
|
||||
gi.configstring (CS_STATUSBAR, dm_statusbar);
|
||||
else
|
||||
gi.configstring (CS_STATUSBAR, single_statusbar);
|
||||
|
||||
//---------------
|
||||
|
||||
|
||||
// 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");
|
||||
|
||||
#if 0 //DISABLED
|
||||
// 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");
|
||||
#endif
|
||||
|
||||
//-------------------
|
||||
|
||||
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");
|
||||
}
|
||||
|
48
ctf/g_svcmds.c
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
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");
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
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
|
||||
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
|
||||
}
|
919
ctf/g_weapon.c
Normal file
@ -0,0 +1,919 @@
|
||||
/*
|
||||
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
|
||||
{
|
||||
if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
|
||||
ignore = tr.ent;
|
||||
else
|
||||
ignore = NULL;
|
||||
|
||||
if ((tr.ent != self) && (tr.ent->takedamage))
|
||||
T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_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);
|
12
ctf/layout.txt
Normal file
@ -0,0 +1,12 @@
|
||||
01234567890123456789012345678901234567890123456789012345678901234567890123456789
|
||||
|
||||
Name Score Kills Deaths BaseDef CarrierDef Efficiency
|
||||
0123456789012345 01234 01234 012345 0123456 0123456789 0123456789
|
||||
>BC>Zoid 110 40 10 5 10 75%
|
||||
|
||||
Name |Score|Kills|Deaths|BaseDef|CarrierDef|Efficiency|
|
||||
----------------+-----+-----+------+-------+----------+----------+
|
||||
0123456789012345|01234|01234|012345|0123456|0123456789|0123456789|
|
||||
>BC>Zoid | 110| 40| 10| 5| 10| 75%|
|
||||
|
||||
%-16.16s|%5d|%5d|%6d|%7d|%10d|%10d|
|
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
|
||||
|
||||
|
1726
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)];
|
||||
}
|
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;
|
||||
|
||||
sprintf(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);
|
||||
}
|